Skip to content

Commit 6a06a11

Browse files
committed
Allow parsing expr fragments from string/script
Standard/Sane scripts refer to miniscripts that are described in the spec. Insane scripts refer to miniscript that are valid but can contain timelock mixes, not all paths would require signature, etc. Experimental features with ext flag current only allow parsing hash160 expr_raw_pkh scripts from string and bitcoin::Script. All regular APIs fail while parsing scripts with raw_pkh. The interpreter module relies on partial information from the blockchian without the descriptor. All the parsing in the interpreter module in done with all exprimental features. Similarly, our psbt finalizer also uses the expr features as it relies on inferring the descriptor from witness script and then puts things into the correct place.
1 parent 94d5eba commit 6a06a11

File tree

8 files changed

+245
-26
lines changed

8 files changed

+245
-26
lines changed

src/interpreter/inner.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use bitcoin::util::taproot::{ControlBlock, TAPROOT_ANNEX_PREFIX};
2020
use super::{stack, BitcoinKey, Error, Stack};
2121
use crate::miniscript::context::{NoChecks, ScriptContext, SigType};
2222
use crate::prelude::*;
23-
use crate::{BareCtx, Legacy, Miniscript, Segwitv0, Tap, ToPublicKey, Translator};
23+
use crate::{BareCtx, ExtParams, Legacy, Miniscript, Segwitv0, Tap, ToPublicKey, Translator};
2424

2525
/// Attempts to parse a slice as a Bitcoin public key, checking compressedness
2626
/// if asked to, but otherwise dropping it
@@ -54,9 +54,11 @@ fn script_from_stack_elem<Ctx: ScriptContext>(
5454
elem: &stack::Element<'_>,
5555
) -> Result<Miniscript<Ctx::Key, Ctx>, Error> {
5656
match *elem {
57-
stack::Element::Push(sl) => {
58-
Miniscript::parse_insane(&bitcoin::Script::from(sl.to_owned())).map_err(Error::from)
59-
}
57+
stack::Element::Push(sl) => Miniscript::parse_with_ext(
58+
&bitcoin::Script::from(sl.to_owned()),
59+
&ExtParams::allow_all(),
60+
)
61+
.map_err(Error::from),
6062
stack::Element::Satisfied => {
6163
Miniscript::from_ast(crate::Terminal::True).map_err(Error::from)
6264
}
@@ -345,7 +347,10 @@ pub(super) fn from_txdata<'txin>(
345347
} else {
346348
if wit_stack.is_empty() {
347349
// Bare script parsed in BareCtx
348-
let miniscript = Miniscript::<bitcoin::PublicKey, BareCtx>::parse_insane(spk)?;
350+
let miniscript = Miniscript::<bitcoin::PublicKey, BareCtx>::parse_with_ext(
351+
spk,
352+
&ExtParams::allow_all(),
353+
)?;
349354
let miniscript = miniscript.to_no_checks_ms();
350355
Ok((
351356
Inner::Script(miniscript, ScriptType::Bare),
@@ -416,6 +421,7 @@ mod tests {
416421
use bitcoin::{self, Script};
417422

418423
use super::*;
424+
use crate::miniscript::analyzable::ExtParams;
419425

420426
struct KeyTestData {
421427
pk_spk: bitcoin::Script,
@@ -754,11 +760,13 @@ mod tests {
754760
}
755761

756762
fn ms_inner_script(ms: &str) -> (Miniscript<BitcoinKey, NoChecks>, bitcoin::Script) {
757-
let ms = Miniscript::<bitcoin::PublicKey, Segwitv0>::from_str_insane(ms).unwrap();
763+
let ms = Miniscript::<bitcoin::PublicKey, Segwitv0>::from_str_ext(ms, &ExtParams::insane())
764+
.unwrap();
758765
let spk = ms.encode();
759766
let miniscript = ms.to_no_checks_ms();
760767
(miniscript, spk)
761768
}
769+
762770
#[test]
763771
fn script_bare() {
764772
let preimage = b"12345678----____12345678----____";

src/interpreter/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,7 @@ mod tests {
10481048

10491049
use super::inner::ToNoChecks;
10501050
use super::*;
1051+
use crate::miniscript::analyzable::ExtParams;
10511052
use crate::miniscript::context::NoChecks;
10521053
use crate::{Miniscript, ToPublicKey};
10531054

@@ -1608,14 +1609,15 @@ mod tests {
16081609
// By design there is no support for parse a miniscript with BitcoinKey
16091610
// because it does not implement FromStr
16101611
fn no_checks_ms(ms: &str) -> Miniscript<BitcoinKey, NoChecks> {
1612+
// Parsing should allow raw hashes in the interpreter
16111613
let elem: Miniscript<bitcoin::PublicKey, NoChecks> =
1612-
Miniscript::from_str_insane(ms).unwrap();
1614+
Miniscript::from_str_ext(ms, &ExtParams::allow_all()).unwrap();
16131615
elem.to_no_checks_ms()
16141616
}
16151617

16161618
fn x_only_no_checks_ms(ms: &str) -> Miniscript<BitcoinKey, NoChecks> {
16171619
let elem: Miniscript<bitcoin::XOnlyPublicKey, NoChecks> =
1618-
Miniscript::from_str_insane(ms).unwrap();
1620+
Miniscript::from_str_ext(ms, &ExtParams::allow_all()).unwrap();
16191621
elem.to_no_checks_ms()
16201622
}
16211623
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ use bitcoin::hashes::{hash160, ripemd160, sha256, Hash};
141141

142142
pub use crate::descriptor::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey};
143143
pub use crate::interpreter::Interpreter;
144+
pub use crate::miniscript::analyzable::{AnalysisError, ExtParams};
144145
pub use crate::miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0, SigType, Tap};
145146
pub use crate::miniscript::decode::Terminal;
146147
pub use crate::miniscript::satisfy::{Preimage32, Satisfier};

src/macros.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
/// `ms_str!("c:or_i(pk({}),pk({}))", pk1, pk2)`
77
#[cfg(test)]
88
macro_rules! ms_str {
9-
($($arg:tt)*) => (Miniscript::from_str_insane(&format!($($arg)*)).unwrap())
9+
($($arg:tt)*) => (Miniscript::from_str_ext(&format!($($arg)*), &$crate::ExtParams::allow_all()).unwrap())
1010
}
1111

1212
/// Allows tests to create a concrete policy directly from string as

src/miniscript/analyzable.rs

Lines changed: 144 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,117 @@ use core::fmt;
2222
use std::error;
2323

2424
use crate::prelude::*;
25-
use crate::{Miniscript, MiniscriptKey, ScriptContext};
25+
use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal};
26+
27+
/// Params for parsing miniscripts that either non-sane or non-specified(experimental) in the spec.
28+
/// Used as a parameter [`Miniscript::from_str_ext`] and [`Miniscript::parse_with_ext`].
29+
///
30+
/// This allows parsing miniscripts if
31+
/// 1. It is unsafe(does not require a digital signature to spend it)
32+
/// 2. It contains a unspendable path because of either
33+
/// a. Resource limitations
34+
/// b. Timelock Mixing
35+
/// 3. The script is malleable and thereby some of satisfaction weight
36+
/// guarantees are not satisfied.
37+
/// 4. It has repeated public keys
38+
/// 5. raw pkh fragments without the pk. This could be obtained when parsing miniscript from script
39+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
40+
pub struct ExtParams {
41+
/// Allow parsing of non-safe miniscripts
42+
pub top_unsafe: bool,
43+
/// Allow parsing of miniscripts with unspendable paths
44+
pub resource_limitations: bool,
45+
/// Allow parsing of miniscripts with timelock mixing
46+
pub timelock_mixing: bool,
47+
/// Allow parsing of malleable miniscripts
48+
pub malleability: bool,
49+
/// Allow parsing of miniscripts with repeated public keys
50+
pub repeated_pk: bool,
51+
/// Allow parsing of miniscripts with raw pkh fragments without the pk.
52+
/// This could be obtained when parsing miniscript from script
53+
pub raw_pkh: bool,
54+
}
55+
56+
impl ExtParams {
57+
/// Create a new ExtParams that with all the sanity rules
58+
pub fn new() -> ExtParams {
59+
ExtParams {
60+
top_unsafe: false,
61+
resource_limitations: false,
62+
timelock_mixing: false,
63+
malleability: false,
64+
repeated_pk: false,
65+
raw_pkh: false,
66+
}
67+
}
68+
69+
/// Create a new ExtParams that allows all the sanity rules
70+
pub fn sane() -> ExtParams {
71+
ExtParams::new()
72+
}
73+
74+
/// Create a new ExtParams that insanity rules
75+
/// This enables parsing well specified but "insane" miniscripts.
76+
/// Refer to the [`ExtParams`] documentation for more details on "insane" miniscripts.
77+
pub fn insane() -> ExtParams {
78+
ExtParams {
79+
top_unsafe: true,
80+
resource_limitations: true,
81+
timelock_mixing: true,
82+
malleability: true,
83+
repeated_pk: true,
84+
raw_pkh: false,
85+
}
86+
}
87+
88+
/// Enable all non-sane rules and experimental rules
89+
pub fn allow_all() -> ExtParams {
90+
ExtParams {
91+
top_unsafe: true,
92+
resource_limitations: true,
93+
timelock_mixing: true,
94+
malleability: true,
95+
repeated_pk: true,
96+
raw_pkh: true,
97+
}
98+
}
99+
100+
/// Builder that allows non-safe miniscripts.
101+
pub fn top_unsafe(mut self) -> ExtParams {
102+
self.top_unsafe = true;
103+
self
104+
}
105+
106+
/// Builder that allows miniscripts with exceed resource limitations.
107+
pub fn exceed_resource_limitations(mut self) -> ExtParams {
108+
self.resource_limitations = true;
109+
self
110+
}
111+
112+
/// Builder that allows miniscripts with timelock mixing.
113+
pub fn timelock_mixing(mut self) -> ExtParams {
114+
self.timelock_mixing = true;
115+
self
116+
}
117+
118+
/// Builder that allows malleable miniscripts.
119+
pub fn malleability(mut self) -> ExtParams {
120+
self.malleability = true;
121+
self
122+
}
123+
124+
/// Builder that allows miniscripts with repeated public keys.
125+
pub fn repeated_pk(mut self) -> ExtParams {
126+
self.repeated_pk = true;
127+
self
128+
}
129+
130+
/// Builder that allows miniscripts with raw pkh fragments.
131+
pub fn raw_pkh(mut self) -> ExtParams {
132+
self.raw_pkh = true;
133+
self
134+
}
135+
}
26136

27137
/// Possible reasons Miniscript guarantees can fail
28138
/// We currently mark Miniscript as Non-Analyzable if
@@ -45,6 +155,8 @@ pub enum AnalysisError {
45155
HeightTimelockCombination,
46156
/// Malleable script
47157
Malleable,
158+
/// Contains partial descriptor raw pkh
159+
ContainsRawPkh,
48160
}
49161

50162
impl fmt::Display for AnalysisError {
@@ -62,7 +174,8 @@ impl fmt::Display for AnalysisError {
62174
AnalysisError::HeightTimelockCombination => {
63175
f.write_str("Contains a combination of heightlock and timelock")
64176
}
65-
AnalysisError::Malleable => f.write_str("Miniscript is malleable")
177+
AnalysisError::Malleable => f.write_str("Miniscript is malleable"),
178+
AnalysisError::ContainsRawPkh => f.write_str("Miniscript contains raw pkh"),
66179
}
67180
}
68181
}
@@ -77,7 +190,8 @@ impl error::Error for AnalysisError {
77190
| RepeatedPubkeys
78191
| BranchExceedResouceLimits
79192
| HeightTimelockCombination
80-
| Malleable => None,
193+
| Malleable
194+
| ContainsRawPkh => None,
81195
}
82196
}
83197
}
@@ -116,6 +230,14 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
116230
unique_pkhs_len != all_pkhs_len
117231
}
118232

233+
/// Whether the given miniscript contains a raw pkh fragment
234+
pub fn contains_raw_pkh(&self) -> bool {
235+
self.iter().any(|ms| match ms.node {
236+
Terminal::RawPkH(_) => true,
237+
_ => false,
238+
})
239+
}
240+
119241
/// Check whether the underlying Miniscript is safe under the current context
120242
/// Lifting these polices would create a semantic representation that does
121243
/// not represent the underlying semantics when miniscript is spent.
@@ -140,4 +262,23 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
140262
Ok(())
141263
}
142264
}
265+
266+
/// Check whether the miniscript follows the given Extra policy [`ExtParams`]
267+
pub fn ext_check(&self, ext: &ExtParams) -> Result<(), AnalysisError> {
268+
if !ext.top_unsafe && !self.requires_sig() {
269+
Err(AnalysisError::SiglessBranch)
270+
} else if !ext.malleability && !self.is_non_malleable() {
271+
Err(AnalysisError::Malleable)
272+
} else if !ext.resource_limitations && !self.within_resource_limits() {
273+
Err(AnalysisError::BranchExceedResouceLimits)
274+
} else if !ext.repeated_pk && self.has_repeated_keys() {
275+
Err(AnalysisError::RepeatedPubkeys)
276+
} else if !ext.timelock_mixing && self.has_mixed_timelocks() {
277+
Err(AnalysisError::HeightTimelockCombination)
278+
} else if !ext.raw_pkh && self.contains_raw_pkh() {
279+
Err(AnalysisError::ContainsRawPkh)
280+
} else {
281+
Ok(())
282+
}
283+
}
143284
}

src/miniscript/astelem.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use core::fmt;
2323
use core::str::FromStr;
2424

2525
use bitcoin::blockdata::{opcodes, script};
26+
use bitcoin::hashes::hash160;
2627
use bitcoin::{LockTime, Sequence};
2728
use sync::Arc;
2829

@@ -457,6 +458,9 @@ impl_from_tree!(
457458
}
458459
}
459460
let mut unwrapped = match (frag_name, top.args.len()) {
461+
("expr_raw_pkh", 1) => expression::terminal(&top.args[0], |x| {
462+
hash160::Hash::from_str(x).map(Terminal::RawPkH)
463+
}),
460464
("pk_k", 1) => {
461465
expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkK))
462466
}

0 commit comments

Comments
 (0)