PoC: tooling for OP_TEMPLATEHASH#108
Open
darosior wants to merge 16 commits into
Open
Conversation
Author
|
Windows CI failure seems unrelated: |
cafd902 to
c064b18
Compare
Collaborator
|
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsNo conflicts as of last run. |
This was referenced Feb 22, 2026
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.
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).
c064b18 to
4430066
Compare
Author
|
Rebased after #100 was merged. This is technically a pull request, but this is mostly to make this demonstration available. I am happy to keep it around as a draft if you prefer that. |
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.
See this mailing list post for details and rationale on the various design choices. 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.