Skip to content

Commit fd28e86

Browse files
committed
Merge #775: Rewrite expression parser to be non-recursive
75f400f error: remove two more variants which were redundant with threshold errors (Andrew Poelstra) cdac32e error: remove 3 now-unused variants from the global Error enum (Andrew Poelstra) ccb9c70 expression: drop Error::ParseTree variant (Andrew Poelstra) 0b6fcf4 expression: unify Taproot and non-Taproot parsing (Andrew Poelstra) 360ed59 Introduce `error` module with single `ParseError` enum in it. (Andrew Poelstra) 179dc87 expression: move some ad-hoc validation from descriptor module (Andrew Poelstra) f5dcafd expression: start tracking parenthesis-type and string position (Andrew Poelstra) 853a01a expression: rewrite parser to be non-recursive (Andrew Poelstra) 20066bd checksum: use array::from_fn (Andrew Poelstra) df75cbc update fuzz/README.md to use hex crate (Andrew Poelstra) Pull request description: This PR does several things but the only big commit is "unify Taproot and non-Taproot parsing" which replaces the ad-hoc logic in `Tr::from_str` to handle Taproot parsing with unified expression-parsing logic. In addition to this, we also: * rewrite the expression parser to be non-recursive, which is a reduction in LOC thanks to our prepatory work * introduces an `error` module whose types will eventually replace the top-level `Error` enum * relatedly, drops several error variants including the stringly-typed `BadDescriptor` (it does not get rid of `Unexpected` which is *also* a stringly-typed catch-all error, but we will..) ACKs for top commit: sanket1729: ACK 75f400f Tree-SHA512: 8c535f420f39cf565900adb938f7a30c24cfc93fe30f09268f1b2d70fff5d7dc1f06ca0a3972c118f4e6889b107040feeab849b22e74bd8355f816a9dd3d4448
2 parents 76b27da + 75f400f commit fd28e86

File tree

18 files changed

+534
-429
lines changed

18 files changed

+534
-429
lines changed

fuzz/README.md

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -118,28 +118,12 @@ as follows:
118118
```
119119
#[cfg(test)]
120120
mod tests {
121-
fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) {
122-
let mut b = 0;
123-
for (idx, c) in hex.as_bytes().iter().enumerate() {
124-
b <<= 4;
125-
match *c {
126-
b'A'...b'F' => b |= c - b'A' + 10,
127-
b'a'...b'f' => b |= c - b'a' + 10,
128-
b'0'...b'9' => b |= c - b'0',
129-
_ => panic!("Bad hex"),
130-
}
131-
if (idx & 1) == 1 {
132-
out.push(b);
133-
b = 0;
134-
}
135-
}
136-
}
121+
use miniscript::bitcoin::hex::FromHex;
137122
138123
#[test]
139124
fn duplicate_crash() {
140-
let mut a = Vec::new();
141-
extend_vec_from_hex("048531e80700ae6400670000af5168", &mut a);
142-
super::do_test(&a);
125+
let v = Vec::from_hex("abcd").unwrap();
126+
super::do_test(&v);
143127
}
144128
}
145129
```

src/descriptor/bare.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -370,15 +370,11 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for Pkh<Pk> {
370370

371371
impl<Pk: FromStrKey> FromTree for Pkh<Pk> {
372372
fn from_tree(top: &expression::Tree) -> Result<Self, Error> {
373-
if top.name == "pkh" && top.args.len() == 1 {
374-
Ok(Pkh::new(expression::terminal(&top.args[0], |pk| Pk::from_str(pk))?)?)
375-
} else {
376-
Err(Error::Unexpected(format!(
377-
"{}({} args) while parsing pkh descriptor",
378-
top.name,
379-
top.args.len(),
380-
)))
381-
}
373+
let top = top
374+
.verify_toplevel("pkh", 1..=1)
375+
.map_err(From::from)
376+
.map_err(Error::Parse)?;
377+
Ok(Pkh::new(expression::terminal(top, |pk| Pk::from_str(pk))?)?)
382378
}
383379
}
384380

src/descriptor/checksum.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
//! [BIP-380]: <https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki>
99
1010
use core::convert::TryFrom;
11-
use core::fmt;
1211
use core::iter::FromIterator;
12+
use core::{array, fmt};
1313

1414
use bech32::primitives::checksum::PackedFe32;
1515
use bech32::{Checksum, Fe32};
@@ -115,10 +115,10 @@ pub fn verify_checksum(s: &str) -> Result<&str, Error> {
115115
eng.input_unchecked(s[..last_hash_pos].as_bytes());
116116

117117
let expected = eng.checksum_chars();
118-
let mut actual = ['_'; CHECKSUM_LENGTH];
119-
for (act, ch) in actual.iter_mut().zip(checksum_str.chars()) {
120-
*act = ch;
121-
}
118+
119+
let mut iter = checksum_str.chars();
120+
let actual: [char; CHECKSUM_LENGTH] =
121+
array::from_fn(|_| iter.next().expect("length checked above"));
122122

123123
if expected != actual {
124124
return Err(Error::InvalidChecksum { actual, expected });

src/descriptor/mod.rs

Lines changed: 30 additions & 13 deletions
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

@@ -1102,7 +1103,7 @@ mod tests {
11021103
StdDescriptor::from_str("sh(sortedmulti)")
11031104
.unwrap_err()
11041105
.to_string(),
1105-
"expected threshold, found terminal",
1106+
"sortedmulti must have at least 1 children, but found 0"
11061107
); //issue 202
11071108
assert_eq!(
11081109
StdDescriptor::from_str(&format!("sh(sortedmulti(2,{}))", &TEST_PK[3..69]))
@@ -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");
@@ -1836,9 +1852,10 @@ mod tests {
18361852
// https://github.com/bitcoin/bitcoin/blob/7ae86b3c6845873ca96650fc69beb4ae5285c801/src/test/descriptor_tests.cpp#L355-L360
18371853
macro_rules! check_invalid_checksum {
18381854
($secp: ident,$($desc: expr),*) => {
1855+
use crate::{ParseError, ParseTreeError};
18391856
$(
18401857
match Descriptor::parse_descriptor($secp, $desc) {
1841-
Err(Error::ParseTree(crate::ParseTreeError::Checksum(_))) => {},
1858+
Err(Error::Parse(ParseError::Tree(ParseTreeError::Checksum(_)))) => {},
18421859
Err(e) => panic!("Expected bad checksum for {}, got '{}'", $desc, e),
18431860
_ => panic!("Invalid checksum treated as valid: {}", $desc),
18441861
};

src/descriptor/segwitv0.rs

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -248,21 +248,17 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for Wsh<Pk> {
248248

249249
impl<Pk: FromStrKey> crate::expression::FromTree for Wsh<Pk> {
250250
fn from_tree(top: &expression::Tree) -> Result<Self, Error> {
251-
if top.name == "wsh" && top.args.len() == 1 {
252-
let top = &top.args[0];
253-
if top.name == "sortedmulti" {
254-
return Ok(Wsh { inner: WshInner::SortedMulti(SortedMultiVec::from_tree(top)?) });
255-
}
256-
let sub = Miniscript::from_tree(top)?;
257-
Segwitv0::top_level_checks(&sub)?;
258-
Ok(Wsh { inner: WshInner::Ms(sub) })
259-
} else {
260-
Err(Error::Unexpected(format!(
261-
"{}({} args) while parsing wsh descriptor",
262-
top.name,
263-
top.args.len(),
264-
)))
251+
let top = top
252+
.verify_toplevel("wsh", 1..=1)
253+
.map_err(From::from)
254+
.map_err(Error::Parse)?;
255+
256+
if top.name == "sortedmulti" {
257+
return Ok(Wsh { inner: WshInner::SortedMulti(SortedMultiVec::from_tree(top)?) });
265258
}
259+
let sub = Miniscript::from_tree(top)?;
260+
Segwitv0::top_level_checks(&sub)?;
261+
Ok(Wsh { inner: WshInner::Ms(sub) })
266262
}
267263
}
268264

@@ -488,15 +484,11 @@ impl<Pk: MiniscriptKey> Liftable<Pk> for Wpkh<Pk> {
488484

489485
impl<Pk: FromStrKey> crate::expression::FromTree for Wpkh<Pk> {
490486
fn from_tree(top: &expression::Tree) -> Result<Self, Error> {
491-
if top.name == "wpkh" && top.args.len() == 1 {
492-
Ok(Wpkh::new(expression::terminal(&top.args[0], |pk| Pk::from_str(pk))?)?)
493-
} else {
494-
Err(Error::Unexpected(format!(
495-
"{}({} args) while parsing wpkh descriptor",
496-
top.name,
497-
top.args.len(),
498-
)))
499-
}
487+
let top = top
488+
.verify_toplevel("wpkh", 1..=1)
489+
.map_err(From::from)
490+
.map_err(Error::Parse)?;
491+
Ok(Wpkh::new(expression::terminal(top, |pk| Pk::from_str(pk))?)?)
500492
}
501493
}
502494

src/descriptor/sh.rs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -82,26 +82,22 @@ impl<Pk: MiniscriptKey> fmt::Display for Sh<Pk> {
8282

8383
impl<Pk: FromStrKey> crate::expression::FromTree for Sh<Pk> {
8484
fn from_tree(top: &expression::Tree) -> Result<Self, Error> {
85-
if top.name == "sh" && top.args.len() == 1 {
86-
let top = &top.args[0];
87-
let inner = match top.name {
88-
"wsh" => ShInner::Wsh(Wsh::from_tree(top)?),
89-
"wpkh" => ShInner::Wpkh(Wpkh::from_tree(top)?),
90-
"sortedmulti" => ShInner::SortedMulti(SortedMultiVec::from_tree(top)?),
91-
_ => {
92-
let sub = Miniscript::from_tree(top)?;
93-
Legacy::top_level_checks(&sub)?;
94-
ShInner::Ms(sub)
95-
}
96-
};
97-
Ok(Sh { inner })
98-
} else {
99-
Err(Error::Unexpected(format!(
100-
"{}({} args) while parsing sh descriptor",
101-
top.name,
102-
top.args.len(),
103-
)))
104-
}
85+
let top = top
86+
.verify_toplevel("sh", 1..=1)
87+
.map_err(From::from)
88+
.map_err(Error::Parse)?;
89+
90+
let inner = match top.name {
91+
"wsh" => ShInner::Wsh(Wsh::from_tree(top)?),
92+
"wpkh" => ShInner::Wpkh(Wpkh::from_tree(top)?),
93+
"sortedmulti" => ShInner::SortedMulti(SortedMultiVec::from_tree(top)?),
94+
_ => {
95+
let sub = Miniscript::from_tree(top)?;
96+
Legacy::top_level_checks(&sub)?;
97+
ShInner::Ms(sub)
98+
}
99+
};
100+
Ok(Sh { inner })
105101
}
106102
}
107103

src/descriptor/sortedmulti.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
6464
Pk: FromStr,
6565
<Pk as FromStr>::Err: fmt::Display,
6666
{
67+
tree.verify_toplevel("sortedmulti", 1..)
68+
.map_err(From::from)
69+
.map_err(Error::Parse)?;
70+
6771
let ret = Self {
6872
inner: tree
6973
.to_null_threshold()

0 commit comments

Comments
 (0)