Skip to content

Commit ace6dbf

Browse files
committed
miniscript: replace from_str_ext with from_str_with_validation_params
This moves us toward being able to drop ExtParams. It also eliminates the NonTopLevel error variant, which is now ValidationError::NonBase and controllable via ValidationParams::allow_non_b. Also tweaks the "hack" in Tr::from_tree to be more sensible, though it's still a hack. A later commit will change the FromTree trait to incorporate validation and then the inconsistency this hack reflects will go away. Leaves the from_str_insane method, with cleaned up doccomment. Probably we should deprecate this since I don't like the term "insane", but we can do that in a followup if that's what we want to do.
1 parent d559f26 commit ace6dbf

7 files changed

Lines changed: 48 additions & 55 deletions

File tree

src/descriptor/tr/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,9 +383,9 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Tr<Pk> {
383383
} else {
384384
let script = Miniscript::from_tree(node)?;
385385
// FIXME hack for https://github.com/rust-bitcoin/rust-miniscript/issues/734
386-
if script.ty.corr.base != crate::miniscript::types::Base::B {
387-
return Err(Error::NonTopLevel(format!("{:?}", script)));
388-
};
386+
script
387+
.validate(&Tap::CONSENSUS)
388+
.map_err(Error::Validation)?;
389389

390390
tree_builder.push_leaf(script);
391391
tap_tree_iter.skip_descendants();

src/interpreter/mod.rs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,7 +1059,6 @@ mod tests {
10591059

10601060
use super::inner::ToNoChecks;
10611061
use super::*;
1062-
use crate::miniscript::analyzable::ExtParams;
10631062

10641063
#[allow(clippy::type_complexity)]
10651064
fn setup_keys_sigs(
@@ -1562,14 +1561,12 @@ mod tests {
15621561
// because it does not implement FromStr
15631562
fn no_checks_ms(ms: &str) -> Miniscript<BitcoinKey, NoChecks> {
15641563
// Parsing should allow raw hashes in the interpreter
1565-
let elem: Miniscript<bitcoin::PublicKey, NoChecks> =
1566-
Miniscript::from_str_ext(ms, &ExtParams::allow_all()).unwrap();
1564+
let elem: Miniscript<bitcoin::PublicKey, NoChecks> = ms.parse().unwrap();
15671565
elem.to_no_checks_ms()
15681566
}
15691567

15701568
fn x_only_no_checks_ms(ms: &str) -> Miniscript<BitcoinKey, NoChecks> {
1571-
let elem: Miniscript<bitcoin::XOnlyPublicKey, NoChecks> =
1572-
Miniscript::from_str_ext(ms, &ExtParams::allow_all()).unwrap();
1569+
let elem: Miniscript<bitcoin::XOnlyPublicKey, NoChecks> = ms.parse().unwrap();
15731570
elem.to_no_checks_ms()
15741571
}
15751572
}

src/lib.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,6 @@ pub enum Error {
443443
Unexpected(String),
444444
/// Encountered a wrapping character that we don't recognize
445445
UnknownWrapper(char),
446-
/// Parsed a miniscript and the result was not of type T
447-
NonTopLevel(String),
448446
/// Parsed a miniscript but there were more script opcodes after it
449447
Trailing(String),
450448
/// Could not satisfy a script (fragment) because of a missing signature
@@ -515,7 +513,6 @@ impl fmt::Display for Error {
515513
Error::UnexpectedStart => f.write_str("unexpected start of script"),
516514
Error::Unexpected(ref s) => write!(f, "unexpected «{}»", s),
517515
Error::UnknownWrapper(ch) => write!(f, "unknown wrapper «{}:»", ch),
518-
Error::NonTopLevel(ref s) => write!(f, "non-T miniscript: {}", s),
519516
Error::Trailing(ref s) => write!(f, "trailing tokens: {}", s),
520517
Error::MissingSig(ref pk) => write!(f, "missing signature for key {:?}", pk),
521518
Error::CouldNotSatisfy => f.write_str("could not satisfy"),
@@ -565,7 +562,6 @@ impl std::error::Error for Error {
565562
UnexpectedStart
566563
| Unexpected(_)
567564
| UnknownWrapper(_)
568-
| NonTopLevel(_)
569565
| Trailing(_)
570566
| MissingSig(_)
571567
| CouldNotSatisfy

src/macros.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
/// `ms_str!("c:or_i(pk({}),pk({}))", pk1, pk2)`
99
#[cfg(test)]
1010
macro_rules! ms_str {
11-
($($arg:tt)*) => (Miniscript::from_str_ext(&format!($($arg)*), &$crate::ExtParams::allow_all()).unwrap())
11+
($($arg:tt)*) => (Miniscript::from_str_with_validation_params(&format!($($arg)*), &$crate::ValidationParams::MAX).unwrap())
1212
}
1313

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

src/miniscript/context.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use crate::miniscript::limits::{
1313
MAX_OPS_PER_SCRIPT, MAX_SCRIPTSIG_SIZE, MAX_SCRIPT_ELEMENT_SIZE, MAX_SCRIPT_SIZE,
1414
MAX_STACK_SIZE, MAX_STANDARD_P2WSH_SCRIPT_SIZE, MAX_STANDARD_P2WSH_STACK_ITEMS,
1515
};
16-
use crate::miniscript::types;
1716
use crate::prelude::*;
1817
use crate::{hash256, Error, ForEachKey, Miniscript, MiniscriptKey, Terminal, ValidationParams};
1918

@@ -283,9 +282,6 @@ where
283282

284283
/// Check whether the top-level is type B
285284
fn top_level_type_check<Pk: MiniscriptKey>(ms: &Miniscript<Pk, Self>) -> Result<(), Error> {
286-
if ms.ty.corr.base != types::Base::B {
287-
return Err(Error::NonTopLevel(format!("{:?}", ms)));
288-
}
289285
// (Ab)use `for_each_key` to record the number of derivation paths a multipath key has.
290286
#[derive(PartialEq)]
291287
enum MultipathLenChecker {
@@ -939,10 +935,7 @@ impl ScriptContext for NoChecks {
939935
Ok(())
940936
}
941937

942-
fn top_level_type_check<Pk: MiniscriptKey>(ms: &Miniscript<Pk, Self>) -> Result<(), Error> {
943-
if ms.ty.corr.base != types::Base::B {
944-
return Err(Error::NonTopLevel(format!("{:?}", ms)));
945-
}
938+
fn top_level_type_check<Pk: MiniscriptKey>(_: &Miniscript<Pk, Self>) -> Result<(), Error> {
946939
Ok(())
947940
}
948941

src/miniscript/mod.rs

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use bitcoin::hashes::hash160;
1818
use bitcoin::script;
1919
use bitcoin::taproot::{LeafVersion, TapLeafHash};
2020

21-
use self::analyzable::ExtParams;
2221
pub use self::context::{BareCtx, Legacy, Segwitv0, Tap};
2322
use crate::iter::TreeLike;
2423
use crate::prelude::*;
@@ -1003,33 +1002,26 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
10031002
}
10041003

10051004
impl<Pk: FromStrKey, Ctx: ScriptContext> Miniscript<Pk, Ctx> {
1006-
/// Attempt to parse an insane(scripts don't clear sanity checks)
1007-
/// from string into a Miniscript representation.
1008-
/// Use this to parse scripts with repeated pubkeys, timelock mixing, malleable
1009-
/// scripts without sig or scripts that can exceed resource limits.
1010-
/// Some of the analysis guarantees of miniscript are lost when dealing with
1011-
/// insane scripts. In general, in a multi-party setting users should only
1012-
/// accept sane scripts.
1013-
pub fn from_str_insane(s: &str) -> Result<Miniscript<Pk, Ctx>, Error> {
1014-
Miniscript::from_str_ext(s, &ExtParams::insane())
1005+
/// Attempt to parse a Miniscript, checking only for consensus compatibility,
1006+
/// lack of raw pubkeyhashes, and no other checks.
1007+
///
1008+
/// It is not recommended to use scripts which require this function in order
1009+
/// to parse, especially in a multiparty setting.
1010+
pub fn from_str_insane(s: &str) -> Result<Self, Error> {
1011+
let params = ValidationParams { allow_raw_pkh: false, ..Ctx::CONSENSUS };
1012+
Miniscript::from_str_with_validation_params(s, &params)
10151013
}
10161014

1017-
/// Attempt to parse an Miniscripts that don't follow the spec.
1018-
/// Use this to parse scripts with repeated pubkeys, timelock mixing, malleable
1019-
/// scripts, raw pubkey hashes without sig or scripts that can exceed resource limits.
1020-
///
1021-
/// Use [`ExtParams`] builder to specify the types of non-sane rules to allow while parsing.
1022-
pub fn from_str_ext(s: &str, ext: &ExtParams) -> Result<Miniscript<Pk, Ctx>, Error> {
1015+
/// Attempt to parse a Miniscript, specifying which validation parameters to apply.
1016+
pub fn from_str_with_validation_params(
1017+
s: &str,
1018+
params: &ValidationParams,
1019+
) -> Result<Self, Error> {
10231020
// This checks for invalid ASCII chars
10241021
let top = expression::Tree::from_str(s)?;
10251022
let ms: Miniscript<Pk, Ctx> = expression::FromTree::from_tree(top.root())?;
1026-
ms.ext_check(ext)?;
1027-
1028-
if ms.ty.corr.base != types::Base::B {
1029-
Err(Error::NonTopLevel(format!("{:?}", ms)))
1030-
} else {
1031-
Ok(ms)
1032-
}
1023+
ms.validate(params).map_err(Error::Validation)?;
1024+
Ok(ms)
10331025
}
10341026
}
10351027

@@ -1247,7 +1239,7 @@ impl<Pk: FromStrKey, Ctx: ScriptContext> str::FromStr for Miniscript<Pk, Ctx> {
12471239
/// See [Miniscript::from_str_insane] to parse scripts from string that
12481240
/// do not clear the [Miniscript::sanity_check] checks.
12491241
fn from_str(s: &str) -> Result<Miniscript<Pk, Ctx>, Error> {
1250-
let ms = Self::from_str_ext(s, &ExtParams::sane())?;
1242+
let ms = Self::from_str_with_validation_params(s, &Ctx::SANE)?;
12511243
Ok(ms)
12521244
}
12531245
}
@@ -1276,13 +1268,12 @@ mod tests {
12761268
use bitcoin::taproot::TapLeafHash;
12771269
use sync::Arc;
12781270

1279-
use super::{Miniscript, ScriptContext, Segwitv0, Tap};
1271+
use super::*;
12801272
use crate::miniscript::{types, Terminal};
12811273
use crate::policy::Liftable;
1282-
use crate::prelude::*;
12831274
use crate::test_utils::{StrKeyTranslator, StrXOnlyKeyTranslator};
12841275
use crate::{
1285-
hex_script, BareCtx, Error, ExtParams, Legacy, RelLockTime, Satisfier, ToPublicKey,
1276+
hex_script, BareCtx, Error, Legacy, RelLockTime, Satisfier, ToPublicKey, ValidationError,
12861277
};
12871278

12881279
type Segwitv0Script = Miniscript<bitcoin::PublicKey, Segwitv0>;
@@ -1912,7 +1903,7 @@ mod tests {
19121903
// Test that parsing raw hash160 from string does not work without extra features
19131904
SegwitMs::from_str(ms_str).unwrap_err();
19141905
SegwitMs::from_str_insane(ms_str).unwrap_err();
1915-
let ms = SegwitMs::from_str_ext(ms_str, &ExtParams::allow_all()).unwrap();
1906+
let ms = SegwitMs::from_str_with_validation_params(ms_str, &ValidationParams::MAX).unwrap();
19161907

19171908
let script = ms.encode();
19181909
// The same test, but parsing from script. Notice that unlike the previous "insane"
@@ -1951,7 +1942,7 @@ mod tests {
19511942
fn duplicate_keys() {
19521943
// You cannot parse a Miniscript that has duplicate keys
19531944
let err = Miniscript::<String, Segwitv0>::from_str("and_v(v:pk(A),pk(A))").unwrap_err();
1954-
assert!(matches!(err, Error::AnalysisError(crate::AnalysisError::RepeatedPubkeys)));
1945+
assert!(matches!(err, Error::Validation(ValidationError::DuplicateKeys)));
19551946

19561947
// ...though you can parse one with from_str_insane
19571948
let ok_insane =
@@ -1974,10 +1965,7 @@ mod tests {
19741965
"and_v(v:and_v(v:older(4194304),pk(A)),and_v(v:older(1),pk(B)))",
19751966
)
19761967
.unwrap_err();
1977-
assert!(matches!(
1978-
err,
1979-
Error::AnalysisError(crate::AnalysisError::HeightTimelockCombination)
1980-
));
1968+
assert!(matches!(err, Error::Validation(ValidationError::MixedTimeLocks)));
19811969

19821970
// Though you can in an or() rather than and()
19831971
let ok_or = Miniscript::<String, Segwitv0>::from_str(
@@ -2001,6 +1989,15 @@ mod tests {
20011989
ok_insane.lift().unwrap_err(),
20021990
Error::LiftError(crate::policy::LiftError::HeightTimelockCombination)
20031991
));
1992+
// nor can it have sane rules applied to it
1993+
assert_eq!(
1994+
ok_insane.validate(&ValidationParams::SANE).unwrap_err(),
1995+
ValidationError::MixedTimeLocks,
1996+
);
1997+
assert_eq!(
1998+
ok_insane.validate(&ValidationParams::SANE).unwrap_err(),
1999+
ValidationError::MixedTimeLocks,
2000+
);
20042001
}
20052002

20062003
#[test]

src/validation.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,17 @@ impl ValidationParams {
237237
where
238238
Pk: crate::MiniscriptKey,
239239
{
240-
if !self.allow_compressed_keys && !key.is_uncompressed() && !key.is_x_only_key() {
240+
// Compressed keys are allowed if -either- compressed keys or x-only keys are allowed:
241+
// for purposes of validating a constructed Miniscript, we treat compressed keys as
242+
// x-only ones, and when encoding them we will just drop their parity. (When decoding
243+
// a Miniscript from Script, we are strict and do not allow compressed keys in place
244+
// of x-only ones. But there we have explicit logic, rather than using this function
245+
// for rejection.)
246+
if !self.allow_compressed_keys
247+
&& !self.allow_x_only_keys
248+
&& !key.is_uncompressed()
249+
&& !key.is_x_only_key()
250+
{
241251
return Err(KeyError::IllegalCompressedKey(key.to_string()));
242252
}
243253
if !self.allow_uncompressed_keys && key.is_uncompressed() {

0 commit comments

Comments
 (0)