@@ -1569,4 +1569,156 @@ TEST(NmcAuxChainIdConformance, BuildVerifySymmetryOnFactoryDefault)
15691569 EXPECT_EQ (static_cast <uint32_t >(p.aux_chain_id ), 1u ); // build-side AuxWork.chain_id
15701570}
15711571
1572+ // ---------------------------------------------------------------------------
1573+ // PD-prep GOLDEN FIXTURE: dual-target coinbase merged-mining commitment layout.
1574+ //
1575+ // Pins PD's acceptance target AHEAD of the build code. PD's dual-target coinbase
1576+ // path (PR #408's nmc::coin::build_mm_commitment) must emit the merged-mining
1577+ // marker in EXACTLY the Namecoin/Bitcoin auxpow.cpp reference layout:
1578+ //
1579+ // [ fa be 6d 6d ] 4-byte MM magic ("\xfa\xbe" + "mm")
1580+ // [ 32 bytes ] chain-merkle root, REVERSED (big-endian display order)
1581+ // [ 4 bytes LE ] chain-merkle tree size = 2^chain_height
1582+ // [ 4 bytes LE ] nonce
1583+ //
1584+ // The expected 44-byte marker is hand-computed here from that reference oracle
1585+ // and asserted as an explicit byte LITERAL. This fixture DELIBERATELY does NOT
1586+ // import or invoke build_mm_commitment: that is PD's surface, and calling it
1587+ // would re-introduce the #408 dependency and merely mirror a build bug instead
1588+ // of catching it. The literal is the independent oracle PD's output reproduces.
1589+ //
1590+ // Binding to the already-merged consensus surface (build-independent):
1591+ // * the literal is fed through scan_mm_commitment (the verify-side SSOT) and
1592+ // must return MMScan::MATCH;
1593+ // * the deterministic slot is cross-checked against aux_expected_index, and a
1594+ // wrong slot is shown to be rejected (the slot field is load-bearing);
1595+ // * the local hand layout (root_reversed + mm_script, NOT build_mm_commitment)
1596+ // is asserted byte-equal to the literal so a layout drift on either side reds.
1597+ //
1598+ // Per-coin isolation (P0 fence #4): src/impl/nmc/ test target only; additive;
1599+ // rides the already-allowlisted nmc_auxpow_merkle_test exe (no build.yml change,
1600+ // no consensus-value change). Pure layout/byte-order pin, no other-coin tree.
1601+ // ---------------------------------------------------------------------------
1602+
1603+ // uint256 whose 32 INTERNAL (little-endian) bytes ascend from `start`.
1604+ static uint256 root_ascending (unsigned char start)
1605+ {
1606+ uint256 u; u.SetNull ();
1607+ // begin() is a typed (word-strided) iterator; cast to raw bytes like
1608+ // root_reversed does so we set 32 CONTIGUOUS internal bytes, not every 4th.
1609+ unsigned char * p = reinterpret_cast <unsigned char *>(u.begin ());
1610+ for (size_t i = 0 ; i < uint256::BYTES ; ++i)
1611+ p[i] = static_cast <unsigned char >(start + i);
1612+ return u;
1613+ }
1614+
1615+ TEST (NmcPdGoldenFixture, SingleSlotMarkerMatchesHandComputedLiteral)
1616+ {
1617+ // Live NMC-under-BTC posture: a length-1 chain tree (height 0 => size 1),
1618+ // so the aux chain always occupies slot 0 regardless of nonce. A legible
1619+ // non-zero nonce exercises the little-endian nonce field.
1620+ uint256 root = root_ascending (0x01 ); // internal LE = 01 02 .. 20
1621+ const unsigned h = 0 ;
1622+ const int32_t cid = 1 ;
1623+ const uint32_t nonce = 0x12345678u ;
1624+
1625+ // Hand-computed golden marker (44 bytes), reference layout above.
1626+ const std::vector<unsigned char > golden = {
1627+ 0xfa , 0xbe , 0x6d , 0x6d , // MM magic
1628+ // chain-merkle root, reversed (big-endian display order):
1629+ 0x20 , 0x1f , 0x1e , 0x1d , 0x1c , 0x1b , 0x1a , 0x19 ,
1630+ 0x18 , 0x17 , 0x16 , 0x15 , 0x14 , 0x13 , 0x12 , 0x11 ,
1631+ 0x10 , 0x0f , 0x0e , 0x0d , 0x0c , 0x0b , 0x0a , 0x09 ,
1632+ 0x08 , 0x07 , 0x06 , 0x05 , 0x04 , 0x03 , 0x02 , 0x01 ,
1633+ 0x01 , 0x00 , 0x00 , 0x00 , // size = 2^0 = 1 (LE)
1634+ 0x78 , 0x56 , 0x34 , 0x12 , // nonce 0x12345678 (LE)
1635+ };
1636+ ASSERT_EQ (golden.size (), 4u + uint256::BYTES + 8u );
1637+
1638+ // Independent hand layout (NOT build_mm_commitment) reproduces the literal.
1639+ // mm_script() frames the marker with a 4-byte dummy coinbase-height prefix
1640+ // (build_mm_commitment emits ONLY the marker payload; the caller prepends the
1641+ // scriptSig framing), so strip that prefix before the byte-equality check.
1642+ std::vector<unsigned char > framed = mm_script (root_reversed (root), 1u << h, nonce);
1643+ std::vector<unsigned char > payload (framed.begin () + 4 , framed.end ());
1644+ EXPECT_EQ (payload, golden);
1645+
1646+ // Deterministic slot is 0 and is what the verify-side arithmetic demands.
1647+ const uint32_t slot = aux_expected_index (nonce, cid, h);
1648+ EXPECT_EQ (slot, 0u );
1649+
1650+ // The merged verify-side scanner accepts exactly this layout, both as the
1651+ // bare marker and embedded after scriptSig framing (found by search).
1652+ EXPECT_EQ (scan_mm_commitment (golden, root, h, cid, slot), MMScan::MATCH );
1653+ EXPECT_EQ (scan_mm_commitment (framed, root, h, cid, slot), MMScan::MATCH );
1654+ }
1655+
1656+ TEST (NmcPdGoldenFixture, MultiSlotMarkerPinsDeterministicSlotAndLiteral)
1657+ {
1658+ // height 2 => 4-leaf chain tree; nonce 2 pins the aux chain to slot 1
1659+ // (aux_expected_index(2, 1, 2) == 1), exercising both the LE tree-size
1660+ // field (= 4) and the deterministic-slot binding the single-slot case cannot.
1661+ uint256 root = root_ascending (0x41 ); // internal LE = 41 42 .. 60
1662+ const unsigned h = 2 ;
1663+ const int32_t cid = 1 ;
1664+ const uint32_t nonce = 2u ;
1665+
1666+ const std::vector<unsigned char > golden = {
1667+ 0xfa , 0xbe , 0x6d , 0x6d , // MM magic
1668+ 0x60 , 0x5f , 0x5e , 0x5d , 0x5c , 0x5b , 0x5a , 0x59 ,
1669+ 0x58 , 0x57 , 0x56 , 0x55 , 0x54 , 0x53 , 0x52 , 0x51 ,
1670+ 0x50 , 0x4f , 0x4e , 0x4d , 0x4c , 0x4b , 0x4a , 0x49 ,
1671+ 0x48 , 0x47 , 0x46 , 0x45 , 0x44 , 0x43 , 0x42 , 0x41 ,
1672+ 0x04 , 0x00 , 0x00 , 0x00 , // size = 2^2 = 4 (LE)
1673+ 0x02 , 0x00 , 0x00 , 0x00 , // nonce 2 (LE)
1674+ };
1675+ ASSERT_EQ (golden.size (), 44u );
1676+
1677+ std::vector<unsigned char > framed = mm_script (root_reversed (root), 1u << h, nonce);
1678+ std::vector<unsigned char > payload (framed.begin () + 4 , framed.end ());
1679+ EXPECT_EQ (payload, golden);
1680+
1681+ const uint32_t slot = aux_expected_index (nonce, cid, h);
1682+ EXPECT_EQ (slot, 1u ); // hand-pinned: nonce 2, chain_id 1, height 2 => slot 1
1683+
1684+ EXPECT_EQ (scan_mm_commitment (golden, root, h, cid, slot), MMScan::MATCH );
1685+
1686+ // Negative pin: the SAME marker presented for the WRONG slot is rejected,
1687+ // proving the slot field is load-bearing, not decorative.
1688+ EXPECT_EQ (scan_mm_commitment (golden, root, h, cid, slot ^ 1u ), MMScan::MISMATCH );
1689+ }
1690+
1691+ TEST (NmcPdGoldenFixture, MarkerFieldOffsetsAreThePinnedLayout)
1692+ {
1693+ // Pin the exact field offsets PD's build path must emit. Slicing the marker
1694+ // proves WHERE each field sits; each slice is checked against an independent
1695+ // literal (not against the builder), so a field-order/width drift reds.
1696+ uint256 root = root_ascending (0x01 );
1697+ std::vector<unsigned char > framed =
1698+ mm_script (root_reversed (root), /* size=*/ 1u , /* nonce=*/ 0x12345678u );
1699+ // Drop the 4-byte dummy coinbase-height prefix; index the bare marker from 0.
1700+ const std::vector<unsigned char > marker (framed.begin () + 4 , framed.end ());
1701+ ASSERT_EQ (marker.size (), 44u );
1702+
1703+ // [0,4) MM magic.
1704+ EXPECT_EQ ((std::vector<unsigned char >(marker.begin (), marker.begin () + 4 )),
1705+ (std::vector<unsigned char >{0xfa , 0xbe , 0x6d , 0x6d }));
1706+
1707+ // [4,36) reversed root == big-endian display order of the internal bytes,
1708+ // re-derived here by an independent reverse of the ascending internal bytes.
1709+ std::vector<unsigned char > expect_root (uint256::BYTES );
1710+ for (size_t i = 0 ; i < uint256::BYTES ; ++i)
1711+ expect_root[i] = static_cast <unsigned char >(0x01 + (uint256::BYTES - 1 - i));
1712+ EXPECT_EQ ((std::vector<unsigned char >(marker.begin () + 4 , marker.begin () + 36 )),
1713+ expect_root);
1714+
1715+ // [36,40) tree size = 1 (LE).
1716+ EXPECT_EQ ((std::vector<unsigned char >(marker.begin () + 36 , marker.begin () + 40 )),
1717+ (std::vector<unsigned char >{0x01 , 0x00 , 0x00 , 0x00 }));
1718+
1719+ // [40,44) nonce 0x12345678 (LE).
1720+ EXPECT_EQ ((std::vector<unsigned char >(marker.begin () + 40 , marker.begin () + 44 )),
1721+ (std::vector<unsigned char >{0x78 , 0x56 , 0x34 , 0x12 }));
1722+ }
1723+
15721724} // namespace
0 commit comments