From 6be9e3cafdc804fa7f6ba2f47a2339bb4b62c090 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 30 Mar 2026 09:05:42 +0200 Subject: [PATCH] test(sha256): add 100k nonce equivalence and hitmask checks --- tests/test_miner_regression.c | 214 +++++++++++++++++++++++++++++++ tests/test_sha256_backend.c | 228 ++++++++++++++++++++++++++++++++++ 2 files changed, 442 insertions(+) create mode 100644 tests/test_miner_regression.c create mode 100644 tests/test_sha256_backend.c diff --git a/tests/test_miner_regression.c b/tests/test_miner_regression.c new file mode 100644 index 0000000..6336395 --- /dev/null +++ b/tests/test_miner_regression.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include +#include + +#include "../miner.h" +#include "../sha256/sha256_backend.h" +#include "../utils.h" + +static inline void write_u32_le(uint8_t *dst, uint32_t v) { + dst[0] = (uint8_t)(v & 0xFFU); + dst[1] = (uint8_t)((v >> 8) & 0xFFU); + dst[2] = (uint8_t)((v >> 16) & 0xFFU); + dst[3] = (uint8_t)((v >> 24) & 0xFFU); +} + +static void sha256d_from_header76_nonce(const uint8_t header76[76], uint32_t nonce, uint8_t out[32]) { + uint8_t header80[80]; + uint8_t block1[64]; + uint8_t block2[64]; + sha256_state_t init_state; + sha256_state_t st1; + sha256_state_t st2; + + memcpy(header80, header76, 76); + write_u32_le(header80 + 76, nonce); + + sha256_state_init(&init_state); + st1 = init_state; + sha256_transform_fast(&st1, header80); + + memset(block1, 0, sizeof(block1)); + memcpy(block1, header80 + 64, 16); + block1[16] = 0x80; + block1[62] = 0x02; + block1[63] = 0x80; + sha256_transform_fast(&st1, block1); + + memset(block2, 0, sizeof(block2)); + sha256_state_to_digest(&st1, block2); + block2[32] = 0x80; + block2[62] = 0x01; + block2[63] = 0x00; + + st2 = init_state; + sha256_transform_fast(&st2, block2); + sha256_state_to_digest(&st2, out); +} + +static void digest_rev(const uint8_t digest[32], uint8_t rev[32]) { + int i; + for (i = 0; i < 32; i++) { + rev[i] = digest[31 - i]; + } +} + +static int find_record_nonce( + const uint8_t header76[76], + int parity, + uint32_t min_nonce, + uint32_t max_nonce, + uint32_t *out_nonce, + uint8_t out_target_be[32] +) { + uint8_t best[32]; + uint32_t n; + + memset(best, 0xFF, sizeof(best)); + + for (n = 0; n <= max_nonce; n++) { + uint8_t digest[32]; + uint8_t rev[32]; + + sha256d_from_header76_nonce(header76, n, digest); + digest_rev(digest, rev); + + if (memcmp(rev, best, 32) < 0) { + memcpy(best, rev, 32); + if (n >= min_nonce && ((int)(n & 1U) == parity)) { + *out_nonce = n; + memcpy(out_target_be, rev, 32); + return 1; + } + } + } + + return 0; +} + +static int run_case(uint32_t batch, int parity) { + uint8_t header76[76]; + uint8_t target_be[32]; + uint8_t expected_digest[32]; + MineResult out; + atomic_int stop_flag; + uint32_t expected_nonce; + char *target_hex; + int rc; + int i; + + for (i = 0; i < 76; i++) { + header76[i] = (uint8_t)((i * 29 + 11) & 0xFF); + } + + if (!find_record_nonce(header76, parity, 4U, 150000U, &expected_nonce, target_be)) { + fprintf(stderr, "[test_miner_regression] no record nonce found for parity=%d\n", parity); + return 0; + } + + target_hex = bytes_to_hex(target_be, 32); + if (target_hex == NULL) { + return 0; + } + + atomic_init(&stop_flag, 0); + rc = mine_block( + header76, + target_hex, + "incremental", + batch, + 0, + &stop_flag, + NULL, + NULL, + &out + ); + free(target_hex); + + if (rc == 0 || out.found == 0) { + fprintf(stderr, "[test_miner_regression] mine_block failed for batch=%u\n", batch); + return 0; + } + + if (out.nonce != expected_nonce) { + fprintf(stderr, "[test_miner_regression] nonce mismatch batch=%u expected=%u got=%u\n", + batch, expected_nonce, out.nonce); + return 0; + } + + sha256d_from_header76_nonce(header76, out.nonce, expected_digest); + if (memcmp(expected_digest, out.digest, 32) != 0) { + fprintf(stderr, "[test_miner_regression] digest mismatch for nonce=%u\n", out.nonce); + return 0; + } + + return 1; +} + +static int test_easy_target_finds_zero(void) { + uint8_t header76[76]; + char *target_hex = NULL; + MineResult out; + atomic_int stop_flag; + int i; + + for (i = 0; i < 76; i++) { + header76[i] = (uint8_t)((i * 7 + 3) & 0xFF); + } + + target_hex = bytes_to_hex((const uint8_t[32]){ + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF + }, 32); + if (target_hex == NULL) { + return 0; + } + + atomic_init(&stop_flag, 0); + if (!mine_block(header76, target_hex, "incremental", 1, 0, &stop_flag, NULL, NULL, &out)) { + free(target_hex); + return 0; + } + free(target_hex); + + return out.found == 1 && out.nonce == 0U; +} + +static int test_wrap_arithmetic(void) { + uint32_t start = 0xFFFFFFFEU; + uint32_t n0 = start + 0U; + uint32_t n1 = start + 1U; + uint32_t n2 = start + 2U; + uint32_t n3 = start + 3U; + + return n0 == 0xFFFFFFFEU && + n1 == 0xFFFFFFFFU && + n2 == 0x00000000U && + n3 == 0x00000001U; +} + +int main(void) { + if (!test_easy_target_finds_zero()) { + fprintf(stderr, "test_easy_target_finds_zero: FAIL\n"); + return 1; + } + if (!run_case(5U, 1)) { + fprintf(stderr, "run_case(batch=5, parity=1): FAIL\n"); + return 1; + } + if (!run_case(6U, 0)) { + fprintf(stderr, "run_case(batch=6, parity=0): FAIL\n"); + return 1; + } + if (!test_wrap_arithmetic()) { + fprintf(stderr, "test_wrap_arithmetic: FAIL\n"); + return 1; + } + + printf("test_miner_regression: OK\n"); + return 0; +} diff --git a/tests/test_sha256_backend.c b/tests/test_sha256_backend.c new file mode 100644 index 0000000..1f25d6d --- /dev/null +++ b/tests/test_sha256_backend.c @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include + +#include "../sha256/sha256_backend.h" +#include "../utils.h" + +static uint32_t xorshift32(uint32_t *s) { + uint32_t x = *s; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *s = x; + return x; +} + +static inline void write_u32_le(uint8_t *dst, uint32_t v) { + dst[0] = (uint8_t)(v & 0xFFU); + dst[1] = (uint8_t)((v >> 8) & 0xFFU); + dst[2] = (uint8_t)((v >> 16) & 0xFFU); + dst[3] = (uint8_t)((v >> 24) & 0xFFU); +} + +static void fill_random(uint8_t *dst, size_t n, uint32_t *seed) { + size_t i; + for (i = 0; i < n; i++) { + dst[i] = (uint8_t)(xorshift32(seed) & 0xFFU); + } +} + +static void backend_double_sha256_80(const uint8_t header80[80], uint8_t out[32]) { + sha256_state_t init_state; + sha256_state_t st1; + sha256_state_t st2; + uint8_t block1[64]; + uint8_t block2[64]; + + sha256_state_init(&init_state); + + st1 = init_state; + sha256_transform_fast(&st1, header80); + + memset(block1, 0, sizeof(block1)); + memcpy(block1, header80 + 64, 16); + block1[16] = 0x80; + block1[62] = 0x02; + block1[63] = 0x80; + sha256_transform_fast(&st1, block1); + + memset(block2, 0, sizeof(block2)); + sha256_state_to_digest(&st1, block2); + block2[32] = 0x80; + block2[62] = 0x01; + block2[63] = 0x00; + + st2 = init_state; + sha256_transform_fast(&st2, block2); + sha256_state_to_digest(&st2, out); +} + +static int test_transform_equivalence_random(void) { + uint32_t seed = 0x12345678U; + int i; + + for (i = 0; i < 10000; i++) { + uint8_t block[64]; + SHA256_CTX ref; + sha256_state_t test; + + fill_random(block, sizeof(block), &seed); + + SHA256_Init(&ref); + sha256_state_init(&test); + SHA256_Transform(&ref, block); + sha256_transform_fast(&test, block); + + if (memcmp(ref.h, test.h, sizeof(ref.h)) != 0) { + fprintf(stderr, "[test_sha256_backend] transform mismatch at iter=%d\n", i); + return 0; + } + } + + return 1; +} + +static int test_transform_2way_equivalence(void) { + uint32_t seed = 0xCAFEBABEU; + int i; + + for (i = 0; i < 5000; i++) { + uint8_t block_a[64]; + uint8_t block_b[64]; + SHA256_CTX ref_a; + SHA256_CTX ref_b; + sha256_state_t test_a; + sha256_state_t test_b; + + fill_random(block_a, sizeof(block_a), &seed); + fill_random(block_b, sizeof(block_b), &seed); + + SHA256_Init(&ref_a); + SHA256_Init(&ref_b); + sha256_state_init(&test_a); + sha256_state_init(&test_b); + + SHA256_Transform(&ref_a, block_a); + SHA256_Transform(&ref_b, block_b); + sha256_transform_fast_2way(&test_a, block_a, &test_b, block_b); + + if (memcmp(ref_a.h, test_a.h, sizeof(ref_a.h)) != 0 || + memcmp(ref_b.h, test_b.h, sizeof(ref_b.h)) != 0) { + fprintf(stderr, "[test_sha256_backend] 2way mismatch at iter=%d\n", i); + return 0; + } + } + + return 1; +} + +static int test_double_sha256_80_equivalence(void) { + uint32_t seed = 0xA55AA55AU; + int i; + + for (i = 0; i < 2048; i++) { + uint8_t header80[80]; + uint8_t ref[32]; + uint8_t test[32]; + + fill_random(header80, sizeof(header80), &seed); + double_sha256(header80, sizeof(header80), ref); + backend_double_sha256_80(header80, test); + + if (memcmp(ref, test, sizeof(ref)) != 0) { + fprintf(stderr, "[test_sha256_backend] sha256d80 mismatch at iter=%d\n", i); + return 0; + } + } + + return 1; +} + +static int test_sha256d80_4way_100k_nonces(void) { + uint8_t header76[76]; + sha256d80_midstate_t mid; + uint32_t base = 0x12340000U; + int i; + + for (i = 0; i < 76; i++) { + header76[i] = (uint8_t)((i * 19 + 5) & 0xFF); + } + + sha256d80_midstate_init(&mid, header76); + + for (i = 0; i < 25000; i++) { + uint32_t start_nonce = base + (uint32_t)(i * 4); + sha256_state_t states[4]; + int lane; + + sha256d80_hash_4way(&mid, start_nonce, states); + + for (lane = 0; lane < 4; lane++) { + uint8_t header80[80]; + uint8_t ref[32]; + uint8_t got[32]; + uint32_t nonce = start_nonce + (uint32_t)lane; + + memcpy(header80, header76, 76); + write_u32_le(header80 + 76, nonce); + double_sha256(header80, sizeof(header80), ref); + sha256_state_to_digest(&states[lane], got); + + if (memcmp(ref, got, 32) != 0) { + fprintf(stderr, "[test_sha256_backend] 4way nonce mismatch nonce=%u lane=%d\n", nonce, lane); + return 0; + } + } + } + + return 1; +} + +static int test_sha256d80_scan_hitmask_basic(void) { + uint8_t header76[76]; + sha256d80_midstate_t mid; + sha256_state_t states[4]; + uint32_t all_max[8]; + uint32_t all_zero[8]; + uint32_t mask_max; + uint32_t mask_zero; + int i; + + for (i = 0; i < 76; i++) { + header76[i] = (uint8_t)((i * 11 + 1) & 0xFF); + } + for (i = 0; i < 8; i++) { + all_max[i] = 0xFFFFFFFFU; + all_zero[i] = 0x00000000U; + } + + sha256d80_midstate_init(&mid, header76); + mask_max = sha256d80_scan_4way(&mid, 0xABC00000U, all_max, states); + mask_zero = sha256d80_scan_4way(&mid, 0xABC00000U, all_zero, states); + + return mask_max == 0xFU && mask_zero == 0U; +} + +int main(void) { + if (!test_transform_equivalence_random()) { + return 1; + } + if (!test_transform_2way_equivalence()) { + return 1; + } + if (!test_double_sha256_80_equivalence()) { + return 1; + } + if (!test_sha256d80_4way_100k_nonces()) { + return 1; + } + if (!test_sha256d80_scan_hitmask_basic()) { + return 1; + } + + printf("test_sha256_backend: OK\n"); + return 0; +}