Skip to content

Commit 4490289

Browse files
committed
Merge #732: Refactor extract_key logic in Taproot compiler
a195c81 fuzz: add compile_taproot fuzztest (Andrew Poelstra) a6ca4ce policy: clean up error types in compile_tr (Andrew Poelstra) bedb6d7 policy: unwrap a few impossible error cases in compile_tr (Andrew Poelstra) a3338fb policy: use new tapleaf_probability_iter for num_tap_leaves (Andrew Poelstra) 3522703 policy: reduce error paths in Concrete::extract_key to one (Andrew Poelstra) 9f53c01 compiler: split segwit_limits test into 2 (Andrew Poelstra) 7de96d3 policy: split up error enum for concrete and semantic (Andrew Poelstra) Pull request description: Right now our Taproot compiler does some complicated analysis to identify a key within a policy that may be extracted to use as an internal key. Specifically, if you look at the "disjunction tree" at the root of a policy, every leaf of this tree can be a tapbranch and any leaf which is just a pubkey-check and nothing else is a candidate to be the internal key. However, our logic to do this is convoluted, has many unnecessary error paths, and lots of gratuitous allocations (which ironically make the code harder, not easier, to follow). This PR smooths all that out by introducing a new iterator over the potential tapbranches of a policy (along with their probabilities). It also cleans up much of the error handling in the policy module, eliminating 3 calls to the stringly-typed `errstr` function. ACKs for top commit: sanket1729: ACK a195c81 Tree-SHA512: 62278a0d778c4e68faf69f757d2ad2fedc43e6b213df7cf9fef6d5a898c6db9b18d5c9a753d974bad26cda693c5eb170bf5e5b28d14c7196d12d64fb6f49e712
2 parents 1f9fdb8 + a195c81 commit 4490289

File tree

9 files changed

+195
-131
lines changed

9 files changed

+195
-131
lines changed

.github/workflows/cron-daily-fuzz.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
# over that limit with fuzzing because of the hour run time.
2020
fuzz_target: [
2121
compile_descriptor,
22+
compile_taproot,
2223
parse_descriptor,
2324
parse_descriptor_secret,
2425
roundtrip_concrete,

fuzz/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ regex = "1.0"
1919
name = "compile_descriptor"
2020
path = "fuzz_targets/compile_descriptor.rs"
2121

22+
[[bin]]
23+
name = "compile_taproot"
24+
path = "fuzz_targets/compile_taproot.rs"
25+
2226
[[bin]]
2327
name = "parse_descriptor"
2428
path = "fuzz_targets/parse_descriptor.rs"

fuzz/fuzz_targets/compile_taproot.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#![allow(unexpected_cfgs)]
2+
3+
use std::str::FromStr;
4+
5+
use honggfuzz::fuzz;
6+
use miniscript::{policy, Miniscript, Tap};
7+
use policy::Liftable;
8+
9+
type Script = Miniscript<String, Tap>;
10+
type Policy = policy::Concrete<String>;
11+
12+
fn do_test(data: &[u8]) {
13+
let data_str = String::from_utf8_lossy(data);
14+
if let Ok(pol) = Policy::from_str(&data_str) {
15+
// Compile
16+
if let Ok(desc) = pol.compile::<Tap>() {
17+
// Lift
18+
assert_eq!(desc.lift().unwrap().sorted(), pol.lift().unwrap().sorted());
19+
// Try to roundtrip the output of the compiler
20+
let output = desc.to_string();
21+
if let Ok(desc) = Script::from_str(&output) {
22+
let rtt = desc.to_string();
23+
assert_eq!(output.to_lowercase(), rtt.to_lowercase());
24+
} else {
25+
panic!("compiler output something unparseable: {}", output)
26+
}
27+
}
28+
}
29+
}
30+
31+
fn main() {
32+
loop {
33+
fuzz!(|data| {
34+
do_test(data);
35+
});
36+
}
37+
}

fuzz/generate-files.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ publish = false
2222
cargo-fuzz = true
2323
2424
[dependencies]
25-
honggfuzz = { version = "0.5.55", default-features = false }
25+
honggfuzz = { version = "0.5.56", default-features = false }
2626
miniscript = { path = "..", features = [ "compiler" ] }
2727
2828
regex = "1.0"

src/lib.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,9 @@ pub enum Error {
464464
/// Compiler related errors
465465
CompilerError(crate::policy::compiler::CompilerError),
466466
/// Errors related to policy
467-
PolicyError(policy::concrete::PolicyError),
467+
SemanticPolicy(policy::semantic::PolicyError),
468+
/// Errors related to policy
469+
ConcretePolicy(policy::concrete::PolicyError),
468470
/// Errors related to lifting
469471
LiftError(policy::LiftError),
470472
/// Forward script context related errors
@@ -529,7 +531,8 @@ impl fmt::Display for Error {
529531
Error::ContextError(ref e) => fmt::Display::fmt(e, f),
530532
#[cfg(feature = "compiler")]
531533
Error::CompilerError(ref e) => fmt::Display::fmt(e, f),
532-
Error::PolicyError(ref e) => fmt::Display::fmt(e, f),
534+
Error::SemanticPolicy(ref e) => fmt::Display::fmt(e, f),
535+
Error::ConcretePolicy(ref e) => fmt::Display::fmt(e, f),
533536
Error::LiftError(ref e) => fmt::Display::fmt(e, f),
534537
Error::MaxRecursiveDepthExceeded => write!(
535538
f,
@@ -595,7 +598,8 @@ impl error::Error for Error {
595598
Secp(e) => Some(e),
596599
#[cfg(feature = "compiler")]
597600
CompilerError(e) => Some(e),
598-
PolicyError(e) => Some(e),
601+
ConcretePolicy(e) => Some(e),
602+
SemanticPolicy(e) => Some(e),
599603
LiftError(e) => Some(e),
600604
ContextError(e) => Some(e),
601605
AnalysisError(e) => Some(e),
@@ -649,11 +653,6 @@ impl From<crate::policy::compiler::CompilerError> for Error {
649653
fn from(e: crate::policy::compiler::CompilerError) -> Error { Error::CompilerError(e) }
650654
}
651655

652-
#[doc(hidden)]
653-
impl From<policy::concrete::PolicyError> for Error {
654-
fn from(e: policy::concrete::PolicyError) -> Error { Error::PolicyError(e) }
655-
}
656-
657656
fn errstr(s: &str) -> Error { Error::Unexpected(s.to_owned()) }
658657

659658
/// The size of an encoding of a number in Script

src/policy/compiler.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ pub enum CompilerError {
5050
/// There may exist other miniscripts which are under these limits but the
5151
/// compiler currently does not find them.
5252
LimitsExceeded,
53+
/// In a Taproot compilation, no "unspendable key" was provided and no in-policy
54+
/// key could be used as an internal key.
55+
NoInternalKey,
56+
/// When compiling to Taproot, policy had too many Tapleaves
57+
TooManyTapleaves {
58+
/// Number of Tapleaves inferred from the policy.
59+
n: usize,
60+
/// Maximum allowed number of Tapleaves.
61+
max: usize,
62+
},
5363
///Policy related errors
5464
PolicyError(policy::concrete::PolicyError),
5565
}
@@ -66,6 +76,12 @@ impl fmt::Display for CompilerError {
6676
CompilerError::LimitsExceeded => f.write_str(
6777
"At least one spending path has exceeded the standardness or consensus limits",
6878
),
79+
CompilerError::NoInternalKey => {
80+
f.write_str("Taproot compilation had no internal key available")
81+
}
82+
CompilerError::TooManyTapleaves { n, max } => {
83+
write!(f, "Policy had too many Tapleaves (found {}, maximum {})", n, max)
84+
}
6985
CompilerError::PolicyError(ref e) => fmt::Display::fmt(e, f),
7086
}
7187
}
@@ -77,7 +93,11 @@ impl error::Error for CompilerError {
7793
use self::CompilerError::*;
7894

7995
match self {
80-
TopLevelNonSafe | ImpossibleNonMalleableCompilation | LimitsExceeded => None,
96+
TopLevelNonSafe
97+
| ImpossibleNonMalleableCompilation
98+
| LimitsExceeded
99+
| NoInternalKey
100+
| TooManyTapleaves { .. } => None,
81101
PolicyError(e) => Some(e),
82102
}
83103
}
@@ -1511,7 +1531,7 @@ mod tests {
15111531
}
15121532

15131533
#[test]
1514-
fn segwit_limits() {
1534+
fn segwit_limits_1() {
15151535
// Hit the maximum witness script size limit.
15161536
// or(thresh(52, [pubkey; 52]), thresh(52, [pubkey; 52])) results in a 3642-bytes long
15171537
// witness script with only 54 stack elements
@@ -1537,7 +1557,10 @@ mod tests {
15371557
"Compilation succeeded with a witscript size of '{:?}'",
15381558
script_size,
15391559
);
1560+
}
15401561

1562+
#[test]
1563+
fn segwit_limits_2() {
15411564
// Hit the maximum witness stack elements limit
15421565
let (keys, _) = pubkeys_and_a_sig(100);
15431566
let keys: Vec<Arc<Concrete<bitcoin::PublicKey>>> = keys

0 commit comments

Comments
 (0)