Skip to content

nmc(P1c): complete AuxPow::check_proof — all four merge-mining legs + parent-PoW gate - #185

Merged
frstrtr merged 4 commits into
masterfrom
nmc/p1c-step4-parent-pow
Jun 19, 2026
Merged

nmc(P1c): complete AuxPow::check_proof — all four merge-mining legs + parent-PoW gate#185
frstrtr merged 4 commits into
masterfrom
nmc/p1c-step4-parent-pow

Conversation

@frstrtr

@frstrtr frstrtr commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Summary

Completes nmc::coin::AuxPow::check_proof() — promotes it from the P0-DEFER stub to a full merge-mining proof verifier. Builds on the merkle-walk primitive landed in #178 (@417b20af).

Four legs now wired (one commit per step, KAT per step):

  • step 1 (P1b, d80ed51) — chain-merkle leg via aux_merkle_root(); reconstructs the merged-mining root from (aux_block_hash, chain branch, chain index).
  • step 2 (3c25d11) — MM-marker commitment + chain_id/slot binding; rejects duplicate/missing merged-mining headers.
  • step 3 (89b321e) — parent-coinbase tx-merkle leg; txid is witness-stripped legacy hash (NOT wtxid) — pinned by KAT.
  • step 4 (1e85e7e) — parent proof-of-work vs the AUX target (aux_bits expanded via shared chain::bits_to_target); the parents OWN bits are explicitly NOT the gate (KAT ParentOwnBitsAreNotTheAuxGate).

A proof is VALID only when all legs pass: structural merkle reconstruction + matched MM marker + real parent header + parent PoW clearing the aux target. Anything short stays INCOMPLETE (fail-closed). aux_bits == 0 is the leg-only-fixture sentinel.

Test

nmc_auxpow_merkle_test26/26 PASS (NmcAuxMerkle 5, NmcAuxCheckProof 4, NmcAuxParentCoinbase, NmcAuxStep2 7, NmcAuxStep4 4). Build green: nmc_coin, nmc_auxpow_merkle_test targets.

Scope

NMC-only (src/impl/nmc/). No btc-tree changes. DOGE-under-LTC analog (M3). Merge operator/integrator-gated — I do not self-merge.

frstrtr added 4 commits June 19, 2026 01:05
check_proof() reconstructs the merged-mining merkle root from the aux
block hash through chain_merkle_branch/chain_merkle_index via the P1a
aux_merkle_root() primitive. New INCOMPLETE CheckResult state: a
structurally-consistent chain-merkle walk returns INCOMPLETE (never
VALID) because step 2 (MM-marker commitment + chain_id/slot binding),
step 3 (parent-coinbase tx-merkle leg) and step 4 (parent PoW) are not
built yet -- NMC still MUST NOT block-validate off this leaf. Malformed
slot indices (negative, or wider than branch depth) return INVALID.

4 KATs pin the INCOMPLETE-vs-INVALID boundary so the leg cannot silently
regress into asserting VALID. nmc_coin linked into the test (AuxPow ctor
pulls MutableTransaction). 9/9 PASS. NMC-local, btc tree READ-ONLY.
…p 3) + witness-stripped txid KAT

Adds nmc::coin::parent_coinbase_txid() (Hash over TX_NO_WITNESS serialization,
byte-identical to btc compute_txid() but NMC-local per the coin fence) and
wires AuxPow::check_proof step 3: reconstruct the parent block tx-merkle-root
from the witness-stripped coinbase txid through parent_coinbase_branch/index
via aux_merkle_root(). When a parent header is present the reconstructed root
MUST equal parent_header.m_merkle_root, else INVALID; malformed legs (negative
index, index wider than branch depth) are INVALID. Steps 2 (MM-marker
commitment) and 4 (parent PoW) remain unbuilt, so a structurally-consistent
proof still returns INCOMPLETE, never VALID -- NMC MUST NOT block-validate off
this leaf.

The load-bearing KAT asserts the exact byte serialization: the txid is hashed
over the LEGACY (no-witness) layout -- txid, NOT wtxid -- re-derived field-by-
field without SerializeTransaction's witness/marker path, and is shown to
differ from the wtxid for a coinbase carrying the BIP141 reserved value. Plus
4 check_proof boundary KATs (match=>INCOMPLETE, mismatch/negative/too-wide=>
INVALID). nmc_auxpow_merkle_test 14/14 PASS exit 0. NMC-local, btc tree
READ-ONLY, test target name unchanged (CI allowlist untouched).
…_id/slot binding

Step 2 of AuxPow verification: confirm the chain-merkle root reconstructed in
step 1 is the one committed inside the parent (BTC) coinbase merged-mining
marker, and that this aux chain occupies the deterministic slot its
(nonce, chain_id) demands.

NMC-local port of Namecoin/Bitcoin auxpow.cpp CAuxPow::check coinbase scan
(coin fence: only std + core/* types, no btc include):
  * scan_mm_commitment(): the 4-byte pchMergedMiningHeader (fa be 6d 6d) must
    appear exactly once; the chain-merkle root must follow it immediately in
    reversed (big-endian display) byte order; the next LE uint32 tree size must
    equal 2^height; the following LE uint32 nonce must place the chain at
    chain_index == aux_expected_index(nonce, chain_id, height).
  * aux_expected_index(): byte-faithful CAuxPow::getExpectedIndex LCG.

Staged posture preserved: a coinbase carrying no marker (leg-only fixture or
empty parent coinbase) returns ABSENT -> step 2 skipped, mirroring the
null-parent-header gate in step 3. A marker that commits a different root,
wrong tree size, wrong slot, or is duplicated -> INVALID. The proof still never
reaches VALID -- step 4 (parent PoW vs target, btc tree READ-ONLY) is unbuilt,
so a structurally-consistent proof stays INCOMPLETE and NMC MUST NOT
block-validate off this leaf.

KATs: aux_expected_index pinned to offline-computed LCG values; the committed
root laid out by hand (magic|reversed-root|LE size|LE nonce) without calling
scan_mm_commitment, so a parse/scan bug is caught not mirrored.
nmc_auxpow_merkle_test 22/22 PASS exit 0 (was 14/14, +8).
…ep 4)

Complete the four-leg AuxPow verification walk: verify the parent (BTC)
block proof-of-work. Per Namecoin CheckAuxPowProofOfWork the parent block
SHA256d hash must satisfy the AUX (NMC) block difficulty target, NOT the
parent header own nBits, so check_proof() gains a uint32_t aux_bits param
(default 0). The PoW gate fires only with a real parent header and a
non-zero aux_bits: pow_hash(parent_header) > bits_to_target(aux_bits) is
INVALID (parent has insufficient work); otherwise the leg passes.

With all four legs satisfied -- chain-merkle, MM-marker (matched),
parent-coinbase tx-merkle, and parent PoW -- against a real parent header,
check_proof() now finally returns VALID. Anything short (null parent, no
matched marker, or aux_bits unset) stays INCOMPLETE so NMC never
block-validates off a partial proof; aux_bits == 0 is the leg-only-fixture
sentinel that preserves the staged posture.

No btc-tree dependency: chain::bits_to_target lives in src/core and the PoW
hash is the nmc-local pow_hash(); src/impl/btc is referenced read-only only.
NMC-local per fence #4. +4 KATs (sufficient->VALID, insufficient->INVALID,
no-aux_bits->INCOMPLETE, parent-own-bits-are-not-the-gate); the VALID KATs
mine the parent nonce against an easy target so the success path is
deterministic. nmc_auxpow_merkle_test 26/26 PASS exit 0.
@frstrtr
frstrtr merged commit 6bb8dd4 into master Jun 19, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant