|
| 1 | +// --------------------------------------------------------------------------- |
| 2 | +// nmc::coin::aux_merkle_root KAT (P1 — AuxPow merkle-proof walk). |
| 3 | +// |
| 4 | +// Pins the merge-mining merkle-branch walk that AuxPow::check_proof() (still a |
| 5 | +// P-DEFER stub) will consume for step-1 (chain-merkle-root) and step-3 (parent |
| 6 | +// tx-merkle-root). The walk is a byte-faithful port of legacy |
| 7 | +// libcoind/data.cpp check_merkle_link() — the same SSOT btc uses — so these |
| 8 | +// KATs lock the consensus-relevant ordering: |
| 9 | +// * empty branch => root == leaf (identity); |
| 10 | +// * index bit i selects whether branch[i] is the LEFT or RIGHT sibling at |
| 11 | +// depth i, folded with double-SHA256 of the 64-byte concatenation; |
| 12 | +// * an index that does not fit in branch.size() bits is rejected. |
| 13 | +// |
| 14 | +// Expected roots are derived INDEPENDENTLY here via core::Hash on the explicit |
| 15 | +// concatenation (not by calling aux_merkle_root), so a side-swap or fold bug in |
| 16 | +// the walk is caught rather than mirrored. Self-derived => no fixture file. |
| 17 | +// |
| 18 | +// Per-coin isolation: src/impl/nmc/ only; btc tree consumed READ-ONLY (this is |
| 19 | +// an independent NMC-local re-derivation, fence #4). MUST appear in BOTH |
| 20 | +// test/CMakeLists.txt AND the build.yml --target allowlist or it becomes a |
| 21 | +// NOT_BUILT sentinel that reds master. |
| 22 | +// --------------------------------------------------------------------------- |
| 23 | + |
| 24 | +#include <gtest/gtest.h> |
| 25 | + |
| 26 | +#include <stdexcept> |
| 27 | +#include <vector> |
| 28 | + |
| 29 | +#include <core/hash.hpp> |
| 30 | +#include <core/pack.hpp> |
| 31 | +#include <core/uint256.hpp> |
| 32 | + |
| 33 | +#include "../coin/header_chain.hpp" |
| 34 | + |
| 35 | +namespace { |
| 36 | + |
| 37 | +using nmc::coin::aux_merkle_root; |
| 38 | + |
| 39 | +// Build a uint256 whose first byte is `b` (rest zero) — distinct, legible leaves. |
| 40 | +static uint256 leaf_of(unsigned char b) |
| 41 | +{ |
| 42 | + uint256 u; |
| 43 | + u.SetNull(); |
| 44 | + *(u.begin()) = b; |
| 45 | + return u; |
| 46 | +} |
| 47 | + |
| 48 | +// Independent reference combine: double-SHA256 of l||r, mirroring the legacy |
| 49 | +// merkle node hash but computed here WITHOUT touching aux_merkle_root. |
| 50 | +static uint256 combine(const uint256& l, const uint256& r) |
| 51 | +{ |
| 52 | + PackStream ps; |
| 53 | + ps << l; |
| 54 | + ps << r; |
| 55 | + auto sp = std::span<const unsigned char>( |
| 56 | + reinterpret_cast<const unsigned char*>(ps.data()), ps.size()); |
| 57 | + return Hash(sp); |
| 58 | +} |
| 59 | + |
| 60 | +TEST(NmcAuxMerkle, EmptyBranchIsIdentity) |
| 61 | +{ |
| 62 | + uint256 leaf = leaf_of(0x11); |
| 63 | + EXPECT_EQ(aux_merkle_root(leaf, {}, 0), leaf); |
| 64 | + // index is ignored when there is no branch to walk. |
| 65 | + EXPECT_EQ(aux_merkle_root(leaf, {}, 12345u), leaf); |
| 66 | +} |
| 67 | + |
| 68 | +TEST(NmcAuxMerkle, SingleStepIndexZeroLeafOnLeft) |
| 69 | +{ |
| 70 | + uint256 leaf = leaf_of(0x11); |
| 71 | + uint256 sib = leaf_of(0x22); |
| 72 | + // index bit 0 == 0 => leaf is LEFT, sibling RIGHT. |
| 73 | + EXPECT_EQ(aux_merkle_root(leaf, {sib}, 0), combine(leaf, sib)); |
| 74 | +} |
| 75 | + |
| 76 | +TEST(NmcAuxMerkle, SingleStepIndexOneLeafOnRight) |
| 77 | +{ |
| 78 | + uint256 leaf = leaf_of(0x11); |
| 79 | + uint256 sib = leaf_of(0x22); |
| 80 | + // index bit 0 == 1 => sibling LEFT, leaf RIGHT. |
| 81 | + EXPECT_EQ(aux_merkle_root(leaf, {sib}, 1), combine(sib, leaf)); |
| 82 | +} |
| 83 | + |
| 84 | +TEST(NmcAuxMerkle, TwoStepFoldRespectsPerLevelIndexBits) |
| 85 | +{ |
| 86 | + uint256 leaf = leaf_of(0x11); |
| 87 | + uint256 b0 = leaf_of(0x22); |
| 88 | + uint256 b1 = leaf_of(0x33); |
| 89 | + // index = 0b10: level0 bit=0 (leaf left), level1 bit=1 (accum right). |
| 90 | + uint256 lvl0 = combine(leaf, b0); |
| 91 | + uint256 want = combine(b1, lvl0); |
| 92 | + EXPECT_EQ(aux_merkle_root(leaf, {b0, b1}, 0b10u), want); |
| 93 | +} |
| 94 | + |
| 95 | +TEST(NmcAuxMerkle, IndexTooLargeForBranchThrows) |
| 96 | +{ |
| 97 | + uint256 leaf = leaf_of(0x11); |
| 98 | + uint256 sib = leaf_of(0x22); |
| 99 | + // one-element branch admits indices 0..1; 2 overflows the implied tree. |
| 100 | + EXPECT_THROW(aux_merkle_root(leaf, {sib}, 2u), std::invalid_argument); |
| 101 | +} |
| 102 | + |
| 103 | +} // namespace |
0 commit comments