Conversation
…ache key Add PrecomputedTransactionData::m_block_hashes, a table of block hashes (by height) that inputs may commit to in their signature message. It is chain context that cannot be derived from the transaction, so validation fills it in rather than Init(). The script execution cache is keyed on the wtxid and flags only, on the assumption that everything a script check depends on is committed to by the wtxid. A block hash in the signature message breaks that assumption, so include the table in the key. Without this, a transaction verified against one chain would be reported valid from cache after a reorg that replaced the referenced block. The table is always empty until a later commit adds a rule that fills it. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
taproot_construct() gains a witver parameter so the resulting scriptPubKey can be a witness v2 program, and TaprootSignatureMsg() gains a block_hash parameter that sets ext_flag bit 1 (spend_type bit 2) and appends the hash after the tapscript extension. block_ref_annex() builds the annex used to reference a block by height. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Introduce SCRIPT_VERIFY_TAPROOT_V2 and validate 32-byte witness v2 programs under the BIP341/342 rules, reusing the v1 code path. This is the host for block references: a rule that changes the signature message cannot be added to existing v1 outputs as a soft fork, so it needs a witness version that old nodes treat as anyone-can-spend. Activation is a buried deployment. It is never active on mainnet, testnet and signet, always active on regtest, and can be moved with -testactivationheight=taproot_v2@height. Witness v2 spends stay non-standard for now, so the functional test mines them directly. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Add SCRIPT_VERIFY_TAPROOT_V2 to the standard script flags, apply the Taproot witness limits to v2 spends in IsWitnessStandard, and teach the solver about the new output type so AreInputsStandard no longer rejects the input as an undefined witness program. Annexes stay non-standard. The output type is wired through the switch statements that need it; signing support comes with the wallet changes later. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
A witness v2 spend whose annex payload starts with type byte 0x01 references the block at the 4-byte little endian height that follows. Two rules attach to the reference (see doc/block-reference.md): - The block containing the spend must be at least COINBASE_MATURITY blocks after the referenced block. This is checked at transaction level in ConnectBlock and in mempool acceptance, so it also holds under assumevalid, and it makes a referencing transaction exactly as reorg-safe as a spend of a matured coinbase. - Every signature on the input commits to the hash of the referenced block: ext_flag bit 1 is set in the BIP341 message and the hash is appended after the BIP342 extension. A signature made for one chain does not verify on any chain whose block at that height differs. The script interpreter cannot look up block hashes itself, so validation resolves them from the chain and passes them in via PrecomputedTransactionData::m_block_hashes before the script checks run. Bytes after the height and annexes with other type bytes keep their BIP341 treatment and are ignored. A block reference annex is still non-standard, so the functional test exercises the rules through block submission. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
A block reference annex of exactly six bytes is standard for v2 spends. Every other annex stays non-standard, including one with trailing bytes, which consensus ignores but which nobody has a use for yet. The functional test now covers mempool acceptance: an immature reference is rejected with a retryable error, a signature for another chain is rejected without punishing the relaying peer, and a mature reference is relayed and mined. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
A transaction with a block reference is valid only while the referenced block is in the active chain and at least COINBASE_MATURITY deep. The block hash is not recoverable from the transaction, so the mempool entry stores the referenced CBlockIndex pointers resolved at acceptance (next to LockPoints, which serve the same purpose for BIP68), and the reorg filter evicts the entry when a referenced block left the chain or is no longer mature for the next block. Transactions from disconnected blocks already go through full mempool acceptance and need no special handling; a comment at the invalidateblock site notes that its re-add limit does not apply to normal reorgs. The functional test covers a shallow reorg (transaction returns to the mempool and confirms again), a reorg to a shorter chain via invalidateblock (reference no longer mature, evicted) and a deep reorg that replaces the referenced block. For the last case it also submits a block containing the invalidated transactions, which the node would accept from the script execution cache without the earlier cache key change; that assertion fails when the key change is reverted. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Two nodes diverge for COINBASE_MATURITY blocks, then spend the same witness v2 coin, each referencing its own side's first post-split block. Each spend is rejected by the other side before its own is accepted, and after the sides reconnect only the winning side's spend exists. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The target verifies with MissingDataBehavior::ASSERT_FAIL, so a witness v2 spend with a block reference annex would abort in the signature hash computation for lack of a block hash. Supply one per referenced height (an arbitrary deterministic value) so the path is fuzzable. Verified with a crafted input that aborts without this change. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sjors
force-pushed
the
2026/09/replay
branch
from
September 9, 2026 16:19
c46599b to
e2347d0
Compare
SignOptions gains a block_reference (height and hash). When set, a witness v2 input is signed over the message extended with that hash, the block reference annex is appended to the witness, and the hash is added to the precomputed transaction data so that the signer's own verification, which runs the real interpreter, sees the same message. Signing a v2 output without the option produces a plain BIP341 spend. The unit test signs a v2 spend and checks that it verifies only against the referenced hash. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
tr2(KEY, TREE) is tr() with a witness v2 output, so the wallet can receive to and spend from outputs that support block references. The descriptor is inferred back from v2 scripts when the tree is known; the rawtr() fallback stays v1-only. The maximum satisfaction weight of tr2 includes the 7 witness bytes of a block reference annex, so fee estimation covers a referencing spend. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sjors
force-pushed
the
2026/09/replay
branch
from
September 9, 2026 16:44
e2347d0 to
da8c2e3
Compare
Add a per-input PSBT field (type 0x7f, prototype, not assigned by any BIP) carrying the referenced block's height and hash. Signing a PSBT input with the field set produces the annex and the message commitment; PrecomputePSBTData supplies the hash so both signing and the final witness verification see it. The field survives finalization, since verifying the final witness needs it, and decodepsbt shows it. The wallet's send, sendall and walletcreatefundedpsbt gain a block_reference option. When set, every witness v2 input references the block at tip - 99, the most recent one that consensus allows, so the transaction is only valid on the wallet's current chain. IsPayToTaproot() now also matches v2 so PSBT signing uses SIGHASH_DEFAULT for those inputs. The functional test imports tr2() descriptors into a wallet, spends with and without the option, and walks the reference through the PSBT workflow. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Alongside the existing P2TR benchmarks. The extra annex hashing and 32-byte message extension cost a few percent on top of a plain key-path verification, which is dominated by the Schnorr check. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
A transaction with a block reference is invalidated for good when a reorg replaces the referenced block, so rebroadcasting it forever is pointless and its inputs should become spendable again. The wallet remembers the hash of every block a reorg disconnects; when a block is connected at that height with a different hash, unconfirmed wallet transactions referencing that height are abandoned. A reorg that puts the same block back (invalidateblock followed by reconsiderblock, or a shallow reorg) leaves them alone. The functional test covers both a shallow reorg, after which the transaction returns to the mempool and confirms again, and a reorg from below the referenced block, after which it is abandoned and the balance is restored. Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sjors
force-pushed
the
2026/09/replay
branch
from
September 9, 2026 18:42
da8c2e3 to
b4b64df
Compare
When creating a PSBT with block references and a nonzero height locktime, include the headers from the referenced block through the locktime height. An external signer can use that endpoint without knowing the wallet tip. Leave locktime unchanged and omit headers if the range is unavailable or the locktime is zero or timestamp-based. Store headers once per PSBT in prototype global records keyed by a 4-byte little endian height. Expose the records in decodepsbt and preserve them through combining, joining and finalization. Combiners reject conflicting headers at the same height. Read headers from the block index along one chain so this works with pruning and does not mix branches during a reorg. Headers remain optional metadata; authenticating heights and choosing a fork are left to the signer. Add PSBT round-trip and merge coverage and functional checks for header ranges, conflicts, and the signing workflow. Co-authored-by: GPT-6 Astra <noreply@openai.com>
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.
Prototype of opt-in replay protection for chain splits, as described in my reply on Delving1 to moonsettler's Universal opt-in replay protection? proposal.
Soft fork rules
Witness version 22 outputs with a 32-byte program follow the BIP3413/BIP3424 rules, with one addition. If the spend's annex5 starts with type byte
0x01, the next four bytes are a block height and the input references that block:0x01is a block reference: a 4-byte little endian height, with any further bytes ignored. Other type bytes keep their BIP341 treatment (no meaning, always valid), which leaves0x00free for the unstructured use proposed on the list67. Using the annex to commit to a block was first suggested by aj8 and sipa9 in the Taproot proposal thread.COINBASE_MATURITY(100) blocks after the referenced block.ext_flag10 bit 1 is set in the BIP341 message and the 32-byte hash is appended. The hash itself is not in the transaction.A referencing transaction therefore never expires, is only as reorg-unsafe as a matured coinbase spend, and its signatures do not verify on any chain whose block at that height differs. Cost: 7 witness bytes (1.75 vB). See
doc/block-reference.md.Implementation
SCRIPT_VERIFY_TAPROOT_V2and thetaproot_v2deployment; the interpreter parses the annex and extends the signature message. Validation resolves the referenced hashes from the chain and hands them to the script checks throughPrecomputedTransactionData, whose contents are now part of the script execution cache key.tr2()descriptor for v2 outputs; ablock_referenceoption onsend,sendallandwalletcreatefundedpsbtreferences the block attip - 99from every v2 input (error if none was selected); a PSBT11 input field (0x7f, unassigned) carries height and hash for signers; transactions whose referenced block is replaced by a reorg are abandoned.feature_block_reference.py(consensus, policy, shallow and deep reorgs, a fork split, activation) andwallet_block_reference.py; unit tests for the cache key, signing, descriptors and PSBT headers;script_flagsfuzz target updated; averify_scriptbench.Footnotes
https://delvingbitcoin.org/t/universal-opt-in-replay-protection/2792/11 ↩
A new witness version is not justified by this proposal on its own, but it can be combined with one that does need one (see the Delving reply for candidates). ↩
https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki ↩
https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki ↩
https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#script-validation-rules ↩
Joost Jager, "Standardisation of an unstructured taproot annex", 2023: https://gnusha.org/pi/bitcoindev/CAJBJmV-L4FusaMNV=_7L39QFDKnPKK_Z1QE6YU-wp2ZLjc=RrQ@mail.gmail.com/ ↩
Peter Todd, "Standard Unstructured Annex", 2025: https://gnusha.org/pi/bitcoindev/Z9tg-NbTNnYciSOh@petertodd.org/ ↩
https://gnusha.org/pi/bitcoindev/20190508044928.z52oaxevwcppkvna@erisian.com.au/ ↩
https://gnusha.org/pi/bitcoindev/CAPg+sBjqgyu=Do-8P=7Q1S3tehr30K58=o_SokAE7H_SP-pf8g@mail.gmail.com/ ↩
https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message ↩
https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki ↩