Skip to content

miniscript: change tap_leaf_internal to return an Option in the satisfier#941

Merged
apoelstra merged 3 commits into
rust-bitcoin:masterfrom
apoelstra:2026-05/tapleaf-option
May 20, 2026
Merged

miniscript: change tap_leaf_internal to return an Option in the satisfier#941
apoelstra merged 3 commits into
rust-bitcoin:masterfrom
apoelstra:2026-05/tapleaf-option

Conversation

@apoelstra

Copy link
Copy Markdown
Member

Now that we have #895 and #930 we have room to change the API for helpers to the satisfaction functions.

Change the method tap_leaf_internal, which currently computes the "tapleaf hash" for an arbitrary Miniscript, even non-Taproot ones, to detect whether this is a Taproot script and only compute the tapleaf hash for Taproot scripts.

Once we are returning an Option from tap_leaf_internal, we can change a bunch of satisfaction code that currently takes a Ctx paramater and introspects it to determine whether we are in Taproot mode or not, and have it simply use the Option to decide.

Aside from speeding up and simplifying the code, this removes several instances of the Ctx parameter, which we want to eventually eliminate.

@apoelstra

Copy link
Copy Markdown
Member Author

On 1f9e818 successfully ran local tests

Comment thread src/blanket_traits.rs Outdated
Comment on lines +106 to +108
pub(crate) trait DowncastMiniscript<Pk: MiniscriptKey> {
fn downcast<Ctx: ScriptContext>(&self) -> Option<&Miniscript<Pk, Ctx>>;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Any reason not to make this a method on Miniscript (in the impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> block)?

This builds for me. NarrowedCtx came from an AI suggestion and is allegedly nomenclature from Typescript.

        #[allow(unsafe_code)]
        pub(super) fn downcast<NarrowedCtx: ScriptContext>(&self) -> Option<&Miniscript<Pk, NarrowedCtx>> {
            use core::any::TypeId;
            if TypeId::of::<Ctx>() == TypeId::of::<NarrowedCtx>() {
                // SAFETY: this pointer cast is a no-op, as guaranteed by the if guard
                // In Rust 1.76 we can use core::ptr::from_ref in place of this cast.
                Some(unsafe { &*(self as *const Self).cast() }) // cast needed til Rust 1.76
            } else {
                None
            }
        }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, I like this. Let me change it.

@tcharding tcharding left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ACK 1f9e818

apoelstra added 2 commits May 20, 2026 14:11
Right now we have a method `tap_leaf_internal` which unconditionally
computes the "tapleaf" of a Miniscript. If the Miniscript is a Taproot
script, this method is exposed by the public function `tap_leaf`.

This diff inverts `tap_leaf` and `tap_leaf_internal` -- now `tap_leaf`
does the actual calculation (and is implemented on a wider set of
Miniscripts, no longer restricting the key type unnecessarily) and
`tap_leaf_internal` calls `tap_leaf` after downcasting.

The internal method is called by Miniscript::satisfy, which passes it
into the recursive functions of src/miniscript/satisfy.rs, which carefully
avoid using it except when Ctx is equal to Tap. Aside from being wasteful,
this logic is hard to verify the correctness of, and won't survive the
removal of the Ctx parameter.

This commit just sets the TapLeafHash to all zeroes in the case that it's
unused. The next commit will replace it with an Option<TapLeafHash>, but
I didn't want to do that in this commit because it increased the diff a
lot by forcing a bunch of API changes.

The goal of this commit is just to introduce the downcasting logic, which
uses unsafe code and ought to be reviewed carefully.
We now return an Option<TapLeafHash> from tap_leaf_internal, and use the
optionality of this to determine whether we are doing a Schnorr signature
or an ECDSA signature. This lets us remove a Ctx parameter from the
internal Witness::signature method, and several methods that call it.
Also eliminates some logic which theoretically could lead to an ECDSA
signature produced for multi_a or a Schnorr sig for multi; now we have
one new `expect()` which unwraps the Option for multi_a.

There are still Ctx parameters in Witness::pkh_signature and pkh_public_key,
which are used for key size estimation. These will be eliminated when we
introduce ValidationParams, which will do size estimation in a more flexible
way.
@apoelstra apoelstra force-pushed the 2026-05/tapleaf-option branch from 1f9e818 to 6fd1cc3 Compare May 20, 2026 14:12
@apoelstra

Copy link
Copy Markdown
Member Author

ACK 6fd1cc3; successfully ran local tests

@tcharding tcharding left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ACK 6fd1cc3

@apoelstra apoelstra merged commit 38cef7c into rust-bitcoin:master May 20, 2026
27 checks passed
@apoelstra apoelstra deleted the 2026-05/tapleaf-option branch May 25, 2026 13:00
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.

2 participants