Skip to content

Commit d5b764a

Browse files
taproot descriptor parsing done
1 parent 7d33643 commit d5b764a

File tree

5 files changed

+364
-6
lines changed

5 files changed

+364
-6
lines changed

src/descriptor/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ 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};
@@ -53,6 +54,7 @@ pub use self::sortedmulti::SortedMultiVec;
5354

5455
mod checksum;
5556
mod key;
57+
5658
pub use self::key::{
5759
ConversionError, DescriptorKeyParseError, DescriptorPublicKey, DescriptorSecretKey,
5860
DescriptorSinglePriv, DescriptorSinglePub, DescriptorXKey, InnerXKey, Wildcard,

src/descriptor/tr.rs

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

0 commit comments

Comments
 (0)