|
| 1 | +// --------------------------------------------------------------------------- |
| 2 | +// dgb_hash_format_parity_test -- FENCED, additive KAT pinning the pure GBT |
| 3 | +// block-hash display formatter dgb::coin::u256_be_display_hex lifted into |
| 4 | +// coin/hash_format.hpp. |
| 5 | +// |
| 6 | +// u256_be_display_hex renders a 256-bit value (coin/dgb_arith256.hpp u256, a |
| 7 | +// little-endian limb array with limb[0] least-significant) as the |
| 8 | +// GBT-conventional big-endian display hex: most-significant limb first, 64 |
| 9 | +// lowercase hex digits, no 0x prefix -- the encoding the stratum work source |
| 10 | +// and the embedded work path both emit for "previousblockhash". A 1-nibble |
| 11 | +// divergence in limb/byte ordering there means the two callers of |
| 12 | +// build_work_template could emit a prevhash the parent daemon never accepts. |
| 13 | +// |
| 14 | +// The anchor is NON-CIRCULAR: the DigiByte mainnet genesis block hash |
| 15 | +// 7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496 is the |
| 16 | +// external constant the DGB oracle pins at frstrtr/p2pool-dgb-scrypt |
| 17 | +// bitcoin/networks/digibyte.py:55 (helper.check_block_header(..., 'genesis')). |
| 18 | +// The test parses that literal display hex into 32 MSB-first bytes with an |
| 19 | +// independent hex reader, reverses to the on-the-wire little-endian byte order, |
| 20 | +// feeds u256::from_le_bytes (the exact storage path a real tip hash takes), and |
| 21 | +// asserts u256_be_display_hex reproduces the original oracle string byte for |
| 22 | +// byte. The formatter under test is the only code computing the output. |
| 23 | +// |
| 24 | +// DIAGNOSTICS / WIRE-DISPLAY ONLY: no consensus surface, no payout/subsidy/ |
| 25 | +// version-gate value. Pure header (<cstdint>/<string>) -> links only GTest. No |
| 26 | +// call site rewired. MUST also be in BOTH build.yml --target allowlists |
| 27 | +// (#143 NOT_BUILT trap). |
| 28 | +// --------------------------------------------------------------------------- |
| 29 | + |
| 30 | +#include <gtest/gtest.h> |
| 31 | + |
| 32 | +#include <array> |
| 33 | +#include <cstdint> |
| 34 | +#include <string> |
| 35 | + |
| 36 | +#include "../coin/hash_format.hpp" |
| 37 | + |
| 38 | +using dgb::coin::u256; |
| 39 | +using dgb::coin::u256_be_display_hex; |
| 40 | + |
| 41 | +namespace { |
| 42 | + |
| 43 | +// Independent hex reader (does NOT share the formatter's nibble/limb logic): |
| 44 | +// parse a 64-char big-endian display hex string into 32 MSB-first bytes. |
| 45 | +std::array<unsigned char, 32> be_hex_to_msb_bytes(const std::string& hex) |
| 46 | +{ |
| 47 | + EXPECT_EQ(hex.size(), 64u); |
| 48 | + auto nib = [](char c) -> int { |
| 49 | + if (c >= '0' && c <= '9') return c - '0'; |
| 50 | + if (c >= 'a' && c <= 'f') return c - 'a' + 10; |
| 51 | + if (c >= 'A' && c <= 'F') return c - 'A' + 10; |
| 52 | + return -1; |
| 53 | + }; |
| 54 | + std::array<unsigned char, 32> out{}; |
| 55 | + for (int i = 0; i < 32; ++i) |
| 56 | + out[i] = (unsigned char)((nib(hex[2 * i]) << 4) | nib(hex[2 * i + 1])); |
| 57 | + return out; |
| 58 | +} |
| 59 | + |
| 60 | +// Build a u256 from a big-endian display hex string via the production |
| 61 | +// from_le_bytes path: reverse MSB-first display bytes into little-endian |
| 62 | +// storage order (the byte order a parsed tip hash actually arrives in). |
| 63 | +u256 u256_from_display_hex(const std::string& hex) |
| 64 | +{ |
| 65 | + auto msb = be_hex_to_msb_bytes(hex); |
| 66 | + unsigned char le[32]; |
| 67 | + for (int i = 0; i < 32; ++i) le[i] = msb[31 - i]; |
| 68 | + return u256::from_le_bytes(le); |
| 69 | +} |
| 70 | + |
| 71 | +} // namespace |
| 72 | + |
| 73 | +// Oracle anchor: the DGB mainnet genesis hash (digibyte.py:55) must round-trip |
| 74 | +// through the LE storage path back to its canonical display hex. |
| 75 | +TEST(DgbHashFormatParity, GenesisHashRendersOracleDisplayHex) |
| 76 | +{ |
| 77 | + const std::string genesis = |
| 78 | + "7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496"; |
| 79 | + EXPECT_EQ(u256_be_display_hex(u256_from_display_hex(genesis)), genesis); |
| 80 | +} |
| 81 | + |
| 82 | +// Zero renders 64 zero nibbles -- no truncation, full left-padding. |
| 83 | +TEST(DgbHashFormatParity, ZeroRendersSixtyFourZeros) |
| 84 | +{ |
| 85 | + EXPECT_EQ(u256_be_display_hex(u256{}), std::string(64, '0')); |
| 86 | +} |
| 87 | + |
| 88 | +// A tiny value pins the left zero-padding and that limb[0] is the LOW nibble. |
| 89 | +TEST(DgbHashFormatParity, OneRendersPaddedToSixtyFourDigits) |
| 90 | +{ |
| 91 | + u256 v; |
| 92 | + v.limb[0] = 1; |
| 93 | + EXPECT_EQ(u256_be_display_hex(v), std::string(63, '0') + "1"); |
| 94 | +} |
| 95 | + |
| 96 | +// Distinct per-limb values pin most-significant-limb-FIRST ordering (limb[3] |
| 97 | +// is the leading 16 hex digits, limb[0] the trailing 16). |
| 98 | +TEST(DgbHashFormatParity, LimbOrderingMostSignificantFirst) |
| 99 | +{ |
| 100 | + u256 v; |
| 101 | + v.limb[3] = 0x0123456789abcdefULL; |
| 102 | + v.limb[2] = 0xfedcba9876543210ULL; |
| 103 | + v.limb[1] = 0x00000000ffffffffULL; |
| 104 | + v.limb[0] = 0xdeadbeefcafef00dULL; |
| 105 | + EXPECT_EQ(u256_be_display_hex(v), |
| 106 | + "0123456789abcdef" |
| 107 | + "fedcba9876543210" |
| 108 | + "00000000ffffffff" |
| 109 | + "deadbeefcafef00d"); |
| 110 | +} |
| 111 | + |
| 112 | +// All-ones renders 64 'f' nibbles (every limb fully populated). |
| 113 | +TEST(DgbHashFormatParity, AllOnesRendersSixtyFourEffs) |
| 114 | +{ |
| 115 | + u256 v; |
| 116 | + for (int i = 0; i < 4; ++i) v.limb[i] = 0xffffffffffffffffULL; |
| 117 | + EXPECT_EQ(u256_be_display_hex(v), std::string(64, 'f')); |
| 118 | +} |
| 119 | + |
| 120 | +// from_le_bytes with a fully-distinct 0x00..0x1f pattern pins the byte |
| 121 | +// reversal: LE byte 0 (0x00) is the LOW byte -> trailing "00", LE byte 31 |
| 122 | +// (0x1f) is the HIGH byte -> leading "1f". |
| 123 | +TEST(DgbHashFormatParity, FromLeBytesByteReversalIsBigEndianDisplay) |
| 124 | +{ |
| 125 | + unsigned char le[32]; |
| 126 | + for (int i = 0; i < 32; ++i) le[i] = (unsigned char)i; |
| 127 | + EXPECT_EQ(u256_be_display_hex(u256::from_le_bytes(le)), |
| 128 | + "1f1e1d1c1b1a1918" |
| 129 | + "1716151413121110" |
| 130 | + "0f0e0d0c0b0a0908" |
| 131 | + "0706050403020100"); |
| 132 | +} |
0 commit comments