Skip to content

Commit 6a717bf

Browse files
SarcasticNastiksanket1729
authored andcommitted
Implement Taproot descriptor tree parsing
fixups to Tr descriptor code from SarcasticNastik
1 parent b8d04ac commit 6a717bf

File tree

4 files changed

+366
-4
lines changed

4 files changed

+366
-4
lines changed

src/descriptor/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,17 @@ mod bare;
4646
mod segwitv0;
4747
mod sh;
4848
mod sortedmulti;
49+
mod tr;
4950
// Descriptor Exports
5051
pub use self::bare::{Bare, Pkh};
5152
pub use self::segwitv0::{Wpkh, Wsh, WshInner};
5253
pub use self::sh::{Sh, ShInner};
5354
pub use self::sortedmulti::SortedMultiVec;
55+
pub use self::tr::{TapTree, Tr};
5456

5557
mod checksum;
5658
mod key;
59+
5760
pub use self::key::{
5861
ConversionError, DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey,
5962
DescriptorSinglePriv, DescriptorSinglePub, DescriptorXKey, InnerXKey, Wildcard,

src/descriptor/tr.rs

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
// Tapscript
2+
3+
use super::checksum::{desc_checksum, verify_checksum};
4+
use errstr;
5+
use expression::{self, FromTree, Tree};
6+
use miniscript::{limits::TAPROOT_MAX_NODE_COUNT, Miniscript};
7+
use std::cmp::max;
8+
use std::sync::Arc;
9+
use std::{fmt, str::FromStr};
10+
use Tap;
11+
use {Error, MiniscriptKey};
12+
13+
/// A Taproot Tree representation.
14+
// Hidden leaves are not yet supported in descriptor spec. Conceptually, it should
15+
// be simple to integrate those here, but it is best to wait on core for the exact syntax.
16+
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
17+
pub enum TapTree<Pk: MiniscriptKey> {
18+
/// A taproot tree structure
19+
Tree(Arc<TapTree<Pk>>, Arc<TapTree<Pk>>),
20+
/// A taproot leaf denoting a spending condition
21+
Leaf(Arc<Miniscript<Pk, Tap>>),
22+
}
23+
24+
/// A taproot descriptor
25+
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
26+
pub struct Tr<Pk: MiniscriptKey> {
27+
/// A taproot internal key
28+
internal_key: Pk,
29+
/// Optional Taproot Tree with spending conditions
30+
tree: Option<TapTree<Pk>>,
31+
}
32+
33+
impl<Pk: MiniscriptKey> TapTree<Pk> {
34+
// Helper function to compute height
35+
// TODO: Instead of computing this every time we add a new leaf, we should
36+
// add height as a separate field in taptree
37+
fn taptree_height(&self) -> usize {
38+
match *self {
39+
TapTree::Tree(ref left_tree, ref right_tree) => {
40+
1 + max(left_tree.taptree_height(), right_tree.taptree_height())
41+
}
42+
TapTree::Leaf(_) => 1,
43+
}
44+
}
45+
}
46+
47+
impl<Pk: MiniscriptKey> fmt::Display for TapTree<Pk> {
48+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49+
match self {
50+
TapTree::Tree(ref left, ref right) => write!(f, "{{{},{}}}", *left, *right),
51+
TapTree::Leaf(ref script) => write!(f, "{}", *script),
52+
}
53+
}
54+
}
55+
56+
impl<Pk: MiniscriptKey> Tr<Pk> {
57+
/// Create a new [`Tr`] descriptor from internal key and [`TapTree`]
58+
pub fn new(internal_key: Pk, tree: Option<TapTree<Pk>>) -> Result<Self, Error> {
59+
let nodes = tree.as_ref().map(|t| t.taptree_height()).unwrap_or(0);
60+
61+
if nodes <= TAPROOT_MAX_NODE_COUNT {
62+
Ok(Self { internal_key, tree })
63+
} else {
64+
Err(Error::MaxRecursiveDepthExceeded)
65+
}
66+
}
67+
68+
fn to_string_no_checksum(&self) -> String {
69+
let key = &self.internal_key;
70+
match self.tree {
71+
Some(ref s) => format!("tr({},{})", key, s),
72+
None => format!("tr({})", key),
73+
}
74+
}
75+
76+
/// Obtain the internal key of [`Tr`] descriptor
77+
pub fn internal_key(&self) -> &Pk {
78+
&self.internal_key
79+
}
80+
81+
/// Obtain the [`TapTree`] of the [`Tr`] descriptor
82+
pub fn taptree(&self) -> &Option<TapTree<Pk>> {
83+
&self.tree
84+
}
85+
}
86+
87+
impl<Pk: MiniscriptKey> FromTree for Tr<Pk>
88+
where
89+
Pk: MiniscriptKey + FromStr,
90+
Pk::Hash: FromStr,
91+
<Pk as FromStr>::Err: ToString,
92+
<<Pk as MiniscriptKey>::Hash as FromStr>::Err: ToString,
93+
{
94+
fn from_tree(top: &Tree) -> Result<Self, Error> {
95+
// Helper function to parse taproot script path
96+
fn parse_tr_script_spend<Pk: MiniscriptKey>(tree: &Tree) -> Result<TapTree<Pk>, Error>
97+
where
98+
Pk: MiniscriptKey + FromStr,
99+
Pk::Hash: FromStr,
100+
<Pk as FromStr>::Err: ToString,
101+
<<Pk as MiniscriptKey>::Hash as FromStr>::Err: ToString,
102+
{
103+
match tree {
104+
Tree { name, args } if name.len() > 0 && args.len() == 0 => {
105+
let script = Miniscript::<Pk, Tap>::from_str(name)?;
106+
Ok(TapTree::Leaf(Arc::new(script)))
107+
}
108+
Tree { name, args } if name.len() == 0 && args.len() == 2 => {
109+
let left = parse_tr_script_spend(&args[0])?;
110+
let right = parse_tr_script_spend(&args[1])?;
111+
Ok(TapTree::Tree(Arc::new(left), Arc::new(right)))
112+
}
113+
_ => {
114+
return Err(Error::Unexpected(
115+
"unknown format for script spending paths while parsing taproot descriptor"
116+
.to_string(),
117+
));
118+
}
119+
}
120+
}
121+
122+
if top.name == "tr" {
123+
match top.args.len() {
124+
1 => {
125+
let key = &top.args[0];
126+
if key.args.len() > 0 {
127+
return Err(Error::Unexpected(format!(
128+
"#{} script associated with `key-path` while parsing taproot descriptor",
129+
key.args.len()
130+
)));
131+
}
132+
Ok(Tr {
133+
internal_key: expression::terminal(key, Pk::from_str)?,
134+
tree: None,
135+
})
136+
}
137+
2 => {
138+
let ref key = top.args[0];
139+
if key.args.len() > 0 {
140+
return Err(Error::Unexpected(format!(
141+
"#{} script associated with `key-path` while parsing taproot descriptor",
142+
key.args.len()
143+
)));
144+
}
145+
let ref tree = top.args[1];
146+
let ret = parse_tr_script_spend(tree)?;
147+
Ok(Tr {
148+
internal_key: expression::terminal(key, Pk::from_str)?,
149+
tree: Some(ret),
150+
})
151+
}
152+
_ => {
153+
return Err(Error::Unexpected(format!(
154+
"{}[#{} args] while parsing taproot descriptor",
155+
top.name,
156+
top.args.len()
157+
)));
158+
}
159+
}
160+
} else {
161+
return Err(Error::Unexpected(format!(
162+
"{}[#{} args] while parsing taproot descriptor",
163+
top.name,
164+
top.args.len()
165+
)));
166+
}
167+
}
168+
}
169+
170+
impl<Pk: MiniscriptKey> FromStr for Tr<Pk>
171+
where
172+
Pk: MiniscriptKey + FromStr,
173+
Pk::Hash: FromStr,
174+
<Pk as FromStr>::Err: ToString,
175+
<<Pk as MiniscriptKey>::Hash as FromStr>::Err: ToString,
176+
{
177+
type Err = Error;
178+
179+
fn from_str(s: &str) -> Result<Self, Self::Err> {
180+
let desc_str = verify_checksum(s)?;
181+
let top = parse_tr_tree(desc_str)?;
182+
Self::from_tree(&top)
183+
}
184+
}
185+
186+
impl<Pk: MiniscriptKey> fmt::Display for Tr<Pk> {
187+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
188+
let desc = self.to_string_no_checksum();
189+
let checksum = desc_checksum(&desc).map_err(|_| fmt::Error)?;
190+
write!(f, "{}#{}", &desc, &checksum)
191+
}
192+
}
193+
194+
// Helper function to parse string into miniscript tree form
195+
fn parse_tr_tree(s: &str) -> Result<Tree, Error> {
196+
for ch in s.bytes() {
197+
if ch > 0x7f {
198+
return Err(Error::Unprintable(ch));
199+
}
200+
}
201+
202+
let ret = if s.len() > 3 && &s[..3] == "tr(" && s.as_bytes()[s.len() - 1] == b')' {
203+
let rest = &s[3..s.len() - 1];
204+
if !rest.contains(',') {
205+
let internal_key = Tree {
206+
name: rest,
207+
args: vec![],
208+
};
209+
return Ok(Tree {
210+
name: "tr",
211+
args: vec![internal_key],
212+
});
213+
}
214+
// use str::split_once() method to refactor this when compiler version bumps up
215+
let (key, script) = split_once(rest, ',')
216+
.ok_or_else(|| Error::BadDescriptor("invalid taproot descriptor".to_string()))?;
217+
218+
let internal_key = Tree {
219+
name: key,
220+
args: vec![],
221+
};
222+
if script.is_empty() {
223+
return Ok(Tree {
224+
name: "tr",
225+
args: vec![internal_key],
226+
});
227+
}
228+
let (tree, rest) = expression::Tree::from_slice_helper_curly(script, 1)?;
229+
if rest.is_empty() {
230+
Ok(Tree {
231+
name: "tr",
232+
args: vec![internal_key, tree],
233+
})
234+
} else {
235+
Err(errstr(rest))
236+
}
237+
} else {
238+
Err(Error::Unexpected("invalid taproot descriptor".to_string()))
239+
};
240+
return ret;
241+
}
242+
243+
fn split_once(inp: &str, delim: char) -> Option<(&str, &str)> {
244+
let ret = if inp.len() == 0 {
245+
None
246+
} else {
247+
let mut found = inp.len();
248+
for (idx, ch) in inp.chars().enumerate() {
249+
if ch == delim {
250+
found = idx;
251+
break;
252+
}
253+
}
254+
// No comma or trailing comma found
255+
if found >= inp.len() - 1 {
256+
Some((&inp[..], ""))
257+
} else {
258+
Some((&inp[..found], &inp[found + 1..]))
259+
}
260+
};
261+
return ret;
262+
}

0 commit comments

Comments
 (0)