nmc(P1d): wire AuxPow::check_proof gate into add_auxpow_header - #189
Merged
Conversation
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.
verify_auxpow_header() factors the four-leg AuxPow::check_proof into a unit-testable consensus gate: an incoming merge-mined header is admissible only when check_proof(block_hash(header), aux_chain_id, header.m_bits) returns VALID. add_auxpow_header() now rejects (warns + returns false) on any non-VALID verdict; header storage/connection and the height-derived is_auxpow_active() activation gate remain P0-DEFER (need a connected parent). P1d ships the rejection half: nothing unproven gets in. +4 KATs (NmcP1dGate) bind the aux hash to a real header via the block_hash() production seam: fully-verified proof passes; wrong chain_id, insufficient parent work, and missing aux_bits are each rejected. Suite 26->30. NMC-only under src/impl/nmc/; btc tree consumed READ-ONLY.
frstrtr
added a commit
that referenced
this pull request
Jun 19, 2026
nmc(P1e): activation-height admission gate + 4 KATs (stacked on #189)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P1d - AuxPow verification gate in the header-accept path
Stacked on #185 (P1c-step4, full four-leg check_proof). Wires that verifier into the accept path so the share/header-accept gate can never admit an unproven merge-mined header.
Change
verify_auxpow_header(header, auxpow)- factors the four-legAuxPow::check_proofinto a unit-testable consensus gate:check_proof(block_hash(header), m_params.aux_chain_id, header.m_bits). Per Namecoin's aux-bits rule the parent PoW is checked against the AUX header's nBits.add_auxpow_header- now rejects (warn + return false) on any non-VALID verdict. Header storage/connection and the height-derivedis_auxpow_active()activation gate remain P0-DEFER (need a connected parent). P1d ships the rejection half: nothing unproven gets in.Tests (+4 NmcP1dGate, suite 26->30, local green)
Bind the aux hash to a real header via the
block_hash()production seam (not a bare leaf):aux_chain_id-> rejectedaux_bits(leg-only sentinel) -> INCOMPLETE, rejectedScope / fences
NMC-only under
src/impl/nmc/; btc tree consumed READ-ONLY.nmc_auxpow_merkle_testalready in CMake + build.yml allowlist. I do not self-merge - held for operator/integrator gating.