Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/impl/nmc/coin/header_chain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,43 @@ inline MMScan scan_mm_commitment(const std::vector<unsigned char>& script,
return MMScan::MATCH;
}

/// Build side -- the inverse of scan_mm_commitment(). Emit the canonical
/// merged-mining commitment payload a parent (BTC) coinbase scriptSig must carry
/// so this aux chain's work is provable. The byte layout is exactly the sequence
/// scan_mm_commitment() walks:
/// MM_HEADER_MAGIC(4) | chain_merkle_root REVERSED(32) | size_le32 | nonce_le32
/// where size = 2^chain_height is the chain-merkle tree size and the aux chain
/// occupies slot aux_expected_index(nonce, chain_id, chain_height). The root is
/// stored reversed (big-endian display order) to match auxpow.cpp. This emits
/// ONLY the MM marker payload; the caller (PD dual-target build path) prepends
/// any coinbase scriptSig framing (height push, extranonce). Kept NMC-LOCAL per
/// the coin fence (only std + core types). chain_merkle_root is taken by value
/// because base_uint::begin() is non-const (same as scan_mm_commitment).
inline std::vector<unsigned char> build_mm_commitment(uint256 chain_merkle_root,
unsigned chain_height,
uint32_t nonce) {
std::vector<unsigned char> out;
out.reserve(4 + uint256::BYTES + 8);
out.insert(out.end(), MM_HEADER_MAGIC, MM_HEADER_MAGIC + 4);

// Chain-merkle root committed reversed (big-endian display order).
const unsigned char* rb =
reinterpret_cast<const unsigned char*>(chain_merkle_root.begin());
std::vector<unsigned char> root_be(rb, rb + uint256::BYTES);
std::reverse(root_be.begin(), root_be.end());
out.insert(out.end(), root_be.begin(), root_be.end());

auto put_le32 = [&out](uint32_t x) {
out.push_back(static_cast<unsigned char>( x & 0xff));
out.push_back(static_cast<unsigned char>((x >> 8) & 0xff));
out.push_back(static_cast<unsigned char>((x >> 16) & 0xff));
out.push_back(static_cast<unsigned char>((x >> 24) & 0xff));
};
put_le32(1u << chain_height); // tree size = 2^chain_height
put_le32(nonce);
return out;
}

// ─── AuxPow (merge-mining proof) ─────────────────────────────────────────────

/// One aux-chain slot inside a parent coinbase's merged-mining merkle tree.
Expand Down
58 changes: 58 additions & 0 deletions src/impl/nmc/test/auxpow_merkle_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ TEST(NmcAuxCheckProof, ParentCoinbaseIndexTooWideForDepthIsInvalid)
using nmc::coin::scan_mm_commitment;
using nmc::coin::aux_expected_index;
using nmc::coin::MMScan;
using nmc::coin::build_mm_commitment;

static const unsigned char MM_MAGIC[4] = {0xfa, 0xbe, 'm', 'm'};

Expand Down Expand Up @@ -450,6 +451,63 @@ TEST(NmcAuxStep2, NoMarkerLeavesProofIncomplete)
EXPECT_EQ(ap.check_proof(aux, 1), AuxPow::CheckResult::INCOMPLETE);
}

// ---------------------------------------------------------------------------
// build_mm_commitment -- the BUILD-side inverse of scan_mm_commitment (PC-prep
// auxpow commitment scaffolding, overlapping PD's dual-target parent coinbase).
// The commitment a c2pool-built parent coinbase embeds must round-trip through
// the SAME scanner the verifier runs: emit then scan the marker for the slot
// aux_expected_index(nonce, chain_id, height) and it MUST return MATCH; any
// tamper (committed root / slot / tree size) MUST NOT. The byte layout is pinned
// explicitly so a build-side layout drift is caught, not mirrored.
// Per-coin isolation: src/impl/nmc/ only; btc tree consumed READ-ONLY.
// ---------------------------------------------------------------------------

TEST(NmcBuildMMCommitment, RoundTripsThroughScannerAtExpectedSlot)
{
uint256 aux = leaf_of(0x01), sib = leaf_of(0x55);
uint256 root = combine(aux, sib);
const unsigned h = 1; const int32_t cid = 1; const uint32_t nonce = 1;

auto payload = build_mm_commitment(root, h, nonce);
const uint32_t slot = aux_expected_index(nonce, cid, h);
EXPECT_EQ(scan_mm_commitment(payload, root, h, cid, slot), MMScan::MATCH);
}

TEST(NmcBuildMMCommitment, ByteLayoutIsMagicReversedRootSizeNonce)
{
uint256 root = leaf_of(0x01);
const unsigned h = 3; const uint32_t nonce = 0x04030201u;

auto got = build_mm_commitment(root, h, nonce);

std::vector<unsigned char> want;
want.insert(want.end(), MM_MAGIC, MM_MAGIC + 4);
auto rr = root_reversed(root);
want.insert(want.end(), rr.begin(), rr.end());
put_le32(want, 1u << h); // tree size = 2^h = 8
put_le32(want, nonce);

EXPECT_EQ(got, want);
EXPECT_EQ(got.size(), static_cast<size_t>(4 + 32 + 4 + 4));
}

TEST(NmcBuildMMCommitment, TamperedRootOrSlotFailsScan)
{
uint256 aux = leaf_of(0x02), sib = leaf_of(0x77);
uint256 root = combine(aux, sib);
const unsigned h = 2; const int32_t cid = 1; const uint32_t nonce = 7;

auto payload = build_mm_commitment(root, h, nonce);
const uint32_t slot = aux_expected_index(nonce, cid, h);

EXPECT_EQ(scan_mm_commitment(payload, root, h, cid, slot), MMScan::MATCH);
// Wrong committed root.
EXPECT_EQ(scan_mm_commitment(payload, leaf_of(0x99), h, cid, slot), MMScan::MISMATCH);
// Right marker read at the wrong deterministic slot.
EXPECT_EQ(scan_mm_commitment(payload, root, h, cid, (slot + 1u) & ((1u << h) - 1u)),
MMScan::MISMATCH);
}

// ---------------------------------------------------------------------------
// P1c-step4: AuxPow::check_proof parent proof-of-work leg (step 4).
//
Expand Down
Loading