Skip to content

Commit 7b6f49d

Browse files
committed
expression: unify Taproot and non-Taproot parsing
The `expression::Tree` type now parses expression trees containing both {} and () as children. It marks which is which, and it is the responsibility of FromTree implementors to make sure that the correct style is used. This eliminates a ton of ad-hoc string parsing code from descriptor/tr.rs, including 8 instances of Error::Unexpected. It is also the first change that preserves (sorta) a pubkey parsing error rather than converting it to a string. Because of rust-bitcoin#734 this inserts a call to `Descriptor::sanity_check` when parsing a string, specifically for Taproot descriptors. This is weird and wrong but we want to address it separately from this PR, whose goal is to preserve all behavior.
1 parent beacc8d commit 7b6f49d

File tree

3 files changed

+158
-183
lines changed

3 files changed

+158
-183
lines changed

src/descriptor/mod.rs

+27-11
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use bitcoin::{
2121
};
2222
use sync::Arc;
2323

24+
use crate::expression::FromTree as _;
2425
use crate::miniscript::decode::Terminal;
2526
use crate::miniscript::{satisfy, Legacy, Miniscript, Segwitv0};
2627
use crate::plan::{AssetProvider, Plan};
@@ -981,17 +982,17 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Descriptor<Pk> {
981982
impl<Pk: FromStrKey> FromStr for Descriptor<Pk> {
982983
type Err = Error;
983984
fn from_str(s: &str) -> Result<Descriptor<Pk>, Error> {
984-
// tr tree parsing has special code
985-
// Tr::from_str will check the checksum
986-
// match "tr(" to handle more extensibly
987-
let desc = if s.starts_with("tr(") {
988-
Ok(Descriptor::Tr(Tr::from_str(s)?))
989-
} else {
990-
let top = expression::Tree::from_str(s)?;
991-
expression::FromTree::from_tree(&top)
992-
}?;
993-
994-
Ok(desc)
985+
let top = expression::Tree::from_str(s)?;
986+
let ret = Self::from_tree(&top)?;
987+
if let Descriptor::Tr(ref inner) = ret {
988+
// FIXME preserve weird/broken behavior from 12.x.
989+
// See https://github.com/rust-bitcoin/rust-miniscript/issues/734
990+
ret.sanity_check()?;
991+
for (_, ms) in inner.iter_scripts() {
992+
ms.ext_check(&crate::miniscript::analyzable::ExtParams::sane())?;
993+
}
994+
}
995+
Ok(ret)
995996
}
996997
}
997998

@@ -1545,6 +1546,21 @@ mod tests {
15451546
)
15461547
}
15471548

1549+
#[test]
1550+
fn tr_named_branch() {
1551+
use crate::{ParseError, ParseTreeError};
1552+
1553+
assert!(matches!(
1554+
StdDescriptor::from_str(
1555+
"tr(0202d44008000010100000000084F0000000dd0dd00000000000201dceddd00d00,abc{0,0})"
1556+
),
1557+
Err(Error::Parse(ParseError::Tree(ParseTreeError::IncorrectName {
1558+
expected: "",
1559+
..
1560+
}))),
1561+
));
1562+
}
1563+
15481564
#[test]
15491565
fn roundtrip_tests() {
15501566
let descriptor = Descriptor::<bitcoin::PublicKey>::from_str("multi");

src/descriptor/tr.rs

+104-130
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// SPDX-License-Identifier: CC0-1.0
22

3-
use core::str::FromStr;
43
use core::{cmp, fmt, hash};
54

65
#[cfg(not(test))] // https://github.com/rust-lang/rust/issues/121684
@@ -12,9 +11,10 @@ use bitcoin::taproot::{
1211
use bitcoin::{opcodes, Address, Network, ScriptBuf, Weight};
1312
use sync::Arc;
1413

15-
use super::checksum::{self, verify_checksum};
14+
use super::checksum;
1615
use crate::descriptor::DefiniteDescriptorKey;
1716
use crate::expression::{self, FromTree};
17+
use crate::iter::TreeLike as _;
1818
use crate::miniscript::satisfy::{Placeholder, Satisfaction, SchnorrSigType, Witness};
1919
use crate::miniscript::Miniscript;
2020
use crate::plan::AssetProvider;
@@ -23,8 +23,8 @@ use crate::policy::Liftable;
2323
use crate::prelude::*;
2424
use crate::util::{varint_len, witness_size};
2525
use crate::{
26-
Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, ScriptContext, Tap, Threshold,
27-
ToPublicKey, TranslateErr, Translator,
26+
Error, ForEachKey, FromStrKey, MiniscriptKey, ParseError, Satisfier, ScriptContext, Tap,
27+
Threshold, ToPublicKey, TranslateErr, Translator,
2828
};
2929

3030
/// A Taproot Tree representation.
@@ -490,79 +490,114 @@ where
490490
}
491491
}
492492

493-
#[rustfmt::skip]
494-
impl<Pk: FromStrKey> Tr<Pk> {
495-
// Helper function to parse taproot script path
496-
fn parse_tr_script_spend(tree: &expression::Tree,) -> Result<TapTree<Pk>, Error> {
497-
match tree {
498-
expression::Tree { name, args, .. } if !name.is_empty() && args.is_empty() => {
499-
let script = Miniscript::<Pk, Tap>::from_str(name)?;
500-
Ok(TapTree::Leaf(Arc::new(script)))
501-
}
502-
expression::Tree { name, args, .. } if name.is_empty() && args.len() == 2 => {
503-
let left = Self::parse_tr_script_spend(&args[0])?;
504-
let right = Self::parse_tr_script_spend(&args[1])?;
505-
Ok(TapTree::combine(left, right))
506-
}
507-
_ => {
508-
Err(Error::Unexpected(
509-
"unknown format for script spending paths while parsing taproot descriptor"
510-
.to_string(),
511-
))},
512-
}
493+
impl<Pk: FromStrKey> core::str::FromStr for Tr<Pk> {
494+
type Err = Error;
495+
496+
fn from_str(s: &str) -> Result<Self, Self::Err> {
497+
let expr_tree = expression::Tree::from_str(s)?;
498+
Self::from_tree(&expr_tree)
513499
}
514500
}
515501

516502
impl<Pk: FromStrKey> crate::expression::FromTree for Tr<Pk> {
517-
fn from_tree(top: &expression::Tree) -> Result<Self, Error> {
518-
if top.name == "tr" {
519-
match top.args.len() {
520-
1 => {
521-
let key = &top.args[0];
522-
if !key.args.is_empty() {
523-
return Err(Error::Unexpected(format!(
524-
"#{} script associated with `key-path` while parsing taproot descriptor",
525-
key.args.len()
526-
)));
503+
fn from_tree(expr_tree: &expression::Tree) -> Result<Self, Error> {
504+
use crate::expression::{Parens, ParseTreeError};
505+
506+
expr_tree
507+
.verify_toplevel("tr", 1..=2)
508+
.map_err(From::from)
509+
.map_err(Error::Parse)?;
510+
511+
let mut round_paren_depth = 0;
512+
513+
let mut internal_key = None;
514+
let mut tree_stack = vec![];
515+
516+
for item in expr_tree.verbose_pre_order_iter() {
517+
// Top-level "tr" node.
518+
if item.index == 0 {
519+
if item.is_complete {
520+
debug_assert!(
521+
internal_key.is_some(),
522+
"checked above that top-level 'tr' has children"
523+
);
524+
}
525+
} else if item.index == 1 {
526+
// First child of tr, which must be the internal key
527+
if !item.node.args.is_empty() {
528+
return Err(Error::Parse(ParseError::Tree(
529+
ParseTreeError::IncorrectNumberOfChildren {
530+
description: "internal key",
531+
n_children: item.node.args.len(),
532+
minimum: Some(0),
533+
maximum: Some(0),
534+
},
535+
)));
536+
}
537+
internal_key = Some(
538+
Pk::from_str(item.node.name)
539+
.map_err(ParseError::box_from_str)
540+
.map_err(Error::Parse)?,
541+
);
542+
} else {
543+
// From here on we are into the taptree.
544+
if item.n_children_yielded == 0 {
545+
match item.node.parens {
546+
Parens::Curly => {
547+
if !item.node.name.is_empty() {
548+
return Err(Error::Parse(ParseError::Tree(
549+
ParseTreeError::IncorrectName {
550+
actual: item.node.name.to_owned(),
551+
expected: "",
552+
},
553+
)));
554+
}
555+
if round_paren_depth > 0 {
556+
return Err(Error::Parse(ParseError::Tree(
557+
ParseTreeError::IllegalCurlyBrace {
558+
pos: item.node.children_pos,
559+
},
560+
)));
561+
}
562+
}
563+
Parens::Round => round_paren_depth += 1,
564+
_ => {}
527565
}
528-
Tr::new(expression::terminal(key, Pk::from_str)?, None)
529566
}
530-
2 => {
531-
let key = &top.args[0];
532-
if !key.args.is_empty() {
533-
return Err(Error::Unexpected(format!(
534-
"#{} script associated with `key-path` while parsing taproot descriptor",
535-
key.args.len()
536-
)));
567+
if item.is_complete {
568+
if item.node.parens == Parens::Curly {
569+
if item.n_children_yielded == 2 {
570+
let rchild = tree_stack.pop().unwrap();
571+
let lchild = tree_stack.pop().unwrap();
572+
tree_stack.push(TapTree::combine(lchild, rchild));
573+
} else {
574+
return Err(Error::Parse(ParseError::Tree(
575+
ParseTreeError::IncorrectNumberOfChildren {
576+
description: "Taptree node",
577+
n_children: item.n_children_yielded,
578+
minimum: Some(2),
579+
maximum: Some(2),
580+
},
581+
)));
582+
}
583+
} else {
584+
if item.node.parens == Parens::Round {
585+
round_paren_depth -= 1;
586+
}
587+
if round_paren_depth == 0 {
588+
let script = Miniscript::from_tree(item.node)?;
589+
// FIXME hack for https://github.com/rust-bitcoin/rust-miniscript/issues/734
590+
if script.ty.corr.base != crate::miniscript::types::Base::B {
591+
return Err(Error::NonTopLevel(format!("{:?}", script)));
592+
};
593+
tree_stack.push(TapTree::Leaf(Arc::new(script)));
594+
}
537595
}
538-
let tree = &top.args[1];
539-
let ret = Self::parse_tr_script_spend(tree)?;
540-
Tr::new(expression::terminal(key, Pk::from_str)?, Some(ret))
541596
}
542-
_ => Err(Error::Unexpected(format!(
543-
"{}[#{} args] while parsing taproot descriptor",
544-
top.name,
545-
top.args.len()
546-
))),
547597
}
548-
} else {
549-
Err(Error::Unexpected(format!(
550-
"{}[#{} args] while parsing taproot descriptor",
551-
top.name,
552-
top.args.len()
553-
)))
554598
}
555-
}
556-
}
557-
558-
impl<Pk: FromStrKey> core::str::FromStr for Tr<Pk> {
559-
type Err = Error;
560-
fn from_str(s: &str) -> Result<Self, Self::Err> {
561-
let desc_str = verify_checksum(s)
562-
.map_err(From::from)
563-
.map_err(Error::ParseTree)?;
564-
let top = parse_tr_tree(desc_str)?;
565-
Self::from_tree(&top)
599+
assert!(tree_stack.len() <= 1);
600+
Tr::new(internal_key.unwrap(), tree_stack.pop())
566601
}
567602
}
568603

@@ -588,69 +623,6 @@ impl<Pk: MiniscriptKey> fmt::Display for Tr<Pk> {
588623
}
589624
}
590625

591-
// Helper function to parse string into miniscript tree form
592-
fn parse_tr_tree(s: &str) -> Result<expression::Tree, Error> {
593-
if s.len() > 3 && &s[..3] == "tr(" && s.as_bytes()[s.len() - 1] == b')' {
594-
let rest = &s[3..s.len() - 1];
595-
if !rest.contains(',') {
596-
let key = expression::Tree::from_str(rest)?;
597-
if !key.args.is_empty() {
598-
return Err(Error::Unexpected("invalid taproot internal key".to_string()));
599-
}
600-
let internal_key = expression::Tree {
601-
name: key.name,
602-
parens: expression::Parens::None,
603-
children_pos: 0,
604-
args: vec![],
605-
};
606-
return Ok(expression::Tree {
607-
name: "tr",
608-
parens: expression::Parens::Round,
609-
children_pos: 0,
610-
args: vec![internal_key],
611-
});
612-
}
613-
// use str::split_once() method to refactor this when compiler version bumps up
614-
let (key, script) = split_once(rest, ',')
615-
.ok_or_else(|| Error::BadDescriptor("invalid taproot descriptor".to_string()))?;
616-
617-
let key = expression::Tree::from_str(key)?;
618-
if !key.args.is_empty() {
619-
return Err(Error::Unexpected("invalid taproot internal key".to_string()));
620-
}
621-
let internal_key = expression::Tree {
622-
name: key.name,
623-
parens: expression::Parens::None,
624-
children_pos: 0,
625-
args: vec![],
626-
};
627-
let tree = expression::Tree::from_slice_delim(script, expression::Deliminator::Taproot)
628-
.map_err(Error::ParseTree)?;
629-
Ok(expression::Tree {
630-
name: "tr",
631-
parens: expression::Parens::Round,
632-
children_pos: 0,
633-
args: vec![internal_key, tree],
634-
})
635-
} else {
636-
Err(Error::Unexpected("invalid taproot descriptor".to_string()))
637-
}
638-
}
639-
640-
fn split_once(inp: &str, delim: char) -> Option<(&str, &str)> {
641-
if inp.is_empty() {
642-
None
643-
} else {
644-
// find the first character that matches delim
645-
let res = inp
646-
.chars()
647-
.position(|ch| ch == delim)
648-
.map(|idx| (&inp[..idx], &inp[idx + 1..]))
649-
.unwrap_or((inp, ""));
650-
Some(res)
651-
}
652-
}
653-
654626
impl<Pk: MiniscriptKey> Liftable<Pk> for TapTree<Pk> {
655627
fn lift(&self) -> Result<Policy<Pk>, Error> {
656628
fn lift_helper<Pk: MiniscriptKey>(s: &TapTree<Pk>) -> Result<Policy<Pk>, Error> {
@@ -767,6 +739,8 @@ where
767739

768740
#[cfg(test)]
769741
mod tests {
742+
use core::str::FromStr;
743+
770744
use super::*;
771745

772746
fn descriptor() -> String {

0 commit comments

Comments
 (0)