Skip to content

Commit db41cb8

Browse files
SarcasticNastiksanket1729
authored andcommitted
taproot descriptor parsing done
1 parent 6dc83da commit db41cb8

File tree

4 files changed

+372
-4
lines changed

4 files changed

+372
-4
lines changed

src/descriptor/mod.rs

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

5456
mod checksum;
5557
mod key;
58+
5659
pub use self::key::{
5760
ConversionError, DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey,
5861
DescriptorSinglePriv, DescriptorSinglePub, DescriptorXKey, InnerXKey, Wildcard,

src/descriptor/tr.rs

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

0 commit comments

Comments
 (0)