Skip to content

Commit c362d90

Browse files
committed
tr: move TapTree depth-checking to construction rather than embedding in Tr
This makes it impossible to construct a `TapTree` whose depth exceeds the limit, which means that we can assume that our maximum depth is 128 in all of our code. Also introduces a new error type for this case distinct from "maximum recursive depth exceeded" which is supposed to be about limitations of this library, or at least of Script, rather than about the Taproot limit.
1 parent e49c1db commit c362d90

File tree

6 files changed

+58
-23
lines changed

6 files changed

+58
-23
lines changed

src/descriptor/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub use self::bare::{Bare, Pkh};
4242
pub use self::segwitv0::{Wpkh, Wsh, WshInner};
4343
pub use self::sh::{Sh, ShInner};
4444
pub use self::sortedmulti::SortedMultiVec;
45-
pub use self::tr::{TapTree, TapTreeIter, TapTreeIterItem, Tr};
45+
pub use self::tr::{TapTree, TapTreeDepthError, TapTreeIter, TapTreeIterItem, Tr};
4646

4747
pub mod checksum;
4848
mod key;

src/descriptor/tr/mod.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use core::{cmp, fmt, hash};
66
use bitcoin::secp256k1;
77
use bitcoin::taproot::{
88
LeafVersion, TaprootBuilder, TaprootSpendInfo, TAPROOT_CONTROL_BASE_SIZE,
9-
TAPROOT_CONTROL_MAX_NODE_COUNT, TAPROOT_CONTROL_NODE_SIZE,
9+
TAPROOT_CONTROL_NODE_SIZE,
1010
};
1111
use bitcoin::{opcodes, Address, Network, ScriptBuf, Weight};
1212
use sync::Arc;
@@ -28,7 +28,7 @@ use crate::{
2828

2929
mod taptree;
3030

31-
pub use self::taptree::{TapTree, TapTreeIter, TapTreeIterItem};
31+
pub use self::taptree::{TapTree, TapTreeDepthError, TapTreeIter, TapTreeIterItem};
3232

3333
/// A taproot descriptor
3434
pub struct Tr<Pk: MiniscriptKey> {
@@ -98,13 +98,7 @@ impl<Pk: MiniscriptKey> Tr<Pk> {
9898
/// Create a new [`Tr`] descriptor from internal key and [`TapTree`]
9999
pub fn new(internal_key: Pk, tree: Option<TapTree<Pk>>) -> Result<Self, Error> {
100100
Tap::check_pk(&internal_key)?;
101-
let nodes = tree.as_ref().map(|t| t.height()).unwrap_or(0);
102-
103-
if nodes <= TAPROOT_CONTROL_MAX_NODE_COUNT {
104-
Ok(Self { internal_key, tree, spend_info: Mutex::new(None) })
105-
} else {
106-
Err(Error::MaxRecursiveDepthExceeded)
107-
}
101+
Ok(Self { internal_key, tree, spend_info: Mutex::new(None) })
108102
}
109103

110104
/// Obtain the internal key of [`Tr`] descriptor
@@ -399,18 +393,23 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Tr<Pk> {
399393
impl<'s, Pk: MiniscriptKey> TreeStack<'s, Pk> {
400394
fn new() -> Self { Self { inner: Vec::with_capacity(128) } }
401395

402-
fn push(&mut self, parent: expression::TreeIterItem<'s>, tree: TapTree<Pk>) {
396+
fn push(
397+
&mut self,
398+
parent: expression::TreeIterItem<'s>,
399+
tree: TapTree<Pk>,
400+
) -> Result<(), Error> {
403401
let mut next_push = (parent, tree);
404402
while let Some(top) = self.inner.pop() {
405403
if next_push.0.index() == top.0.index() {
406404
next_push.0 = top.0.parent().unwrap();
407-
next_push.1 = TapTree::combine(top.1, next_push.1);
405+
next_push.1 = TapTree::combine(top.1, next_push.1)?;
408406
} else {
409407
self.inner.push(top);
410408
break;
411409
}
412410
}
413411
self.inner.push(next_push);
412+
Ok(())
414413
}
415414

416415
fn pop_final(&mut self) -> Option<TapTree<Pk>> {
@@ -457,7 +456,7 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Tr<Pk> {
457456
return Err(Error::NonTopLevel(format!("{:?}", script)));
458457
};
459458

460-
tree_stack.push(node.parent().unwrap(), TapTree::Leaf(Arc::new(script)));
459+
tree_stack.push(node.parent().unwrap(), TapTree::Leaf(Arc::new(script)))?;
461460
tap_tree_iter.skip_descendants();
462461
}
463462
}

src/descriptor/tr/taptree.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,28 @@
22

33
use core::{cmp, fmt};
44

5-
use bitcoin::taproot::{LeafVersion, TapLeafHash};
5+
use bitcoin::taproot::{LeafVersion, TapLeafHash, TAPROOT_CONTROL_MAX_NODE_COUNT};
66

77
use crate::miniscript::context::Tap;
88
use crate::policy::{Liftable, Semantic};
99
use crate::prelude::Vec;
1010
use crate::sync::Arc;
1111
use crate::{Miniscript, MiniscriptKey, Threshold, ToPublicKey, TranslateErr, Translator};
1212

13+
/// Tried to construct Taproot tree which was too deep.
14+
#[derive(PartialEq, Eq, Debug)]
15+
#[non_exhaustive]
16+
pub struct TapTreeDepthError;
17+
18+
impl fmt::Display for TapTreeDepthError {
19+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20+
f.write_str("maximum Taproot tree depth (128) exceeded")
21+
}
22+
}
23+
24+
#[cfg(feature = "std")]
25+
impl std::error::Error for TapTreeDepthError {}
26+
1327
/// A Taproot Tree representation.
1428
// Hidden leaves are not yet supported in descriptor spec. Conceptually, it should
1529
// be simple to integrate those here, but it is best to wait on core for the exact syntax.
@@ -33,9 +47,13 @@ pub enum TapTree<Pk: MiniscriptKey> {
3347

3448
impl<Pk: MiniscriptKey> TapTree<Pk> {
3549
/// Creates a `TapTree` by combining `left` and `right` tree nodes.
36-
pub fn combine(left: TapTree<Pk>, right: TapTree<Pk>) -> Self {
50+
pub fn combine(left: TapTree<Pk>, right: TapTree<Pk>) -> Result<Self, TapTreeDepthError> {
3751
let height = 1 + cmp::max(left.height(), right.height());
38-
TapTree::Tree { left: Arc::new(left), right: Arc::new(right), height }
52+
if height <= TAPROOT_CONTROL_MAX_NODE_COUNT {
53+
Ok(TapTree::Tree { left: Arc::new(left), right: Arc::new(right), height })
54+
} else {
55+
Err(TapTreeDepthError)
56+
}
3957
}
4058

4159
/// Returns the height of this tree.

src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,8 @@ pub enum Error {
450450
LiftError(policy::LiftError),
451451
/// Forward script context related errors
452452
ContextError(miniscript::context::ScriptContextError),
453+
/// Tried to construct a Taproot tree which was too deep.
454+
TapTreeDepthError(crate::descriptor::TapTreeDepthError),
453455
/// Recursion depth exceeded when parsing policy/miniscript from string
454456
MaxRecursiveDepthExceeded,
455457
/// Anything but c:pk(key) (P2PK), c:pk_h(key) (P2PKH), and thresh_m(k,...)
@@ -509,6 +511,7 @@ impl fmt::Display for Error {
509511
Error::TypeCheck(ref e) => write!(f, "typecheck: {}", e),
510512
Error::Secp(ref e) => fmt::Display::fmt(e, f),
511513
Error::ContextError(ref e) => fmt::Display::fmt(e, f),
514+
Error::TapTreeDepthError(ref e) => fmt::Display::fmt(e, f),
512515
#[cfg(feature = "compiler")]
513516
Error::CompilerError(ref e) => fmt::Display::fmt(e, f),
514517
Error::ConcretePolicy(ref e) => fmt::Display::fmt(e, f),
@@ -573,6 +576,7 @@ impl std::error::Error for Error {
573576
ConcretePolicy(e) => Some(e),
574577
LiftError(e) => Some(e),
575578
ContextError(e) => Some(e),
579+
TapTreeDepthError(e) => Some(e),
576580
AnalysisError(e) => Some(e),
577581
PubKeyCtxError(e, _) => Some(e),
578582
AbsoluteLockTime(e) => Some(e),
@@ -594,6 +598,11 @@ impl From<policy::LiftError> for Error {
594598
fn from(e: policy::LiftError) -> Error { Error::LiftError(e) }
595599
}
596600

601+
#[doc(hidden)]
602+
impl From<crate::descriptor::TapTreeDepthError> for Error {
603+
fn from(e: crate::descriptor::TapTreeDepthError) -> Error { Error::TapTreeDepthError(e) }
604+
}
605+
597606
#[doc(hidden)]
598607
impl From<miniscript::context::ScriptContextError> for Error {
599608
fn from(e: miniscript::context::ScriptContextError) -> Error { Error::ContextError(e) }

src/policy/concrete.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,11 @@ fn with_huffman_tree<Pk: MiniscriptKey>(ms: Vec<(OrdF64, Miniscript<Pk, Tap>)>)
965965
let (p2, s2) = node_weights.pop().expect("len must at least be two");
966966

967967
let p = (p1.0).0 + (p2.0).0;
968-
node_weights.push((Reverse(OrdF64(p)), TapTree::combine(s1, s2)));
968+
node_weights.push((
969+
Reverse(OrdF64(p)),
970+
TapTree::combine(s1, s2)
971+
.expect("huffman tree cannot produce depth > 128 given sane weights"),
972+
));
969973
}
970974

971975
debug_assert!(node_weights.len() == 1);

src/policy/mod.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ mod tests {
393393

394394
let left = TapTree::Leaf(left_ms_compilation);
395395
let right = TapTree::Leaf(right_ms_compilation);
396-
let tree = TapTree::combine(left, right);
396+
let tree = TapTree::combine(left, right).unwrap();
397397

398398
let expected_descriptor =
399399
Descriptor::new_tr(unspendable_key.clone(), Some(tree)).unwrap();
@@ -460,18 +460,23 @@ mod tests {
460460

461461
// Arrange leaf compilations (acc. to probabilities) using huffman encoding into a TapTree
462462
let tree = TapTree::combine(
463-
TapTree::combine(node_compilations[4].clone(), node_compilations[5].clone()),
463+
TapTree::combine(node_compilations[4].clone(), node_compilations[5].clone())
464+
.unwrap(),
464465
TapTree::combine(
465466
TapTree::combine(
466467
TapTree::combine(
467468
node_compilations[0].clone(),
468469
node_compilations[1].clone(),
469-
),
470+
)
471+
.unwrap(),
470472
node_compilations[3].clone(),
471-
),
473+
)
474+
.unwrap(),
472475
node_compilations[6].clone(),
473-
),
474-
);
476+
)
477+
.unwrap(),
478+
)
479+
.unwrap();
475480

476481
let expected_descriptor = Descriptor::new_tr("E".to_string(), Some(tree)).unwrap();
477482
assert_eq!(descriptor, expected_descriptor);

0 commit comments

Comments
 (0)