Skip to content

fix: harden BTQ audit issues across Dilithium, P2MR, and constants#57

Open
Oskii wants to merge 10 commits into
masterfrom
fix/p2mr-metadata-idempotent
Open

fix: harden BTQ audit issues across Dilithium, P2MR, and constants#57
Oskii wants to merge 10 commits into
masterfrom
fix/p2mr-metadata-idempotent

Conversation

@Oskii

@Oskii Oskii commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Audit Basis

This PR is the remediation branch for a deep audit pass against the local audit materials:

  • AUDIT_REPORT_2026-05-19.md
  • Audit Scope of Work BTQ.pdf

The audit was treated as a test-driven hardening exercise. The work here does not rely on visual inspection alone: every confirmed issue was either covered by an existing failing/insufficient test that was corrected, a new regression test, a new lint guard, or a focused functional test run that exercises the affected behavior end to end.

This branch also preserves the earlier P2MR metadata idempotency fix already present on fix/p2mr-metadata-idempotent, then extends the branch with the broader audit remediation commit.

Summary

Fixes #58 by accepting signer-produced Dilithium script signatures that carry the required trailing sighash byte (raw Dilithium signature || sighash type) before CheckDilithiumSignature() strips that byte for raw Dilithium verification. The regression coverage now proves that raw 2,420-byte stack elements are rejected, 2,421-byte stack elements are accepted, and the wallet signing path emits verifier-accepted 2,421-byte P2PK and P2PKH Dilithium satisfactions.

This PR hardens BTQ consensus/test constants, Dilithium wallet handling, P2MR signing behavior, raw transaction RPC behavior, standardness limits, and wallet fee-bump sizing. It also expands the test surface so the BTQ-specific values and post-quantum wallet paths are covered by automated checks instead of relying on inherited Bitcoin defaults.

The broad theme is removing hidden Bitcoin assumptions from BTQ-specific paths and making Dilithium/P2MR behavior explicit where generic ECDSA/Schnorr code paths were previously too narrow.

Consensus, Policy, and Test Constant Alignment

The audit found that several Python functional-test framework values and C++ expectations still reflected Bitcoin defaults or partially migrated BTQ values. This is risky because functional tests can pass for the wrong protocol when the harness is not using the same constants as consensus/policy code.

Changes include:

  • Align BTQ block weight expectations at 8,000,000 weight units.
  • Align witness scale factor expectations at 16.
  • Align max protocol message length expectations at 70,000,000 bytes.
  • Align the future block time window at 15 * 60 seconds.
  • Align genesis-time and chain identity checks with BTQ values.
  • Align subsidy, maturity, halving, script element, sigop, and Dilithium sigop-cost expectations in affected tests.
  • Update feature tests that were implicitly calculating block weight, sigops, oversized messages, or reward schedules using Bitcoin assumptions.
  • Add test/lint/lint-btq-consensus-constants.py so C++ and Python BTQ constants cannot silently drift apart again.

This affects both the implementation-adjacent tests and the reusable functional test framework files under test/functional/test_framework/.

Standardness and Script Limit Hardening

The audit identified places where standardness limits were not consistently derived from the BTQ script element limit. This can create contradictory behavior between consensus-like script handling, standardness policy, and tests.

Changes include:

  • Derive standard scriptSig and stack item limits from MAX_SCRIPT_ELEMENT_SIZE instead of duplicating stale constants.
  • Add static assertions around standardness constants so future changes fail at compile time if they diverge incorrectly.
  • Extend transaction_tests coverage for standardness limits across legacy, P2WSH, taproot, and P2MR-style witness programs.
  • Add explicit 15,000-byte versus 15,001-byte stack-item boundary coverage.

Dilithium Signing and Script Verification

The audit found several Dilithium-specific paths that were not as explicit as the ECDSA/Schnorr paths they extend. The fixes make Dilithium behavior visible to the signing, deferring, and extraction layers.

Changes include:

  • Forward Dilithium checks through DeferringSignatureChecker.
  • Record Dilithium signatures through SignatureExtractorChecker so callers that estimate signature cost can observe them.
  • Preserve and correctly handle Dilithium signature byte material in interpreter paths.
  • Accept signer-produced BTQ_DILITHIUM_SIGNATURE_SIZE + 1 OP_CHECKSIGDILITHIUM signatures at the interpreter size gate, matching the existing downstream sighash-byte stripping logic.
  • Add direct issue script: OP_CHECKSIGDILITHIUM size check rejects signer-produced 2,421-byte signatures #58 regression coverage for raw 2,420-byte Dilithium signatures, manual 2,421-byte script signatures, and ProduceSignature()-generated P2PK/P2PKH Dilithium script satisfactions.
  • Add regression coverage for oversized ECDSA signature rejection under standard verification flags, preserving strictness while Dilithium support is enabled.

Dilithium Wallet Encryption and Locked Wallet Behavior

The audit identified wallet encryption and unlock behavior as a high-risk area because Dilithium private-key handling is separate from the historical Bitcoin key flow. A wallet can appear to support a key type while failing to retrieve, decrypt, migrate, back up, or sign with it correctly under encrypted/locked states.

Changes include:

  • Update Dilithium encrypted-key IV derivation to use a hash of the key id.
  • Preserve legacy encrypted-key decrypt compatibility with a fallback path.
  • Validate decrypted Dilithium keys by key id and sign/verify challenge behavior.
  • Require unlocked wallet state before signmessagewithdilithium can access private Dilithium material.
  • Add encrypted descriptor wallet coverage for Dilithium signing before and after lock/unlock transitions.
  • Ensure descriptor signing providers can populate Dilithium keys when the wallet is unlocked.
  • Ensure private Dilithium keys are not retrieved while the wallet is locked.

Dilithium Descriptor, Import, Backup, and Migration Paths

The audit found that supporting Dilithium addresses is not enough by itself; persistence and migration paths also need explicit handling or keys can be omitted from backup/import flows.

Changes include:

  • Load and persist Dilithium key metadata through wallet database paths.
  • Add import and backup support for Dilithium key metadata.
  • Extend wallet database tests to cover Dilithium metadata records.
  • Extend wallet tests for Dilithium backup/import behavior.
  • Handle Dilithium affected-key discovery for script and descriptor paths.
  • Derive descriptor DILITHIUM_LEGACY entropy from private descriptor material plus descriptor id/index/type, avoiding weak or repeated entropy sources.
  • Keep legacy import handling explicit: WITNESS_V2_P2MR is rejected as unrecognized by the legacy import switch instead of being accidentally misclassified.

P2MR Signing and RPC Completion Semantics

The audit found that P2MR signing behavior could report completion too optimistically in some fallback cases. That is dangerous because callers may treat complete=true as spendable even if the assembled witness does not actually satisfy the script.

Changes include:

  • Preserve WITNESS_V2_P2MR witness stacks in ProduceSignature() instead of clearing them through a generic path.
  • In signp2mrtransaction, verify assembled fallback witnesses before marking a transaction complete.
  • Clear invalid fallback witnesses instead of returning misleading witness data.
  • Add a negative functional test where an OP_DROP OP_TRUE single-leaf P2MR script cannot be satisfied by an empty stack and must return complete=false.
  • Add C++ wallet tests that sign a real Schnorr P2MR leaf, preserve the witness, and verify it through VerifyScript.

Raw Transaction and Output-Type RPC Behavior

The audit found RPC surfaces where P2MR/Dilithium script types were either too implicitly handled or omitted from generic output-type enumeration.

Changes include:

  • Update decodescript so WITNESS_V2_P2MR is explicitly treated as non-wrappable and does not receive inappropriate P2SH/segwit wrapper suggestions.
  • Add rpc_rawtransaction.py regression coverage for P2MR decodescript no-wrapper behavior.
  • Make GetAllOutputTypes() explicit and complete for the BTQ/Dilithium output types.
  • Add RPC unit coverage to ensure P2MR and Dilithium output names remain included.

Fee Bumping and Signature Weight Estimation

The audit found that wallet fee bumping needed better accounting for non-ECDSA signature families. Underestimating signature weight is a wallet correctness and UX risk because fee calculations can be wrong for larger post-quantum signatures.

Changes include:

  • Update SignatureWeightChecker to record ECDSA, Schnorr, and Dilithium signatures.
  • Track per-family maximum signature sizes instead of assuming one signature model.
  • Account for witness/base scaling correctly under BTQ’s witness scale factor.
  • Add feebumper_tests coverage for BTQ weight expectations and Schnorr/Dilithium weight recording.

P2P and Functional Test Framework Updates

The functional test harness was updated where protocol-level values changed or where the previous assumptions made tests validate the wrong thing.

Changes include:

  • Update P2P message-size handling for the BTQ 70 MB max protocol message size.
  • Keep RPC sendmsgtopeer tests realistic when oversized payload hex cannot fit within the RPC body limit.
  • Update feature block and segwit tests for BTQ block weight, witness scaling, sigops, subsidy, and future-time behavior.
  • Update block construction helpers and message constants used by multiple functional tests.

Test Strategy

The validation strategy was intentionally layered:

  • Compile/build tests catch API and static assertion failures.
  • Unit tests cover local C++ behavior around policy, script, RPC utilities, wallet encryption, wallet DB metadata, signing, fee bumping, P2MR, chain params, PoW, and sigops.
  • Functional tests cover node/RPC behavior across block validation, raw transaction decoding, P2P message limits, segwit behavior, P2MR RPC signing, Dilithium wallet sending, Dilithium sigops, chain identity, and regtest mining.
  • A new lint test checks BTQ constant consistency across C++ and Python test code to catch future drift early.

Test Evidence

Style/build/lint:

  • git diff --cached --check
  • python3 test/lint/lint-btq-consensus-constants.py
  • make -C src -j2 test/test_btq btqd btq-cli

Focused C++ tests:

  • src/test/test_btq --run_test=p2mr_tests/*
  • src/test/test_btq --run_test=feebumper_tests/*
  • src/test/test_btq --run_test=rpc_tests/rpc_all_output_types_includes_btq_types
  • src/test/test_btq --run_test=scriptpubkeyman_tests/*
  • src/test/test_btq --run_test=transaction_tests/test_IsStandard
  • src/test/test_btq --run_test=transaction_tests/test_IsWitnessStandard_stack_item_size_limits
  • src/test/test_btq --run_test=dilithium_basic_tests/*
  • src/test/test_btq --run_test=dilithium_wallet_tests/*
  • src/test/test_btq --run_test=pow_tests/*
  • src/test/test_btq --run_test=chainparams_genesis_tests/*
  • src/test/test_btq --run_test=sigopcount_tests/*

Functional tests:

  • python3 test/functional/test_runner.py --portseed=31112 -t /tmp/btq_func_block5 feature_block.py -j 1
  • python3 test/functional/test_runner.py --portseed=31113 -t /tmp/btq_func_p2mrrpc feature_p2mr_rpc.py -j 1
  • python3 test/functional/test_runner.py --portseed=31115 -t /tmp/btq_func_rawtx2 rpc_rawtransaction.py -j 1
  • python3 test/functional/test_runner.py --portseed=31120 -t /tmp/btq_func_segwit5 feature_segwit.py -j 1
  • python3 test/functional/test_runner.py --portseed=31122 -t /tmp/btq_func_rpcnet2 rpc_net.py -j 1
  • python3 test/functional/test_runner.py --portseed=31123 -t /tmp/btq_func_p2pmsg p2p_invalid_messages.py -j 1
  • python3 test/functional/test_runner.py --portseed=31124 -t /tmp/btq_func_wallet wallet_dilithium_send.py -j 1
  • python3 test/functional/test_runner.py --portseed=31129 -t /tmp/btq_func_dilsigops feature_dilithium_sigops.py -j 1
  • python3 test/functional/btq_chain_identity.py --cachedir=/home/o/btq-core/test/cache --configfile=/home/o/btq-core/test/config.ini --portseed=31126 --tmpdir=/tmp/btq_chain_identity_direct
  • python3 test/functional/btq_regtest_mining.py --cachedir=/home/o/btq-core/test/cache --configfile=/home/o/btq-core/test/config.ini --portseed=31128 --tmpdir=/tmp/btq_regtest_mining_direct_2

Residual Closure Update

The previous validation boundaries have now been closed in commit d6ddba6.

What changed in this closure pass:

  • The broad wallet_tests/* unit sweep no longer trips time-too-new. TestChain100Setup now seeds mock time from the BTQ regtest genesis time and advances by the configured BTQ target spacing instead of using stale Bitcoin-era one-second mock-time increments.
  • Wallet unit expectations now derive immature/mature coinbase amounts from GetBlockSubsidy() instead of hard-coded 50 * COIN values.
  • Descriptor-wallet output-type coverage no longer asks a descriptor wallet to generate Dilithium legacy/bech32 aliases from inactive managers; Dilithium wallet behavior remains covered by dedicated Dilithium tests.
  • BTQ nested P2WPKH dummy input sizing now reflects BTQ's 16x witness scale, and the fee-bumper tests cover the affected sizing behavior.
  • The legacy wallet segwit functional variant is now exercised under a BDB-enabled local build instead of remaining skipped.
  • Legacy feature_segwit.py import setup now imports each concrete witness script form explicitly, including P2SH-P2WSH redeem/witness scripts.
  • Wallet legacy import wrappers now invalidate stale IsMine cache entries after successful script/key/pubkey/scriptPubKey imports. This fixes the concrete P2SH-P2WSH case where importmulti could cache ISMINE_NO before adding the material that made the output spendable.
  • feature_segwit.py listunspent matrix outputs were scaled from 0.1 BTQ to 0.01 BTQ so the large synthetic output matrix fits BTQ's 5 BTQ regtest subsidy while preserving the same recognition/spendability assertions.
  • btq_chain_identity.py and btq_regtest_mining.py are now first-class test_runner.py entries, and the runner accepts the btq_ prefix.

Additional residual-closure validation:

  • make -C src -j2 test/test_btq btqd btq-cli
  • src/test/test_btq --run_test=wallet_tests/*
  • src/test/test_btq --run_test=ismine_tests/ismine_standard
  • src/test/test_btq --run_test=feebumper_tests/*
  • python3 test/lint/lint-btq-consensus-constants.py
  • python3 test/functional/test_runner.py --portseed=31216 -t /tmp/btq_func_segwit_legacy_amountfix "feature_segwit.py --legacy-wallet" -j 1
  • python3 test/functional/test_runner.py --portseed=31223 -t /tmp/btq_func_segwit_desc_seq "feature_segwit.py --descriptors" -j 1
    This runner invocation completed both feature_segwit.py --descriptors and feature_segwit.py --descriptors --v2transport.
  • python3 test/functional/test_runner.py --portseed=31222 -t /tmp/btq_func_segwit_desc_v2_seq2 "feature_segwit.py --descriptors --v2transport" -j 1
  • python3 test/functional/test_runner.py --portseed=31220 -t /tmp/btq_runner_btq_residual_final_seq btq_chain_identity.py btq_regtest_mining.py -j 1
  • git diff --check

Environment note: BDB was built locally through depends and the working tree was configured with BDB and SQLite enabled for the legacy-wallet validation. The generated depends/build artifacts are ignored and are not part of this PR.

Issue #58 targeted validation:

  • make -C src -j2 test/test_btq
  • src/test/test_btq --run_test=dilithium_basic_tests/dilithium_script_signature_with_sighash_byte
  • src/test/test_btq --run_test=dilithium_basic_tests/*
  • src/test/test_btq --run_test=dilithium_address_script_tests/*
  • src/test/test_btq --run_test=dilithium_network_policy_tests/*
  • src/test/test_btq --run_test=dilithium_descriptor_tests/*
  • src/test/test_btq --run_test=dilithium_key_tests/*
  • src/test/test_btq --run_test=script_standard_tests/script_standard_Solver_success
  • src/test/test_btq --run_test=script_standard_tests/script_standard_Solver_failure
  • src/test/test_btq --run_test=script_standard_tests/script_standard_ExtractDestination
  • src/test/test_btq --run_test=script_standard_tests/script_standard_GetScriptFor_*
  • git diff --check

Boundary observed during issue #58 validation: src/test/test_btq --run_test=script_standard_tests/* was not counted as passing evidence because unrelated upstream taproot address-vector cases still compare BTQ qbtc... encoded destinations against Bitcoin bc1... expected vectors. The solver and script-construction subsets relevant to this issue passed.

Review Notes

Suggested review order:

  1. Consensus/test constant alignment and the new lint guard.
  2. Dilithium wallet encryption, descriptor, import, backup, and signing changes.
  3. P2MR signing/RPC completion semantics.
  4. Fee-bump signature weight accounting.
  5. Functional test expectation updates.

The riskiest areas are wallet private-key handling, P2MR completion semantics, and protocol constant changes. Those areas have the densest new regression coverage in this PR.

@Oskii Oskii changed the title fix(wallet): make P2MR metadata idempotent fix: harden BTQ audit issues across Dilithium, P2MR, and constants Jun 1, 2026
static bool EvalChecksigDilithium(const valtype& sig, const valtype& pubkey, CScript::const_iterator pbegincodehash, CScript::const_iterator pend, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success)
{
if (!sig.empty() && sig.size() != BTQ_DILITHIUM_SIGNATURE_SIZE) {
if (!sig.empty() && sig.size() != BTQ_DILITHIUM_SIGNATURE_SIZE + 1) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just flagging that this is a consensus change therefore a hardfork required

Comment thread src/policy/policy.h

#include <consensus/amount.h>
#include <consensus/consensus.h>
#include <consensus/consensus.h>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate include, we can remove this

m_map_signing_providers[index] = *out_keys;
}

// Always add Dilithium pubkeys for fee estimation (even if include_private=false)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the fee estimator can't recognize the Dilithium inputs. It either fails to identify them or treats them as something smaller, so it under-estimates the signature weight (assuming a ~72-byte sig instead of ~2,421). The transaction's estimated size is too small, so the fee is set too low.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested fix
Decouple pubkey exposure (needed for estimation, not secret) from private-key decryption (needs unlock). For example, populate dilithium_pubkeys from a non-secret source regardless of lock state

BarneyChambers and others added 2 commits June 2, 2026 15:20
Harden the wallet_dilithium_send regression so it proves the property
issue #41 is about: sendtoaddress/sendmany spending a Dilithium UTXO must
return a valid, broadcastable transaction, not merely relay one.

- Force coin selection onto the Dilithium UTXO and assert it is the input
  actually spent (exercises the OP_CHECKSIGDILITHIUM signing path).
- Mine the broadcast tx and assert it confirms, proving the Dilithium
  signature verifies under consensus rather than only being accepted to
  the mempool.
- Cover sendmany spending a Dilithium UTXO in addition to sendtoaddress.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Oskii

Oskii commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator Author

Stage 0 Mainnet Audit Tranche

Commit: f2c56aa (test: close stage zero BTQ audit gaps)

This tranche starts the staged component-by-component mainnet audit program. It does not claim mainnet readiness. It closes audit-harness gaps and several concrete defects that would have made later evidence unreliable, then records the remaining component matrix in MAINNET_AUDIT_MATRIX_2026-06-03.md.

What changed

  • Added MAINNET_AUDIT_MATRIX_2026-06-03.md with validation levels, Stage 0 evidence, 37 component rows, staged execution order, and explicit no-go items.
  • Registered src/test/dilithium_mixed_mode_tests.cpp in src/Makefile.test.include and replaced placeholder mixed-mode tests with real ECDSA/Dilithium transaction, signing, and script-execution coverage.
  • Added lint coverage so unregistered C++ unit tests and missing functional runner scripts fail test/lint/lint-tests.py.
  • Restored wallet_bip360_send_paths.py and registered it under descriptor mode in test_runner.py.
  • Fixed wallet-created P2MR receive ownership: valid tracked P2MR metadata now participates in CWallet::IsMine, stale IsMine cache entries are invalidated when P2MR metadata is written, and corrupt/incomplete metadata is not treated as spendable.
  • Fixed SignatureData::MergeSignatureData() so partial-signing state is merged for Dilithium signatures/pubkeys, Taproot fields, missing key/signature vectors, and preimages instead of only the legacy signature map.
  • Changed btq_dilithium_sk_to_pk() to fail closed. Raw Dilithium2 secret keys do not contain the packed public key; the old helper copied invalid bytes and returned success.

New or restored test coverage

  • dilithium_mixed_mode_tests/*: mixed ECDSA/Dilithium construction, raw signature separation, dual-family script execution, and ProduceSignature() coverage.
  • p2mr_tests/wallet_is_mine_recognizes_valid_p2mr_metadata: P2MR metadata ownership, cache invalidation, and corrupt-metadata rejection.
  • dilithium_basic_tests/dilithium_signature_data_merge_preserves_partial_state: Dilithium partial-signature merge regression.
  • dilithium_key_tests/dilithium_raw_secret_key_to_public_key_fails_closed: raw wrapper fail-closed regression plus positive verification using the generated public key.
  • wallet_bip360_send_paths.py --descriptors: ordinary sendtoaddress() funding of a wallet-created P2MR address, P2MR signing/broadcast/mining, and sendtop2mr idempotent metadata behavior.
  • lint-tests.py: repository-level guard against silently skipped unit tests and missing functional tests.

Validation run after rebase

Builds:

  • make -C src -j2 test/test_btq
  • make -C src -j2 btqd btq-cli

Focused unit tranche:

  • src/test/test_btq --run_test=dilithium_key_tests/* --run_test=dilithium_address_script_tests/* --run_test=dilithium_descriptor_tests/* --run_test=dilithium_basic_tests/* --run_test=dilithium_mixed_mode_tests/* --run_test=dilithium_network_policy_tests/* --run_test=p2mr_tests/* --run_test=ismine_tests/* --run_test=wallet_tests/* --run_test=script_tests/script_combineSigs --run_test=transaction_tests/test_witness
  • Result: 79 test cases, no errors.

Functional tranche:

  • python3 test/functional/test_runner.py --portseed=31408 -t /tmp/btq_func_stage0_rebased wallet_bip360_send_paths.py feature_p2mr_rpc.py wallet_dilithium_send.py -j 1
  • Result: all three descriptor-mode tests passed.

Audit/lint/registration:

  • python3 test/lint/lint-tests.py
  • python3 test/lint/lint-btq-consensus-constants.py
  • git diff --check
  • Source-vs-binary registration probe: missing make registrations 0, missing explicit Boost suites 0.
  • Functional runner probe: 296 entries, missing functional scripts 0.

Remaining no-go items

The new matrix intentionally keeps the branch no-go for mainnet until later stages close the remaining evidence gaps, especially:

  • Real valid P2MR Dilithium spends through mempool, mining, block validation, and mutation-failure tests.
  • LWMA activation and difficulty-transition functional coverage.
  • Revalidation of prior chainparams findings: mainnet chainwork/assumevalid, genesis/network identity, signet/testnet/regtest separation.
  • Dilithium KATs plus malformed key/pubkey/load tests.
  • Wallet encrypted/reload/import/rescan coverage for Dilithium and P2MR.
  • P2P stale header corpus replacement and invalid-block tests.
  • Dedicated P2MR/Dilithium fuzz targets and resource/performance stress.

Note: AUDIT_REPORT_2026-05-19.md was readable and used as the baseline. The local PDF scope document could not be text-extracted in this environment because no PDF parser/extractor was installed; the matrix records that limitation and requires reconciliation against extracted PDF text before final sign-off.

@Oskii

Oskii commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator Author

Stage 1 component audit tranche

Pushed commit ab9d3f2 (test: close stage one audit gaps) to continue the component-level pre-mainnet audit.

What this tranche closes

  • P2MR Dilithium script-path signing

    • Added P2MR Dilithium signature hashing through the BIP341-style/P2MR path.
    • Treats OP_2 P2MR witness programs as requiring BIP341-style precomputed transaction data.
    • Extends wallet P2MR signing providers so descriptor/legacy wallet Dilithium keys referenced inside P2MR leaves are exported for signing.
    • Adds real P2MR Dilithium pubkey and pubkeyhash leaf tests, descriptor-provider coverage, wrong-amount failure, mutated-signature failure, mutated-script failure, and RPC mined-spend coverage.
  • Dilithium key invariant hardening

    • CDilithiumKey::Load() / Set() now reject stored sk||pk blobs when the stored public key cannot verify a signature produced by the secret key.
    • Extended Dilithium key decode now rejects non-hardened child metadata, matching hardened-only derivation semantics.
  • LWMA activation and retarget residuals

    • GetNextWorkRequired() now honors fPowNoRetargeting before entering LWMA.
    • LWMA low-target arithmetic no longer collapses valid small targets to zero through early division.
    • getblocktemplate now advertises !lwma for the block being assembled, not only after the previous block is already active.
    • -testactivationheight help now documents lwma.
    • Added feature_lwma_activation.py and registered it in the functional runner.
  • Dilithium NULLFAIL

    • OP_CHECKSIGDILITHIUM now mirrors ECDSA/Dilithium-multisig NULLFAIL behavior: failed non-empty signatures raise SCRIPT_ERR_SIG_NULLFAIL when SCRIPT_VERIFY_NULLFAIL is set.
    • Covered both BASE script and P2MR script-path execution.
  • Audit matrix update

    • Updated MAINNET_AUDIT_MATRIX_2026-06-03.md with Stage 1 evidence, component status changes, and explicit no-go residuals.

Validation run

Passed:

  • git diff --check
  • make -C src -j2 test/test_btq btqd btq-cli
  • src/test/test_btq --run_test=p2mr_tests,dilithium_basic_tests,dilithium_mixed_mode_tests,dilithium_network_policy_tests,dilithium_key_tests,dilithium_wallet_tests,feebumper_tests,sighash_tests,pow_lwma_tests,pow_tests
  • src/test/test_btq --run_test=argsman_tests/testactivationheight_help_lists_lwma
  • test/functional/test_runner.py -j1 feature_p2mr_rpc.py feature_lwma_activation.py wallet_bip360_send_paths.py wallet_dilithium_send.py

Validation boundary still open:

  • src/test/test_btq --run_test=script_tests --log_level=test_suite still fails with 73 stale-vector/harness failures. Current buckets are uncompressed P2WPKH expectations, Dilithium opcode reservation expectations, BTQ push/script-size limit drift from Bitcoin vectors, and missing newer script-error names in script_tests.cpp. I recorded this explicitly in the audit matrix; it is not claimed green.

BarneyChambers added a commit that referenced this pull request Jun 9, 2026
… interpreter

PR #57 changed the GetTxSigOpCost P2WPKH/P2SH-P2WPKH assertions to expect
SCRIPT_ERR_WITNESS_PUBKEYTYPE, matching its own (now superseded) witness
program handling. The merge adopted master/#59's VerifyWitnessProgram, which
gates the witness-pubkeytype check behind SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
(not in the test's flags), so a 33-byte non-compressed witness pubkey now
fails the OP_EQUALVERIFY (HASH160 mismatch) instead. Restore the upstream/
master expectation of SCRIPT_ERR_EQUALVERIFY so the test matches the
interpreter we kept. Fixes a sigopcount_tests abort + arg-pollution cascade.
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.

script: OP_CHECKSIGDILITHIUM size check rejects signer-produced 2,421-byte signatures

2 participants