@@ -541,4 +541,98 @@ TEST(NmcAuxStep4, ParentOwnBitsAreNotTheAuxGate)
541541 EXPECT_EQ (ap.check_proof (aux, 1 , aux_bits), AuxPow::CheckResult::VALID );
542542}
543543
544+ // ---------------------------------------------------------------------------
545+ // P1d - HeaderChain::add_auxpow_header / verify_auxpow_header consensus GATE.
546+ //
547+ // The gate wires AuxPow::check_proof into the header-accept path: an incoming
548+ // merge-mined header is admissible ONLY when its proof fully verifies against
549+ // THIS chain's params (aux_chain_id) and the header's own nBits as the aux PoW
550+ // target. These KATs pin the rejection contract - nothing unproven gets in -
551+ // reusing the step-4 complete_proof/mine_parent fixtures but binding the aux
552+ // block hash to a REAL header via block_hash(header), the production seam.
553+ // Storage stays P0-DEFER, so a verified header is not persisted yet (add
554+ // returns false on both arms); the assertion is on the verify verdict.
555+ // ---------------------------------------------------------------------------
556+
557+ using nmc::coin::HeaderChain;
558+ using nmc::coin::NMCChainParams;
559+ using nmc::coin::BlockHeaderType;
560+ using nmc::coin::block_hash;
561+
562+ // Params with a pinned aux_chain_id == 1 so the fixture's MM-marker slot
563+ // (chain_id=1) binds. Activation height stays unpinned: the P1d gate under test
564+ // is the cryptographic check_proof, not the (deferred) height-activation path.
565+ static NMCChainParams params_chain_id_1 ()
566+ {
567+ NMCChainParams p = NMCChainParams::mainnet ();
568+ p.aux_chain_id = 1 ;
569+ return p;
570+ }
571+
572+ // A header whose block_hash() becomes the AUX hash the proof commits to; m_bits
573+ // carries the aux PoW target the parent hash must clear (Namecoin aux-bits rule).
574+ static BlockHeaderType aux_header_for (uint32_t aux_bits)
575+ {
576+ BlockHeaderType h{};
577+ h.m_version = 1 ;
578+ h.m_bits = aux_bits;
579+ return h;
580+ }
581+
582+ TEST (NmcP1dGate, FullyVerifiedAuxPowPassesTheGate)
583+ {
584+ uint32_t aux_bits = 0x207fffffu ; // regtest-style easy target
585+ BlockHeaderType h = aux_header_for (aux_bits);
586+ uint256 aux = block_hash (h); // production seam
587+ AuxPow ap = complete_proof (aux, /* parent_own_bits=*/ 0x1d00ffffu );
588+ ASSERT_TRUE (mine_parent (ap, chain::bits_to_target (aux_bits)));
589+
590+ HeaderChain chain_ (params_chain_id_1 ());
591+ EXPECT_EQ (chain_.verify_auxpow_header (h, ap), AuxPow::CheckResult::VALID );
592+ // Storage is still P0-DEFER, so even a verified header is not persisted yet.
593+ EXPECT_FALSE (chain_.add_auxpow_header (h, ap));
594+ EXPECT_FALSE (chain_.has_header (aux));
595+ }
596+
597+ TEST (NmcP1dGate, WrongChainIdIsNotValidAndIsRejected)
598+ {
599+ uint32_t aux_bits = 0x207fffffu ;
600+ BlockHeaderType h = aux_header_for (aux_bits);
601+ uint256 aux = block_hash (h);
602+ AuxPow ap = complete_proof (aux, 0x1d00ffffu );
603+ ASSERT_TRUE (mine_parent (ap, chain::bits_to_target (aux_bits)));
604+
605+ NMCChainParams p = NMCChainParams::mainnet ();
606+ p.aux_chain_id = 2 ; // != fixture's chain_id 1
607+ HeaderChain chain_ (p);
608+ EXPECT_NE (chain_.verify_auxpow_header (h, ap), AuxPow::CheckResult::VALID );
609+ EXPECT_FALSE (chain_.add_auxpow_header (h, ap));
610+ }
611+
612+ TEST (NmcP1dGate, InsufficientParentWorkIsRejected)
613+ {
614+ BlockHeaderType h = aux_header_for (/* aux_bits=*/ 0x03000001u ); // target == 1
615+ uint256 aux = block_hash (h);
616+ AuxPow ap = complete_proof (aux, 0x1d00ffffu );
617+ // Unmined parent: a real double-SHA256 hash exceeds target 1 => INVALID.
618+ ASSERT_TRUE (pow_hash (ap.parent_header ) > chain::bits_to_target (h.m_bits ));
619+
620+ HeaderChain chain_ (params_chain_id_1 ());
621+ EXPECT_EQ (chain_.verify_auxpow_header (h, ap), AuxPow::CheckResult::INVALID );
622+ EXPECT_FALSE (chain_.add_auxpow_header (h, ap));
623+ }
624+
625+ TEST (NmcP1dGate, MissingAuxBitsKeepsProofIncompleteAndRejected)
626+ {
627+ BlockHeaderType h = aux_header_for (/* aux_bits=*/ 0u ); // 0 == leg-only sentinel
628+ uint256 aux = block_hash (h);
629+ AuxPow ap = complete_proof (aux, 0x1d00ffffu );
630+
631+ HeaderChain chain_ (params_chain_id_1 ());
632+ // header.m_bits == 0 feeds check_proof's leg-only sentinel: step 4 skipped,
633+ // the proof stays INCOMPLETE, and the gate rejects.
634+ EXPECT_EQ (chain_.verify_auxpow_header (h, ap), AuxPow::CheckResult::INCOMPLETE );
635+ EXPECT_FALSE (chain_.add_auxpow_header (h, ap));
636+ }
637+
544638} // namespace
0 commit comments