-
Notifications
You must be signed in to change notification settings - Fork 139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
descriptor: introduce several Taproot accessors #751
descriptor: introduce several Taproot accessors #751
Conversation
When working with Taproot descriptors you typically need to do an annoying (and hard to discover) `match` statement to get the `Tr` out of the descriptor, and then call accessors on that to get the actual data out. Add two new methods to `Descriptor` that directly access the internal key and the taptree. Document that the actual leaves can be obtained by calling `.iter` on the taptree. Next, when a user is trying to sign a Taproot branch, they need to obtain a TapLeafHash. We have internal code which does this (which I have pulled into a helper function since there is some room to optimize it there..) but no exposed code, forcing the user to go digging through the rust-bitcoin docs to figure it out (including knowing the standard Taproot leaf version, which is an arcane detail of the sort that Miniscript otherwise hides). Add a new method `leaf_hash` on Taproot miniscripts, so that the user can directly obtain the leaf hashes. Now you can write e.g. for script in trdesc.tap_tree_iter() { let leaf_hash = script.leaf_hash(); // Do whatever you want... } vs the previous code which was roughly let tr = match trdesc { Descriptor::Tr(ref tr) => tr, _ => unreachable!("I know this is a Taproot descriptor"), }; // Or tr.tap_tree().unwrap().iter() in case you miss the weirdly-named // Tr::iter_scripts for script in tr.iter_scripts() { // Hope you know your rust-bitcoin docs by heart, and also that // .encode is the way to convert a Miniscript to a Script! let leaf_hash = TapLeafHash::from_script( LeafVersion::TapScript, script.encode(), ); }
cc @miketwenty1 this should make your life much easier. Also, we should forward-port this to elements-miniscript (where there is a bit more work to do). |
One still-missing piece of API surface is that there is no accessor for the BIP340 "standard unspendable key". This is difficult to implement because there are currently no const constructors for XOnlyPublicKey, so I didn't include it here. Probably it will depend on updates to rust-secp to do key-parsing in pure Rust. |
Nice, on my reviewlist for this weekend. |
How about a |
@@ -241,6 +241,40 @@ impl<Pk: MiniscriptKey> Descriptor<Pk> { | |||
Ok(Descriptor::Tr(Tr::new(key, script)?)) | |||
} | |||
|
|||
/// For a Taproot descriptor, returns the internal key. | |||
pub fn internal_key(&self) -> Option<&Pk> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps name it tap_internal_key()
to keep it consistent with the other methods and to make it clearer that its taproot-related and taproot-only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other methods yield a TapTree
and are named as such, with no prefix. It's just more obvious that a "taptree" is a taproot object while an "internal key" is less obvious.
I can stick a taproot_
prefix onto here if you want, though that makes the method name longer and won't make it look consistent with the other methods. Since Taproot is the "default" output type for the forseeable future, I would almost rather stick non_taproot_
onto everything else..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Taproot is considered the 'default' then not having a prefix definitely makes sense. I didn't think of it as such.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh, well, today it's definitely not the default (as evidenced by you, and me, and miketwenty1, running into a tons of API issues just in the last couple months). But things are trending that way and all the extensions to Bitcoin I see on the horizon work within Taproot, so I think we should assume (for API purposes) that this is the way things are going.
Having said that, maybe the way to do this is to foreground Tr
, as you say, and let Taproot-only users use that in place of Descriptor
? Not sure.
I can add a taproot_
prefix here if you really want. But I weakly prefer not to.
|
||
/// For a Taproot descriptor, returns an iterator over the scripts in the Taptree. | ||
/// | ||
/// If the descriptor is not a Taproot descriptor, **or** if the descriptor is a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this kind of strange, I would prefer to differentiate these two cases so I can handle the 'not Taproot descriptor' as an error case if I was expecting it to be a Taproot.
But I guess that if the distinction is important, one could also explicitly use (my suggested) tap()
, handle the None
case, then iter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can differentiate them by calling .tap_tree()
, dealing with the Option, then calling iter_scripts
. I added this method specifically to get rid of the extra unwrap
that would require.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, actually to distinguish a non-taproot descriptor from a keyspend-only descriptor, but without matching and without being able to distinguish further, you would need to call .internal_key().is_some()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this method specifically to get rid of the extra
unwrap
that would require.
In what scenarios would it make sense to have a code path that handles both taproot and non-taproot descriptors in that way? It seems to me that using iter_tap_tree
on non-taproot descriptors would typically be a coding mistake, can't really think of a case where I'd want to do it and have it return a empty iterator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see from your other comment that you were thinking about already known-taproot descriptors, say if the same code initializes the tr descriptor and calls methods on it. It can make sense for such cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, if you are trying to "just sign a transaction" you might take an arbitrary descriptor, grab its scriptcode to produce a pre-Taproot sighash, then also iterate through all the Taproot branches (if any) to produce Taproot sighashes. In this case you could use the same code for both Taproot and non-Taproot descriptors.
/// Taproot descriptor containing only a keyspend, returns an empty iterator. | ||
pub fn tap_tree_iter(&self) -> tr::TapTreeIter<Pk> { | ||
if let Descriptor::Tr(ref tr) = self { | ||
if let Some(ref tree) = tr.tap_tree() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use tr.iter_scripts()
?
@shesek I am not going to add a |
I found myself also needing to access the inner |
Ah, good point. Also, I realize that the structure we're talking about is I also see that there isn't a direct accessor for the external key. To get this from a descriptor you need to call Will you be in Atlanta for TABConf? Maybe we should try to whiteboard out all the Taproot data structures and try to come up with a sensible API to organize them. |
Same is also true for the merkle root.
Was not planning to... but maybe 👀 I do have some ideas and some taproot stuff I implemented as extension traits that would be nice to have as part of rust-{miniscript,bitcoin}. We could also discuss it online (IRC?) |
Yep, I'm on IRC. Let's move there ##miniscript or #rust-bitcoin on Libera to discuss the bigger picture. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 86305f0
I'm going to merge this because I think it's a strict improvement. Will try to meet with @sanket1729 this week at TABConf and we can brainstorm the Taproot API. |
But for the record -- @shesek would like an accessor for the |
At TABConf @sanket1729 and I worked out a new API which has all of the functionality of the existing I will open a new PR this week with a detailed description and implementation. |
When working with Taproot descriptors you typically need to do an annoying (and hard to discover)
match
statement to get theTr
out of the descriptor, and then call accessors on that to get the actual data out.Add two new methods to
Descriptor
that directly access the internal key and the taptree. Document that the actual leaves can be obtained by calling.iter
on the taptree.Next, when a user is trying to sign a Taproot branch, they need to obtain a TapLeafHash. We have internal code which does this (which I have pulled into a helper function since there is some room to optimize it there..) but no exposed code, forcing the user to go digging through the rust-bitcoin docs to figure it out (including knowing the standard Taproot leaf version, which is an arcane detail of the sort that Miniscript otherwise hides).
Add a new method
leaf_hash
on Taproot miniscripts, so that the user can directly obtain the leaf hashes.Now you can write e.g.
vs the previous code which was roughly