PoC: tooling for OP_TEMPLATEHASH#108
Open
darosior wants to merge 21 commits intobitcoin-inquisition:29.xfrom
Open
PoC: tooling for OP_TEMPLATEHASH#108darosior wants to merge 21 commits intobitcoin-inquisition:29.xfrom
darosior wants to merge 21 commits intobitcoin-inquisition:29.xfrom
Conversation
This introduces a new Script operation exclusively available in Tapscript context: OP_TEMPLATEHASH. This operation pushes the hash of the spending transaction on the stack. See BIP 446 for details. This operation is introduced as replacing OP_SUCCESS206 (0xce). Co-Authored-By: Greg Sanders <gsanders87@gmail.com>
Sanity check the template hash by using it to commit to the transaction that must spend an output. Malleating committed fields must lead to a consensus failure, and changing non-committed fields is fine. We also add the option to generate test vectors from this unit test.
We introduce one specialized target focused on exercising the new `GetTemplateHash()` logic introduced for OP_TEMPLATEHASH, and one broader fuzz target which exercises using OP_TEMPLATEHASH on a variety of transactions while asserting invariants.
This leverages the extensive feature_taproot.py test framework to generate coverage for the numerous scenarii and mutations exercised there. Additionally, a separate feature_templatehash.py functional test is introduced for end-to-end testing of the commit-to-next-transaction use case, which does not fit nicely into the feature_taproot.py framework (assumes input independence).
We use the 's' property in two ways: 1. To reason about malleability, as per the Miniscript specifications; 2. To sanity check that a top-level Miniscript fragment requires a transaction signature to be spent. This is fine as long as the only way to fix the transaction is by using a signature, and as long as all signatures are over the transaction. But in the following commits we are going to introduce fragments that either fix the transaction but do not require access to a private key (hence should not be 's' when reasoning about malleability), or allow to sign messages that are no the transaction itself and therefore should not be considered for the sanity check. Therefore, separate the two roles into two properties. Keep 's' for reasoning about malleability, and introduce a 't' property that determines whether the fragment's satisfaction fixes the transaction.
Author
|
Windows CI failure seems unrelated: |
cafd902 to
c064b18
Compare
This is a bit convoluted since no other fragment relied on the Taproot internal key yet, and it needs to be passed from the context, and therefore all places where we create a context: in signing, descriptor parsing / inference, unit tests and fuzz tests. The approach taken is to only query the internal key once at parsing / inference time and store it in the fragment's keys vector. This way it naturally integrates with the existing code for pk_k(), such for serialization, signing and duplicate key checks. Care was taken in fuzz harnesses to not invalidate existing seeds, but also use a meaningful key in TestNode() (i.e. in miniscript_smart and miniscript_stable targets). A new optional internal_key parameter is adding down the call chain as an "out" parameter: the first time the fuzzer generates a pk_i() fragment (if any) the internal key is set and reused for all pk_i() fragments. This way we can roundtrip ser/parsing, make it available to the satisfier to sign, etc..
Likewise pk() and pkh(), this is syntactic sugar for the common case of an internal public key signature check.
This fragment checks the spending transaction's template hash. Because it commits to the spending transaction (with more malleable fields than SIGHASH_ALL, but less than SIGHASH_SINGLE/NONE) then we give it the 's' property. Note this breaks the invariant that an 's' fragment always contains at least one key. In the miniscript_smart and miniscript_stable fuzz harnesses we use the message hash for dummy signatures as the template hash. We sometimes create th() fragments with this hash (making them satisfiable), and sometimes not.
The satisfaction algorithm checks for signatures when going over key fragments, as a signature-checking fragment may have more than one key fragment as "descendant" where not all of them are available or some are more desirable to use than others. The key fragments have therefore no knowledge of the message to be signed to satisfy their signature-checking "ancestor" fragment. This was not an issue up until now since Miniscript only supported transaction signature checking, and therefore signatures had implicitly to be provided for the transaction itself. But we are about to introduce a Miniscript fragment that check signatures for arbitrary messages in the upcoming commits. Therefore in preparation make key fragments aware of the message their "ancestor" signature-checking fragment expect them to be signing.
…trary message A cms() fragment that takes as inputs a key expression and a message to check the signature against. The message is forwarded to key expressions and the satisfier made aware of a potential custom message to sign in place of the customary sighash. The chosen order of arguments did not require introduce more state to the parser, but did require introducing more to the decoder (where previously it was only necessary for thresh()).
This fragment is the equivalent of 'c:' but for rebindable signatures. It is a specialization of the 'cms()' fragment for a specific message that is the TEMPLATEHASH of the spending transaction.
This field allows a verifier to validate the transaction template(s) committed to in a transaction output. One caveat is that transaction are Bitcoin-serialized, which includes some field not committed to in a template hash.
The existing PSBT output field for a Taproot internal key is not keyed, which makes it so only a single Taproot internal key may be specified. This makes sense since there may be at most a single one per Taproot output, but since we introduce the capability of committing to the template of a spending transaction, it is often useful for a verifier to validate the outputs of the spending transaction.
We are going to reuse them in the following commit.
The rationale here is the same as for the additional Taproot internal keys, be able to inspect the outputs of transaction templates committed to in this output.
This is a specially crafted PSBT of a transaction that pays to a Taproot with a leaf with a TEMPLATEHASH equality check for a transaction that pays to 2 Taproot outputs. This highlights the use of all introduced fields, as well as existing ones (BIP32 derivations).
Collaborator
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones: If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
This was referenced Feb 22, 2026
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.
This is a PoC implementation of extensions to standard tooling (PSBT and Miniscript descriptors) to support our "Taproot-native (re-)bindable transactions" proposal. It's based on #100.
I will post on the mailing list the rationale for the various design choices, but in short this PR introduces 4 new Miniscript fragments, and 3 new PSBT output fields. The Miniscript fragments are the following:
Xrequired typepk_iOP_INTERNALKEYth(h)<h> OP_TEMPLATEHASH OP_EQUALcms(X,m)[X] <m> OP_SWAP OP_CHECKSIGFROMSTACKr:X[X] OP_TEMPLATEHASH OP_SWAP OP_CHECKSIGFROMSTACKThe 't' property is not currently described in the BIP or on the Miniscript website. This is a new property i had to introduce to split two related properties "a spending path needs a signature" and "the spending transaction is encumbered". See commit a3e3ad1 for details.
The PSBT fields introduced are the following:
<keytype><keydata><valuedata>PSBT_OUT_COMMITTED_TXS = 0x0b<32 bytes of template hash><bytes of Bitcoin-serialized transaction committed>PSBT_OUT_TAP_INTERNAL_KEYS = 0x0c<bytes taproot output key><32-byte internal key>PSBT_OUT_TAP_TREES = 0x0d<bytes taproot output key>{<8-bit uint depth> <8-bit uint leaf version> <compact size uint scriptlen> <bytes script>}*This is PoC quality, but should still give some reasonable assurance given the unit and end-to-end sanity checks, and especially the integration of the new fragments in the existing thorough Miniscript fuzz targets.