From 4a9f29d716ab2e0d3b68fdb60e84ba3a946d75a1 Mon Sep 17 00:00:00 2001 From: f4u57ox <72417751+f4u57ox@users.noreply.github.com> Date: Sun, 6 Sep 2026 14:02:53 -0600 Subject: [PATCH 1/6] utils: move strict hex decoding out of PoW --- src/datum_pow.c | 18 ------------------ src/datum_pow.h | 2 -- src/datum_pow_tests.c | 25 ++++++++++++------------- src/datum_utils.c | 19 +++++++++++++++++++ src/datum_utils.h | 1 + src/datum_utils_tests.c | 9 +++++++++ 6 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/datum_pow.c b/src/datum_pow.c index 8ce50f0a..05fab208 100644 --- a/src/datum_pow.c +++ b/src/datum_pow.c @@ -40,24 +40,6 @@ #include "datum_pow.h" #include "datum_utils.h" -static int datum_hex_nibble(const char c) { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - return -1; -} - -bool datum_pow_decode_hex_exact(const char *hex, size_t out_len, unsigned char *out) { - size_t i; - if (!hex || !out) return false; - for (i = 0; i < out_len; i++) { - if (datum_hex_nibble(hex[i<<1]) < 0) return false; - if (datum_hex_nibble(hex[(i<<1)+1]) < 0) return false; - out[i] = hex2bin_uchar(&hex[i<<1]); - } - return hex[out_len<<1] == 0; -} - bool datum_blake2b_time_on_wire(uint32_t *out, uint64_t ntime, uint64_t offset, uint8_t flags) { if (!out) return false; if (ntime > UINT32_MAX) return false; diff --git a/src/datum_pow.h b/src/datum_pow.h index 05db0e27..7607db3d 100644 --- a/src/datum_pow.h +++ b/src/datum_pow.h @@ -50,8 +50,6 @@ #define DATUM_POW_RESERVED_BLAKE2B_USE_TIME_OFFSET 0x01 #define DATUM_POW_FLAG_BLAKE2B 0x08 -bool datum_pow_decode_hex_exact(const char *hex, size_t out_len, unsigned char *out); - bool datum_blake2b_time_on_wire(uint32_t *out, uint64_t ntime, uint64_t offset, uint8_t flags); /* The nTime a node reads from a header-v2 block built from this share: the * job's wire time plus the hasher's four time-offset bytes when the offset diff --git a/src/datum_pow_tests.c b/src/datum_pow_tests.c index e9aa804e..107f152d 100644 --- a/src/datum_pow_tests.c +++ b/src/datum_pow_tests.c @@ -187,10 +187,10 @@ static void datum_pow_blake2b_vector_tests(void) { datum_test(datum_blake2b_header_commitment(commitment, 0x20000000, prevhash, 12345, merkle, 0x6553412f, 0x207fffff, 3, DATUM_BLAKE2B_USE_TIME_OFFSET, 13, xor_key, rhs)); - datum_test(datum_pow_decode_hex_exact(expected_commitment_hex, 32, expected)); + datum_test(hex_to_bin_exact(expected_commitment_hex, expected, 32)); datum_test(!memcmp(commitment, expected, 32)); datum_test(datum_blake2b_work_root(root, commitment, extranonce)); - datum_test(datum_pow_decode_hex_exact(expected_root_hex, 32, expected)); + datum_test(hex_to_bin_exact(expected_root_hex, expected, 32)); datum_test(!memcmp(root, expected, 32)); datum_blake2b_coinb1(coinb1, commitment); memcpy(arbitrary_tx, coinb1, sizeof(coinb1)); @@ -199,19 +199,18 @@ static void datum_pow_blake2b_vector_tests(void) { datum_test(datum_blake2b_256(expected, leaf_preimage, sizeof(leaf_preimage))); datum_test(!memcmp(root, expected, 32)); datum_blake2b_build_work_header(work, prevhash, nonce, ntime, root); - datum_test(datum_pow_decode_hex_exact(expected_work_hex, sizeof(work), expected)); + datum_test(hex_to_bin_exact(expected_work_hex, expected, sizeof(work))); datum_test(!memcmp(work, expected, sizeof(work))); datum_blake2b_prevblock_hidden(expected, prevhash); datum_test(!memcmp(expected, work, 32)); datum_test(datum_blake2b_pow_hash_le(hash_le, work, xor_key, 13)); - datum_test(datum_pow_decode_hex_exact(expected_hash_le_hex, 32, expected)); + datum_test(hex_to_bin_exact(expected_hash_le_hex, expected, 32)); datum_test(!memcmp(hash_le, expected, 32)); datum_blake2b_serialize_block_header(header, 0x20000000, prevhash, merkle, 0x6553412f, 0x207fffff, nonce, ntime, extranonce, 3, DATUM_BLAKE2B_USE_TIME_OFFSET, 13, xor_key, 12345, rhs); - datum_test(datum_pow_decode_hex_exact(expected_header_hex, sizeof(header), expected)); + datum_test(hex_to_bin_exact(expected_header_hex, expected, sizeof(header))); datum_test(!memcmp(header, expected, sizeof(header))); - datum_test(!datum_pow_decode_hex_exact("xyz", 1, nonce)); /* Canonical profile-0 vector published with Knots' header-v2 implementation: * profile_0_time_offset from src/test/data/block_header_v2.json. Its "h2", @@ -240,9 +239,9 @@ static void datum_pow_blake2b_vector_tests(void) { unsigned char knots_prevhash[32], knots_merkle[32], knots_rhs[32]; unsigned char knots_nonce[8], knots_ntime[8]; - datum_test(datum_pow_decode_hex_exact(knots_prevhash_hex, 32, knots_prevhash)); - datum_test(datum_pow_decode_hex_exact(knots_merkle_hex, 32, knots_merkle)); - datum_test(datum_pow_decode_hex_exact(knots_rhs_hex, 32, knots_rhs)); + datum_test(hex_to_bin_exact(knots_prevhash_hex, knots_prevhash, 32)); + datum_test(hex_to_bin_exact(knots_merkle_hex, knots_merkle, 32)); + datum_test(hex_to_bin_exact(knots_rhs_hex, knots_rhs, 32)); memset(xor_key, 0, sizeof(xor_key)); pk_u32le(knots_nonce, 0, UINT32_C(0x0badf00d)); pk_u32le(knots_nonce, 4, UINT32_C(0x11223344)); @@ -252,16 +251,16 @@ static void datum_pow_blake2b_vector_tests(void) { datum_test(datum_blake2b_header_commitment(commitment, 0x20000000, knots_prevhash, 840000, knots_merkle, UINT32_C(2000000000) - 600, 0x1d00ffff, 3, 0x1c, 0, xor_key, knots_rhs)); - datum_test(datum_pow_decode_hex_exact(knots_commitment_hex, 32, expected)); + datum_test(hex_to_bin_exact(knots_commitment_hex, expected, 32)); datum_test(!memcmp(commitment, expected, 32)); - datum_test(datum_pow_decode_hex_exact(knots_root_hex, 32, root)); + datum_test(hex_to_bin_exact(knots_root_hex, root, 32)); datum_blake2b_build_work_header(work, knots_prevhash, knots_nonce, knots_ntime, root); - datum_test(datum_pow_decode_hex_exact(knots_work_hex, sizeof(work), expected)); + datum_test(hex_to_bin_exact(knots_work_hex, expected, sizeof(work))); datum_test(!memcmp(work, expected, sizeof(work))); datum_test(datum_blake2b_pow_hash_le(hash_le, work, xor_key, 0)); - datum_test(datum_pow_decode_hex_exact(knots_hash_le_hex, 32, expected)); + datum_test(hex_to_bin_exact(knots_hash_le_hex, expected, 32)); datum_test(!memcmp(hash_le, expected, 32)); } } diff --git a/src/datum_utils.c b/src/datum_utils.c index 9d296d9d..b52a903a 100644 --- a/src/datum_utils.c +++ b/src/datum_utils.c @@ -356,6 +356,25 @@ void hex_to_bin(const char *hex, unsigned char *bin) { } } +static int hex_value(const char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +bool hex_to_bin_exact(const char *hex, unsigned char *bin, const size_t bin_len) { + int high, low; + if (!hex || !bin) return false; + for (size_t i = 0; i < bin_len; i++) { + high = hex_value(hex[i<<1]); + low = hex_value(hex[(i<<1)+1]); + if (high < 0 || low < 0) return false; + bin[i] = (unsigned char)((high << 4) | low); + } + return hex[bin_len<<1] == 0; +} + void panic_from_thread(int a) { // set panic flag panic_mode = 1; diff --git a/src/datum_utils.h b/src/datum_utils.h index ff4ac339..e15cfb01 100644 --- a/src/datum_utils.h +++ b/src/datum_utils.h @@ -71,6 +71,7 @@ void panic_from_thread(int a); bool double_sha256(void *out, const void *in, size_t length); void hex_to_bin_le(const char *hex, unsigned char *bin); void hex_to_bin(const char *hex, unsigned char *bin); +bool hex_to_bin_exact(const char *hex, unsigned char *bin, size_t bin_len); void hash2hex(unsigned char *bytes, char *hexString); uint64_t roundDownToPowerOfTwo_64(uint64_t x); int addr_2_output_script(const char *addr, unsigned char *script, int max_len); diff --git a/src/datum_utils_tests.c b/src/datum_utils_tests.c index 58820b07..db744763 100644 --- a/src/datum_utils_tests.c +++ b/src/datum_utils_tests.c @@ -60,6 +60,7 @@ void datum_utils_tests_hex_to_bin(const uint8_t c, char * const x, const char * void datum_utils_tests_hex(void) { char x[6], x2[6]; + unsigned char exact[2] = {0x0e, 0x0e}; strcpy(&x[2], "00"); for (unsigned int c = 0; ; ++c) { datum_utils_tests_hex_to_bin(c, &x2[1], "%2.2X"); @@ -94,6 +95,14 @@ void datum_utils_tests_hex(void) { ++x[3]; } } + + datum_test(hex_to_bin_exact("00fF", exact, sizeof(exact))); + datum_test(exact[0] == 0 && exact[1] == 0xff); + datum_test(!hex_to_bin_exact("00fg", exact, sizeof(exact))); + datum_test(!hex_to_bin_exact("00f", exact, sizeof(exact))); + datum_test(!hex_to_bin_exact("00ff0", exact, sizeof(exact))); + datum_test(!hex_to_bin_exact(NULL, exact, sizeof(exact))); + datum_test(!hex_to_bin_exact("00ff", NULL, sizeof(exact))); } void datum_utils_tests_secure_strequals(void) { From fc053eba01601d33464e1389f239144948b2c0f5 Mon Sep 17 00:00:00 2001 From: f4u57ox <72417751+f4u57ox@users.noreply.github.com> Date: Sun, 6 Sep 2026 14:03:17 -0600 Subject: [PATCH 2/6] utils: add strict 32-bit hex decoding --- src/datum_utils.c | 8 ++++++++ src/datum_utils.h | 1 + src/datum_utils_tests.c | 13 +++++++++++++ 3 files changed, 22 insertions(+) diff --git a/src/datum_utils.c b/src/datum_utils.c index b52a903a..ad09a1d2 100644 --- a/src/datum_utils.c +++ b/src/datum_utils.c @@ -375,6 +375,14 @@ bool hex_to_bin_exact(const char *hex, unsigned char *bin, const size_t bin_len) return hex[bin_len<<1] == 0; } +bool hex_to_u32(const char *hex, uint32_t *out) { + unsigned char bin[4]; + if (!out || !hex_to_bin_exact(hex, bin, sizeof(bin))) return false; + *out = ((uint32_t)bin[0] << 24) | ((uint32_t)bin[1] << 16) | + ((uint32_t)bin[2] << 8) | bin[3]; + return true; +} + void panic_from_thread(int a) { // set panic flag panic_mode = 1; diff --git a/src/datum_utils.h b/src/datum_utils.h index e15cfb01..0236eb65 100644 --- a/src/datum_utils.h +++ b/src/datum_utils.h @@ -72,6 +72,7 @@ bool double_sha256(void *out, const void *in, size_t length); void hex_to_bin_le(const char *hex, unsigned char *bin); void hex_to_bin(const char *hex, unsigned char *bin); bool hex_to_bin_exact(const char *hex, unsigned char *bin, size_t bin_len); +bool hex_to_u32(const char *hex, uint32_t *out); void hash2hex(unsigned char *bytes, char *hexString); uint64_t roundDownToPowerOfTwo_64(uint64_t x); int addr_2_output_script(const char *addr, unsigned char *script, int max_len); diff --git a/src/datum_utils_tests.c b/src/datum_utils_tests.c index db744763..42283800 100644 --- a/src/datum_utils_tests.c +++ b/src/datum_utils_tests.c @@ -61,6 +61,7 @@ void datum_utils_tests_hex_to_bin(const uint8_t c, char * const x, const char * void datum_utils_tests_hex(void) { char x[6], x2[6]; unsigned char exact[2] = {0x0e, 0x0e}; + uint32_t value; strcpy(&x[2], "00"); for (unsigned int c = 0; ; ++c) { datum_utils_tests_hex_to_bin(c, &x2[1], "%2.2X"); @@ -103,6 +104,18 @@ void datum_utils_tests_hex(void) { datum_test(!hex_to_bin_exact("00ff0", exact, sizeof(exact))); datum_test(!hex_to_bin_exact(NULL, exact, sizeof(exact))); datum_test(!hex_to_bin_exact("00ff", NULL, sizeof(exact))); + + datum_test(hex_to_u32("00000000", &value)); + datum_test(value == 0); + datum_test(hex_to_u32("1234aBcD", &value)); + datum_test(value == UINT32_C(0x1234abcd)); + datum_test(hex_to_u32("FFFFFFFF", &value)); + datum_test(value == UINT32_MAX); + datum_test(!hex_to_u32("1234567", &value)); + datum_test(!hex_to_u32("1234567g", &value)); + datum_test(!hex_to_u32("123456789", &value)); + datum_test(!hex_to_u32(NULL, &value)); + datum_test(!hex_to_u32("00000000", NULL)); } void datum_utils_tests_secure_strequals(void) { From 443aecb2ec2151209f80bd89765064f4f16eb65b Mon Sep 17 00:00:00 2001 From: f4u57ox <72417751+f4u57ox@users.noreply.github.com> Date: Sun, 6 Sep 2026 14:03:51 -0600 Subject: [PATCH 3/6] stratum: reject malformed job submission fields --- src/datum_stratum.c | 16 ++++++++++++---- src/datum_stratum_tests.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/datum_stratum.c b/src/datum_stratum.c index a1818faf..bc70e354 100644 --- a/src/datum_stratum.c +++ b/src/datum_stratum.c @@ -1005,6 +1005,7 @@ int client_mining_submit(T_DATUM_CLIENT_DATA *c, uint64_t id, json_t *params_obj unsigned char coinbase_index = 0; T_DATUM_STRATUM_COINBASE *cb = NULL; unsigned char extranonce_bin[12]; + unsigned char job_id_bin[8]; unsigned char block_header[80]; unsigned char share_hash[40]; @@ -1046,6 +1047,11 @@ int client_mining_submit(T_DATUM_CLIENT_DATA *c, uint64_t id, json_t *params_obj return 0; } } + if (!hex_to_bin_exact(job_id_s, job_id_bin, sizeof(job_id_bin))) { + send_unknown_work_error(c,id); + stratum_note_share(m, false, m->last_sent_diff); // guestimate here + return 0; + } // jobID is // 4 bytes time (who cares) @@ -1054,7 +1060,7 @@ int client_mining_submit(T_DATUM_CLIENT_DATA *c, uint64_t id, json_t *params_obj // 1 byte coinbase index used // 6625a3d53cc0e500 // 0123456789ABCDEF - g_job_index = (hex2bin_uchar(&job_id_s[0xA])<<8) | hex2bin_uchar(&job_id_s[0xC]); + g_job_index = (job_id_bin[5]<<8) | job_id_bin[6]; g_job_index ^= STRATUM_JOB_INDEX_XOR; if (g_job_index >= MAX_STRATUM_JOBS) { send_unknown_work_error(c,id); @@ -1101,12 +1107,14 @@ int client_mining_submit(T_DATUM_CLIENT_DATA *c, uint64_t id, json_t *params_obj stratum_note_share(m, false, job_diff); return 0; } - for(i=0;i<8;i++) { - extranonce_bin[i+4] = hex2bin_uchar(&extranonce2_s[i<<1]); + if (!hex_to_bin_exact(extranonce2_s, extranonce_bin + 4, 8)) { + send_unknown_work_error(c, id); + stratum_note_share(m, false, job_diff); + return 0; } // need to build the full coinbase txn - coinbase_index = hex2bin_uchar(&job_id_s[0xE]); + coinbase_index = job_id_bin[7]; if (coinbase_index >= MAX_COINBASE_TYPES) { if (!(empty_work && coinbase_index == DATUM_COINBASE_ID_EMPTY)) { send_unknown_work_error(c, id); diff --git a/src/datum_stratum_tests.c b/src/datum_stratum_tests.c index 17199bba..d7d991c3 100644 --- a/src/datum_stratum_tests.c +++ b/src/datum_stratum_tests.c @@ -199,6 +199,42 @@ static void datum_blake2b_h_not_zero_tests(void) { global_cur_stratum_jobs[0] = saved_job; } +static void datum_blake2b_malformed_submit_job_tests(void) { + T_DATUM_CLIENT_DATA client = {0}; + T_DATUM_MINER_DATA miner = {0}; + T_DATUM_STRATUM_JOB job = {0}; + T_DATUM_TEMPLATE_DATA tdata = {0}; + T_DATUM_STRATUM_JOB *saved_job = global_cur_stratum_jobs[0]; + static const char * const submits[] = { + "{\"id\":8,\"method\":\"mining.submit\",\"params\":[\"miner\",\"0000000000c0dg00\",\"0000000000000000\",\"00000000\",\"00000000\"]}", + "{\"id\":8,\"method\":\"mining.submit\",\"params\":[\"miner\",\"0000000000c0de00\",\"000000000000000g\",\"00000000\",\"00000000\"]}", + }; + static const char expected[] = + "{\"error\":[20,\"unknown-work\",null],\"id\":8,\"result\":null}\n"; + char submit[192]; + + client.app_client_data = &miner; + job.block_template = &tdata; + job.target_pot_index = 0; + job.coinbase[0].coinb1_len = 1; + job.coinbase[0].coinb1_bin[0] = 0xff; + strcpy(job.job_id, "0000000000c0de00"); + miner.stratum_job_diffs[0] = 1; + global_cur_stratum_jobs[0] = &job; + + for (size_t i = 0; i < sizeof(submits) / sizeof(submits[0]); i++) { + const uint64_t rejected_before = miner.share_count_rejected; + client.out_buf = 0; + strcpy(submit, submits[i]); + datum_test(datum_stratum_v1_socket_thread_client_cmd(&client, submit) == 0); + datum_test(miner.share_count_rejected == rejected_before + 1); + datum_test(client.out_buf == (int)strlen(expected)); + datum_test(!memcmp(client.w_buffer, expected, strlen(expected))); + } + + global_cur_stratum_jobs[0] = saved_job; +} + static void datum_blake2b_coinbase_selection_tests(void) { T_DATUM_STRATUM_THREADPOOL_DATA *sdata = calloc(1, sizeof(*sdata)); T_DATUM_STRATUM_JOB job = {0}; @@ -509,6 +545,7 @@ void datum_stratum_tests(void) { datum_stratum_string_request_id_tests(); datum_blake2b_coinbase_selection_tests(); datum_blake2b_h_not_zero_tests(); + datum_blake2b_malformed_submit_job_tests(); datum_blake2b_client_pot_commitment_tests(); datum_blake2b_unmasked_block_tests(); datum_stratum_abw_block_request_tests(); From d982623848604ce85874c90e9154011a2525ccec Mon Sep 17 00:00:00 2001 From: f4u57ox <72417751+f4u57ox@users.noreply.github.com> Date: Sun, 6 Sep 2026 14:04:21 -0600 Subject: [PATCH 4/6] stratum: reject malformed time and nonce fields --- src/datum_stratum.c | 24 ++++++++++++++++++++---- src/datum_stratum_tests.c | 4 ++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/datum_stratum.c b/src/datum_stratum.c index bc70e354..d2ac4d72 100644 --- a/src/datum_stratum.c +++ b/src/datum_stratum.c @@ -1177,10 +1177,18 @@ int client_mining_submit(T_DATUM_CLIENT_DATA *c, uint64_t id, json_t *params_obj return 0; } if (ntime_len == 8) { - ntime_val = (uint32_t)strtoul(ntime_s, NULL, 16); + if (!hex_to_u32(ntime_s, &ntime_val)) { + send_unknown_work_error(c, id); + stratum_note_share(m, false, job_diff); + return 0; + } pk_u32le(ntime8, 0, ntime_val); } else { - for(i=0;i<8;i++) ntime8[i] = hex2bin_uchar(&ntime_s[i << 1]); + if (!hex_to_bin_exact(ntime_s, ntime8, 8)) { + send_unknown_work_error(c, id); + stratum_note_share(m, false, job_diff); + return 0; + } ntime_val = upk_u32le(ntime8, 0); } ntime64 = upk_u64le(ntime8, 0); @@ -1205,10 +1213,18 @@ int client_mining_submit(T_DATUM_CLIENT_DATA *c, uint64_t id, json_t *params_obj return 0; } if (nonce_len == 8) { - nonce_val = (uint32_t)strtoul(nonce_s, NULL, 16); + if (!hex_to_u32(nonce_s, &nonce_val)) { + send_unknown_work_error(c, id); + stratum_note_share(m, false, job_diff); + return 0; + } pk_u32le(nonce8, 0, nonce_val); } else { - for(i=0;i<8;i++) nonce8[i] = hex2bin_uchar(&nonce_s[i << 1]); + if (!hex_to_bin_exact(nonce_s, nonce8, 8)) { + send_unknown_work_error(c, id); + stratum_note_share(m, false, job_diff); + return 0; + } nonce_val = upk_u32le(nonce8, 0); } nonce64 = upk_u64le(nonce8, 0); diff --git a/src/datum_stratum_tests.c b/src/datum_stratum_tests.c index d7d991c3..69771a98 100644 --- a/src/datum_stratum_tests.c +++ b/src/datum_stratum_tests.c @@ -208,6 +208,10 @@ static void datum_blake2b_malformed_submit_job_tests(void) { static const char * const submits[] = { "{\"id\":8,\"method\":\"mining.submit\",\"params\":[\"miner\",\"0000000000c0dg00\",\"0000000000000000\",\"00000000\",\"00000000\"]}", "{\"id\":8,\"method\":\"mining.submit\",\"params\":[\"miner\",\"0000000000c0de00\",\"000000000000000g\",\"00000000\",\"00000000\"]}", + "{\"id\":8,\"method\":\"mining.submit\",\"params\":[\"miner\",\"0000000000c0de00\",\"0000000000000000\",\"0000000g\",\"00000000\"]}", + "{\"id\":8,\"method\":\"mining.submit\",\"params\":[\"miner\",\"0000000000c0de00\",\"0000000000000000\",\"000000000000000g\",\"00000000\"]}", + "{\"id\":8,\"method\":\"mining.submit\",\"params\":[\"miner\",\"0000000000c0de00\",\"0000000000000000\",\"00000000\",\"0000000g\"]}", + "{\"id\":8,\"method\":\"mining.submit\",\"params\":[\"miner\",\"0000000000c0de00\",\"0000000000000000\",\"00000000\",\"000000000000000g\"]}", }; static const char expected[] = "{\"error\":[20,\"unknown-work\",null],\"id\":8,\"result\":null}\n"; From 259cb4f3464f269481fd1f37c75ea488f2edfb26 Mon Sep 17 00:00:00 2001 From: f4u57ox <72417751+f4u57ox@users.noreply.github.com> Date: Sun, 6 Sep 2026 14:04:59 -0600 Subject: [PATCH 5/6] stratum: cache hidden prevblock per BLAKE2b job --- src/datum_pow.c | 14 ++++++++++++-- src/datum_pow.h | 5 +++++ src/datum_pow_tests.c | 4 +++- src/datum_stratum.c | 7 +++---- src/datum_stratum.h | 2 ++ src/datum_stratum_tests.c | 9 +++++++++ 6 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/datum_pow.c b/src/datum_pow.c index 05fab208..594c1f74 100644 --- a/src/datum_pow.c +++ b/src/datum_pow.c @@ -206,13 +206,23 @@ void datum_blake2b_prevblock_hidden(unsigned char *out, const unsigned char *pre memset(out, 0, 6); } -void datum_blake2b_build_work_header(unsigned char *work, const unsigned char *prevhash, const unsigned char *nonce, const unsigned char *ntime, const unsigned char *root) { - datum_blake2b_prevblock_hidden(work, prevhash); +void datum_blake2b_build_work_header_from_hidden( + unsigned char *work, const unsigned char *prevhash_hidden, + const unsigned char *nonce, const unsigned char *ntime, + const unsigned char *root +) { + memcpy(work, prevhash_hidden, 32); memcpy(work + 32, nonce, 8); memcpy(work + 40, ntime, 8); memcpy(work + 48, root, 32); } +void datum_blake2b_build_work_header(unsigned char *work, const unsigned char *prevhash, const unsigned char *nonce, const unsigned char *ntime, const unsigned char *root) { + unsigned char prevhash_hidden[32]; + datum_blake2b_prevblock_hidden(prevhash_hidden, prevhash); + datum_blake2b_build_work_header_from_hidden(work, prevhash_hidden, nonce, ntime, root); +} + bool datum_blake2b_header_commitment_from_key_hash( unsigned char *commitment, uint32_t version, diff --git a/src/datum_pow.h b/src/datum_pow.h index 7607db3d..d6cfa5f2 100644 --- a/src/datum_pow.h +++ b/src/datum_pow.h @@ -64,6 +64,11 @@ long double datum_blake2b_accounting_difficulty(long double x); bool datum_blake2b_256(unsigned char *out, const unsigned char *in, size_t len); void datum_blake2b_coinb1(unsigned char *out, const unsigned char *commitment); void datum_blake2b_prevblock_hidden(unsigned char *out, const unsigned char *prevhash); +void datum_blake2b_build_work_header_from_hidden( + unsigned char *work, const unsigned char *prevhash_hidden, + const unsigned char *nonce, const unsigned char *ntime, + const unsigned char *root +); void datum_blake2b_build_work_header(unsigned char *work, const unsigned char *prevhash, const unsigned char *nonce, const unsigned char *ntime, const unsigned char *root); /* version may be with or without 0x80000000; H1 always includes the v2 bit. */ diff --git a/src/datum_pow_tests.c b/src/datum_pow_tests.c index 107f152d..48d8eeb1 100644 --- a/src/datum_pow_tests.c +++ b/src/datum_pow_tests.c @@ -126,7 +126,7 @@ static void datum_pow_blake2b_vector_tests(void) { "101112131415161718191a1b1c1d1e1f39300000" "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"; unsigned char merkle[32], xor_key[16], rhs[32], extranonce[12], prevhash[32]; - unsigned char nonce[8], ntime[8], commitment[32], root[32], work[80], hash_le[32]; + unsigned char nonce[8], ntime[8], commitment[32], root[32], work[80], cached_work[80], hash_le[32]; unsigned char share_target[32]; unsigned char coinb1[39], arbitrary_tx[51], leaf_preimage[52] = {0}; unsigned char header[DATUM_BLAKE2B_BLOCK_HEADER_SIZE], expected[DATUM_BLAKE2B_BLOCK_HEADER_SIZE]; @@ -203,6 +203,8 @@ static void datum_pow_blake2b_vector_tests(void) { datum_test(!memcmp(work, expected, sizeof(work))); datum_blake2b_prevblock_hidden(expected, prevhash); datum_test(!memcmp(expected, work, 32)); + datum_blake2b_build_work_header_from_hidden(cached_work, expected, nonce, ntime, root); + datum_test(!memcmp(cached_work, work, sizeof(work))); datum_test(datum_blake2b_pow_hash_le(hash_le, work, xor_key, 13)); datum_test(hex_to_bin_exact(expected_hash_le_hex, expected, 32)); datum_test(!memcmp(hash_le, expected, 32)); diff --git a/src/datum_stratum.c b/src/datum_stratum.c index d2ac4d72..296547ae 100644 --- a/src/datum_stratum.c +++ b/src/datum_stratum.c @@ -1243,7 +1243,7 @@ int client_mining_submit(T_DATUM_CLIENT_DATA *c, uint64_t id, json_t *params_obj stratum_note_share(m, false, job_diff); return 0; } - datum_blake2b_build_work_header(work, job->prevhash_bin, nonce8, ntime8, root); + datum_blake2b_build_work_header_from_hidden(work, job->blake2b_prevblock_hidden, nonce8, ntime8, root); memcpy(block_header, work, 80); if (!datum_blake2b_pow_hash_le(share_hash, work, (const unsigned char[16]){0}, 0)) { send_unknown_work_error(c, id); @@ -2073,7 +2073,6 @@ bool datum_stratum_job_blake2b_commitment(T_DATUM_STRATUM_JOB *s, const T_DATUM_ void datum_stratum_job_refresh_blake2b(T_DATUM_STRATUM_JOB *s) { T_DATUM_TEMPLATE_DATA *block_template; - unsigned char prevblock_hidden[32]; uint32_t time_on_wire; int i; @@ -2099,10 +2098,10 @@ void datum_stratum_job_refresh_blake2b(T_DATUM_STRATUM_JOB *s) { } s->blake2b_time_on_wire = time_on_wire; - datum_blake2b_prevblock_hidden(prevblock_hidden, block_template->previousblockhash_bin); + datum_blake2b_prevblock_hidden(s->blake2b_prevblock_hidden, block_template->previousblockhash_bin); for(i=0;i<32;i++) { - uchar_to_hex(&s->prevhash[i << 1], prevblock_hidden[i]); + uchar_to_hex(&s->prevhash[i << 1], s->blake2b_prevblock_hidden[i]); } s->prevhash[64] = 0; } diff --git a/src/datum_stratum.h b/src/datum_stratum.h index 3cb1fd43..dde60288 100644 --- a/src/datum_stratum.h +++ b/src/datum_stratum.h @@ -136,6 +136,8 @@ typedef struct T_DATUM_STRATUM_JOB { // BLAKE2b job fields uint32_t blake2b_time_on_wire; uint8_t blake2b_flags; + // Tagged hash shared by every submitted share for this job. + unsigned char blake2b_prevblock_hidden[32]; T_DATUM_TEMPLATE_DATA *block_template; diff --git a/src/datum_stratum_tests.c b/src/datum_stratum_tests.c index 69771a98..5d943f9e 100644 --- a/src/datum_stratum_tests.c +++ b/src/datum_stratum_tests.c @@ -48,6 +48,8 @@ int client_mining_submit(T_DATUM_CLIENT_DATA *c, uint64_t id, json_t *params_obj static void datum_blake2b_refresh_time_offset_tests(void) { T_DATUM_TEMPLATE_DATA tdata; T_DATUM_STRATUM_JOB job; + unsigned char expected_hidden[32]; + char expected_hex[65]; /* A job snapshots whether miners may submit a time offset. */ memset(&tdata, 0, sizeof(tdata)); @@ -58,6 +60,13 @@ static void datum_blake2b_refresh_time_offset_tests(void) { datum_stratum_job_refresh_blake2b(&job); datum_test(job.blake2b_time_on_wire == 2000000000u); datum_test(job.blake2b_flags == DATUM_BLAKE2B_USE_TIME_OFFSET); + datum_blake2b_prevblock_hidden(expected_hidden, tdata.previousblockhash_bin); + datum_test(!memcmp(job.blake2b_prevblock_hidden, expected_hidden, sizeof(expected_hidden))); + for (size_t i = 0; i < sizeof(expected_hidden); i++) { + uchar_to_hex(expected_hex + (i << 1), expected_hidden[i]); + } + expected_hex[64] = 0; + datum_test(!strcmp(job.prevhash, expected_hex)); /* Without the flag the offset is ignored and curtime goes on the wire as is. */ tdata.curtime = 2000000000; From a35e10ff6fae1e7f2fa48ab37158616f8882b086 Mon Sep 17 00:00:00 2001 From: f4u57ox <72417751+f4u57ox@users.noreply.github.com> Date: Tue, 8 Sep 2026 16:20:10 -0600 Subject: [PATCH 6/6] utils: reject truncated hex before reading the next byte --- src/datum_utils.c | 3 ++- src/datum_utils_tests.c | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/datum_utils.c b/src/datum_utils.c index ad09a1d2..d08afe5b 100644 --- a/src/datum_utils.c +++ b/src/datum_utils.c @@ -368,8 +368,9 @@ bool hex_to_bin_exact(const char *hex, unsigned char *bin, const size_t bin_len) if (!hex || !bin) return false; for (size_t i = 0; i < bin_len; i++) { high = hex_value(hex[i<<1]); + if (high < 0) return false; low = hex_value(hex[(i<<1)+1]); - if (high < 0 || low < 0) return false; + if (low < 0) return false; bin[i] = (unsigned char)((high << 4) | low); } return hex[bin_len<<1] == 0; diff --git a/src/datum_utils_tests.c b/src/datum_utils_tests.c index 42283800..84a46046 100644 --- a/src/datum_utils_tests.c +++ b/src/datum_utils_tests.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -116,6 +117,18 @@ void datum_utils_tests_hex(void) { datum_test(!hex_to_u32("123456789", &value)); datum_test(!hex_to_u32(NULL, &value)); datum_test(!hex_to_u32("00000000", NULL)); + + /* Exact allocations expose reads past the terminator under ASan. */ + for (size_t len = 0; len < 8; ++len) { + unsigned char bin[4]; + char *truncated = malloc(len + 1); + if (!datum_test(truncated != NULL)) break; + memcpy(truncated, "1234aBcD", len); + truncated[len] = '\0'; + datum_test(!hex_to_bin_exact(truncated, bin, sizeof(bin))); + datum_test(!hex_to_u32(truncated, &value)); + free(truncated); + } } void datum_utils_tests_secure_strequals(void) {