From e5c9bc32d1c5a5cc3c651b2d3db43bdd048e29d2 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 11 Nov 2024 16:57:12 +0000 Subject: [PATCH 1/6] tr: move tr.rs to tr/mod.rs --- src/descriptor/{tr.rs => tr/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/descriptor/{tr.rs => tr/mod.rs} (100%) diff --git a/src/descriptor/tr.rs b/src/descriptor/tr/mod.rs similarity index 100% rename from src/descriptor/tr.rs rename to src/descriptor/tr/mod.rs From 2232979deddc208223958e558efca40b4e03e769 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 11 Nov 2024 17:19:58 +0000 Subject: [PATCH 2/6] tr: encapsulate object which is yielded by TapTreeIter All this does is wrap the existing (depth, script) return value in a structure that has a pile of accessors on it. The accessors include a couple of expensive computations which are currently done manually. A later commit/PR will introduce a `SpendInfo` structure which will cache these computations and amortize much of the hashing needed to produce control blocks. But these new accessors simplify some things. --- bitcoind-tests/tests/test_desc.rs | 6 ++-- examples/taproot.rs | 6 ++-- src/descriptor/mod.rs | 5 +-- src/descriptor/tr/mod.rs | 48 +++++++++++++------------ src/descriptor/tr/taptree.rs | 58 +++++++++++++++++++++++++++++++ src/psbt/mod.rs | 16 +++++---- 6 files changed, 103 insertions(+), 36 deletions(-) create mode 100644 src/descriptor/tr/taptree.rs diff --git a/bitcoind-tests/tests/test_desc.rs b/bitcoind-tests/tests/test_desc.rs index 99b93584b..418eaf862 100644 --- a/bitcoind-tests/tests/test_desc.rs +++ b/bitcoind-tests/tests/test_desc.rs @@ -186,9 +186,9 @@ pub fn test_desc_satisfy( // ------------------ script spend ------------- let x_only_keypairs_reqd: Vec<(secp256k1::Keypair, TapLeafHash)> = tr .iter_scripts() - .flat_map(|(_depth, ms)| { - let leaf_hash = TapLeafHash::from_script(&ms.encode(), LeafVersion::TapScript); - ms.iter_pk().filter_map(move |pk| { + .flat_map(|leaf| { + let leaf_hash = TapLeafHash::from_script(&leaf.compute_script(), LeafVersion::TapScript); + leaf.miniscript().iter_pk().filter_map(move |pk| { let i = x_only_pks.iter().position(|&x| x.to_public_key() == pk); i.map(|idx| (xonly_keypairs[idx], leaf_hash)) }) diff --git a/examples/taproot.rs b/examples/taproot.rs index 1e04f7b60..5e6d593b6 100644 --- a/examples/taproot.rs +++ b/examples/taproot.rs @@ -67,15 +67,17 @@ fn main() { // Iterate through scripts let mut iter = p.iter_scripts(); + let mut next = iter.next().unwrap(); assert_eq!( - iter.next().unwrap(), + (next.depth(), next.miniscript().as_ref()), ( 1u8, &Miniscript::::from_str("and_v(vc:pk_k(In),older(9))").unwrap() ) ); + next = iter.next().unwrap(); assert_eq!( - iter.next().unwrap(), + (next.depth(), next.miniscript().as_ref()), (1u8, &Miniscript::::from_str("and_v(v:pk(hA),pk(S))").unwrap()) ); assert_eq!(iter.next(), None); diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index d85834358..44d9203a2 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -988,8 +988,9 @@ impl FromStr for Descriptor { // FIXME preserve weird/broken behavior from 12.x. // See https://github.com/rust-bitcoin/rust-miniscript/issues/734 ret.sanity_check()?; - for (_, ms) in inner.iter_scripts() { - ms.ext_check(&crate::miniscript::analyzable::ExtParams::sane())?; + for item in inner.iter_scripts() { + item.miniscript() + .ext_check(&crate::miniscript::analyzable::ExtParams::sane())?; } } Ok(ret) diff --git a/src/descriptor/tr/mod.rs b/src/descriptor/tr/mod.rs index ab385c940..443de0a32 100644 --- a/src/descriptor/tr/mod.rs +++ b/src/descriptor/tr/mod.rs @@ -26,6 +26,10 @@ use crate::{ Threshold, ToPublicKey, TranslateErr, Translator, }; +mod taptree; + +pub use self::taptree::TapTreeIterItem; + /// A Taproot Tree representation. // Hidden leaves are not yet supported in descriptor spec. Conceptually, it should // be simple to integrate those here, but it is best to wait on core for the exact syntax. @@ -226,10 +230,10 @@ impl Tr { TaprootSpendInfo::new_key_spend(&secp, self.internal_key.to_x_only_pubkey(), None) } else { let mut builder = TaprootBuilder::new(); - for (depth, ms) in self.iter_scripts() { - let script = ms.encode(); + for leaf in self.iter_scripts() { + let script = leaf.miniscript().encode(); builder = builder - .add_leaf(depth, script) + .add_leaf(leaf.depth(), script) .expect("Computing spend data on a valid Tree should always succeed"); } // Assert builder cannot error here because we have a well formed descriptor @@ -245,8 +249,8 @@ impl Tr { /// Checks whether the descriptor is safe. pub fn sanity_check(&self) -> Result<(), Error> { - for (_depth, ms) in self.iter_scripts() { - ms.sanity_check()?; + for leaf in self.iter_scripts() { + leaf.miniscript().sanity_check()?; } Ok(()) } @@ -276,11 +280,11 @@ impl Tr { let wu = tree .iter() - .filter_map(|(depth, ms)| { - let script_size = ms.script_size(); - let max_sat_elems = ms.max_satisfaction_witness_elements().ok()?; - let max_sat_size = ms.max_satisfaction_size().ok()?; - let control_block_size = control_block_len(depth); + .filter_map(|leaf| { + let script_size = leaf.miniscript().script_size(); + let max_sat_elems = leaf.miniscript().max_satisfaction_witness_elements().ok()?; + let max_sat_size = leaf.miniscript().max_satisfaction_size().ok()?; + let control_block_size = control_block_len(leaf.depth()); // stack varint difference (+1 for ctrl block, witness script already included) let stack_varint_diff = varint_len(max_sat_elems + 1) - varint_len(0); @@ -326,11 +330,11 @@ impl Tr { }; tree.iter() - .filter_map(|(depth, ms)| { - let script_size = ms.script_size(); - let max_sat_elems = ms.max_satisfaction_witness_elements().ok()?; - let max_sat_size = ms.max_satisfaction_size().ok()?; - let control_block_size = control_block_len(depth); + .filter_map(|leaf| { + let script_size = leaf.miniscript().script_size(); + let max_sat_elems = leaf.miniscript().max_satisfaction_witness_elements().ok()?; + let max_sat_size = leaf.miniscript().max_satisfaction_size().ok()?; + let control_block_size = control_block_len(leaf.depth()); Some( // scriptSig len byte 4 + @@ -473,7 +477,7 @@ impl<'a, Pk> Iterator for TapTreeIter<'a, Pk> where Pk: MiniscriptKey + 'a, { - type Item = (u8, &'a Miniscript); + type Item = TapTreeIterItem<'a, Pk>; fn next(&mut self) -> Option { while let Some((depth, last)) = self.stack.pop() { @@ -482,7 +486,7 @@ where self.stack.push((depth + 1, right)); self.stack.push((depth + 1, left)); } - TapTree::Leaf(ref ms) => return Some((depth, ms)), + TapTree::Leaf(ref ms) => return Some(TapTreeIterItem { node: ms, depth }), } } None @@ -629,7 +633,7 @@ impl ForEachKey for Tr { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool { let script_keys_res = self .iter_scripts() - .all(|(_d, ms)| ms.for_each_key(&mut pred)); + .all(|leaf| leaf.miniscript().for_each_key(&mut pred)); script_keys_res && pred(&self.internal_key) } } @@ -673,14 +677,14 @@ where absolute_timelock: None, }; let mut min_wit_len = None; - for (_depth, ms) in desc.iter_scripts() { + for leaf in desc.iter_scripts() { let mut satisfaction = if allow_mall { - match ms.build_template(provider) { + match leaf.miniscript().build_template(provider) { s @ Satisfaction { stack: Witness::Stack(_), .. } => s, _ => continue, // No witness for this script in tr descriptor, look for next one } } else { - match ms.build_template_mall(provider) { + match leaf.miniscript().build_template_mall(provider) { s @ Satisfaction { stack: Witness::Stack(_), .. } => s, _ => continue, // No witness for this script in tr descriptor, look for next one } @@ -690,7 +694,7 @@ where _ => unreachable!(), }; - let leaf_script = (ms.encode(), LeafVersion::TapScript); + let leaf_script = (leaf.compute_script(), LeafVersion::TapScript); let control_block = spend_info .control_block(&leaf_script) .expect("Control block must exist in script map for every known leaf"); diff --git a/src/descriptor/tr/taptree.rs b/src/descriptor/tr/taptree.rs new file mode 100644 index 000000000..d04b776e5 --- /dev/null +++ b/src/descriptor/tr/taptree.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: CC0-1.0 + +use bitcoin::taproot::{LeafVersion, TapLeafHash}; + +use crate::miniscript::context::Tap; +use crate::sync::Arc; +use crate::{Miniscript, MiniscriptKey, ToPublicKey}; + +/// Iterator over all of the leaves of a Taproot tree. +/// +/// If there is no tree (i.e. this is a keyspend-only Taproot descriptor) +/// then the iterator will yield nothing. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct TapTreeIterItem<'tr, Pk: MiniscriptKey> { + pub(super) node: &'tr Arc>, + pub(super) depth: u8, +} + +impl<'tr, Pk: MiniscriptKey> TapTreeIterItem<'tr, Pk> { + /// The Tapscript in the leaf. + /// + /// To obtain a [`bitcoin::Script`] from this node, call [`Miniscript::encode`] + /// on the returned value. + #[inline] + pub fn miniscript(&self) -> &'tr Arc> { self.node } + + /// The depth of this leaf. + /// + /// This is useful for reconstructing the shape of the tree. + #[inline] + pub fn depth(&self) -> u8 { self.depth } + + /// The Tapleaf version of this leaf. + /// + /// This function returns a constant value, since there is only one version in use + /// on the Bitcoin network; however, it may be useful to use this method in case + /// you wish to be forward-compatible with future versions supported by this + /// library. + #[inline] + pub fn leaf_version(&self) -> LeafVersion { LeafVersion::TapScript } +} + +impl TapTreeIterItem<'_, Pk> { + /// Computes the Bitcoin Script of the leaf. + /// + /// This function is potentially expensive. + #[inline] + pub fn compute_script(&self) -> bitcoin::ScriptBuf { self.node.encode() } + + /// Computes the [`TapLeafHash`] of the leaf. + /// + /// This function is potentially expensive, since it serializes the full Bitcoin + /// Script of the leaf and hashes this data. + #[inline] + pub fn compute_tap_leaf_hash(&self) -> TapLeafHash { + TapLeafHash::from_script(&self.compute_script(), self.leaf_version()) + } +} diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 4eaf2f186..82febfcdc 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -1112,14 +1112,12 @@ fn update_item_with_descriptor_helper( let mut builder = taproot::TaprootBuilder::new(); - for ((_depth_der, ms_derived), (depth, ms)) in - tr_derived.iter_scripts().zip(tr_xpk.iter_scripts()) - { - debug_assert_eq!(_depth_der, depth); - let leaf_script = (ms_derived.encode(), LeafVersion::TapScript); + for (leaf_derived, leaf) in tr_derived.iter_scripts().zip(tr_xpk.iter_scripts()) { + debug_assert_eq!(leaf_derived.depth(), leaf.depth()); + let leaf_script = (leaf_derived.compute_script(), leaf_derived.leaf_version()); let tapleaf_hash = TapLeafHash::from_script(&leaf_script.0, leaf_script.1); builder = builder - .add_leaf(depth, leaf_script.0.clone()) + .add_leaf(leaf_derived.depth(), leaf_script.0.clone()) .expect("Computing spend data on a valid tree should always succeed"); if let Some(tap_scripts) = item.tap_scripts() { let control_block = spend_info @@ -1128,7 +1126,11 @@ fn update_item_with_descriptor_helper( tap_scripts.insert(control_block, leaf_script); } - for (pk_pkh_derived, pk_pkh_xpk) in ms_derived.iter_pk().zip(ms.iter_pk()) { + for (pk_pkh_derived, pk_pkh_xpk) in leaf_derived + .miniscript() + .iter_pk() + .zip(leaf.miniscript().iter_pk()) + { let (xonly, xpk) = (pk_pkh_derived.to_x_only_pubkey(), pk_pkh_xpk); let xpk_full_derivation_path = xpk From 82f58ceb68f5ce614f8c2abafb9ef97a5af684fd Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 30 Mar 2025 15:13:43 +0000 Subject: [PATCH 3/6] tr: move TapTreeIter into taptree module We are going to move TapTree into the module (hence the name), and before we can do this we need to move the iterator. --- src/descriptor/mod.rs | 2 +- src/descriptor/tr/mod.rs | 48 +++------------------------------ src/descriptor/tr/taptree.rs | 52 ++++++++++++++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 48 deletions(-) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 44d9203a2..53ffef408 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -42,7 +42,7 @@ pub use self::bare::{Bare, Pkh}; pub use self::segwitv0::{Wpkh, Wsh, WshInner}; pub use self::sh::{Sh, ShInner}; pub use self::sortedmulti::SortedMultiVec; -pub use self::tr::{TapTree, Tr}; +pub use self::tr::{TapTree, TapTreeIter, TapTreeIterItem, Tr}; pub mod checksum; mod key; diff --git a/src/descriptor/tr/mod.rs b/src/descriptor/tr/mod.rs index 443de0a32..0c18e92f0 100644 --- a/src/descriptor/tr/mod.rs +++ b/src/descriptor/tr/mod.rs @@ -28,7 +28,7 @@ use crate::{ mod taptree; -pub use self::taptree::TapTreeIterItem; +pub use self::taptree::{TapTreeIter, TapTreeIterItem}; /// A Taproot Tree representation. // Hidden leaves are not yet supported in descriptor spec. Conceptually, it should @@ -132,7 +132,7 @@ impl TapTree { /// Iterates over all miniscripts in DFS walk order compatible with the /// PSBT requirements (BIP 371). - pub fn iter(&self) -> TapTreeIter { TapTreeIter { stack: vec![(0, self)] } } + pub fn iter(&self) -> TapTreeIter { TapTreeIter::from_tree(self) } // Helper function to translate keys fn translate_helper(&self, t: &mut T) -> Result, TranslateErr> @@ -201,7 +201,7 @@ impl Tr { pub fn iter_scripts(&self) -> TapTreeIter { match self.tree { Some(ref t) => t.iter(), - None => TapTreeIter { stack: vec![] }, + None => TapTreeIter::empty(), } } @@ -451,48 +451,6 @@ impl Tr { } } -/// Iterator for Taproot structures -/// Yields a pair of (depth, miniscript) in a depth first walk -/// For example, this tree: -/// - N0 - -/// / \\ -/// N1 N2 -/// / \ / \\ -/// A B C N3 -/// / \\ -/// D E -/// would yield (2, A), (2, B), (2,C), (3, D), (3, E). -/// -#[derive(Debug, Clone)] -pub struct TapTreeIter<'a, Pk: MiniscriptKey> { - stack: Vec<(u8, &'a TapTree)>, -} - -impl TapTreeIter<'_, Pk> { - /// Helper function to return an empty iterator from Descriptor::tap_tree_iter. - pub(super) fn empty() -> Self { Self { stack: vec![] } } -} - -impl<'a, Pk> Iterator for TapTreeIter<'a, Pk> -where - Pk: MiniscriptKey + 'a, -{ - type Item = TapTreeIterItem<'a, Pk>; - - fn next(&mut self) -> Option { - while let Some((depth, last)) = self.stack.pop() { - match *last { - TapTree::Tree { ref left, ref right, height: _ } => { - self.stack.push((depth + 1, right)); - self.stack.push((depth + 1, left)); - } - TapTree::Leaf(ref ms) => return Some(TapTreeIterItem { node: ms, depth }), - } - } - None - } -} - impl core::str::FromStr for Tr { type Err = Error; diff --git a/src/descriptor/tr/taptree.rs b/src/descriptor/tr/taptree.rs index d04b776e5..1e7b1375d 100644 --- a/src/descriptor/tr/taptree.rs +++ b/src/descriptor/tr/taptree.rs @@ -2,18 +2,66 @@ use bitcoin::taproot::{LeafVersion, TapLeafHash}; +use super::TapTree; use crate::miniscript::context::Tap; +use crate::prelude::Vec; use crate::sync::Arc; use crate::{Miniscript, MiniscriptKey, ToPublicKey}; +/// Iterator over the leaves of a Taptree. +/// +/// Yields a pair of (depth, miniscript) in a depth first walk +/// For example, this tree: +/// - N0 - +/// / \\ +/// N1 N2 +/// / \ / \\ +/// A B C N3 +/// / \\ +/// D E +/// would yield (2, A), (2, B), (2,C), (3, D), (3, E). +/// +#[derive(Debug, Clone)] +pub struct TapTreeIter<'a, Pk: MiniscriptKey> { + stack: Vec<(u8, &'a TapTree)>, +} + +impl<'tr, Pk: MiniscriptKey> TapTreeIter<'tr, Pk> { + /// An empty iterator. + pub fn empty() -> Self { Self { stack: vec![] } } + + /// An iterator over a given tree. + pub(super) fn from_tree(tree: &'tr TapTree) -> Self { Self { stack: vec![(0, tree)] } } +} + +impl<'a, Pk> Iterator for TapTreeIter<'a, Pk> +where + Pk: MiniscriptKey + 'a, +{ + type Item = TapTreeIterItem<'a, Pk>; + + fn next(&mut self) -> Option { + while let Some((depth, last)) = self.stack.pop() { + match *last { + TapTree::Tree { ref left, ref right, height: _ } => { + self.stack.push((depth + 1, right)); + self.stack.push((depth + 1, left)); + } + TapTree::Leaf(ref ms) => return Some(TapTreeIterItem { node: ms, depth }), + } + } + None + } +} + /// Iterator over all of the leaves of a Taproot tree. /// /// If there is no tree (i.e. this is a keyspend-only Taproot descriptor) /// then the iterator will yield nothing. #[derive(Clone, PartialEq, Eq, Debug)] pub struct TapTreeIterItem<'tr, Pk: MiniscriptKey> { - pub(super) node: &'tr Arc>, - pub(super) depth: u8, + node: &'tr Arc>, + depth: u8, } impl<'tr, Pk: MiniscriptKey> TapTreeIterItem<'tr, Pk> { From b46300741fdcf8cc3d120ebb50aaae64f4a3e38b Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 30 Mar 2025 15:18:42 +0000 Subject: [PATCH 4/6] tr: move TapTree into taptree module This completes the move of all the taptree-related stuff into its own module, where it will be easier to mess around with this stuff. --- src/descriptor/tr/mod.rs | 81 +------------------------------- src/descriptor/tr/taptree.rs | 89 ++++++++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 83 deletions(-) diff --git a/src/descriptor/tr/mod.rs b/src/descriptor/tr/mod.rs index 0c18e92f0..f5b650872 100644 --- a/src/descriptor/tr/mod.rs +++ b/src/descriptor/tr/mod.rs @@ -28,28 +28,7 @@ use crate::{ mod taptree; -pub use self::taptree::{TapTreeIter, TapTreeIterItem}; - -/// A Taproot Tree representation. -// Hidden leaves are not yet supported in descriptor spec. Conceptually, it should -// be simple to integrate those here, but it is best to wait on core for the exact syntax. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub enum TapTree { - /// A taproot tree structure - Tree { - /// Left tree branch. - left: Arc>, - /// Right tree branch. - right: Arc>, - /// Tree height, defined as `1 + max(left_height, right_height)`. - height: usize, - }, - /// A taproot leaf denoting a spending condition - // A new leaf version would require a new Context, therefore there is no point - // in adding a LeafVersion with Leaf type here. All Miniscripts right now - // are of Leafversion::default - Leaf(Arc>), -} +pub use self::taptree::{TapTree, TapTreeIter, TapTreeIterItem}; /// A taproot descriptor pub struct Tr { @@ -115,64 +94,6 @@ impl hash::Hash for Tr { } } -impl TapTree { - /// Creates a `TapTree` by combining `left` and `right` tree nodes. - pub fn combine(left: TapTree, right: TapTree) -> Self { - let height = 1 + cmp::max(left.height(), right.height()); - TapTree::Tree { left: Arc::new(left), right: Arc::new(right), height } - } - - /// Returns the height of this tree. - pub fn height(&self) -> usize { - match *self { - TapTree::Tree { left: _, right: _, height } => height, - TapTree::Leaf(..) => 0, - } - } - - /// Iterates over all miniscripts in DFS walk order compatible with the - /// PSBT requirements (BIP 371). - pub fn iter(&self) -> TapTreeIter { TapTreeIter::from_tree(self) } - - // Helper function to translate keys - fn translate_helper(&self, t: &mut T) -> Result, TranslateErr> - where - T: Translator, - { - let frag = match *self { - TapTree::Tree { ref left, ref right, ref height } => TapTree::Tree { - left: Arc::new(left.translate_helper(t)?), - right: Arc::new(right.translate_helper(t)?), - height: *height, - }, - TapTree::Leaf(ref ms) => TapTree::Leaf(Arc::new(ms.translate_pk(t)?)), - }; - Ok(frag) - } -} - -impl fmt::Display for TapTree { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TapTree::Tree { ref left, ref right, height: _ } => { - write!(f, "{{{},{}}}", *left, *right) - } - TapTree::Leaf(ref script) => write!(f, "{}", *script), - } - } -} - -impl fmt::Debug for TapTree { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TapTree::Tree { ref left, ref right, height: _ } => { - write!(f, "{{{:?},{:?}}}", *left, *right) - } - TapTree::Leaf(ref script) => write!(f, "{:?}", *script), - } - } -} - impl Tr { /// Create a new [`Tr`] descriptor from internal key and [`TapTree`] pub fn new(internal_key: Pk, tree: Option>) -> Result { diff --git a/src/descriptor/tr/taptree.rs b/src/descriptor/tr/taptree.rs index 1e7b1375d..3a333e01b 100644 --- a/src/descriptor/tr/taptree.rs +++ b/src/descriptor/tr/taptree.rs @@ -1,12 +1,95 @@ // SPDX-License-Identifier: CC0-1.0 +use core::{cmp, fmt}; + use bitcoin::taproot::{LeafVersion, TapLeafHash}; -use super::TapTree; use crate::miniscript::context::Tap; use crate::prelude::Vec; use crate::sync::Arc; -use crate::{Miniscript, MiniscriptKey, ToPublicKey}; +use crate::{Miniscript, MiniscriptKey, ToPublicKey, TranslateErr, Translator}; + +/// A Taproot Tree representation. +// Hidden leaves are not yet supported in descriptor spec. Conceptually, it should +// be simple to integrate those here, but it is best to wait on core for the exact syntax. +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum TapTree { + /// A taproot tree structure + Tree { + /// Left tree branch. + left: Arc>, + /// Right tree branch. + right: Arc>, + /// Tree height, defined as `1 + max(left_height, right_height)`. + height: usize, + }, + /// A taproot leaf denoting a spending condition + // A new leaf version would require a new Context, therefore there is no point + // in adding a LeafVersion with Leaf type here. All Miniscripts right now + // are of Leafversion::default + Leaf(Arc>), +} + +impl TapTree { + /// Creates a `TapTree` by combining `left` and `right` tree nodes. + pub fn combine(left: TapTree, right: TapTree) -> Self { + let height = 1 + cmp::max(left.height(), right.height()); + TapTree::Tree { left: Arc::new(left), right: Arc::new(right), height } + } + + /// Returns the height of this tree. + pub fn height(&self) -> usize { + match *self { + TapTree::Tree { left: _, right: _, height } => height, + TapTree::Leaf(..) => 0, + } + } + + /// Iterates over all miniscripts in DFS walk order compatible with the + /// PSBT requirements (BIP 371). + pub fn iter(&self) -> TapTreeIter { TapTreeIter::from_tree(self) } + + // Helper function to translate keys + pub(super) fn translate_helper( + &self, + t: &mut T, + ) -> Result, TranslateErr> + where + T: Translator, + { + let frag = match *self { + TapTree::Tree { ref left, ref right, ref height } => TapTree::Tree { + left: Arc::new(left.translate_helper(t)?), + right: Arc::new(right.translate_helper(t)?), + height: *height, + }, + TapTree::Leaf(ref ms) => TapTree::Leaf(Arc::new(ms.translate_pk(t)?)), + }; + Ok(frag) + } +} + +impl fmt::Display for TapTree { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TapTree::Tree { ref left, ref right, height: _ } => { + write!(f, "{{{},{}}}", *left, *right) + } + TapTree::Leaf(ref script) => write!(f, "{}", *script), + } + } +} + +impl fmt::Debug for TapTree { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TapTree::Tree { ref left, ref right, height: _ } => { + write!(f, "{{{:?},{:?}}}", *left, *right) + } + TapTree::Leaf(ref script) => write!(f, "{:?}", *script), + } + } +} /// Iterator over the leaves of a Taptree. /// @@ -31,7 +114,7 @@ impl<'tr, Pk: MiniscriptKey> TapTreeIter<'tr, Pk> { pub fn empty() -> Self { Self { stack: vec![] } } /// An iterator over a given tree. - pub(super) fn from_tree(tree: &'tr TapTree) -> Self { Self { stack: vec![(0, tree)] } } + fn from_tree(tree: &'tr TapTree) -> Self { Self { stack: vec![(0, tree)] } } } impl<'a, Pk> Iterator for TapTreeIter<'a, Pk> From 86bee150a8e8b6236d35974fa04c9aaed5376743 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 10 Apr 2025 20:38:53 +0000 Subject: [PATCH 5/6] tr: move Liftable impl for TapTree Moving this separately from the rest of the TapTree stuff because I changed the Policy import to Semantic and the Error import to just crate::Error (since this is the only place that type is used in the taptree module, and I don't really like this type). So this is a move-only diff but it might not look like it, so I figured I should at least make the diff small. --- src/descriptor/tr/mod.rs | 16 ---------------- src/descriptor/tr/taptree.rs | 19 ++++++++++++++++++- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/descriptor/tr/mod.rs b/src/descriptor/tr/mod.rs index f5b650872..9f2d66c47 100644 --- a/src/descriptor/tr/mod.rs +++ b/src/descriptor/tr/mod.rs @@ -480,22 +480,6 @@ impl fmt::Display for Tr { } } -impl Liftable for TapTree { - fn lift(&self) -> Result, Error> { - fn lift_helper(s: &TapTree) -> Result, Error> { - match *s { - TapTree::Tree { ref left, ref right, height: _ } => Ok(Policy::Thresh( - Threshold::or(Arc::new(lift_helper(left)?), Arc::new(lift_helper(right)?)), - )), - TapTree::Leaf(ref leaf) => leaf.lift(), - } - } - - let pol = lift_helper(self)?; - Ok(pol.normalized()) - } -} - impl Liftable for Tr { fn lift(&self) -> Result, Error> { match &self.tree { diff --git a/src/descriptor/tr/taptree.rs b/src/descriptor/tr/taptree.rs index 3a333e01b..bfe5f09c2 100644 --- a/src/descriptor/tr/taptree.rs +++ b/src/descriptor/tr/taptree.rs @@ -5,9 +5,10 @@ use core::{cmp, fmt}; use bitcoin::taproot::{LeafVersion, TapLeafHash}; use crate::miniscript::context::Tap; +use crate::policy::{Liftable, Semantic}; use crate::prelude::Vec; use crate::sync::Arc; -use crate::{Miniscript, MiniscriptKey, ToPublicKey, TranslateErr, Translator}; +use crate::{Miniscript, MiniscriptKey, Threshold, ToPublicKey, TranslateErr, Translator}; /// A Taproot Tree representation. // Hidden leaves are not yet supported in descriptor spec. Conceptually, it should @@ -69,6 +70,22 @@ impl TapTree { } } +impl Liftable for TapTree { + fn lift(&self) -> Result, crate::Error> { + fn lift_helper(s: &TapTree) -> Result, crate::Error> { + match *s { + TapTree::Tree { ref left, ref right, height: _ } => Ok(Semantic::Thresh( + Threshold::or(Arc::new(lift_helper(left)?), Arc::new(lift_helper(right)?)), + )), + TapTree::Leaf(ref leaf) => leaf.lift(), + } + } + + let pol = lift_helper(self)?; + Ok(pol.normalized()) + } +} + impl fmt::Display for TapTree { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { From 8e5d809b1f2a8d55799476b20fbbb2c783ecd72d Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 11 Nov 2024 17:50:00 +0000 Subject: [PATCH 6/6] tr: rename `iter_scripts` to `leaves` The name `iter_scripts` is dumb and undiscoverable. More importantly, it's misleading -- this iterator does not yield scripts. It yields Miniscripts, which can be converted to scripts if you have ToPublicKey and are willing to pay a cost. --- bitcoind-tests/tests/test_desc.rs | 2 +- examples/taproot.rs | 2 +- src/descriptor/mod.rs | 4 ++-- src/descriptor/tr/mod.rs | 23 +++++++++++++++-------- src/descriptor/tr/taptree.rs | 8 +++++--- src/psbt/mod.rs | 2 +- 6 files changed, 25 insertions(+), 16 deletions(-) diff --git a/bitcoind-tests/tests/test_desc.rs b/bitcoind-tests/tests/test_desc.rs index 418eaf862..30c54f169 100644 --- a/bitcoind-tests/tests/test_desc.rs +++ b/bitcoind-tests/tests/test_desc.rs @@ -185,7 +185,7 @@ pub fn test_desc_satisfy( } // ------------------ script spend ------------- let x_only_keypairs_reqd: Vec<(secp256k1::Keypair, TapLeafHash)> = tr - .iter_scripts() + .leaves() .flat_map(|leaf| { let leaf_hash = TapLeafHash::from_script(&leaf.compute_script(), LeafVersion::TapScript); leaf.miniscript().iter_pk().filter_map(move |pk| { diff --git a/examples/taproot.rs b/examples/taproot.rs index 5e6d593b6..aea13f6ce 100644 --- a/examples/taproot.rs +++ b/examples/taproot.rs @@ -66,7 +66,7 @@ fn main() { assert_eq!(p.internal_key(), "Ca"); // Iterate through scripts - let mut iter = p.iter_scripts(); + let mut iter = p.leaves(); let mut next = iter.next().unwrap(); assert_eq!( (next.depth(), next.miniscript().as_ref()), diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 53ffef408..47da23b8b 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -252,7 +252,7 @@ impl Descriptor { /// For a Taproot descriptor, returns the [`TapTree`] describing the Taproot tree. /// - /// To obtain the individual leaves of the tree, call [`TapTree::iter`] on the + /// To obtain the individual leaves of the tree, call [`TapTree::leaves`] on the /// returned value. pub fn tap_tree(&self) -> Option<&TapTree> { if let Descriptor::Tr(ref tr) = self { @@ -269,7 +269,7 @@ impl Descriptor { pub fn tap_tree_iter(&self) -> tr::TapTreeIter { if let Descriptor::Tr(ref tr) = self { if let Some(ref tree) = tr.tap_tree() { - return tree.iter(); + return tree.leaves(); } } tr::TapTreeIter::empty() diff --git a/src/descriptor/tr/mod.rs b/src/descriptor/tr/mod.rs index 9f2d66c47..143376334 100644 --- a/src/descriptor/tr/mod.rs +++ b/src/descriptor/tr/mod.rs @@ -119,9 +119,16 @@ impl Tr { /// Iterate over all scripts in merkle tree. If there is no script path, the iterator /// yields [`None`] - pub fn iter_scripts(&self) -> TapTreeIter { + #[deprecated(since = "TBD", note = "use `leaves` instead")] + pub fn iter_scripts(&self) -> TapTreeIter { self.leaves() } + + /// Iterates over all the leaves of the tree in depth-first preorder. + /// + /// The yielded elements include the Miniscript for each leave as well as its depth + /// in the tree, which is the data required by PSBT (BIP 371). + pub fn leaves(&self) -> TapTreeIter { match self.tree { - Some(ref t) => t.iter(), + Some(ref t) => t.leaves(), None => TapTreeIter::empty(), } } @@ -151,7 +158,7 @@ impl Tr { TaprootSpendInfo::new_key_spend(&secp, self.internal_key.to_x_only_pubkey(), None) } else { let mut builder = TaprootBuilder::new(); - for leaf in self.iter_scripts() { + for leaf in self.leaves() { let script = leaf.miniscript().encode(); builder = builder .add_leaf(leaf.depth(), script) @@ -170,7 +177,7 @@ impl Tr { /// Checks whether the descriptor is safe. pub fn sanity_check(&self) -> Result<(), Error> { - for leaf in self.iter_scripts() { + for leaf in self.leaves() { leaf.miniscript().sanity_check()?; } Ok(()) @@ -200,7 +207,7 @@ impl Tr { }; let wu = tree - .iter() + .leaves() .filter_map(|leaf| { let script_size = leaf.miniscript().script_size(); let max_sat_elems = leaf.miniscript().max_satisfaction_witness_elements().ok()?; @@ -250,7 +257,7 @@ impl Tr { Some(tree) => tree, }; - tree.iter() + tree.leaves() .filter_map(|leaf| { let script_size = leaf.miniscript().script_size(); let max_sat_elems = leaf.miniscript().max_satisfaction_witness_elements().ok()?; @@ -495,7 +502,7 @@ impl Liftable for Tr { impl ForEachKey for Tr { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool { let script_keys_res = self - .iter_scripts() + .leaves() .all(|leaf| leaf.miniscript().for_each_key(&mut pred)); script_keys_res && pred(&self.internal_key) } @@ -540,7 +547,7 @@ where absolute_timelock: None, }; let mut min_wit_len = None; - for leaf in desc.iter_scripts() { + for leaf in desc.leaves() { let mut satisfaction = if allow_mall { match leaf.miniscript().build_template(provider) { s @ Satisfaction { stack: Witness::Stack(_), .. } => s, diff --git a/src/descriptor/tr/taptree.rs b/src/descriptor/tr/taptree.rs index bfe5f09c2..c68033f54 100644 --- a/src/descriptor/tr/taptree.rs +++ b/src/descriptor/tr/taptree.rs @@ -46,9 +46,11 @@ impl TapTree { } } - /// Iterates over all miniscripts in DFS walk order compatible with the - /// PSBT requirements (BIP 371). - pub fn iter(&self) -> TapTreeIter { TapTreeIter::from_tree(self) } + /// Iterates over all the leaves of the tree in depth-first preorder. + /// + /// The yielded elements include the Miniscript for each leave as well as its depth + /// in the tree, which is the data required by PSBT (BIP 371). + pub fn leaves(&self) -> TapTreeIter { TapTreeIter::from_tree(self) } // Helper function to translate keys pub(super) fn translate_helper( diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 82febfcdc..84924f42e 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -1112,7 +1112,7 @@ fn update_item_with_descriptor_helper( let mut builder = taproot::TaprootBuilder::new(); - for (leaf_derived, leaf) in tr_derived.iter_scripts().zip(tr_xpk.iter_scripts()) { + for (leaf_derived, leaf) in tr_derived.leaves().zip(tr_xpk.leaves()) { debug_assert_eq!(leaf_derived.depth(), leaf.depth()); let leaf_script = (leaf_derived.compute_script(), leaf_derived.leaf_version()); let tapleaf_hash = TapLeafHash::from_script(&leaf_script.0, leaf_script.1);