From 682608f2deed939ebfa9c2b6381d5fa7ca8c46ab Mon Sep 17 00:00:00 2001 From: luminousminer Date: Mon, 22 Jun 2026 11:04:08 +0200 Subject: [PATCH 1/6] kHeavyHash - Remove isolate test --- sources/algo/kheavyhash/CMakeLists.txt | 73 -------------------------- 1 file changed, 73 deletions(-) diff --git a/sources/algo/kheavyhash/CMakeLists.txt b/sources/algo/kheavyhash/CMakeLists.txt index 8d98bb62..819aae42 100644 --- a/sources/algo/kheavyhash/CMakeLists.txt +++ b/sources/algo/kheavyhash/CMakeLists.txt @@ -1,74 +1,3 @@ -if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) - -# ============================================================================ -# Standalone CPU-reference / OpenCL-KAT build for kHeavyHash (Kaspa). -# -# This is the correctness oracle for the algorithm. It is intentionally -# dependency-free (no Boost/CUDA) so it can be built and tested on a minimal -# toolchain, including running the OpenCL kernel on the CPU via POCL. -# -# Build + test in isolation: -# cmake -S sources/algo/kheavyhash -B build-kheavyhash [-DKHEAVYHASH_BUILD_OPENCL=ON] -# cmake --build build-kheavyhash -# ctest --test-dir build-kheavyhash --output-on-failure -# ============================================================================ -cmake_minimum_required(VERSION 3.16) -project(kheavyhash_reference CXX) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -add_library(kheavyhash_reference STATIC - matrix.cpp - keccak.cpp - hashers.cpp - kheavyhash.cpp - stratum_math.cpp) - -# sources include themselves as , so the include root is sources/. -target_include_directories(kheavyhash_reference PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../..) -target_compile_options(kheavyhash_reference PRIVATE -Wall -Wextra) - -option(KHEAVYHASH_BUILD_TESTS "Build the kHeavyHash known-answer test suite" ON) -if(KHEAVYHASH_BUILD_TESTS) - find_package(GTest REQUIRED) - enable_testing() - add_executable(kheavyhash_unit_test - tests/matrix_test.cpp - tests/hashers_test.cpp - tests/kheavyhash_test.cpp - tests/stratum_math_test.cpp) - target_include_directories(kheavyhash_unit_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests) - target_link_libraries(kheavyhash_unit_test PRIVATE kheavyhash_reference GTest::gtest GTest::gtest_main) - add_test(NAME kheavyhash_unit_test COMMAND kheavyhash_unit_test) -endif() - -# OpenCL kernel KAT harness. Runs the .cl on any OpenCL device the ICD exposes -# (POCL/CPU in the dev image, a real GPU otherwise) and asserts bit-identity -# with the CPU reference. Off by default so the pure-CPU build needs no ICD. -option(KHEAVYHASH_BUILD_OPENCL "Build the kHeavyHash OpenCL known-answer test suite" OFF) -if(KHEAVYHASH_BUILD_OPENCL) - find_package(GTest REQUIRED) - find_package(OpenCL REQUIRED) - enable_testing() - add_executable(kheavyhash_opencl_test opencl/tests/opencl_kat_test.cpp) - target_include_directories(kheavyhash_opencl_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests) - target_compile_definitions(kheavyhash_opencl_test - PRIVATE KH_CL_PATH="${CMAKE_CURRENT_SOURCE_DIR}/opencl/kheavyhash.cl") - target_compile_options(kheavyhash_opencl_test PRIVATE -Wall -Wextra) - target_link_libraries(kheavyhash_opencl_test - PRIVATE kheavyhash_reference OpenCL::OpenCL GTest::gtest GTest::gtest_main) - add_test(NAME kheavyhash_opencl_test COMMAND kheavyhash_opencl_test) -endif() - -else() - -# ============================================================================ -# Integrated into the main miner build. The CPU reference (matrix/keccak/ -# hashers/kheavyhash) compiles into the miner so the AMD resolver can generate -# the per-job matrix host-side. The .cl is deployed to kernel/kheavyhash/, and -# the CPU known-answer tests fold into the main unit_test target. -# ============================================================================ file(GLOB HEADERS "*.hpp") file(GLOB SOURCES "*.cpp") @@ -88,5 +17,3 @@ add_subdirectory(opencl) add_subdirectory(tests) set(SOURCES_ALGO_KHEAVYHASH ${HEADERS} ${SOURCES} PARENT_SCOPE) - -endif() From ef9b1a17151d3401808ee47d6bfd4f7323bd9205 Mon Sep 17 00:00:00 2001 From: luminousminer Date: Mon, 22 Jun 2026 11:04:25 +0200 Subject: [PATCH 2/6] Listing unit tests --- .github/workflows/unit_test_linux.yml | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/unit_test_linux.yml b/.github/workflows/unit_test_linux.yml index 5bc0c7e1..fb04e246 100644 --- a/.github/workflows/unit_test_linux.yml +++ b/.github/workflows/unit_test_linux.yml @@ -36,17 +36,34 @@ jobs: env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + # Explicit allowlist of the hermetic (no-GPU) suites compiled in this + # NVIDIA/AMD/CPU-OFF build. Excludes the GPU device resolvers + # (Resolver*NvidiaTest / Resolver*AmdTest) and the OpenCL/CPU-gated KATs, + # which aren't compiled here. TEST_FILTER: >- AlgoTypeTest.*: + AutolykosV2CpuShare.*: + Bitwise.*: + Blake3Ref.*: EthashTest.*: FastModTest.*: - Fnv.*: + Fnv1Test.*: HashTest.*: - Kiss.*: + KHeavyHashHashers.*: + KHeavyHashMatrix.*: + KHeavyHashPipeline.*: + Kiss99Test.*: MathTest.*: + ProgpowKernelOpenCLTest.*: RotateByteTest.*: - StratumEthashSubmitNonce.*: - StratumProgpowSeed.* + StatisticalHashrate.*: + StratumAutolykosV2NotifyTest.*: + StratumBlake3ProtocolTest.*: + StratumEthashAuthorizeTest.*: + StratumEthashSubmitNonceTest.*: + StratumMath.*: + StratumProgpowSeed.*: + WritePump.* steps: - uses: actions/checkout@v4 From 992f3776958a9ea07222f1d5f02b9a31e2648287 Mon Sep 17 00:00:00 2001 From: luminousminer Date: Mon, 22 Jun 2026 12:48:49 +0200 Subject: [PATCH 3/6] refacto kHeavyHash archi --- sources/algo/kheavyhash/cuda/kheavyhash.cu | 1 + sources/algo/kheavyhash/hashers.cpp | 62 ++-- sources/algo/kheavyhash/hashers.hpp | 17 +- sources/algo/kheavyhash/keccak.cpp | 76 ++-- sources/algo/kheavyhash/keccak.hpp | 13 +- sources/algo/kheavyhash/kheavyhash.cpp | 79 ++-- sources/algo/kheavyhash/kheavyhash.hpp | 18 +- sources/algo/kheavyhash/matrix.cpp | 113 +++--- sources/algo/kheavyhash/matrix.hpp | 14 +- sources/algo/kheavyhash/opencl/CMakeLists.txt | 11 - .../opencl/tests/opencl_kat_test.cpp | 345 ------------------ sources/algo/kheavyhash/result.hpp | 3 - sources/algo/kheavyhash/stratum_math.cpp | 105 +++--- sources/algo/kheavyhash/stratum_math.hpp | 24 +- .../algo/kheavyhash/tests/hashers_test.cpp | 18 +- ...yhash_test_vectors.hpp => kat_vectors.hpp} | 28 +- .../algo/kheavyhash/tests/kheavyhash_test.cpp | 27 +- sources/algo/kheavyhash/tests/matrix_test.cpp | 18 +- .../kheavyhash/tests/stratum_math_test.cpp | 14 +- sources/algo/kheavyhash/types.hpp | 13 +- sources/algo/kheavyhash/xoshiro.hpp | 65 ++-- sources/benchmark/amd/kheavyhash.cpp | 4 +- sources/resolver/amd/kheavyhash.cpp | 4 +- sources/resolver/nvidia/kheavyhash.cpp | 4 +- sources/stratum/kheavyhash.cpp | 4 +- 25 files changed, 360 insertions(+), 720 deletions(-) delete mode 100644 sources/algo/kheavyhash/opencl/tests/opencl_kat_test.cpp rename sources/algo/kheavyhash/tests/{kheavyhash_test_vectors.hpp => kat_vectors.hpp} (98%) diff --git a/sources/algo/kheavyhash/cuda/kheavyhash.cu b/sources/algo/kheavyhash/cuda/kheavyhash.cu index 6405d275..acfbeaa2 100644 --- a/sources/algo/kheavyhash/cuda/kheavyhash.cu +++ b/sources/algo/kheavyhash/cuda/kheavyhash.cu @@ -7,6 +7,7 @@ //////////////////////////////////////////////////////////////////////////////// #include #include +#include #include #include #include diff --git a/sources/algo/kheavyhash/hashers.cpp b/sources/algo/kheavyhash/hashers.cpp index 5440ff45..a0366e06 100644 --- a/sources/algo/kheavyhash/hashers.cpp +++ b/sources/algo/kheavyhash/hashers.cpp @@ -2,9 +2,9 @@ #include -namespace kheavyhash +namespace algo { - namespace + namespace kheavyhash { // Precomputed cSHAKE256 states (domain + trailing pad baked in), copied // verbatim from rusty-kaspa crypto/hashes/src/pow_hashers.rs. @@ -30,7 +30,7 @@ namespace kheavyhash 1592359455985097269ull, }; - inline uint64_t loadLe64(uint8_t const* p) + static uint64_t loadLe64(uint8_t const* p) { uint64_t v{ 0 }; for (int b{ 0 }; b < 8; ++b) @@ -40,7 +40,7 @@ namespace kheavyhash return v; } - inline Hash256 storeLe256(uint64_t const* state) + static Hash256 storeLe256(uint64_t const* state) { Hash256 out{}; for (int w{ 0 }; w < 4; ++w) @@ -52,40 +52,40 @@ namespace kheavyhash } return out; } - } - Hash256 powHash(Hash256 const& prePowHash, uint64_t const timestamp, uint64_t const nonce) - { - uint64_t state[25]; - for (int i{ 0 }; i < 25; ++i) + Hash256 powHash(Hash256 const& prePowHash, uint64_t const timestamp, uint64_t const nonce) { - state[i] = POW_INITIAL_STATE[i]; - } - // message lanes: pre_pow_hash (4 LE words) | timestamp | zero[32] | nonce - for (int w{ 0 }; w < 4; ++w) - { - state[w] ^= loadLe64(prePowHash.data() + w * 8); + uint64_t state[25]; + for (int i{ 0 }; i < 25; ++i) + { + state[i] = POW_INITIAL_STATE[i]; + } + // message lanes: pre_pow_hash (4 LE words) | timestamp | zero[32] | nonce + for (int w{ 0 }; w < 4; ++w) + { + state[w] ^= loadLe64(prePowHash.data() + w * 8); + } + state[4] ^= timestamp; + state[9] ^= nonce; + keccakF1600(state); + return storeLe256(state); } - state[4] ^= timestamp; - state[9] ^= nonce; - keccakF1600(state); - return storeLe256(state); - } - Hash256 kHeavyHash(Hash256 const& input) - { - uint64_t state[25]; - for (int i{ 0 }; i < 25; ++i) + Hash256 kHeavyHash(Hash256 const& input) { - state[i] = HEAVY_INITIAL_STATE[i]; - } - for (int w{ 0 }; w < 4; ++w) - { - state[w] ^= loadLe64(input.data() + w * 8); + uint64_t state[25]; + for (int i{ 0 }; i < 25; ++i) + { + state[i] = HEAVY_INITIAL_STATE[i]; + } + for (int w{ 0 }; w < 4; ++w) + { + state[w] ^= loadLe64(input.data() + w * 8); + } + keccakF1600(state); + return storeLe256(state); } - keccakF1600(state); - return storeLe256(state); } } diff --git a/sources/algo/kheavyhash/hashers.hpp b/sources/algo/kheavyhash/hashers.hpp index dacb2aec..e48fde3c 100644 --- a/sources/algo/kheavyhash/hashers.hpp +++ b/sources/algo/kheavyhash/hashers.hpp @@ -3,13 +3,16 @@ #include -namespace kheavyhash +namespace algo { - // hash1 = cSHAKE256("ProofOfWorkHash") over - // pre_pow_hash[32] || timestamp_u64_LE || zero[32] || nonce_u64_LE - // (rusty-kaspa pow_hashers.rs::PowHash). Output is 32 little-endian bytes. - Hash256 powHash(Hash256 const& prePowHash, uint64_t timestamp, uint64_t nonce); + namespace kheavyhash + { + // hash1 = cSHAKE256("ProofOfWorkHash") over + // pre_pow_hash[32] || timestamp_u64_LE || zero[32] || nonce_u64_LE + // (rusty-kaspa pow_hashers.rs::PowHash). Output is 32 little-endian bytes. + Hash256 powHash(Hash256 const& prePowHash, uint64_t timestamp, uint64_t nonce); - // hash2 step = cSHAKE256("HeavyHash") over 32 bytes (pow_hashers.rs::KHeavyHash). - Hash256 kHeavyHash(Hash256 const& input); + // hash2 step = cSHAKE256("HeavyHash") over 32 bytes (pow_hashers.rs::KHeavyHash). + Hash256 kHeavyHash(Hash256 const& input); + } } diff --git a/sources/algo/kheavyhash/keccak.cpp b/sources/algo/kheavyhash/keccak.cpp index 9c7bec55..17a1b72f 100644 --- a/sources/algo/kheavyhash/keccak.cpp +++ b/sources/algo/kheavyhash/keccak.cpp @@ -1,9 +1,9 @@ #include -namespace kheavyhash +namespace algo { - namespace + namespace kheavyhash { constexpr uint64_t ROUND_CONSTANTS[24]{ 0x0000000000000001ull, 0x0000000000008082ull, 0x800000000000808aull, 0x8000000080008000ull, @@ -21,57 +21,57 @@ namespace kheavyhash 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 }; - inline uint64_t rotl(uint64_t const x, int const k) + static uint64_t rotl(uint64_t const x, int const k) { return (x << k) | (x >> (64 - k)); } - } - void keccakF1600(uint64_t* a) - { - for (int round{ 0 }; round < 24; ++round) + void keccakF1600(uint64_t* a) { - // Theta - uint64_t bc[5]; - for (int i{ 0 }; i < 5; ++i) + for (int round{ 0 }; round < 24; ++round) { - bc[i] = a[i] ^ a[i + 5] ^ a[i + 10] ^ a[i + 15] ^ a[i + 20]; - } - for (int i{ 0 }; i < 5; ++i) - { - uint64_t const t{ bc[(i + 4) % 5] ^ rotl(bc[(i + 1) % 5], 1) }; - for (int j{ 0 }; j < 25; j += 5) + // Theta + uint64_t bc[5]; + for (int i{ 0 }; i < 5; ++i) { - a[j + i] ^= t; + bc[i] = a[i] ^ a[i + 5] ^ a[i + 10] ^ a[i + 15] ^ a[i + 20]; } - } - - // Rho + Pi - uint64_t t{ a[1] }; - for (int i{ 0 }; i < 24; ++i) - { - int const j{ PI_LANE[i] }; - uint64_t const tmp{ a[j] }; - a[j] = rotl(t, ROTATIONS[i]); - t = tmp; - } - - // Chi - for (int j{ 0 }; j < 25; j += 5) - { for (int i{ 0 }; i < 5; ++i) { - bc[i] = a[j + i]; + uint64_t const t{ bc[(i + 4) % 5] ^ rotl(bc[(i + 1) % 5], 1) }; + for (int j{ 0 }; j < 25; j += 5) + { + a[j + i] ^= t; + } } - for (int i{ 0 }; i < 5; ++i) + + // Rho + Pi + uint64_t t{ a[1] }; + for (int i{ 0 }; i < 24; ++i) { - a[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5]; + int const j{ PI_LANE[i] }; + uint64_t const tmp{ a[j] }; + a[j] = rotl(t, ROTATIONS[i]); + t = tmp; } - } - // Iota - a[0] ^= ROUND_CONSTANTS[round]; + // Chi + for (int j{ 0 }; j < 25; j += 5) + { + for (int i{ 0 }; i < 5; ++i) + { + bc[i] = a[j + i]; + } + for (int i{ 0 }; i < 5; ++i) + { + a[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5]; + } + } + + // Iota + a[0] ^= ROUND_CONSTANTS[round]; + } } } } diff --git a/sources/algo/kheavyhash/keccak.hpp b/sources/algo/kheavyhash/keccak.hpp index 58a5387a..35fcc16c 100644 --- a/sources/algo/kheavyhash/keccak.hpp +++ b/sources/algo/kheavyhash/keccak.hpp @@ -3,10 +3,13 @@ #include -namespace kheavyhash +namespace algo { - // Standard Keccak-f[1600] permutation (24 rounds) on a 25-lane state. - // Kaspa's PowHash/KHeavyHash absorb their cSHAKE256 message into a precomputed - // initial state and apply exactly one permutation. - void keccakF1600(uint64_t* state); + namespace kheavyhash + { + // Standard Keccak-f[1600] permutation (24 rounds) on a 25-lane state. + // Kaspa's PowHash/KHeavyHash absorb their cSHAKE256 message into a precomputed + // initial state and apply exactly one permutation. + void keccakF1600(uint64_t* state); + } } diff --git a/sources/algo/kheavyhash/kheavyhash.cpp b/sources/algo/kheavyhash/kheavyhash.cpp index e3abaf37..ec7d60ba 100644 --- a/sources/algo/kheavyhash/kheavyhash.cpp +++ b/sources/algo/kheavyhash/kheavyhash.cpp @@ -3,59 +3,62 @@ #include -namespace kheavyhash +namespace algo { - Hash256 heavyHash(Matrix const& matrix, Hash256 const& hash1) + namespace kheavyhash { - // Expand hash1 to 64 nibbles, high nibble first. - uint16_t vec[64]; - for (int i{ 0 }; i < 32; ++i) + Hash256 heavyHash(Matrix const& matrix, Hash256 const& hash1) { - vec[2 * i] = static_cast(hash1[i] >> 4); - vec[2 * i + 1] = static_cast(hash1[i] & 0x0F); - } + // Expand hash1 to 64 nibbles, high nibble first. + uint16_t vec[64]; + for (int i{ 0 }; i < 32; ++i) + { + vec[2 * i] = static_cast(hash1[i] >> 4); + vec[2 * i + 1] = static_cast(hash1[i] & 0x0F); + } - // Matrix-vector multiply; two rows collapse to one output byte via >>10 nibbles. - Hash256 product{}; - for (int i{ 0 }; i < 32; ++i) - { - uint16_t sum1{ 0 }; - uint16_t sum2{ 0 }; - for (int j{ 0 }; j < 64; ++j) + // Matrix-vector multiply; two rows collapse to one output byte via >>10 nibbles. + Hash256 product{}; + for (int i{ 0 }; i < 32; ++i) { - sum1 = static_cast(sum1 + matrix[2 * i][j] * vec[j]); - sum2 = static_cast(sum2 + matrix[2 * i + 1][j] * vec[j]); + uint16_t sum1{ 0 }; + uint16_t sum2{ 0 }; + for (int j{ 0 }; j < 64; ++j) + { + sum1 = static_cast(sum1 + matrix[2 * i][j] * vec[j]); + sum2 = static_cast(sum2 + matrix[2 * i + 1][j] * vec[j]); + } + product[i] = static_cast(((sum1 >> 10) << 4) | (sum2 >> 10)); } - product[i] = static_cast(((sum1 >> 10) << 4) | (sum2 >> 10)); - } - // XOR with the original hash1 bytes, then KHeavyHash. - for (int i{ 0 }; i < 32; ++i) - { - product[i] ^= hash1[i]; + // XOR with the original hash1 bytes, then KHeavyHash. + for (int i{ 0 }; i < 32; ++i) + { + product[i] ^= hash1[i]; + } + return kHeavyHash(product); } - return kHeavyHash(product); - } - Hash256 calculatePow(Hash256 const& prePowHash, uint64_t const timestamp, uint64_t const nonce) - { - Matrix const matrix{ generateMatrix(prePowHash) }; - Hash256 const hash1{ powHash(prePowHash, timestamp, nonce) }; - return heavyHash(matrix, hash1); - } + Hash256 calculatePow(Hash256 const& prePowHash, uint64_t const timestamp, uint64_t const nonce) + { + Matrix const matrix{ generateMatrix(prePowHash) }; + Hash256 const hash1{ powHash(prePowHash, timestamp, nonce) }; + return heavyHash(matrix, hash1); + } - bool meetsTarget(Hash256 const& powValueLe, Hash256 const& targetLe) - { - // Compare as little-endian 256-bit integers: scan from most-significant byte. - for (int i{ 31 }; i >= 0; --i) + bool meetsTarget(Hash256 const& powValueLe, Hash256 const& targetLe) { - if (powValueLe[i] != targetLe[i]) + // Compare as little-endian 256-bit integers: scan from most-significant byte. + for (int i{ 31 }; i >= 0; --i) { - return powValueLe[i] < targetLe[i]; + if (powValueLe[i] != targetLe[i]) + { + return powValueLe[i] < targetLe[i]; + } } + return true; // equal => pow <= target } - return true; // equal => pow <= target } } diff --git a/sources/algo/kheavyhash/kheavyhash.hpp b/sources/algo/kheavyhash/kheavyhash.hpp index 2e2d413e..5798f9b2 100644 --- a/sources/algo/kheavyhash/kheavyhash.hpp +++ b/sources/algo/kheavyhash/kheavyhash.hpp @@ -3,16 +3,12 @@ #include -namespace kheavyhash +namespace algo { - // Heavy step: expand hash1 to 64 nibbles, matrix-multiply (>>10 to a nibble), - // XOR with hash1, then KHeavyHash. rusty-kaspa matrix.rs::heavy_hash. - Hash256 heavyHash(Matrix const& matrix, Hash256 const& hash1); - - // Full per-nonce PoW: generate matrix from pre_pow_hash, PowHash, heavy step. - // Returns the 32-byte little-endian PoW value. rusty-kaspa lib.rs::calculate_pow. - Hash256 calculatePow(Hash256 const& prePowHash, uint64_t timestamp, uint64_t nonce); - - // Accept test: powValue <= target, both interpreted as little-endian 256-bit ints. - bool meetsTarget(Hash256 const& powValueLe, Hash256 const& targetLe); + namespace kheavyhash + { + Hash256 heavyHash(Matrix const& matrix, Hash256 const& hash1); + Hash256 calculatePow(Hash256 const& prePowHash, uint64_t timestamp, uint64_t nonce); + bool meetsTarget(Hash256 const& powValueLe, Hash256 const& targetLe); + } } diff --git a/sources/algo/kheavyhash/matrix.cpp b/sources/algo/kheavyhash/matrix.cpp index 7e8c817e..ace5eecd 100644 --- a/sources/algo/kheavyhash/matrix.cpp +++ b/sources/algo/kheavyhash/matrix.cpp @@ -4,95 +4,94 @@ #include -namespace kheavyhash +namespace algo { - int computeRank(Matrix const& matrix) + namespace kheavyhash { - // Float Gaussian elimination, mirroring rusty-kaspa matrix.rs::compute_rank. - constexpr double eps{ 1e-9 }; - - double matFloat[MATRIX_DIM][MATRIX_DIM]; - for (size_t i{ 0 }; i < MATRIX_DIM; ++i) + int computeRank(Matrix const& matrix) { - for (size_t j{ 0 }; j < MATRIX_DIM; ++j) - { - matFloat[i][j] = static_cast(matrix[i][j]); - } - } + // Float Gaussian elimination, mirroring rusty-kaspa matrix.rs::compute_rank. + constexpr double eps{ 1e-9 }; - int rank{ 0 }; - bool rowSelected[MATRIX_DIM]{}; - for (size_t i{ 0 }; i < MATRIX_DIM; ++i) - { - size_t j{ 0 }; - while (j < MATRIX_DIM) + double matFloat[MATRIX_DIM][MATRIX_DIM]; + for (size_t i{ 0 }; i < MATRIX_DIM; ++i) { - if (false == rowSelected[j] && std::abs(matFloat[j][i]) > eps) + for (size_t j{ 0 }; j < MATRIX_DIM; ++j) { - break; + matFloat[i][j] = static_cast(matrix[i][j]); } - ++j; } - if (j != MATRIX_DIM) + + int rank{ 0 }; + bool rowSelected[MATRIX_DIM]{}; + for (size_t i{ 0 }; i < MATRIX_DIM; ++i) { - ++rank; - rowSelected[j] = true; - for (size_t p{ i + 1 }; p < MATRIX_DIM; ++p) + size_t j{ 0 }; + while (j < MATRIX_DIM) { - matFloat[j][p] /= matFloat[j][i]; + if (false == rowSelected[j] && std::abs(matFloat[j][i]) > eps) + { + break; + } + ++j; } - for (size_t k{ 0 }; k < MATRIX_DIM; ++k) + if (j != MATRIX_DIM) { - if (k != j && std::abs(matFloat[k][i]) > eps) + ++rank; + rowSelected[j] = true; + for (size_t p{ i + 1 }; p < MATRIX_DIM; ++p) + { + matFloat[j][p] /= matFloat[j][i]; + } + for (size_t k{ 0 }; k < MATRIX_DIM; ++k) { - for (size_t p{ i + 1 }; p < MATRIX_DIM; ++p) + if (k != j && std::abs(matFloat[k][i]) > eps) { - matFloat[k][p] -= matFloat[j][p] * matFloat[k][i]; + for (size_t p{ i + 1 }; p < MATRIX_DIM; ++p) + { + matFloat[k][p] -= matFloat[j][p] * matFloat[k][i]; + } } } } } + return rank; } - return rank; - } - static Matrix randMatrixNoRankCheck(Xoshiro256pp& generator) - { - Matrix mat{}; - for (size_t i{ 0 }; i < MATRIX_DIM; ++i) + static Matrix randMatrixNoRankCheck(Xoshiro256pp& generator) { - uint64_t val{ 0 }; - for (size_t j{ 0 }; j < MATRIX_DIM; ++j) + Matrix mat{}; + for (size_t i{ 0 }; i < MATRIX_DIM; ++i) { - if (0 == j % 16) + uint64_t val{ 0 }; + for (size_t j{ 0 }; j < MATRIX_DIM; ++j) { - val = generator.next(); + if (0 == j % 16) + { + val = generator.next(); + } + mat[i][j] = static_cast((val >> (4 * (j % 16))) & 0x0Full); } - mat[i][j] = static_cast((val >> (4 * (j % 16))) & 0x0Full); } + return mat; } - return mat; - } - Matrix generateMatrix(Hash256 const& seed) - { - // Regenerate from the SAME continuing stream until full rank, per matrix.rs::generate. - // A random nibble matrix is full-rank with overwhelming probability, so this - // converges in 1-2 draws; the bound only guards against a pathological stream - // (e.g. a degenerate seed) turning the retry into an infinite loop. - constexpr int maxAttempts{ 1024 }; - Xoshiro256pp generator{ seed }; - Matrix mat{}; - for (int attempt{ 0 }; attempt < maxAttempts; ++attempt) + Matrix generateMatrix(Hash256 const& seed) { - mat = randMatrixNoRankCheck(generator); - if (static_cast(MATRIX_DIM) == computeRank(mat)) + constexpr int MAX_ATTEMPTS{ 1024 }; + Xoshiro256pp generator{ seed }; + Matrix mat{}; + for (int attempt{ 0 }; attempt < MAX_ATTEMPTS; ++attempt) { - return mat; + mat = randMatrixNoRankCheck(generator); + if (static_cast(MATRIX_DIM) == computeRank(mat)) + { + return mat; + } } + return mat; } - return mat; } } diff --git a/sources/algo/kheavyhash/matrix.hpp b/sources/algo/kheavyhash/matrix.hpp index f74de371..7cdbe70b 100644 --- a/sources/algo/kheavyhash/matrix.hpp +++ b/sources/algo/kheavyhash/matrix.hpp @@ -3,13 +3,11 @@ #include -namespace kheavyhash +namespace algo { - // Rank of the 64x64 matrix over the reals (float Gaussian elimination, EPS 1e-9), - // matching rusty-kaspa matrix.rs::compute_rank. Kaspa rejects matrices with rank < 64. - int computeRank(Matrix const& matrix); - - // Build the 64x64 nibble matrix from a 32-byte seed (pre_pow_hash) via xoshiro256++, - // regenerating (continuing the same stream) until rank == 64. - Matrix generateMatrix(Hash256 const& seed); + namespace kheavyhash + { + int computeRank(Matrix const& matrix); + Matrix generateMatrix(Hash256 const& seed); + } } diff --git a/sources/algo/kheavyhash/opencl/CMakeLists.txt b/sources/algo/kheavyhash/opencl/CMakeLists.txt index 7a04ecb3..71384f07 100644 --- a/sources/algo/kheavyhash/opencl/CMakeLists.txt +++ b/sources/algo/kheavyhash/opencl/CMakeLists.txt @@ -21,14 +21,3 @@ add_custom_target(copy_kheavyhash_opencl_files ALL ${OUT_KHEAVYHASH}/kheavyhash.cl ${OUT_KHEAVYHASH}/kheavyhash_result.cl ) - -if (BUILD_EXE_UNIT_TEST AND BUILD_AMD) - target_sources(${UNIT_TEST_EXE} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/tests/opencl_kat_test.cpp) - target_include_directories(${UNIT_TEST_EXE} PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/../tests - ${OpenCL_INCLUDE_DIRS}) - target_compile_definitions(${UNIT_TEST_EXE} - PRIVATE KH_CL_PATH="${CMAKE_CURRENT_SOURCE_DIR}/kheavyhash.cl" - KH_CL_DIR="${CMAKE_CURRENT_SOURCE_DIR}" - KH_CL_COMMON_DIR="${CMAKE_CURRENT_SOURCE_DIR}/../../../common/opencl") -endif() diff --git a/sources/algo/kheavyhash/opencl/tests/opencl_kat_test.cpp b/sources/algo/kheavyhash/opencl/tests/opencl_kat_test.cpp deleted file mode 100644 index bccee52c..00000000 --- a/sources/algo/kheavyhash/opencl/tests/opencl_kat_test.cpp +++ /dev/null @@ -1,345 +0,0 @@ -// Host harness that runs the kHeavyHash OpenCL kernels on whatever OpenCL device the -// ICD exposes (POCL/CPU in the cross-build & dev harness, a real GPU on the rig) and -// asserts each stage is BIT-IDENTICAL to the CPU reference's known-answer vectors. -// -// The program is assembled and compiled through common::KernelGeneratorOpenCL -- the -// same path the production resolver uses -- instead of a hand-rolled -// clCreateProgramWithSource. The shipped .cl is loaded at runtime (self-contained, no -// inner #include), so there is no second copy of the kernel to drift. CL errors are -// decoded with opencl_error.hpp (openclShowError). - -#include -#include -#include -#include -#include -#include - -#include "kheavyhash_test_vectors.hpp" -#include -#include - -#include -#include -#include -#include - - -using kheavyhash::Hash256; -using kheavyhash::HASH_SIZE; -using kheavyhash::Matrix; -using kheavyhash::MATRIX_DIM; - - -static std::vector flatten(Matrix const& matrix) -{ - std::vector flat{}; - flat.reserve(MATRIX_DIM * MATRIX_DIM); - for (size_t r{ 0 }; r < MATRIX_DIM; ++r) - { - for (size_t c{ 0 }; c < MATRIX_DIM; ++c) - { - flat.push_back(matrix[r][c]); - } - } - return flat; -} - - -// Resolve a .cl by trying each candidate in order; first that opens wins, so the same -// unit_test runs in the dev harness (in-tree source) and on a real GPU (deployed next -// to the binary). Returns "" if none open. -static std::string firstReadable(std::initializer_list candidates) -{ - for (char const* const cand : candidates) - { - if (nullptr != cand) - { - std::ifstream const file{ cand }; - if (file.good()) - { - return cand; - } - } - } - return ""; -} - - -// The search kernel is split across shared helpers (rol_u64, load/store LE), the -// per-algo Result struct, and the kheavyhash body — appended in this order so each -// definition precedes its use, mirroring ResolverAmdKHeavyHash::buildSearch(). -static std::array resolveKernelPaths() -{ - return { - firstReadable({ KH_CL_COMMON_DIR "/rotate_byte.cl", "kernel/common/rotate_byte.cl" }), - firstReadable({ KH_CL_COMMON_DIR "/load_store_le.cl", "kernel/common/load_store_le.cl" }), - firstReadable({ KH_CL_DIR "/kheavyhash_result.cl", "kernel/kheavyhash/kheavyhash_result.cl" }), - firstReadable({ std::getenv("KH_CL_PATH"), KH_CL_PATH, "kernel/kheavyhash/kheavyhash.cl" }), - }; -} - - -// One built program, shared by every test; each test pulls its kernel by name. -class OpenClKat : public ::testing::Test -{ - protected: - static cl::Device device; - static cl::Context context; - static cl::CommandQueue queue; - static common::KernelGeneratorOpenCL generator; - - static void SetUpTestSuite() - { - try - { - std::vector platforms{}; - cl::Platform::get(&platforms); - ASSERT_FALSE(platforms.empty()) << "no OpenCL platform"; - - std::vector devices{}; - platforms[0].getDevices(CL_DEVICE_TYPE_ALL, &devices); - ASSERT_FALSE(devices.empty()) << "no OpenCL device"; - - device = devices[0]; - context = cl::Context(device); - queue = cl::CommandQueue(context, device); - - std::array const clPaths{ resolveKernelPaths() }; - - generator.clear(); - generator.setKernelName("search"); - generator.addDefine("MAX_RESULT", algo::kheavyhash::MAX_RESULT); - for (std::string const& clPath : clPaths) - { - ASSERT_FALSE(clPath.empty()) << "cannot open a kernel source (tried env KH_CL_PATH, the in-tree " - "source dirs, and the deployed kernel/ tree)"; - ASSERT_TRUE(generator.appendFile(clPath)) << "cannot append kernel source: " << clPath; - } - ASSERT_TRUE(generator.build(&device, &context)) << "kernel build failed (see build log above)"; - } - catch (cl::Error const& clErr) - { - FAIL() << openclShowError(clErr.err()) << " - " << clErr.what(); - } - } - - cl::Kernel kernel(char const* name) - { - return cl::Kernel(generator.clProgram, name); - } - - void run(cl::Kernel& k, size_t const global) - { - queue.enqueueNDRangeKernel(k, cl::NullRange, cl::NDRange(global), cl::NullRange); - queue.finish(); - } -}; - -cl::Device OpenClKat::device{}; -cl::Context OpenClKat::context{}; -cl::CommandQueue OpenClKat::queue{}; -common::KernelGeneratorOpenCL OpenClKat::generator{}; - - -static Hash256 toHash(std::array const& a) -{ - Hash256 h{}; - for (size_t i{ 0 }; i < HASH_SIZE; ++i) - { - h[i] = a[i]; - } - return h; -} - - -TEST_F(OpenClKat, powHashMatchesReference) -{ - try - { - std::array pre{}; - for (size_t i{ 0 }; i < HASH_SIZE; ++i) - { - pre[i] = kheavyhash::kat::POW_KAT_PRE[i]; - } - std::array out{}; - - cl::Buffer preBuf{ context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, pre.size(), pre.data() }; - cl::Buffer outBuf{ context, CL_MEM_WRITE_ONLY, out.size() }; - cl::Kernel k{ kernel("test_pow_hash") }; - cl_ulong ts{ kheavyhash::kat::POW_KAT_TIMESTAMP }; - cl_ulong nonce{ kheavyhash::kat::POW_KAT_NONCE }; - k.setArg(0u, preBuf); - k.setArg(1u, ts); - k.setArg(2u, nonce); - k.setArg(3u, outBuf); - run(k, 1); - queue.enqueueReadBuffer(outBuf, CL_TRUE, 0, out.size(), out.data()); - - EXPECT_EQ(out, kheavyhash::kat::POW_KAT_EXPECTED); - } - catch (cl::Error const& clErr) - { - FAIL() << openclShowError(clErr.err()) << " - " << clErr.what(); - } -} - - -TEST_F(OpenClKat, kHeavyHashMatchesReference) -{ - try - { - std::array out{}; - cl::Buffer inBuf{ context, - CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, - kheavyhash::kat::HEAVY_INPUT.size(), - const_cast(kheavyhash::kat::HEAVY_INPUT.data()) }; - cl::Buffer outBuf{ context, CL_MEM_WRITE_ONLY, out.size() }; - cl::Kernel k{ kernel("test_kheavy") }; - k.setArg(0u, inBuf); - k.setArg(1u, outBuf); - run(k, 1); - queue.enqueueReadBuffer(outBuf, CL_TRUE, 0, out.size(), out.data()); - - EXPECT_EQ(out, kheavyhash::kat::KHEAVY_EXPECTED); - } - catch (cl::Error const& clErr) - { - FAIL() << openclShowError(clErr.err()) << " - " << clErr.what(); - } -} - - -TEST_F(OpenClKat, heavyHashMatchesReference) -{ - try - { - std::vector const flat{ flatten(kheavyhash::kat::HEAVY_TEST_MATRIX) }; - std::array out{}; - - cl::Buffer matBuf{ context, - CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, - flat.size() * sizeof(uint16_t), - const_cast(flat.data()) }; - cl::Buffer h1Buf{ context, - CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, - kheavyhash::kat::HEAVY_INPUT.size(), - const_cast(kheavyhash::kat::HEAVY_INPUT.data()) }; - cl::Buffer outBuf{ context, CL_MEM_WRITE_ONLY, out.size() }; - cl::Kernel k{ kernel("test_heavy_hash") }; - k.setArg(0u, matBuf); - k.setArg(1u, h1Buf); - k.setArg(2u, outBuf); - run(k, 1); - queue.enqueueReadBuffer(outBuf, CL_TRUE, 0, out.size(), out.data()); - - EXPECT_EQ(out, kheavyhash::kat::HEAVY_EXPECTED); - } - catch (cl::Error const& clErr) - { - FAIL() << openclShowError(clErr.err()) << " - " << clErr.what(); - } -} - - -// Full per-nonce mining kernel: given the job's matrix/header/target, the work item -// whose nonce reproduces FP_FINAL must report a hit at exactly FP_NONCE, and must NOT -// report one against a target one unit below FP_FINAL. -class SearchKernel : public OpenClKat, public ::testing::WithParamInterface -{ - protected: - // Mirrors the kernel's Result struct (algo::kheavyhash::Result layout). - struct alignas(8) Result - { - bool found{ false }; - uint32_t count{ 0u }; - uint64_t nonces[algo::kheavyhash::MAX_RESULT]{ 0ull, 0ull, 0ull, 0ull }; - }; - - cl_ulong - runSearch(std::array const& target, cl_ulong nonceStart, size_t globalSize, cl_uint& foundCount) - { - Matrix const matrix{ kheavyhash::generateMatrix(toHash(kheavyhash::kat::FP_PRE)) }; - std::vector const flat{ flatten(matrix) }; - - Result result{}; - cl::Buffer matBuf{ context, - CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, - flat.size() * sizeof(uint16_t), - const_cast(flat.data()) }; - cl::Buffer preBuf{ context, - CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, - kheavyhash::kat::FP_PRE.size(), - const_cast(kheavyhash::kat::FP_PRE.data()) }; - cl::Buffer tgtBuf{ context, - CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, - target.size(), - const_cast(target.data()) }; - cl::Buffer resBuf{ context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, sizeof(Result), &result }; - - cl::Kernel k{ kernel(GetParam()) }; - cl_ulong ts{ kheavyhash::kat::FP_TIMESTAMP }; - k.setArg(0u, matBuf); - k.setArg(1u, preBuf); - k.setArg(2u, tgtBuf); - k.setArg(3u, ts); - k.setArg(4u, nonceStart); - k.setArg(5u, resBuf); - run(k, globalSize); - queue.enqueueReadBuffer(resBuf, CL_TRUE, 0, sizeof(Result), &result); - - foundCount = result.count; - return result.nonces[0]; - } -}; - - -// FP_TARGET_PASS == FP_FINAL and FP_TARGET_FAIL == FP_FINAL - 1 (LSB decremented). A -// single work-item at exactly FP_NONCE that passes the first and fails the second pins -// the kernel's end-to-end pow output to FP_FINAL bit-for-bit, and proves the -// hit-reporting (atomic_inc + foundNonce = nonceStart + gid) path. -TEST_P(SearchKernel, reportsHitAtWinningNonce) -{ - try - { - cl_uint count{ 0 }; - cl_ulong const found{ runSearch(kheavyhash::kat::FP_TARGET_PASS, kheavyhash::kat::FP_NONCE, 1, count) }; - - EXPECT_EQ(1u, count); - EXPECT_EQ(kheavyhash::kat::FP_NONCE, found); - } - catch (cl::Error const& clErr) - { - FAIL() << openclShowError(clErr.err()) << " - " << clErr.what(); - } -} - - -TEST_P(SearchKernel, noHitWhenPowExceedsTarget) -{ - try - { - cl_uint count{ 0 }; - runSearch(kheavyhash::kat::FP_TARGET_FAIL, kheavyhash::kat::FP_NONCE, 1, count); - - EXPECT_EQ(0u, count); - } - catch (cl::Error const& clErr) - { - FAIL() << openclShowError(clErr.err()) << " - " << clErr.what(); - } -} - - -// The shipped `search` kernel is pinned bit-identical against the CPU reference: the -// winning-nonce / boundary vectors must hold end-to-end. A drift of a single bit in its -// LDS staging, udot4 matmul, or unrolled keccak fails here. The optimisation variants -// that led to it are exercised by the AMD benchmark. -INSTANTIATE_TEST_SUITE_P( - Search, - SearchKernel, - ::testing::Values("search"), - [](::testing::TestParamInfo const& info) - { - return std::string{ info.param }; - }); diff --git a/sources/algo/kheavyhash/result.hpp b/sources/algo/kheavyhash/result.hpp index 11650e49..38ba02ce 100644 --- a/sources/algo/kheavyhash/result.hpp +++ b/sources/algo/kheavyhash/result.hpp @@ -12,9 +12,6 @@ namespace algo { constexpr uint32_t MAX_RESULT{ 4u }; - // Side length of the square nibble matrix (matrix is MATRIX_DIM x MATRIX_DIM). - constexpr uint32_t MATRIX_DIM{ 64u }; - // One device-side Result per in-flight stream (double-buffering, see // resolver::Resolver::swapIndexStream). The host result cache holds this many. constexpr uint32_t RESULT_BUFFER_COUNT{ 2u }; diff --git a/sources/algo/kheavyhash/stratum_math.cpp b/sources/algo/kheavyhash/stratum_math.cpp index 86bd5225..af3d7566 100644 --- a/sources/algo/kheavyhash/stratum_math.cpp +++ b/sources/algo/kheavyhash/stratum_math.cpp @@ -3,72 +3,75 @@ #include -namespace kheavyhash +namespace algo { - Hash256 prePowFromWords(uint64_t const words[4]) + namespace kheavyhash { - Hash256 out{}; - for (int w{ 0 }; w < 4; ++w) + Hash256 prePowFromWords(uint64_t const words[4]) { - for (int b{ 0 }; b < 8; ++b) + Hash256 out{}; + for (int w{ 0 }; w < 4; ++w) { - out[w * 8 + b] = static_cast((words[w] >> (8 * b)) & 0xFF); + for (int b{ 0 }; b < 8; ++b) + { + out[w * 8 + b] = static_cast((words[w] >> (8 * b)) & 0xFF); + } } + return out; } - return out; - } - Hash256 difficultyToTargetLe(double const diff) - { - Hash256 out{}; - if (diff <= 0.0) + Hash256 difficultyToTargetLe(double const diff) { - return out; // impossible target - } + Hash256 out{}; + if (diff <= 0.0) + { + return out; // impossible target + } - // Quantise diff to Q16.16 so fractional difficulties are honoured while - // the divisor stays <= 2^32 (keeps the long-division remainder in 64 bits). - // Assumes a pool difficulty in (1/65536, 65536), which covers Kaspa testnet. - double const dScaled{ std::round(diff * 65536.0) }; - if (dScaled < 1.0) - { - return out; - } - uint64_t divisor{ static_cast(dScaled) }; - if (divisor > 0xFFFFFFFFull) - { - divisor = 0xFFFFFFFFull; - } + // Quantise diff to Q16.16 so fractional difficulties are honoured while + // the divisor stays <= 2^32 (keeps the long-division remainder in 64 bits). + // Assumes a pool difficulty in (1/65536, 65536), which covers Kaspa testnet. + double const dScaled{ std::round(diff * 65536.0) }; + if (dScaled < 1.0) + { + return out; + } + uint64_t divisor{ static_cast(dScaled) }; + if (divisor > 0xFFFFFFFFull) + { + divisor = 0xFFFFFFFFull; + } - // numerator = (2^224 - 1) << 16, as 8 little-endian 32-bit limbs. - uint32_t maxTarget[8]{ 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, - 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0x00000000u }; - uint32_t num[8]{}; - num[0] = maxTarget[0] << 16; - for (int i{ 1 }; i < 8; ++i) - { - num[i] = static_cast((maxTarget[i] << 16) | (maxTarget[i - 1] >> 16)); - } + // numerator = (2^224 - 1) << 16, as 8 little-endian 32-bit limbs. + uint32_t maxTarget[8]{ 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, + 0xFFFFFFFFu, 0xFFFFFFFFu, 0xFFFFFFFFu, 0x00000000u }; + uint32_t num[8]{}; + num[0] = maxTarget[0] << 16; + for (int i{ 1 }; i < 8; ++i) + { + num[i] = static_cast((maxTarget[i] << 16) | (maxTarget[i - 1] >> 16)); + } - // quotient = numerator / divisor (schoolbook long division, high limb first). - uint32_t quotient[8]{}; - uint64_t remainder{ 0ull }; - for (int i{ 7 }; i >= 0; --i) - { - uint64_t const cur{ (remainder << 32) | static_cast(num[i]) }; - quotient[i] = static_cast(cur / divisor); - remainder = cur % divisor; - } + // quotient = numerator / divisor (schoolbook long division, high limb first). + uint32_t quotient[8]{}; + uint64_t remainder{ 0ull }; + for (int i{ 7 }; i >= 0; --i) + { + uint64_t const cur{ (remainder << 32) | static_cast(num[i]) }; + quotient[i] = static_cast(cur / divisor); + remainder = cur % divisor; + } - // serialise quotient little-endian. - for (int i{ 0 }; i < 8; ++i) - { - for (int b{ 0 }; b < 4; ++b) + // serialise quotient little-endian. + for (int i{ 0 }; i < 8; ++i) { - out[i * 4 + b] = static_cast((quotient[i] >> (8 * b)) & 0xFF); + for (int b{ 0 }; b < 4; ++b) + { + out[i * 4 + b] = static_cast((quotient[i] >> (8 * b)) & 0xFF); + } } + return out; } - return out; } } diff --git a/sources/algo/kheavyhash/stratum_math.hpp b/sources/algo/kheavyhash/stratum_math.hpp index 8b40f5d0..94088b3b 100644 --- a/sources/algo/kheavyhash/stratum_math.hpp +++ b/sources/algo/kheavyhash/stratum_math.hpp @@ -5,23 +5,11 @@ #include -namespace kheavyhash +namespace algo { - // Reconstruct the 32-byte pre-pow hash from the 4 little-endian u64 words a - // Kaspa pool sends in mining.notify. Each word is written back little-endian: - // word w -> bytes[w*8 .. w*8+7]. This is the inverse of the powHash word load. - Hash256 prePowFromWords(uint64_t const words[4]); - - // Convert a stratum difficulty into the 256-bit target the kernel compares - // against (little-endian: out[0] is the least-significant byte). - // - // maxTarget = 2^224 - 1 (kaspa-stratum-bridge hasher.go; 56 hex F's) - // target = floor(maxTarget / diff) - // - // Computed with exact integer long-division (diff is quantised to Q16.16, so - // fractional difficulties down to 1/65536 are honoured). NOTE: the maxTarget - // constant and any per-pool scaling are the single most pool-specific value in - // the protocol and MUST be confirmed against the pool chosen for testing - // before trusting share acceptance (see NOTES.md s4.3). - Hash256 difficultyToTargetLe(double diff); + namespace kheavyhash + { + Hash256 prePowFromWords(uint64_t const words[4]); + Hash256 difficultyToTargetLe(double diff); + } } diff --git a/sources/algo/kheavyhash/tests/hashers_test.cpp b/sources/algo/kheavyhash/tests/hashers_test.cpp index 08580d3c..642e1e7b 100644 --- a/sources/algo/kheavyhash/tests/hashers_test.cpp +++ b/sources/algo/kheavyhash/tests/hashers_test.cpp @@ -1,16 +1,16 @@ -#include "kheavyhash_test_vectors.hpp" #include #include +#include namespace { template - kheavyhash::Hash256 toHash(uint8_t const (&src)[N]) + algo::kheavyhash::Hash256 toHash(uint8_t const (&src)[N]) { static_assert(N == 32); - kheavyhash::Hash256 h{}; + algo::kheavyhash::Hash256 h{}; for (size_t i{ 0 }; i < 32; ++i) { h[i] = src[i]; @@ -23,17 +23,17 @@ namespace // Layer 2: PowHash (cSHAKE256 "ProofOfWorkHash"). Matches rusty-kaspa test_pow_hash inputs. TEST(KHeavyHashHashers, PowHashMatchesKat) { - kheavyhash::Hash256 const pre{ toHash(kheavyhash::kat::POW_KAT_PRE) }; - kheavyhash::Hash256 const out{ - kheavyhash::powHash(pre, kheavyhash::kat::POW_KAT_TIMESTAMP, kheavyhash::kat::POW_KAT_NONCE) + algo::kheavyhash::Hash256 const pre{ toHash(algo::kheavyhash::kat::POW_KAT_PRE) }; + algo::kheavyhash::Hash256 const out{ + algo::kheavyhash::powHash(pre, algo::kheavyhash::kat::POW_KAT_TIMESTAMP, algo::kheavyhash::kat::POW_KAT_NONCE) }; - EXPECT_EQ(out, kheavyhash::kat::POW_KAT_EXPECTED); + EXPECT_EQ(out, algo::kheavyhash::kat::POW_KAT_EXPECTED); } // Layer 2: KHeavyHash (cSHAKE256 "HeavyHash") over 32 bytes. TEST(KHeavyHashHashers, KHeavyHashMatchesKat) { - kheavyhash::Hash256 const out{ kheavyhash::kHeavyHash(kheavyhash::kat::HEAVY_INPUT) }; - EXPECT_EQ(out, kheavyhash::kat::KHEAVY_EXPECTED); + algo::kheavyhash::Hash256 const out{ algo::kheavyhash::kHeavyHash(algo::kheavyhash::kat::HEAVY_INPUT) }; + EXPECT_EQ(out, algo::kheavyhash::kat::KHEAVY_EXPECTED); } diff --git a/sources/algo/kheavyhash/tests/kheavyhash_test_vectors.hpp b/sources/algo/kheavyhash/tests/kat_vectors.hpp similarity index 98% rename from sources/algo/kheavyhash/tests/kheavyhash_test_vectors.hpp rename to sources/algo/kheavyhash/tests/kat_vectors.hpp index a4958841..a8d83fbb 100644 --- a/sources/algo/kheavyhash/tests/kheavyhash_test_vectors.hpp +++ b/sources/algo/kheavyhash/tests/kat_vectors.hpp @@ -1,33 +1,31 @@ -// GENERATED by sources/algo/kheavyhash/dev/oracle.py — DO NOT EDIT BY HAND. -// Known-answer vectors for the kHeavyHash CPU reference. -// Provenance: rusty-kaspa master (matrix.rs / pow_hashers.rs) + pycryptodome cSHAKE256. -#pragma once -#include -#include - -namespace kheavyhash::kat +namespace algo::kheavyhash::kat { - inline constexpr std::array POW_INITIAL_STATE{ - { 0x113cff0da1f6d83dULL, 0x29bf8855b7027e3cULL, 0x1e5f2e720efb44d2ULL, 0x1ba5a4a3f59869a0ULL, + inline constexpr std::array POW_INITIAL_STATE + { + 0x113cff0da1f6d83dULL, 0x29bf8855b7027e3cULL, 0x1e5f2e720efb44d2ULL, 0x1ba5a4a3f59869a0ULL, 0x7b2fafca875e2d65ULL, 0x4aef61d629dce246ULL, 0x183a981ead415b10ULL, 0x776bf60c789bc29cULL, 0xf8ebf13388663140ULL, 0x2e651c3c43285ff0ULL, 0x0f96070540f14a0aULL, 0x44e367875b299152ULL, 0xec70f1a425b13715ULL, 0xe6c85d8f82e9da89ULL, 0xb21a601f85b4b223ULL, 0x3485549064a36a46ULL, 0x0f06dd1c7a2f851aULL, 0xc1a2021d563bb142ULL, 0xba1de5e4451668e4ULL, 0xd102574105095f8dULL, 0x89ca4e849bcecf4aULL, 0x48b09427a8742edbULL, 0xb1fcce9ce78b5272ULL, 0x5d1129cf82afa5bcULL, - 0x02b97c786f824383ULL } + 0x02b97c786f824383ULL }; - inline constexpr std::array HEAVY_INITIAL_STATE{ - { 0x3ad74c52b2248509ULL, 0x79629b0e2f9f4216ULL, 0x7a14ff4816c7f8eeULL, 0x11a75f4c80056498ULL, + constexpr std::array HEAVY_INITIAL_STATE + { + { + 0x3ad74c52b2248509ULL, 0x79629b0e2f9f4216ULL, 0x7a14ff4816c7f8eeULL, 0x11a75f4c80056498ULL, 0xe720e0df44eecedaULL, 0x72c7d82e14f34069ULL, 0xc100ff2a938935baULL, 0x5e219040250fc462ULL, 0x8039f9a60dcf6a48ULL, 0xa0bcaa9f792a3d0cULL, 0xf431c05dd0a9a226ULL, 0xd31f4cc354c18c3fULL, 0x6c6b7d01a769cc3dULL, 0x2ec65bd3562493e4ULL, 0x4ef74b3a99cdb044ULL, 0x774c86835434f2b0ULL, 0x07e961b036bc9416ULL, 0x7e8f1db17765cc07ULL, 0xea8fdb80bac46d39ULL, 0xb992f2d37b34ca58ULL, 0xc776c5048481b957ULL, 0x47c39f675112c22eULL, 0x92bb399db5290c0aULL, 0x549ae0312f9fc615ULL, - 0x1619327d10b9da35ULL } + 0x1619327d10b9da35ULL + } }; - inline constexpr std::array GEN_SEED{ + inline constexpr std::array GEN_SEED + { { 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a } }; diff --git a/sources/algo/kheavyhash/tests/kheavyhash_test.cpp b/sources/algo/kheavyhash/tests/kheavyhash_test.cpp index 295897a2..09a2fc9b 100644 --- a/sources/algo/kheavyhash/tests/kheavyhash_test.cpp +++ b/sources/algo/kheavyhash/tests/kheavyhash_test.cpp @@ -1,33 +1,34 @@ -#include "kheavyhash_test_vectors.hpp" #include #include +#include // Layer 2: heavy step (matrix multiply + XOR + KHeavyHash). // rusty-kaspa matrix.rs::test_heavy_hash literal. -TEST(KHeavyHashPipeline, HeavyHashMatchesReference) +TEST(KHeavyHash, heavyHashMatchesReference) { - kheavyhash::Hash256 const out{ - kheavyhash::heavyHash(kheavyhash::kat::HEAVY_TEST_MATRIX, kheavyhash::kat::HEAVY_INPUT) + algo::kheavyhash::Hash256 const out{ + algo::kheavyhash::heavyHash(algo::kheavyhash::kat::HEAVY_TEST_MATRIX, algo::kheavyhash::kat::HEAVY_INPUT) }; - EXPECT_EQ(out, kheavyhash::kat::HEAVY_EXPECTED); + EXPECT_EQ(out, algo::kheavyhash::kat::HEAVY_EXPECTED); } // Layer 2: full pipeline incl. matrix generation. Oracle-minted (pycryptodome) vector. -TEST(KHeavyHashPipeline, CalculatePowMatchesReference) +TEST(KHeavyHash, calculatePowMatchesReference) { - kheavyhash::Hash256 const out{ - kheavyhash::calculatePow(kheavyhash::kat::FP_PRE, kheavyhash::kat::FP_TIMESTAMP, kheavyhash::kat::FP_NONCE) - }; - EXPECT_EQ(out, kheavyhash::kat::FP_FINAL); + algo::kheavyhash::Hash256 const out{ algo::kheavyhash::calculatePow( + algo::kheavyhash::kat::FP_PRE, + algo::kheavyhash::kat::FP_TIMESTAMP, + algo::kheavyhash::kat::FP_NONCE) }; + EXPECT_EQ(out, algo::kheavyhash::kat::FP_FINAL); } // Accept test: pow <= target (little-endian 256-bit). -TEST(KHeavyHashPipeline, MeetsTargetCompare) +TEST(KHeavyHash, MeetsTargetCompare) { - EXPECT_TRUE(kheavyhash::meetsTarget(kheavyhash::kat::FP_FINAL, kheavyhash::kat::FP_TARGET_PASS)); - EXPECT_FALSE(kheavyhash::meetsTarget(kheavyhash::kat::FP_FINAL, kheavyhash::kat::FP_TARGET_FAIL)); + EXPECT_TRUE(algo::kheavyhash::meetsTarget(algo::kheavyhash::kat::FP_FINAL, algo::kheavyhash::kat::FP_TARGET_PASS)); + EXPECT_FALSE(algo::kheavyhash::meetsTarget(algo::kheavyhash::kat::FP_FINAL, algo::kheavyhash::kat::FP_TARGET_FAIL)); } diff --git a/sources/algo/kheavyhash/tests/matrix_test.cpp b/sources/algo/kheavyhash/tests/matrix_test.cpp index cfe1ee33..684e8184 100644 --- a/sources/algo/kheavyhash/tests/matrix_test.cpp +++ b/sources/algo/kheavyhash/tests/matrix_test.cpp @@ -1,14 +1,14 @@ -#include "kheavyhash_test_vectors.hpp" #include #include +#include namespace { - kheavyhash::Matrix toMatrix(std::array, 64> const& src) + algo::kheavyhash::Matrix toMatrix(std::array, 64> const& src) { - kheavyhash::Matrix m{}; + algo::kheavyhash::Matrix m{}; for (size_t i{ 0 }; i < 64; ++i) { for (size_t j{ 0 }; j < 64; ++j) @@ -24,16 +24,16 @@ namespace // Layer 1: full-rank gate. rusty-kaspa matrix.rs::test_compute_rank. TEST(KHeavyHashMatrix, FullRankMatrixHasRank64) { - kheavyhash::Matrix const m{ toMatrix(kheavyhash::kat::GEN_EXPECTED_MATRIX) }; - EXPECT_EQ(kheavyhash::computeRank(m), 64); + algo::kheavyhash::Matrix const m{ toMatrix(algo::kheavyhash::kat::GEN_EXPECTED_MATRIX) }; + EXPECT_EQ(algo::kheavyhash::computeRank(m), 64); } TEST(KHeavyHashMatrix, DuplicatedRowDropsRank) { - kheavyhash::Matrix m{ toMatrix(kheavyhash::kat::GEN_EXPECTED_MATRIX) }; + algo::kheavyhash::Matrix m{ toMatrix(algo::kheavyhash::kat::GEN_EXPECTED_MATRIX) }; m[0] = m[1]; - EXPECT_EQ(kheavyhash::computeRank(m), 63); + EXPECT_EQ(algo::kheavyhash::computeRank(m), 63); } @@ -41,7 +41,7 @@ TEST(KHeavyHashMatrix, DuplicatedRowDropsRank) // rusty-kaspa matrix.rs::test_generate_matrix (seed = [42; 32]). TEST(KHeavyHashMatrix, GenerateMatchesReference) { - kheavyhash::Matrix const expected{ toMatrix(kheavyhash::kat::GEN_EXPECTED_MATRIX) }; - kheavyhash::Matrix const actual{ kheavyhash::generateMatrix(kheavyhash::kat::GEN_SEED) }; + algo::kheavyhash::Matrix const expected{ toMatrix(algo::kheavyhash::kat::GEN_EXPECTED_MATRIX) }; + algo::kheavyhash::Matrix const actual{ algo::kheavyhash::generateMatrix(algo::kheavyhash::kat::GEN_SEED) }; EXPECT_EQ(actual, expected); } diff --git a/sources/algo/kheavyhash/tests/stratum_math_test.cpp b/sources/algo/kheavyhash/tests/stratum_math_test.cpp index bc34b881..536a6235 100644 --- a/sources/algo/kheavyhash/tests/stratum_math_test.cpp +++ b/sources/algo/kheavyhash/tests/stratum_math_test.cpp @@ -1,7 +1,7 @@ -#include "kheavyhash_test_vectors.hpp" #include #include +#include // 4 little-endian u64 words rebuild the 32-byte pre-pow byte-for-byte. @@ -15,11 +15,11 @@ TEST(StratumMath, PrePowFromWordsRebuildsBytes) 0x1f1e1d1c1b1a1918ull, }; - kheavyhash::Hash256 const pre{ kheavyhash::prePowFromWords(words) }; + algo::kheavyhash::Hash256 const pre{ algo::kheavyhash::prePowFromWords(words) }; for (int i{ 0 }; i < 32; ++i) { - EXPECT_EQ(pre[i], kheavyhash::kat::FP_PRE[i]) << "byte " << i; + EXPECT_EQ(pre[i], algo::kheavyhash::kat::FP_PRE[i]) << "byte " << i; } } @@ -27,7 +27,7 @@ TEST(StratumMath, PrePowFromWordsRebuildsBytes) // diff == 1 => target == maxTarget == 2^224 - 1 == 28 bytes of 0xFF (LE). TEST(StratumMath, DifficultyOneIsMaxTarget) { - kheavyhash::Hash256 const target{ kheavyhash::difficultyToTargetLe(1.0) }; + algo::kheavyhash::Hash256 const target{ algo::kheavyhash::difficultyToTargetLe(1.0) }; for (int i{ 0 }; i < 28; ++i) { @@ -43,7 +43,7 @@ TEST(StratumMath, DifficultyOneIsMaxTarget) // diff == 2 => floor((2^224 - 1) / 2) == 2^223 - 1 == 27 x 0xFF then 0x7F (LE). TEST(StratumMath, DifficultyTwoHalvesTarget) { - kheavyhash::Hash256 const target{ kheavyhash::difficultyToTargetLe(2.0) }; + algo::kheavyhash::Hash256 const target{ algo::kheavyhash::difficultyToTargetLe(2.0) }; for (int i{ 0 }; i < 27; ++i) { @@ -60,8 +60,8 @@ TEST(StratumMath, DifficultyTwoHalvesTarget) // Higher difficulty => smaller target (compare as little-endian 256-bit ints). TEST(StratumMath, HigherDifficultyShrinksTarget) { - kheavyhash::Hash256 const t1{ kheavyhash::difficultyToTargetLe(1.0) }; - kheavyhash::Hash256 const t16{ kheavyhash::difficultyToTargetLe(16.0) }; + algo::kheavyhash::Hash256 const t1{ algo::kheavyhash::difficultyToTargetLe(1.0) }; + algo::kheavyhash::Hash256 const t16{ algo::kheavyhash::difficultyToTargetLe(16.0) }; bool t16Smaller{ false }; for (int i{ 31 }; i >= 0; --i) diff --git a/sources/algo/kheavyhash/types.hpp b/sources/algo/kheavyhash/types.hpp index 2a25cacc..07387e2c 100644 --- a/sources/algo/kheavyhash/types.hpp +++ b/sources/algo/kheavyhash/types.hpp @@ -8,11 +8,14 @@ // Kept free of repo/Boost/CUDA headers so the correctness oracle builds and runs // in a minimal toolchain. GPU/stratum wiring in a later session adapts these to // the repo's algo::hash256 etc. -namespace kheavyhash +namespace algo { - constexpr std::size_t HASH_SIZE{ 32u }; - constexpr std::size_t MATRIX_DIM{ 64u }; + namespace kheavyhash + { + constexpr std::size_t HASH_SIZE{ 32u }; + constexpr std::size_t MATRIX_DIM{ 64u }; - using Hash256 = std::array; - using Matrix = std::array, MATRIX_DIM>; + using Hash256 = std::array; + using Matrix = std::array, MATRIX_DIM>; + } } diff --git a/sources/algo/kheavyhash/xoshiro.hpp b/sources/algo/kheavyhash/xoshiro.hpp index fc8d3c98..3d56f0eb 100644 --- a/sources/algo/kheavyhash/xoshiro.hpp +++ b/sources/algo/kheavyhash/xoshiro.hpp @@ -3,45 +3,48 @@ #include -namespace kheavyhash +namespace algo { - // xoshiro256++ PRNG, seeded from a 32-byte hash read as 4 little-endian u64 words. - // Mirrors rusty-kaspa xoshiro.rs::XoShiRo256PlusPlus. - class Xoshiro256pp + namespace kheavyhash { - public: - explicit Xoshiro256pp(Hash256 const& seed) + // xoshiro256++ PRNG, seeded from a 32-byte hash read as 4 little-endian u64 words. + // Mirrors rusty-kaspa xoshiro.rs::XoShiRo256PlusPlus. + class Xoshiro256pp { - for (int i{ 0 }; i < 4; ++i) + public: + explicit Xoshiro256pp(Hash256 const& seed) { - uint64_t word{ 0 }; - for (int b{ 0 }; b < 8; ++b) + for (int i{ 0 }; i < 4; ++i) { - word |= static_cast(seed[i * 8 + b]) << (8 * b); + uint64_t word{ 0 }; + for (int b{ 0 }; b < 8; ++b) + { + word |= static_cast(seed[i * 8 + b]) << (8 * b); + } + s[i] = word; } - s[i] = word; } - } - uint64_t next() - { - uint64_t const res{ s[0] + rotl(s[0] + s[3], 23) }; - uint64_t const t{ s[1] << 17 }; - s[2] ^= s[0]; - s[3] ^= s[1]; - s[1] ^= s[2]; - s[0] ^= s[3]; - s[2] ^= t; - s[3] = rotl(s[3], 45); - return res; - } + uint64_t next() + { + uint64_t const res{ s[0] + rotl(s[0] + s[3], 23) }; + uint64_t const t{ s[1] << 17 }; + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + s[2] ^= t; + s[3] = rotl(s[3], 45); + return res; + } - private: - static uint64_t rotl(uint64_t const x, int const k) - { - return (x << k) | (x >> (64 - k)); - } + private: + static uint64_t rotl(uint64_t const x, int const k) + { + return (x << k) | (x >> (64 - k)); + } - uint64_t s[4]{}; - }; + uint64_t s[4]{}; + }; + } } diff --git a/sources/benchmark/amd/kheavyhash.cpp b/sources/benchmark/amd/kheavyhash.cpp index f7b75df1..ea194f3f 100644 --- a/sources/benchmark/amd/kheavyhash.cpp +++ b/sources/benchmark/amd/kheavyhash.cpp @@ -46,12 +46,12 @@ bool benchmark::BenchmarkWorkflow::runAmdKHeavyHash() //////////////////////////////////////////////////////////////////////////// // Host-side matrix generation (xoshiro256++ + full-rank gate) -- the CPU // reference the kernel is gated bit-identical against. - ::kheavyhash::Hash256 seed{}; + algo::kheavyhash::Hash256 seed{}; for (uint32_t i{ 0u }; i < 32u; ++i) { seed[i] = headerHash.ubytes[i]; } - ::kheavyhash::Matrix const matrix{ ::kheavyhash::generateMatrix(seed) }; + algo::kheavyhash::Matrix const matrix{ algo::kheavyhash::generateMatrix(seed) }; std::array flatMatrix{}; for (uint32_t r{ 0u }; r < 64u; ++r) { diff --git a/sources/resolver/amd/kheavyhash.cpp b/sources/resolver/amd/kheavyhash.cpp index 2f9ac68c..1b2f54f9 100644 --- a/sources/resolver/amd/kheavyhash.cpp +++ b/sources/resolver/amd/kheavyhash.cpp @@ -76,12 +76,12 @@ bool resolver::ResolverAmdKHeavyHash::updateConstants(stratum::StratumJobInfo co // Host-side matrix generation from the pre-pow header (xoshiro256++ + // full-rank gate). This is the CPU reference that the kernel is gated // bit-identical against; it never runs on the GPU. - ::kheavyhash::Hash256 seed{}; + algo::kheavyhash::Hash256 seed{}; for (uint32_t i{ 0u }; i < 32u; ++i) { seed[i] = jobInfo.headerHash.ubytes[i]; } - ::kheavyhash::Matrix const matrix{ ::kheavyhash::generateMatrix(seed) }; + algo::kheavyhash::Matrix const matrix{ algo::kheavyhash::generateMatrix(seed) }; std::array flat{}; for (uint32_t r{ 0u }; r < 64u; ++r) diff --git a/sources/resolver/nvidia/kheavyhash.cpp b/sources/resolver/nvidia/kheavyhash.cpp index 1795d716..3ad28176 100644 --- a/sources/resolver/nvidia/kheavyhash.cpp +++ b/sources/resolver/nvidia/kheavyhash.cpp @@ -43,12 +43,12 @@ bool resolver::ResolverNvidiaKHeavyHash::updateConstants(stratum::StratumJobInfo //////////////////////////////////////////////////////////////////////////// // Host-side matrix generation (xoshiro256++ + full-rank gate) from the pre-pow // header -- the CPU reference the kernel is gated bit-identical against. - ::kheavyhash::Hash256 seed{}; + algo::kheavyhash::Hash256 seed{}; for (uint32_t i{ 0u }; i < 32u; ++i) { seed[i] = jobInfo.headerHash.ubytes[i]; } - ::kheavyhash::Matrix const matrix{ ::kheavyhash::generateMatrix(seed) }; + algo::kheavyhash::Matrix const matrix{ algo::kheavyhash::generateMatrix(seed) }; for (uint32_t r{ 0u }; r < 64u; ++r) { for (uint32_t c{ 0u }; c < 64u; ++c) diff --git a/sources/stratum/kheavyhash.cpp b/sources/stratum/kheavyhash.cpp index 1c1a2fa6..25b6a5f0 100644 --- a/sources/stratum/kheavyhash.cpp +++ b/sources/stratum/kheavyhash.cpp @@ -105,7 +105,7 @@ void stratum::StratumKHeavyHash::onMiningNotify(boost::json::object const& root) { prePowWords[i] = common::boostJsonGetNumber(words.at(i)); } - kheavyhash::Hash256 const prePow{ kheavyhash::prePowFromWords(prePowWords) }; + algo::kheavyhash::Hash256 const prePow{ algo::kheavyhash::prePowFromWords(prePowWords) }; for (uint32_t i{ 0u }; i < 32u; ++i) { jobInfo.headerHash.ubytes[i] = prePow[i]; @@ -170,7 +170,7 @@ void stratum::StratumKHeavyHash::onMiningSetDifficulty(boost::json::object const boost::json::array const& params(root.at("params").as_array()); double const difficulty{ common::boostJsonGetNumber(params.at(0)) }; - kheavyhash::Hash256 const target{ kheavyhash::difficultyToTargetLe(difficulty) }; + algo::kheavyhash::Hash256 const target{ algo::kheavyhash::difficultyToTargetLe(difficulty) }; for (uint32_t i{ 0u }; i < 32u; ++i) { jobInfo.boundary.ubytes[i] = target[i]; From 2bd1d5927f085e332cc08e0b4a2f1d0c1963a4f6 Mon Sep 17 00:00:00 2001 From: luminousminer Date: Mon, 22 Jun 2026 14:12:27 +0200 Subject: [PATCH 4/6] refacto kHeavyHash archi --- sources/algo/bitwise.hpp | 10 + sources/algo/hash_utils.hpp | 13 + sources/algo/keccak.cpp | 226 +----------------- sources/algo/keccak.hpp | 223 +++++++++++++++++ sources/algo/kheavyhash/hashers.hpp | 18 -- sources/algo/kheavyhash/keccak.cpp | 77 ------ sources/algo/kheavyhash/keccak.hpp | 15 -- sources/algo/kheavyhash/kheavyhash.cpp | 64 ----- sources/algo/kheavyhash/kheavyhash.hpp | 14 -- .../{kheavyhash_test.cpp => heavyHash.cpp} | 20 +- .../{hashers.cpp => tests/helper.hpp} | 94 +++++--- sources/algo/kheavyhash/tests/kHeavyHash.cpp | 12 + .../tests/{matrix_test.cpp => matrix.cpp} | 0 .../tests/{hashers_test.cpp => powHash.cpp} | 12 +- ...stratum_math_test.cpp => stratum_math.cpp} | 0 sources/algo/tests/hash.cpp | 29 +++ 16 files changed, 361 insertions(+), 466 deletions(-) delete mode 100644 sources/algo/kheavyhash/hashers.hpp delete mode 100644 sources/algo/kheavyhash/keccak.cpp delete mode 100644 sources/algo/kheavyhash/keccak.hpp delete mode 100644 sources/algo/kheavyhash/kheavyhash.cpp delete mode 100644 sources/algo/kheavyhash/kheavyhash.hpp rename sources/algo/kheavyhash/tests/{kheavyhash_test.cpp => heavyHash.cpp} (55%) rename sources/algo/kheavyhash/{hashers.cpp => tests/helper.hpp} (52%) create mode 100644 sources/algo/kheavyhash/tests/kHeavyHash.cpp rename sources/algo/kheavyhash/tests/{matrix_test.cpp => matrix.cpp} (100%) rename sources/algo/kheavyhash/tests/{hashers_test.cpp => powHash.cpp} (68%) rename sources/algo/kheavyhash/tests/{stratum_math_test.cpp => stratum_math.cpp} (100%) diff --git a/sources/algo/bitwise.hpp b/sources/algo/bitwise.hpp index e8546017..432c54cd 100644 --- a/sources/algo/bitwise.hpp +++ b/sources/algo/bitwise.hpp @@ -57,6 +57,16 @@ namespace algo return le::uint64(word); } + // Little-endian store of 4 u64 words into a 32-byte buffer. + inline void store256(uint8_t* const data, uint64_t const* const words) + { + for (uint32_t w{ 0u }; w < 4u; ++w) + { + uint64_t const word{ le::uint64(words[w]) }; + __builtin_memcpy(data + w * sizeof(uint64_t), &word, sizeof(uint64_t)); + } + } + inline hash1024 const& uint32(algo::hash1024 const& h) { return h; diff --git a/sources/algo/hash_utils.hpp b/sources/algo/hash_utils.hpp index 44dadc90..d2181d4f 100644 --- a/sources/algo/hash_utils.hpp +++ b/sources/algo/hash_utils.hpp @@ -86,6 +86,19 @@ namespace algo return true; } + template + inline bool isLowerOrEqualLE(T const& value, T const& target) + { + for (int64_t i{ cast64(sizeof(T)) - 1 }; i >= 0; --i) + { + if (value.ubytes[i] != target.ubytes[i]) + { + return value.ubytes[i] < target.ubytes[i]; + } + } + return true; + } + template inline void copyHash(T& dst, T const& src) { diff --git a/sources/algo/keccak.cpp b/sources/algo/keccak.cpp index 63533fe0..46fade30 100644 --- a/sources/algo/keccak.cpp +++ b/sources/algo/keccak.cpp @@ -9,228 +9,6 @@ constexpr uint32_t F800_NUMBER_ROUND{ 22u }; -static inline void keccak_f1600(uint64_t* const state) -{ - constexpr uint64_t roundConstants[24]{ - 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, 0x000000000000808b, - 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, 0x0000000000000088, - 0x0000000080008009, 0x000000008000000a, 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, - 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, - 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 - }; - - uint64_t Aba, Abe, Abi, Abo, Abu; - uint64_t Aga, Age, Agi, Ago, Agu; - uint64_t Aka, Ake, Aki, Ako, Aku; - uint64_t Ama, Ame, Ami, Amo, Amu; - uint64_t Asa, Ase, Asi, Aso, Asu; - - uint64_t Eba, Ebe, Ebi, Ebo, Ebu; - uint64_t Ega, Ege, Egi, Ego, Egu; - uint64_t Eka, Eke, Eki, Eko, Eku; - uint64_t Ema, Eme, Emi, Emo, Emu; - uint64_t Esa, Ese, Esi, Eso, Esu; - - uint64_t Ba, Be, Bi, Bo, Bu; - - uint64_t Da, De, Di, Do, Du; - - Aba = state[0]; - Abe = state[1]; - Abi = state[2]; - Abo = state[3]; - Abu = state[4]; - Aga = state[5]; - Age = state[6]; - Agi = state[7]; - Ago = state[8]; - Agu = state[9]; - Aka = state[10]; - Ake = state[11]; - Aki = state[12]; - Ako = state[13]; - Aku = state[14]; - Ama = state[15]; - Ame = state[16]; - Ami = state[17]; - Amo = state[18]; - Amu = state[19]; - Asa = state[20]; - Ase = state[21]; - Asi = state[22]; - Aso = state[23]; - Asu = state[24]; - - for (size_t n = 0; n < 24; n += 2) - { - // Round (n + 0): Axx -> Exx - - Ba = Aba ^ Aga ^ Aka ^ Ama ^ Asa; - Be = Abe ^ Age ^ Ake ^ Ame ^ Ase; - Bi = Abi ^ Agi ^ Aki ^ Ami ^ Asi; - Bo = Abo ^ Ago ^ Ako ^ Amo ^ Aso; - Bu = Abu ^ Agu ^ Aku ^ Amu ^ Asu; - - Da = Bu ^ algo::rol_u64(Be, 1); - De = Ba ^ algo::rol_u64(Bi, 1); - Di = Be ^ algo::rol_u64(Bo, 1); - Do = Bi ^ algo::rol_u64(Bu, 1); - Du = Bo ^ algo::rol_u64(Ba, 1); - - Ba = Aba ^ Da; - Be = algo::rol_u64(Age ^ De, 44); - Bi = algo::rol_u64(Aki ^ Di, 43); - Bo = algo::rol_u64(Amo ^ Do, 21); - Bu = algo::rol_u64(Asu ^ Du, 14); - Eba = Ba ^ (~Be & Bi) ^ roundConstants[n]; - Ebe = Be ^ (~Bi & Bo); - Ebi = Bi ^ (~Bo & Bu); - Ebo = Bo ^ (~Bu & Ba); - Ebu = Bu ^ (~Ba & Be); - - Ba = algo::rol_u64(Abo ^ Do, 28); - Be = algo::rol_u64(Agu ^ Du, 20); - Bi = algo::rol_u64(Aka ^ Da, 3); - Bo = algo::rol_u64(Ame ^ De, 45); - Bu = algo::rol_u64(Asi ^ Di, 61); - Ega = Ba ^ (~Be & Bi); - Ege = Be ^ (~Bi & Bo); - Egi = Bi ^ (~Bo & Bu); - Ego = Bo ^ (~Bu & Ba); - Egu = Bu ^ (~Ba & Be); - - Ba = algo::rol_u64(Abe ^ De, 1); - Be = algo::rol_u64(Agi ^ Di, 6); - Bi = algo::rol_u64(Ako ^ Do, 25); - Bo = algo::rol_u64(Amu ^ Du, 8); - Bu = algo::rol_u64(Asa ^ Da, 18); - Eka = Ba ^ (~Be & Bi); - Eke = Be ^ (~Bi & Bo); - Eki = Bi ^ (~Bo & Bu); - Eko = Bo ^ (~Bu & Ba); - Eku = Bu ^ (~Ba & Be); - - Ba = algo::rol_u64(Abu ^ Du, 27); - Be = algo::rol_u64(Aga ^ Da, 36); - Bi = algo::rol_u64(Ake ^ De, 10); - Bo = algo::rol_u64(Ami ^ Di, 15); - Bu = algo::rol_u64(Aso ^ Do, 56); - Ema = Ba ^ (~Be & Bi); - Eme = Be ^ (~Bi & Bo); - Emi = Bi ^ (~Bo & Bu); - Emo = Bo ^ (~Bu & Ba); - Emu = Bu ^ (~Ba & Be); - - Ba = algo::rol_u64(Abi ^ Di, 62); - Be = algo::rol_u64(Ago ^ Do, 55); - Bi = algo::rol_u64(Aku ^ Du, 39); - Bo = algo::rol_u64(Ama ^ Da, 41); - Bu = algo::rol_u64(Ase ^ De, 2); - Esa = Ba ^ (~Be & Bi); - Ese = Be ^ (~Bi & Bo); - Esi = Bi ^ (~Bo & Bu); - Eso = Bo ^ (~Bu & Ba); - Esu = Bu ^ (~Ba & Be); - - - // Round (n + 1): Exx -> Axx - - Ba = Eba ^ Ega ^ Eka ^ Ema ^ Esa; - Be = Ebe ^ Ege ^ Eke ^ Eme ^ Ese; - Bi = Ebi ^ Egi ^ Eki ^ Emi ^ Esi; - Bo = Ebo ^ Ego ^ Eko ^ Emo ^ Eso; - Bu = Ebu ^ Egu ^ Eku ^ Emu ^ Esu; - - Da = Bu ^ algo::rol_u64(Be, 1); - De = Ba ^ algo::rol_u64(Bi, 1); - Di = Be ^ algo::rol_u64(Bo, 1); - Do = Bi ^ algo::rol_u64(Bu, 1); - Du = Bo ^ algo::rol_u64(Ba, 1); - - Ba = Eba ^ Da; - Be = algo::rol_u64(Ege ^ De, 44); - Bi = algo::rol_u64(Eki ^ Di, 43); - Bo = algo::rol_u64(Emo ^ Do, 21); - Bu = algo::rol_u64(Esu ^ Du, 14); - Aba = Ba ^ (~Be & Bi) ^ roundConstants[n + 1]; - Abe = Be ^ (~Bi & Bo); - Abi = Bi ^ (~Bo & Bu); - Abo = Bo ^ (~Bu & Ba); - Abu = Bu ^ (~Ba & Be); - - Ba = algo::rol_u64(Ebo ^ Do, 28); - Be = algo::rol_u64(Egu ^ Du, 20); - Bi = algo::rol_u64(Eka ^ Da, 3); - Bo = algo::rol_u64(Eme ^ De, 45); - Bu = algo::rol_u64(Esi ^ Di, 61); - Aga = Ba ^ (~Be & Bi); - Age = Be ^ (~Bi & Bo); - Agi = Bi ^ (~Bo & Bu); - Ago = Bo ^ (~Bu & Ba); - Agu = Bu ^ (~Ba & Be); - - Ba = algo::rol_u64(Ebe ^ De, 1); - Be = algo::rol_u64(Egi ^ Di, 6); - Bi = algo::rol_u64(Eko ^ Do, 25); - Bo = algo::rol_u64(Emu ^ Du, 8); - Bu = algo::rol_u64(Esa ^ Da, 18); - Aka = Ba ^ (~Be & Bi); - Ake = Be ^ (~Bi & Bo); - Aki = Bi ^ (~Bo & Bu); - Ako = Bo ^ (~Bu & Ba); - Aku = Bu ^ (~Ba & Be); - - Ba = algo::rol_u64(Ebu ^ Du, 27); - Be = algo::rol_u64(Ega ^ Da, 36); - Bi = algo::rol_u64(Eke ^ De, 10); - Bo = algo::rol_u64(Emi ^ Di, 15); - Bu = algo::rol_u64(Eso ^ Do, 56); - Ama = Ba ^ (~Be & Bi); - Ame = Be ^ (~Bi & Bo); - Ami = Bi ^ (~Bo & Bu); - Amo = Bo ^ (~Bu & Ba); - Amu = Bu ^ (~Ba & Be); - - Ba = algo::rol_u64(Ebi ^ Di, 62); - Be = algo::rol_u64(Ego ^ Do, 55); - Bi = algo::rol_u64(Eku ^ Du, 39); - Bo = algo::rol_u64(Ema ^ Da, 41); - Bu = algo::rol_u64(Ese ^ De, 2); - Asa = Ba ^ (~Be & Bi); - Ase = Be ^ (~Bi & Bo); - Asi = Bi ^ (~Bo & Bu); - Aso = Bo ^ (~Bu & Ba); - Asu = Bu ^ (~Ba & Be); - } - - state[0] = Aba; - state[1] = Abe; - state[2] = Abi; - state[3] = Abo; - state[4] = Abu; - state[5] = Aga; - state[6] = Age; - state[7] = Agi; - state[8] = Ago; - state[9] = Agu; - state[10] = Aka; - state[11] = Ake; - state[12] = Aki; - state[13] = Ako; - state[14] = Aku; - state[15] = Ama; - state[16] = Ame; - state[17] = Ami; - state[18] = Amo; - state[19] = Amu; - state[20] = Asa; - state[21] = Ase; - state[22] = Asi; - state[23] = Aso; - state[24] = Asu; -} - - void algo::keccak(uint64_t* const out, uint32_t bits, uint8_t const* data, uint32_t size) { constexpr size_t wordSize{ sizeof(uint64_t) }; @@ -250,7 +28,7 @@ void algo::keccak(uint64_t* const out, uint32_t bits, uint8_t const* data, uint3 state[i] ^= le::uint64(data); data += wordSize; } - keccak_f1600(state); + keccakF1600(state); size -= blockSize; } @@ -276,7 +54,7 @@ void algo::keccak(uint64_t* const out, uint32_t bits, uint8_t const* data, uint3 state[(blockSize / wordSize) - 1] ^= 0x8000000000000000; - keccak_f1600(state); + keccakF1600(state); uint32_t const maxIndex{ hashSize / castU32(wordSize) }; for (uint32_t i{ 0u }; i < maxIndex; ++i) diff --git a/sources/algo/keccak.hpp b/sources/algo/keccak.hpp index 0bfe5313..0d7d845f 100644 --- a/sources/algo/keccak.hpp +++ b/sources/algo/keccak.hpp @@ -3,12 +3,235 @@ #include #include +#include namespace algo { void keccak(uint64_t* const out, uint32_t bits, uint8_t const* data, uint32_t size); + inline void keccakF1600(uint64_t* const state) + { + constexpr uint64_t roundConstants[24] + { + 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, 0x000000000000808b, + 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, 0x0000000000000088, + 0x0000000080008009, 0x000000008000000a, 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, + 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, + 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 + }; + + uint64_t Aba, Abe, Abi, Abo, Abu; + uint64_t Aga, Age, Agi, Ago, Agu; + uint64_t Aka, Ake, Aki, Ako, Aku; + uint64_t Ama, Ame, Ami, Amo, Amu; + uint64_t Asa, Ase, Asi, Aso, Asu; + + uint64_t Eba, Ebe, Ebi, Ebo, Ebu; + uint64_t Ega, Ege, Egi, Ego, Egu; + uint64_t Eka, Eke, Eki, Eko, Eku; + uint64_t Ema, Eme, Emi, Emo, Emu; + uint64_t Esa, Ese, Esi, Eso, Esu; + + uint64_t Ba, Be, Bi, Bo, Bu; + + uint64_t Da, De, Di, Do, Du; + + Aba = state[0]; + Abe = state[1]; + Abi = state[2]; + Abo = state[3]; + Abu = state[4]; + Aga = state[5]; + Age = state[6]; + Agi = state[7]; + Ago = state[8]; + Agu = state[9]; + Aka = state[10]; + Ake = state[11]; + Aki = state[12]; + Ako = state[13]; + Aku = state[14]; + Ama = state[15]; + Ame = state[16]; + Ami = state[17]; + Amo = state[18]; + Amu = state[19]; + Asa = state[20]; + Ase = state[21]; + Asi = state[22]; + Aso = state[23]; + Asu = state[24]; + + for (size_t n = 0; n < 24; n += 2) + { + // Round (n + 0): Axx -> Exx + + Ba = Aba ^ Aga ^ Aka ^ Ama ^ Asa; + Be = Abe ^ Age ^ Ake ^ Ame ^ Ase; + Bi = Abi ^ Agi ^ Aki ^ Ami ^ Asi; + Bo = Abo ^ Ago ^ Ako ^ Amo ^ Aso; + Bu = Abu ^ Agu ^ Aku ^ Amu ^ Asu; + + Da = Bu ^ algo::rol_u64(Be, 1); + De = Ba ^ algo::rol_u64(Bi, 1); + Di = Be ^ algo::rol_u64(Bo, 1); + Do = Bi ^ algo::rol_u64(Bu, 1); + Du = Bo ^ algo::rol_u64(Ba, 1); + + Ba = Aba ^ Da; + Be = algo::rol_u64(Age ^ De, 44); + Bi = algo::rol_u64(Aki ^ Di, 43); + Bo = algo::rol_u64(Amo ^ Do, 21); + Bu = algo::rol_u64(Asu ^ Du, 14); + Eba = Ba ^ (~Be & Bi) ^ roundConstants[n]; + Ebe = Be ^ (~Bi & Bo); + Ebi = Bi ^ (~Bo & Bu); + Ebo = Bo ^ (~Bu & Ba); + Ebu = Bu ^ (~Ba & Be); + + Ba = algo::rol_u64(Abo ^ Do, 28); + Be = algo::rol_u64(Agu ^ Du, 20); + Bi = algo::rol_u64(Aka ^ Da, 3); + Bo = algo::rol_u64(Ame ^ De, 45); + Bu = algo::rol_u64(Asi ^ Di, 61); + Ega = Ba ^ (~Be & Bi); + Ege = Be ^ (~Bi & Bo); + Egi = Bi ^ (~Bo & Bu); + Ego = Bo ^ (~Bu & Ba); + Egu = Bu ^ (~Ba & Be); + + Ba = algo::rol_u64(Abe ^ De, 1); + Be = algo::rol_u64(Agi ^ Di, 6); + Bi = algo::rol_u64(Ako ^ Do, 25); + Bo = algo::rol_u64(Amu ^ Du, 8); + Bu = algo::rol_u64(Asa ^ Da, 18); + Eka = Ba ^ (~Be & Bi); + Eke = Be ^ (~Bi & Bo); + Eki = Bi ^ (~Bo & Bu); + Eko = Bo ^ (~Bu & Ba); + Eku = Bu ^ (~Ba & Be); + + Ba = algo::rol_u64(Abu ^ Du, 27); + Be = algo::rol_u64(Aga ^ Da, 36); + Bi = algo::rol_u64(Ake ^ De, 10); + Bo = algo::rol_u64(Ami ^ Di, 15); + Bu = algo::rol_u64(Aso ^ Do, 56); + Ema = Ba ^ (~Be & Bi); + Eme = Be ^ (~Bi & Bo); + Emi = Bi ^ (~Bo & Bu); + Emo = Bo ^ (~Bu & Ba); + Emu = Bu ^ (~Ba & Be); + + Ba = algo::rol_u64(Abi ^ Di, 62); + Be = algo::rol_u64(Ago ^ Do, 55); + Bi = algo::rol_u64(Aku ^ Du, 39); + Bo = algo::rol_u64(Ama ^ Da, 41); + Bu = algo::rol_u64(Ase ^ De, 2); + Esa = Ba ^ (~Be & Bi); + Ese = Be ^ (~Bi & Bo); + Esi = Bi ^ (~Bo & Bu); + Eso = Bo ^ (~Bu & Ba); + Esu = Bu ^ (~Ba & Be); + + + // Round (n + 1): Exx -> Axx + + Ba = Eba ^ Ega ^ Eka ^ Ema ^ Esa; + Be = Ebe ^ Ege ^ Eke ^ Eme ^ Ese; + Bi = Ebi ^ Egi ^ Eki ^ Emi ^ Esi; + Bo = Ebo ^ Ego ^ Eko ^ Emo ^ Eso; + Bu = Ebu ^ Egu ^ Eku ^ Emu ^ Esu; + + Da = Bu ^ algo::rol_u64(Be, 1); + De = Ba ^ algo::rol_u64(Bi, 1); + Di = Be ^ algo::rol_u64(Bo, 1); + Do = Bi ^ algo::rol_u64(Bu, 1); + Du = Bo ^ algo::rol_u64(Ba, 1); + + Ba = Eba ^ Da; + Be = algo::rol_u64(Ege ^ De, 44); + Bi = algo::rol_u64(Eki ^ Di, 43); + Bo = algo::rol_u64(Emo ^ Do, 21); + Bu = algo::rol_u64(Esu ^ Du, 14); + Aba = Ba ^ (~Be & Bi) ^ roundConstants[n + 1]; + Abe = Be ^ (~Bi & Bo); + Abi = Bi ^ (~Bo & Bu); + Abo = Bo ^ (~Bu & Ba); + Abu = Bu ^ (~Ba & Be); + + Ba = algo::rol_u64(Ebo ^ Do, 28); + Be = algo::rol_u64(Egu ^ Du, 20); + Bi = algo::rol_u64(Eka ^ Da, 3); + Bo = algo::rol_u64(Eme ^ De, 45); + Bu = algo::rol_u64(Esi ^ Di, 61); + Aga = Ba ^ (~Be & Bi); + Age = Be ^ (~Bi & Bo); + Agi = Bi ^ (~Bo & Bu); + Ago = Bo ^ (~Bu & Ba); + Agu = Bu ^ (~Ba & Be); + + Ba = algo::rol_u64(Ebe ^ De, 1); + Be = algo::rol_u64(Egi ^ Di, 6); + Bi = algo::rol_u64(Eko ^ Do, 25); + Bo = algo::rol_u64(Emu ^ Du, 8); + Bu = algo::rol_u64(Esa ^ Da, 18); + Aka = Ba ^ (~Be & Bi); + Ake = Be ^ (~Bi & Bo); + Aki = Bi ^ (~Bo & Bu); + Ako = Bo ^ (~Bu & Ba); + Aku = Bu ^ (~Ba & Be); + + Ba = algo::rol_u64(Ebu ^ Du, 27); + Be = algo::rol_u64(Ega ^ Da, 36); + Bi = algo::rol_u64(Eke ^ De, 10); + Bo = algo::rol_u64(Emi ^ Di, 15); + Bu = algo::rol_u64(Eso ^ Do, 56); + Ama = Ba ^ (~Be & Bi); + Ame = Be ^ (~Bi & Bo); + Ami = Bi ^ (~Bo & Bu); + Amo = Bo ^ (~Bu & Ba); + Amu = Bu ^ (~Ba & Be); + + Ba = algo::rol_u64(Ebi ^ Di, 62); + Be = algo::rol_u64(Ego ^ Do, 55); + Bi = algo::rol_u64(Eku ^ Du, 39); + Bo = algo::rol_u64(Ema ^ Da, 41); + Bu = algo::rol_u64(Ese ^ De, 2); + Asa = Ba ^ (~Be & Bi); + Ase = Be ^ (~Bi & Bo); + Asi = Bi ^ (~Bo & Bu); + Aso = Bo ^ (~Bu & Ba); + Asu = Bu ^ (~Ba & Be); + } + + state[0] = Aba; + state[1] = Abe; + state[2] = Abi; + state[3] = Abo; + state[4] = Abu; + state[5] = Aga; + state[6] = Age; + state[7] = Agi; + state[8] = Ago; + state[9] = Agu; + state[10] = Aka; + state[11] = Ake; + state[12] = Aki; + state[13] = Ako; + state[14] = Aku; + state[15] = Ama; + state[16] = Ame; + state[17] = Ami; + state[18] = Amo; + state[19] = Amu; + state[20] = Asa; + state[21] = Ase; + state[22] = Asi; + state[23] = Aso; + state[24] = Asu; + } + template inline T keccak(T const& src) { diff --git a/sources/algo/kheavyhash/hashers.hpp b/sources/algo/kheavyhash/hashers.hpp deleted file mode 100644 index e48fde3c..00000000 --- a/sources/algo/kheavyhash/hashers.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - - -namespace algo -{ - namespace kheavyhash - { - // hash1 = cSHAKE256("ProofOfWorkHash") over - // pre_pow_hash[32] || timestamp_u64_LE || zero[32] || nonce_u64_LE - // (rusty-kaspa pow_hashers.rs::PowHash). Output is 32 little-endian bytes. - Hash256 powHash(Hash256 const& prePowHash, uint64_t timestamp, uint64_t nonce); - - // hash2 step = cSHAKE256("HeavyHash") over 32 bytes (pow_hashers.rs::KHeavyHash). - Hash256 kHeavyHash(Hash256 const& input); - } -} diff --git a/sources/algo/kheavyhash/keccak.cpp b/sources/algo/kheavyhash/keccak.cpp deleted file mode 100644 index 17a1b72f..00000000 --- a/sources/algo/kheavyhash/keccak.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include - - -namespace algo -{ - namespace kheavyhash - { - constexpr uint64_t ROUND_CONSTANTS[24]{ - 0x0000000000000001ull, 0x0000000000008082ull, 0x800000000000808aull, 0x8000000080008000ull, - 0x000000000000808bull, 0x0000000080000001ull, 0x8000000080008081ull, 0x8000000000008009ull, - 0x000000000000008aull, 0x0000000000000088ull, 0x0000000080008009ull, 0x000000008000000aull, - 0x000000008000808bull, 0x800000000000008bull, 0x8000000000008089ull, 0x8000000000008003ull, - 0x8000000000008002ull, 0x8000000000000080ull, 0x000000000000800aull, 0x800000008000000aull, - 0x8000000080008081ull, 0x8000000000008080ull, 0x0000000080000001ull, 0x8000000080008008ull, - }; - - constexpr int ROTATIONS[24]{ 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, - 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 }; - - constexpr int PI_LANE[24]{ - 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 - }; - - static uint64_t rotl(uint64_t const x, int const k) - { - return (x << k) | (x >> (64 - k)); - } - - - void keccakF1600(uint64_t* a) - { - for (int round{ 0 }; round < 24; ++round) - { - // Theta - uint64_t bc[5]; - for (int i{ 0 }; i < 5; ++i) - { - bc[i] = a[i] ^ a[i + 5] ^ a[i + 10] ^ a[i + 15] ^ a[i + 20]; - } - for (int i{ 0 }; i < 5; ++i) - { - uint64_t const t{ bc[(i + 4) % 5] ^ rotl(bc[(i + 1) % 5], 1) }; - for (int j{ 0 }; j < 25; j += 5) - { - a[j + i] ^= t; - } - } - - // Rho + Pi - uint64_t t{ a[1] }; - for (int i{ 0 }; i < 24; ++i) - { - int const j{ PI_LANE[i] }; - uint64_t const tmp{ a[j] }; - a[j] = rotl(t, ROTATIONS[i]); - t = tmp; - } - - // Chi - for (int j{ 0 }; j < 25; j += 5) - { - for (int i{ 0 }; i < 5; ++i) - { - bc[i] = a[j + i]; - } - for (int i{ 0 }; i < 5; ++i) - { - a[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5]; - } - } - - // Iota - a[0] ^= ROUND_CONSTANTS[round]; - } - } - } -} diff --git a/sources/algo/kheavyhash/keccak.hpp b/sources/algo/kheavyhash/keccak.hpp deleted file mode 100644 index 35fcc16c..00000000 --- a/sources/algo/kheavyhash/keccak.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - - -namespace algo -{ - namespace kheavyhash - { - // Standard Keccak-f[1600] permutation (24 rounds) on a 25-lane state. - // Kaspa's PowHash/KHeavyHash absorb their cSHAKE256 message into a precomputed - // initial state and apply exactly one permutation. - void keccakF1600(uint64_t* state); - } -} diff --git a/sources/algo/kheavyhash/kheavyhash.cpp b/sources/algo/kheavyhash/kheavyhash.cpp deleted file mode 100644 index ec7d60ba..00000000 --- a/sources/algo/kheavyhash/kheavyhash.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include - - -namespace algo -{ - namespace kheavyhash - { - Hash256 heavyHash(Matrix const& matrix, Hash256 const& hash1) - { - // Expand hash1 to 64 nibbles, high nibble first. - uint16_t vec[64]; - for (int i{ 0 }; i < 32; ++i) - { - vec[2 * i] = static_cast(hash1[i] >> 4); - vec[2 * i + 1] = static_cast(hash1[i] & 0x0F); - } - - // Matrix-vector multiply; two rows collapse to one output byte via >>10 nibbles. - Hash256 product{}; - for (int i{ 0 }; i < 32; ++i) - { - uint16_t sum1{ 0 }; - uint16_t sum2{ 0 }; - for (int j{ 0 }; j < 64; ++j) - { - sum1 = static_cast(sum1 + matrix[2 * i][j] * vec[j]); - sum2 = static_cast(sum2 + matrix[2 * i + 1][j] * vec[j]); - } - product[i] = static_cast(((sum1 >> 10) << 4) | (sum2 >> 10)); - } - - // XOR with the original hash1 bytes, then KHeavyHash. - for (int i{ 0 }; i < 32; ++i) - { - product[i] ^= hash1[i]; - } - return kHeavyHash(product); - } - - - Hash256 calculatePow(Hash256 const& prePowHash, uint64_t const timestamp, uint64_t const nonce) - { - Matrix const matrix{ generateMatrix(prePowHash) }; - Hash256 const hash1{ powHash(prePowHash, timestamp, nonce) }; - return heavyHash(matrix, hash1); - } - - - bool meetsTarget(Hash256 const& powValueLe, Hash256 const& targetLe) - { - // Compare as little-endian 256-bit integers: scan from most-significant byte. - for (int i{ 31 }; i >= 0; --i) - { - if (powValueLe[i] != targetLe[i]) - { - return powValueLe[i] < targetLe[i]; - } - } - return true; // equal => pow <= target - } - } -} diff --git a/sources/algo/kheavyhash/kheavyhash.hpp b/sources/algo/kheavyhash/kheavyhash.hpp deleted file mode 100644 index 5798f9b2..00000000 --- a/sources/algo/kheavyhash/kheavyhash.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - - -namespace algo -{ - namespace kheavyhash - { - Hash256 heavyHash(Matrix const& matrix, Hash256 const& hash1); - Hash256 calculatePow(Hash256 const& prePowHash, uint64_t timestamp, uint64_t nonce); - bool meetsTarget(Hash256 const& powValueLe, Hash256 const& targetLe); - } -} diff --git a/sources/algo/kheavyhash/tests/kheavyhash_test.cpp b/sources/algo/kheavyhash/tests/heavyHash.cpp similarity index 55% rename from sources/algo/kheavyhash/tests/kheavyhash_test.cpp rename to sources/algo/kheavyhash/tests/heavyHash.cpp index 09a2fc9b..1ea1b4a6 100644 --- a/sources/algo/kheavyhash/tests/kheavyhash_test.cpp +++ b/sources/algo/kheavyhash/tests/heavyHash.cpp @@ -1,12 +1,13 @@ #include -#include +#include +#include #include // Layer 2: heavy step (matrix multiply + XOR + KHeavyHash). // rusty-kaspa matrix.rs::test_heavy_hash literal. -TEST(KHeavyHash, heavyHashMatchesReference) +TEST(KHeavyHashHeavyHash, heavyHashMatchesReference) { algo::kheavyhash::Hash256 const out{ algo::kheavyhash::heavyHash(algo::kheavyhash::kat::HEAVY_TEST_MATRIX, algo::kheavyhash::kat::HEAVY_INPUT) @@ -16,19 +17,14 @@ TEST(KHeavyHash, heavyHashMatchesReference) // Layer 2: full pipeline incl. matrix generation. Oracle-minted (pycryptodome) vector. -TEST(KHeavyHash, calculatePowMatchesReference) +// The pow pipeline is inlined here: generateMatrix -> powHash -> heavyHash. +TEST(KHeavyHashHeavyHash, calculatePowMatchesReference) { - algo::kheavyhash::Hash256 const out{ algo::kheavyhash::calculatePow( + algo::kheavyhash::Matrix const matrix{ algo::kheavyhash::generateMatrix(algo::kheavyhash::kat::FP_PRE) }; + algo::kheavyhash::Hash256 const hash1{ algo::kheavyhash::powHash( algo::kheavyhash::kat::FP_PRE, algo::kheavyhash::kat::FP_TIMESTAMP, algo::kheavyhash::kat::FP_NONCE) }; + algo::kheavyhash::Hash256 const out{ algo::kheavyhash::heavyHash(matrix, hash1) }; EXPECT_EQ(out, algo::kheavyhash::kat::FP_FINAL); } - - -// Accept test: pow <= target (little-endian 256-bit). -TEST(KHeavyHash, MeetsTargetCompare) -{ - EXPECT_TRUE(algo::kheavyhash::meetsTarget(algo::kheavyhash::kat::FP_FINAL, algo::kheavyhash::kat::FP_TARGET_PASS)); - EXPECT_FALSE(algo::kheavyhash::meetsTarget(algo::kheavyhash::kat::FP_FINAL, algo::kheavyhash::kat::FP_TARGET_FAIL)); -} diff --git a/sources/algo/kheavyhash/hashers.cpp b/sources/algo/kheavyhash/tests/helper.hpp similarity index 52% rename from sources/algo/kheavyhash/hashers.cpp rename to sources/algo/kheavyhash/tests/helper.hpp index a0366e06..10ceb0b0 100644 --- a/sources/algo/kheavyhash/hashers.cpp +++ b/sources/algo/kheavyhash/tests/helper.hpp @@ -1,7 +1,15 @@ -#include -#include +#pragma once +#include +#include +#include +#include + + +// CPU reference oracle for kHeavyHash, used only by the unit tests to validate the +// GPU (CUDA/OpenCL) kernels. +// Header-only: not part of the production build. namespace algo { namespace kheavyhash @@ -30,62 +38,84 @@ namespace algo 1592359455985097269ull, }; - static uint64_t loadLe64(uint8_t const* p) + + // hash1 = cSHAKE256("ProofOfWorkHash") over + // pre_pow_hash[32] || timestamp_u64_LE || zero[32] || nonce_u64_LE + // (rusty-kaspa pow_hashers.rs::PowHash). Output is 32 little-endian bytes. + inline Hash256 powHash(Hash256 const& prePowHash, uint64_t const timestamp, uint64_t const nonce) { - uint64_t v{ 0 }; - for (int b{ 0 }; b < 8; ++b) + uint64_t state[25]; + for (int i{ 0 }; i < 25; ++i) { - v |= static_cast(p[b]) << (8 * b); + state[i] = POW_INITIAL_STATE[i]; } - return v; - } - - static Hash256 storeLe256(uint64_t const* state) - { - Hash256 out{}; + // message lanes: pre_pow_hash (4 LE words) | timestamp | zero[32] | nonce for (int w{ 0 }; w < 4; ++w) { - for (int b{ 0 }; b < 8; ++b) - { - out[w * 8 + b] = static_cast((state[w] >> (8 * b)) & 0xFF); - } + state[w] ^= algo::le::uint64(prePowHash.data() + w * 8); } + state[4] ^= timestamp; + state[9] ^= nonce; + algo::keccakF1600(state); + + Hash256 out{}; + algo::le::store256(out.data(), state); return out; } - Hash256 powHash(Hash256 const& prePowHash, uint64_t const timestamp, uint64_t const nonce) + // hash2 step = cSHAKE256("HeavyHash") over 32 bytes (pow_hashers.rs::KHeavyHash). + inline Hash256 kHeavyHash(Hash256 const& input) { uint64_t state[25]; for (int i{ 0 }; i < 25; ++i) { - state[i] = POW_INITIAL_STATE[i]; + state[i] = HEAVY_INITIAL_STATE[i]; } - // message lanes: pre_pow_hash (4 LE words) | timestamp | zero[32] | nonce for (int w{ 0 }; w < 4; ++w) { - state[w] ^= loadLe64(prePowHash.data() + w * 8); + state[w] ^= algo::le::uint64(input.data() + w * 8); } - state[4] ^= timestamp; - state[9] ^= nonce; - keccakF1600(state); - return storeLe256(state); + algo::keccakF1600(state); + + Hash256 out{}; + algo::le::store256(out.data(), state); + return out; } - Hash256 kHeavyHash(Hash256 const& input) + // Matrix-vector multiply (nibble domain) + XOR + kHeavyHash. The defining + // "heavy" step of the algorithm. + inline Hash256 heavyHash(Matrix const& matrix, Hash256 const& hash1) { - uint64_t state[25]; - for (int i{ 0 }; i < 25; ++i) + // Expand hash1 to 64 nibbles, high nibble first. + uint16_t vec[64]; + for (int i{ 0 }; i < 32; ++i) { - state[i] = HEAVY_INITIAL_STATE[i]; + vec[2 * i] = static_cast(hash1[i] >> 4); + vec[2 * i + 1] = static_cast(hash1[i] & 0x0F); } - for (int w{ 0 }; w < 4; ++w) + + // Matrix-vector multiply; two rows collapse to one output byte via >>10 nibbles. + Hash256 product{}; + for (int i{ 0 }; i < 32; ++i) + { + uint16_t sum1{ 0 }; + uint16_t sum2{ 0 }; + for (int j{ 0 }; j < 64; ++j) + { + sum1 = static_cast(sum1 + matrix[2 * i][j] * vec[j]); + sum2 = static_cast(sum2 + matrix[2 * i + 1][j] * vec[j]); + } + product[i] = static_cast(((sum1 >> 10) << 4) | (sum2 >> 10)); + } + + // XOR with the original hash1 bytes, then KHeavyHash. + for (int i{ 0 }; i < 32; ++i) { - state[w] ^= loadLe64(input.data() + w * 8); + product[i] ^= hash1[i]; } - keccakF1600(state); - return storeLe256(state); + return kHeavyHash(product); } } } diff --git a/sources/algo/kheavyhash/tests/kHeavyHash.cpp b/sources/algo/kheavyhash/tests/kHeavyHash.cpp new file mode 100644 index 00000000..6d6270ba --- /dev/null +++ b/sources/algo/kheavyhash/tests/kHeavyHash.cpp @@ -0,0 +1,12 @@ +#include + +#include +#include + + +// Layer 2: KHeavyHash (cSHAKE256 "HeavyHash") over 32 bytes. +TEST(KHeavyHashKHeavyHash, KHeavyHashMatchesKat) +{ + algo::kheavyhash::Hash256 const out{ algo::kheavyhash::kHeavyHash(algo::kheavyhash::kat::HEAVY_INPUT) }; + EXPECT_EQ(out, algo::kheavyhash::kat::KHEAVY_EXPECTED); +} diff --git a/sources/algo/kheavyhash/tests/matrix_test.cpp b/sources/algo/kheavyhash/tests/matrix.cpp similarity index 100% rename from sources/algo/kheavyhash/tests/matrix_test.cpp rename to sources/algo/kheavyhash/tests/matrix.cpp diff --git a/sources/algo/kheavyhash/tests/hashers_test.cpp b/sources/algo/kheavyhash/tests/powHash.cpp similarity index 68% rename from sources/algo/kheavyhash/tests/hashers_test.cpp rename to sources/algo/kheavyhash/tests/powHash.cpp index 642e1e7b..2f2ba08f 100644 --- a/sources/algo/kheavyhash/tests/hashers_test.cpp +++ b/sources/algo/kheavyhash/tests/powHash.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include @@ -21,7 +21,7 @@ namespace // Layer 2: PowHash (cSHAKE256 "ProofOfWorkHash"). Matches rusty-kaspa test_pow_hash inputs. -TEST(KHeavyHashHashers, PowHashMatchesKat) +TEST(KHeavyHashPowHash, PowHashMatchesKat) { algo::kheavyhash::Hash256 const pre{ toHash(algo::kheavyhash::kat::POW_KAT_PRE) }; algo::kheavyhash::Hash256 const out{ @@ -29,11 +29,3 @@ TEST(KHeavyHashHashers, PowHashMatchesKat) }; EXPECT_EQ(out, algo::kheavyhash::kat::POW_KAT_EXPECTED); } - - -// Layer 2: KHeavyHash (cSHAKE256 "HeavyHash") over 32 bytes. -TEST(KHeavyHashHashers, KHeavyHashMatchesKat) -{ - algo::kheavyhash::Hash256 const out{ algo::kheavyhash::kHeavyHash(algo::kheavyhash::kat::HEAVY_INPUT) }; - EXPECT_EQ(out, algo::kheavyhash::kat::KHEAVY_EXPECTED); -} diff --git a/sources/algo/kheavyhash/tests/stratum_math_test.cpp b/sources/algo/kheavyhash/tests/stratum_math.cpp similarity index 100% rename from sources/algo/kheavyhash/tests/stratum_math_test.cpp rename to sources/algo/kheavyhash/tests/stratum_math.cpp diff --git a/sources/algo/tests/hash.cpp b/sources/algo/tests/hash.cpp index fe4984b0..7673d30f 100644 --- a/sources/algo/tests/hash.cpp +++ b/sources/algo/tests/hash.cpp @@ -276,3 +276,32 @@ TEST_F(HashTest, decimalToHashOversizedIsBounded) EXPECT_EQ('F', cast8(hash.ubytes[sizeof(algo::hash512) - 1u])); } + + +TEST_F(HashTest, isLowerOrEqualLE) +{ + // Little-endian 256-bit comparison: byte 31 is the most significant. + algo::hash256 value{}; + algo::hash256 target{}; + for (uint64_t i{ 0ull }; i < algo::LEN_HASH_256; ++i) + { + value.ubytes[i] = 0x11; + target.ubytes[i] = 0x11; + } + + // Equal => value <= target. + EXPECT_TRUE(algo::isLowerOrEqualLE(value, target)); + + // value < target on the most-significant byte. + value.ubytes[31] = 0x00; + target.ubytes[31] = 0x01; + EXPECT_TRUE(algo::isLowerOrEqualLE(value, target)); + + // value > target on the most-significant byte. + EXPECT_FALSE(algo::isLowerOrEqualLE(target, value)); + + // The most-significant byte dominates a larger least-significant byte. + value.ubytes[0] = 0xFF; + target.ubytes[0] = 0x00; + EXPECT_TRUE(algo::isLowerOrEqualLE(value, target)); +} From 72e300b384b792407d31d6aa0d38560b093d4cd9 Mon Sep 17 00:00:00 2001 From: luminousminer Date: Mon, 22 Jun 2026 14:24:28 +0200 Subject: [PATCH 5/6] add missing test --- .github/workflows/unit_test_linux.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit_test_linux.yml b/.github/workflows/unit_test_linux.yml index fb04e246..24b447e2 100644 --- a/.github/workflows/unit_test_linux.yml +++ b/.github/workflows/unit_test_linux.yml @@ -49,9 +49,10 @@ jobs: FastModTest.*: Fnv1Test.*: HashTest.*: - KHeavyHashHashers.*: + KHeavyHashHeavyHash.*: + KHeavyHashKHeavyHash.*: KHeavyHashMatrix.*: - KHeavyHashPipeline.*: + KHeavyHashPowHash.*: Kiss99Test.*: MathTest.*: ProgpowKernelOpenCLTest.*: From 2ccc920ab955751633b9a938a34d506cbf15dd1f Mon Sep 17 00:00:00 2001 From: luminousminer Date: Mon, 22 Jun 2026 14:34:16 +0200 Subject: [PATCH 6/6] ignore claude folder --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c72fc14f..04e343f1 100644 --- a/.gitignore +++ b/.gitignore @@ -463,3 +463,6 @@ dist/ # build-output ignores above (x64/, arm64/). These are tracked source, not build # output, so re-include them. !.github/actions/** + +# AI +.claude