diff --git a/src/blanket_traits.rs b/src/blanket_traits.rs new file mode 100644 index 000000000..bc6dab913 --- /dev/null +++ b/src/blanket_traits.rs @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Blanket Traits +//! +//! Because of this library's heavy use of generics, we often require complicated +//! trait bounds (especially when it comes to [`FromStr`] and its +//! associated error types). These blanket traits act as aliases, allowing easier +//! descriptions of them. +//! +//! While these traits are not sealed, they have blanket-impls which prevent you +//! from directly implementing them on your own types. The traits will be +//! automatically implemented if you satisfy all the bounds. +//! + +use core::fmt; +use core::str::FromStr; + +use crate::MiniscriptKey; + +/// Blanket trait describing a key where all associated types implement `FromStr`, +/// and all `FromStr` errors can be displayed. +pub trait FromStrKey: + MiniscriptKey< + Sha256 = Self::_Sha256, + Hash256 = Self::_Hash256, + Ripemd160 = Self::_Ripemd160, + Hash160 = Self::_Hash160, + > + FromStr +{ + /// Dummy type. Do not use. + type _Sha256: FromStr; + /// Dummy type. Do not use. + type _Sha256FromStrErr: fmt::Debug + fmt::Display; + /// Dummy type. Do not use. + type _Hash256: FromStr; + /// Dummy type. Do not use. + type _Hash256FromStrErr: fmt::Debug + fmt::Display; + /// Dummy type. Do not use. + type _Ripemd160: FromStr; + /// Dummy type. Do not use. + type _Ripemd160FromStrErr: fmt::Debug + fmt::Display; + /// Dummy type. Do not use. + type _Hash160: FromStr; + /// Dummy type. Do not use. + type _Hash160FromStrErr: fmt::Debug + fmt::Display; + /// Dummy type. Do not use. + type _FromStrErr: fmt::Debug + fmt::Display; +} + +impl FromStrKey for T +where + Self: MiniscriptKey + FromStr, + ::Sha256: FromStr, + Self::Hash256: FromStr, + Self::Ripemd160: FromStr, + Self::Hash160: FromStr, + ::Err: fmt::Debug + fmt::Display, + <::Sha256 as FromStr>::Err: fmt::Debug + fmt::Display, + ::Err: fmt::Debug + fmt::Display, + ::Err: fmt::Debug + fmt::Display, + ::Err: fmt::Debug + fmt::Display, +{ + type _Sha256 = ::Sha256; + type _Sha256FromStrErr = <::Sha256 as FromStr>::Err; + type _Hash256 = ::Hash256; + type _Hash256FromStrErr = <::Hash256 as FromStr>::Err; + type _Ripemd160 = ::Ripemd160; + type _Ripemd160FromStrErr = <::Ripemd160 as FromStr>::Err; + type _Hash160 = ::Hash160; + type _Hash160FromStrErr = <::Hash160 as FromStr>::Err; + type _FromStrErr = ::Err; +} diff --git a/src/descriptor/bare.rs b/src/descriptor/bare.rs index 9b9de78cc..79adb6b33 100644 --- a/src/descriptor/bare.rs +++ b/src/descriptor/bare.rs @@ -166,24 +166,22 @@ impl Liftable for Bare { fn lift(&self) -> Result, Error> { self.ms.lift() } } -impl_from_tree!( - Bare, +impl FromTree for Bare { fn from_tree(top: &expression::Tree) -> Result { let sub = Miniscript::::from_tree(top)?; BareCtx::top_level_checks(&sub)?; Bare::new(sub) } -); +} -impl_from_str!( - Bare, - type Err = Error;, +impl core::str::FromStr for Bare { + type Err = Error; fn from_str(s: &str) -> Result { let desc_str = verify_checksum(s)?; let top = expression::Tree::from_str(desc_str)?; Self::from_tree(&top) } -); +} impl ForEachKey for Bare { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool { @@ -366,8 +364,7 @@ impl Liftable for Pkh { } } -impl_from_tree!( - Pkh, +impl FromTree for Pkh { fn from_tree(top: &expression::Tree) -> Result { if top.name == "pkh" && top.args.len() == 1 { Ok(Pkh::new(expression::terminal(&top.args[0], |pk| Pk::from_str(pk))?)?) @@ -379,17 +376,16 @@ impl_from_tree!( ))) } } -); +} -impl_from_str!( - Pkh, - type Err = Error;, +impl core::str::FromStr for Pkh { + type Err = Error; fn from_str(s: &str) -> Result { let desc_str = verify_checksum(s)?; let top = expression::Tree::from_str(desc_str)?; Self::from_tree(&top) } -); +} impl ForEachKey for Pkh { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool { pred(&self.pk) } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 83031fb53..394e17f63 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -918,8 +918,7 @@ impl Descriptor { } } -impl_from_tree!( - Descriptor, +impl crate::expression::FromTree for Descriptor { /// Parse an expression tree into a descriptor. fn from_tree(top: &expression::Tree) -> Result, Error> { Ok(match (top.name, top.args.len() as u32) { @@ -931,11 +930,10 @@ impl_from_tree!( _ => Descriptor::Bare(Bare::from_tree(top)?), }) } -); +} -impl_from_str!( - Descriptor, - type Err = Error;, +impl FromStr for Descriptor { + type Err = Error; fn from_str(s: &str) -> Result, Error> { // tr tree parsing has special code // Tr::from_str will check the checksum @@ -950,7 +948,7 @@ impl_from_str!( Ok(desc) } -); +} impl fmt::Debug for Descriptor { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/descriptor/segwitv0.rs b/src/descriptor/segwitv0.rs index a7455a403..fa57eb582 100644 --- a/src/descriptor/segwitv0.rs +++ b/src/descriptor/segwitv0.rs @@ -231,8 +231,7 @@ impl Liftable for Wsh { } } -impl_from_tree!( - Wsh, +impl crate::expression::FromTree for Wsh { fn from_tree(top: &expression::Tree) -> Result { if top.name == "wsh" && top.args.len() == 1 { let top = &top.args[0]; @@ -250,7 +249,7 @@ impl_from_tree!( ))) } } -); +} impl fmt::Debug for Wsh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -270,15 +269,14 @@ impl fmt::Display for Wsh { } } -impl_from_str!( - Wsh, - type Err = Error;, +impl core::str::FromStr for Wsh { + type Err = Error; fn from_str(s: &str) -> Result { let desc_str = verify_checksum(s)?; let top = expression::Tree::from_str(desc_str)?; Wsh::::from_tree(&top) } -); +} impl ForEachKey for Wsh { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool { @@ -475,8 +473,7 @@ impl Liftable for Wpkh { } } -impl_from_tree!( - Wpkh, +impl crate::expression::FromTree for Wpkh { fn from_tree(top: &expression::Tree) -> Result { if top.name == "wpkh" && top.args.len() == 1 { Ok(Wpkh::new(expression::terminal(&top.args[0], |pk| Pk::from_str(pk))?)?) @@ -488,17 +485,16 @@ impl_from_tree!( ))) } } -); +} -impl_from_str!( - Wpkh, - type Err = Error;, +impl core::str::FromStr for Wpkh { + type Err = Error; fn from_str(s: &str) -> Result { let desc_str = verify_checksum(s)?; let top = expression::Tree::from_str(desc_str)?; Self::from_tree(&top) } -); +} impl ForEachKey for Wpkh { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool { pred(&self.pk) } diff --git a/src/descriptor/sh.rs b/src/descriptor/sh.rs index c50f9c4bb..3f7e9cb08 100644 --- a/src/descriptor/sh.rs +++ b/src/descriptor/sh.rs @@ -81,8 +81,7 @@ impl fmt::Display for Sh { } } -impl_from_tree!( - Sh, +impl crate::expression::FromTree for Sh { fn from_tree(top: &expression::Tree) -> Result { if top.name == "sh" && top.args.len() == 1 { let top = &top.args[0]; @@ -105,17 +104,16 @@ impl_from_tree!( ))) } } -); +} -impl_from_str!( - Sh, - type Err = Error;, +impl core::str::FromStr for Sh { + type Err = Error; fn from_str(s: &str) -> Result { let desc_str = verify_checksum(s)?; let top = expression::Tree::from_str(desc_str)?; Self::from_tree(&top) } -); +} impl Sh { /// Get the Inner diff --git a/src/descriptor/sortedmulti.rs b/src/descriptor/sortedmulti.rs index 9c68db830..3862ccf15 100644 --- a/src/descriptor/sortedmulti.rs +++ b/src/descriptor/sortedmulti.rs @@ -60,7 +60,7 @@ impl SortedMultiVec { pub fn from_tree(tree: &expression::Tree) -> Result where Pk: FromStr, - ::Err: ToString, + ::Err: fmt::Display, { if tree.args.is_empty() { return Err(errstr("no arguments given for sortedmulti")); diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 23545cda8..70e133b6c 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -467,8 +467,7 @@ where } #[rustfmt::skip] -impl_block_str!( - Tr, +impl Tr { // Helper function to parse taproot script path fn parse_tr_script_spend(tree: &expression::Tree,) -> Result, Error> { match tree { @@ -487,10 +486,9 @@ impl_block_str!( )), } } -); +} -impl_from_tree!( - Tr, +impl crate::expression::FromTree for Tr { fn from_tree(top: &expression::Tree) -> Result { if top.name == "tr" { match top.args.len() { @@ -530,17 +528,16 @@ impl_from_tree!( ))) } } -); +} -impl_from_str!( - Tr, - type Err = Error;, +impl core::str::FromStr for Tr { + type Err = Error; fn from_str(s: &str) -> Result { let desc_str = verify_checksum(s)?; let top = parse_tr_tree(desc_str)?; Self::from_tree(&top) } -); +} impl fmt::Debug for Tr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/expression.rs b/src/expression.rs index 7875284de..3db95a44a 100644 --- a/src/expression.rs +++ b/src/expression.rs @@ -2,6 +2,7 @@ //! # Function-like Expression Language //! +use core::fmt; use core::str::FromStr; use crate::prelude::*; @@ -218,7 +219,7 @@ pub fn parse_num(s: &str) -> Result { pub fn terminal(term: &Tree, convert: F) -> Result where F: FnOnce(&str) -> Result, - Err: ToString, + Err: fmt::Display, { if term.args.is_empty() { convert(term.name).map_err(|e| Error::Unexpected(e.to_string())) @@ -259,7 +260,6 @@ where #[cfg(test)] mod tests { - use super::parse_num; #[test] @@ -281,3 +281,30 @@ mod tests { assert_eq!(valid_chars, super::VALID_CHARS); } } + +#[cfg(bench)] +mod benches { + use test::{black_box, Bencher}; + + use super::*; + + #[bench] + pub fn parse_tree(bh: &mut Bencher) { + bh.iter(|| { + let tree = Tree::from_str( + "and(thresh(2,and(sha256(H),or(sha256(H),pk(A))),pk(B),pk(C),pk(D),sha256(H)),pk(E))", + ).unwrap(); + black_box(tree); + }); + } + + #[bench] + pub fn parse_tree_deep(bh: &mut Bencher) { + bh.iter(|| { + let tree = Tree::from_str( + "and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(1,2),3),4),5),6),7),8),9),10),11),12),13),14),15),16),17),18),19),20),21)" + ).unwrap(); + black_box(tree); + }); + } +} diff --git a/src/interpreter/inner.rs b/src/interpreter/inner.rs index cb9fbfc35..9fc3d11b5 100644 --- a/src/interpreter/inner.rs +++ b/src/interpreter/inner.rs @@ -46,12 +46,8 @@ fn script_from_stack_elem( Miniscript::parse_with_ext(bitcoin::Script::from_bytes(sl), &ExtParams::allow_all()) .map_err(Error::from) } - stack::Element::Satisfied => { - Miniscript::from_ast(crate::Terminal::True).map_err(Error::from) - } - stack::Element::Dissatisfied => { - Miniscript::from_ast(crate::Terminal::False).map_err(Error::from) - } + stack::Element::Satisfied => Ok(Miniscript::TRUE), + stack::Element::Dissatisfied => Ok(Miniscript::FALSE), } } diff --git a/src/lib.rs b/src/lib.rs index a06dd62c0..1360bdbfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,6 +117,7 @@ mod macros; #[macro_use] mod pub_macros; +mod blanket_traits; pub mod descriptor; pub mod expression; pub mod interpreter; @@ -139,6 +140,7 @@ use bitcoin::hex::DisplayHex; use bitcoin::locktime::absolute; use bitcoin::{script, Opcode}; +pub use crate::blanket_traits::FromStrKey; pub use crate::descriptor::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey}; pub use crate::interpreter::Interpreter; pub use crate::miniscript::analyzable::{AnalysisError, ExtParams}; @@ -148,7 +150,7 @@ pub use crate::miniscript::satisfy::{Preimage32, Satisfier}; pub use crate::miniscript::{hash256, Miniscript}; use crate::prelude::*; -///Public key trait which can be converted to Hash type +/// Public key trait which can be converted to Hash type pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Hash { /// Returns true if the pubkey is uncompressed. Defaults to `false`. fn is_uncompressed(&self) -> bool { false } diff --git a/src/macros.rs b/src/macros.rs index 6d4b9156b..6e4631f62 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -18,100 +18,6 @@ macro_rules! policy_str { ($($arg:tt)*) => ($crate::policy::Concrete::from_str(&format!($($arg)*)).unwrap()) } -/// Macro for implementing FromTree trait. This avoids copying all the Pk::Associated type bounds -/// throughout the codebase. -macro_rules! impl_from_tree { - ($(;$gen:ident; $gen_con:ident, )* $name: ty, - $(#[$meta:meta])* - fn $fn:ident ( $($arg:ident : $type:ty),* ) -> $ret:ty - $body:block - ) => { - impl $crate::expression::FromTree for $name - where - Pk: MiniscriptKey + core::str::FromStr, - Pk::Sha256: core::str::FromStr, - Pk::Hash256: core::str::FromStr, - Pk::Ripemd160: core::str::FromStr, - Pk::Hash160: core::str::FromStr, - ::Err: $crate::prelude::ToString, - <::Sha256 as core::str::FromStr>::Err: $crate::prelude::ToString, - <::Hash256 as core::str::FromStr>::Err: $crate::prelude::ToString, - <::Ripemd160 as core::str::FromStr>::Err: $crate::prelude::ToString, - <::Hash160 as core::str::FromStr>::Err: $crate::prelude::ToString, - $($gen : $gen_con,)* - { - - $(#[$meta])* - fn $fn($($arg: $type)* ) -> $ret { - $body - } - } - }; -} - -/// Macro for implementing FromStr trait. This avoids copying all the Pk::Associated type bounds -/// throughout the codebase. -macro_rules! impl_from_str { - ($(;$gen:ident; $gen_con:ident, )* $name: ty, - type Err = $err_ty:ty;, - $(#[$meta:meta])* - fn $fn:ident ( $($arg:ident : $type:ty),* ) -> $ret:ty - $body:block - ) => { - impl core::str::FromStr for $name - where - Pk: MiniscriptKey + core::str::FromStr, - Pk::Sha256: core::str::FromStr, - Pk::Hash256: core::str::FromStr, - Pk::Ripemd160: core::str::FromStr, - Pk::Hash160: core::str::FromStr, - ::Err: $crate::prelude::ToString, - <::Sha256 as core::str::FromStr>::Err: $crate::prelude::ToString, - <::Hash256 as core::str::FromStr>::Err: $crate::prelude::ToString, - <::Ripemd160 as core::str::FromStr>::Err: $crate::prelude::ToString, - <::Hash160 as core::str::FromStr>::Err: $crate::prelude::ToString, - $($gen : $gen_con,)* - { - type Err = $err_ty; - - $(#[$meta])* - fn $fn($($arg: $type)* ) -> $ret { - $body - } - } - }; -} - -/// Macro for impl Struct with associated bounds. This avoids copying all the Pk::Associated type bounds -/// throughout the codebase. -macro_rules! impl_block_str { - ($(;$gen:ident; $gen_con:ident, )* $name: ty, - $(#[$meta:meta])* - $v:vis fn $fn:ident ( $($arg:ident : $type:ty, )* ) -> $ret:ty - $body:block - ) => { - impl $name - where - Pk: MiniscriptKey + core::str::FromStr, - Pk::Sha256: core::str::FromStr, - Pk::Hash256: core::str::FromStr, - Pk::Ripemd160: core::str::FromStr, - Pk::Hash160: core::str::FromStr, - ::Err: $crate::prelude::ToString, - <::Sha256 as core::str::FromStr>::Err: $crate::prelude::ToString, - <::Hash256 as core::str::FromStr>::Err: $crate::prelude::ToString, - <::Ripemd160 as core::str::FromStr>::Err: $crate::prelude::ToString, - <::Hash160 as core::str::FromStr>::Err: $crate::prelude::ToString, - $($gen : $gen_con,)* - { - $(#[$meta])* - $v fn $fn($($arg: $type,)* ) -> $ret { - $body - } - } - }; -} - /// A macro that implements serde serialization and deserialization using the /// `fmt::Display` and `str::FromStr` traits. macro_rules! serde_string_impl_pk { @@ -119,20 +25,7 @@ macro_rules! serde_string_impl_pk { #[cfg(feature = "serde")] impl<'de, Pk $(, $gen)*> $crate::serde::Deserialize<'de> for $name where - Pk: $crate::MiniscriptKey + core::str::FromStr, - Pk::Sha256: core::str::FromStr, - Pk::Hash256: core::str::FromStr, - Pk::Ripemd160: core::str::FromStr, - Pk::Hash160: core::str::FromStr, - ::Err: core::fmt::Display, - <::Sha256 as core::str::FromStr>::Err: - core::fmt::Display, - <::Hash256 as core::str::FromStr>::Err: - core::fmt::Display, - <::Ripemd160 as core::str::FromStr>::Err: - core::fmt::Display, - <::Hash160 as core::str::FromStr>::Err: - core::fmt::Display, + Pk: $crate::FromStrKey, $($gen : $gen_con,)* { fn deserialize(deserializer: D) -> Result<$name, D::Error> @@ -147,20 +40,7 @@ macro_rules! serde_string_impl_pk { struct Visitor(PhantomData<(Pk $(, $gen)*)>); impl<'de, Pk $(, $gen)*> $crate::serde::de::Visitor<'de> for Visitor where - Pk: $crate::MiniscriptKey + core::str::FromStr, - Pk::Sha256: core::str::FromStr, - Pk::Hash256: core::str::FromStr, - Pk::Ripemd160: core::str::FromStr, - Pk::Hash160: core::str::FromStr, - ::Err: core::fmt::Display, - <::Sha256 as core::str::FromStr>::Err: - core::fmt::Display, - <::Hash256 as core::str::FromStr>::Err: - core::fmt::Display, - <::Ripemd160 as core::str::FromStr>::Err: - core::fmt::Display, - <::Hash160 as core::str::FromStr>::Err: - core::fmt::Display, + Pk: $crate::FromStrKey, $($gen: $gen_con,)* { type Value = $name; diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index f9e8661d2..e30765548 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -240,72 +240,31 @@ impl fmt::Display for Terminal { } } -impl_from_tree!( - ;Ctx; ScriptContext, - Arc>, +impl crate::expression::FromTree + for Arc> +{ fn from_tree(top: &expression::Tree) -> Result>, Error> { Ok(Arc::new(expression::FromTree::from_tree(top)?)) } -); +} -impl_from_tree!( - ;Ctx; ScriptContext, - Terminal, +impl crate::expression::FromTree for Terminal { fn from_tree(top: &expression::Tree) -> Result, Error> { - let mut aliased_wrap; - let frag_name; - let frag_wrap; - let mut name_split = top.name.split(':'); - match (name_split.next(), name_split.next(), name_split.next()) { - (None, _, _) => { - frag_name = ""; - frag_wrap = ""; - } - (Some(name), None, _) => { - if name == "pk" { - frag_name = "pk_k"; - frag_wrap = "c"; - } else if name == "pkh" { - frag_name = "pk_h"; - frag_wrap = "c"; - } else { - frag_name = name; - frag_wrap = ""; - } - } - (Some(wrap), Some(name), None) => { - if wrap.is_empty() { - return Err(Error::Unexpected(top.name.to_owned())); - } - if name == "pk" { - frag_name = "pk_k"; - aliased_wrap = wrap.to_owned(); - aliased_wrap.push('c'); - frag_wrap = &aliased_wrap; - } else if name == "pkh" { - frag_name = "pk_h"; - aliased_wrap = wrap.to_owned(); - aliased_wrap.push('c'); - frag_wrap = &aliased_wrap; - } else { - frag_name = name; - frag_wrap = wrap; - } - } - (Some(_), Some(_), Some(_)) => { - return Err(Error::MultiColon(top.name.to_owned())); - } - } - let mut unwrapped = match (frag_name, top.args.len()) { + let (frag_name, frag_wrap) = super::split_expression_name(top.name)?; + let unwrapped = match (frag_name, top.args.len()) { ("expr_raw_pkh", 1) => expression::terminal(&top.args[0], |x| { hash160::Hash::from_str(x).map(Terminal::RawPkH) }), ("pk_k", 1) => { expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkK)) } - ("pk_h", 1) => expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkH)), + ("pk_h", 1) => { + expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkH)) + } ("after", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x).map(|x| Terminal::After(AbsLockTime::from(absolute::LockTime::from_consensus(x)))) + expression::parse_num(x).map(|x| { + Terminal::After(AbsLockTime::from(absolute::LockTime::from_consensus(x))) + }) }), ("older", 1) => expression::terminal(&top.args[0], |x| { expression::parse_num(x).map(|x| Terminal::Older(Sequence::from_consensus(x))) @@ -329,7 +288,7 @@ impl_from_tree!( ("and_n", 2) => Ok(Terminal::AndOr( expression::FromTree::from_tree(&top.args[0])?, expression::FromTree::from_tree(&top.args[1])?, - Arc::new(Miniscript::from_ast(Terminal::False)?), + Arc::new(Miniscript::FALSE), )), ("andor", 3) => Ok(Terminal::AndOr( expression::FromTree::from_tree(&top.args[0])?, @@ -386,45 +345,10 @@ impl_from_tree!( top.args.len(), ))), }?; - for ch in frag_wrap.chars().rev() { - // Check whether the wrapper is valid under the current context - let ms = Miniscript::from_ast(unwrapped)?; - Ctx::check_global_validity(&ms)?; - match ch { - 'a' => unwrapped = Terminal::Alt(Arc::new(ms)), - 's' => unwrapped = Terminal::Swap(Arc::new(ms)), - 'c' => unwrapped = Terminal::Check(Arc::new(ms)), - 'd' => unwrapped = Terminal::DupIf(Arc::new(ms)), - 'v' => unwrapped = Terminal::Verify(Arc::new(ms)), - 'j' => unwrapped = Terminal::NonZero(Arc::new(ms)), - 'n' => unwrapped = Terminal::ZeroNotEqual(Arc::new(ms)), - 't' => { - unwrapped = Terminal::AndV( - Arc::new(ms), - Arc::new(Miniscript::from_ast(Terminal::True)?), - ) - } - 'u' => { - unwrapped = Terminal::OrI( - Arc::new(ms), - Arc::new(Miniscript::from_ast(Terminal::False)?), - ) - } - 'l' => { - unwrapped = Terminal::OrI( - Arc::new(Miniscript::from_ast(Terminal::False)?), - Arc::new(ms), - ) - } - x => return Err(Error::UnknownWrapper(x)), - } - } - // Check whether the unwrapped miniscript is valid under the current context - let ms = Miniscript::from_ast(unwrapped)?; - Ctx::check_global_validity(&ms)?; + let ms = super::wrap_into_miniscript(unwrapped, frag_wrap)?; Ok(ms.node) } -); +} /// Helper trait to add a `push_astelem` method to `script::Builder` trait PushAstElem { diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index b8fd8a902..053fc6da1 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -64,6 +64,22 @@ pub struct Miniscript { } impl Miniscript { + /// The `1` combinator. + pub const TRUE: Self = Miniscript { + node: Terminal::True, + ty: types::Type::TRUE, + ext: types::extra_props::ExtData::TRUE, + phantom: PhantomData, + }; + + /// The `0` combinator. + pub const FALSE: Self = Miniscript { + node: Terminal::False, + ty: types::Type::FALSE, + ext: types::extra_props::ExtData::FALSE, + phantom: PhantomData, + }; + /// Add type information(Type and Extdata) to Miniscript based on /// `AstElem` fragment. Dependent on display and clone because of Error /// Display code of type_check. @@ -508,9 +524,100 @@ impl Miniscript { } } -impl_block_str!( - ;Ctx; ScriptContext, - Miniscript, +/// Utility function used when parsing a script from an expression tree. +/// +/// Checks that the name of each fragment has at most one `:`, splits +/// the name at the `:`, and implements aliases for the old `pk`/`pk_h` +/// fragments. +/// +/// Returns the fragment name (right of the `:`) and a list of wrappers +/// (left of the `:`). +fn split_expression_name(name: &str) -> Result<(&str, Cow), Error> { + let mut aliased_wrap; + let frag_name; + let frag_wrap; + let mut name_split = name.split(':'); + match (name_split.next(), name_split.next(), name_split.next()) { + (None, _, _) => { + frag_name = ""; + frag_wrap = "".into(); + } + (Some(name), None, _) => { + if name == "pk" { + frag_name = "pk_k"; + frag_wrap = "c".into(); + } else if name == "pkh" { + frag_name = "pk_h"; + frag_wrap = "c".into(); + } else { + frag_name = name; + frag_wrap = "".into(); + } + } + (Some(wrap), Some(name), None) => { + if wrap.is_empty() { + return Err(Error::Unexpected(name.to_owned())); + } + if name == "pk" { + frag_name = "pk_k"; + aliased_wrap = wrap.to_owned(); + aliased_wrap.push('c'); + frag_wrap = aliased_wrap.into(); + } else if name == "pkh" { + frag_name = "pk_h"; + aliased_wrap = wrap.to_owned(); + aliased_wrap.push('c'); + frag_wrap = aliased_wrap.into(); + } else { + frag_name = name; + frag_wrap = wrap.into(); + } + } + (Some(_), Some(_), Some(_)) => { + return Err(Error::MultiColon(name.to_owned())); + } + } + Ok((frag_name, frag_wrap)) +} + +/// Utility function used when parsing a script from an expression tree. +/// +/// Once a Miniscript fragment has been parsed into a terminal, apply any +/// wrappers that were included in its name. +fn wrap_into_miniscript( + term: Terminal, + frag_wrap: Cow, +) -> Result, Error> +where + Pk: MiniscriptKey, + Ctx: ScriptContext, +{ + let mut unwrapped = term; + for ch in frag_wrap.chars().rev() { + // Check whether the wrapper is valid under the current context + let ms = Miniscript::from_ast(unwrapped)?; + Ctx::check_global_validity(&ms)?; + match ch { + 'a' => unwrapped = Terminal::Alt(Arc::new(ms)), + 's' => unwrapped = Terminal::Swap(Arc::new(ms)), + 'c' => unwrapped = Terminal::Check(Arc::new(ms)), + 'd' => unwrapped = Terminal::DupIf(Arc::new(ms)), + 'v' => unwrapped = Terminal::Verify(Arc::new(ms)), + 'j' => unwrapped = Terminal::NonZero(Arc::new(ms)), + 'n' => unwrapped = Terminal::ZeroNotEqual(Arc::new(ms)), + 't' => unwrapped = Terminal::AndV(Arc::new(ms), Arc::new(Miniscript::TRUE)), + 'u' => unwrapped = Terminal::OrI(Arc::new(ms), Arc::new(Miniscript::FALSE)), + 'l' => unwrapped = Terminal::OrI(Arc::new(Miniscript::FALSE), Arc::new(ms)), + x => return Err(Error::UnknownWrapper(x)), + } + } + // Check whether the unwrapped miniscript is valid under the current context + let ms = Miniscript::from_ast(unwrapped)?; + Ctx::check_global_validity(&ms)?; + Ok(ms) +} + +impl Miniscript { /// Attempt to parse an insane(scripts don't clear sanity checks) /// from string into a Miniscript representation. /// Use this to parse scripts with repeated pubkeys, timelock mixing, malleable @@ -518,22 +625,16 @@ impl_block_str!( /// Some of the analysis guarantees of miniscript are lost when dealing with /// insane scripts. In general, in a multi-party setting users should only /// accept sane scripts. - pub fn from_str_insane(s: &str,) -> Result, Error> - { + pub fn from_str_insane(s: &str) -> Result, Error> { Miniscript::from_str_ext(s, &ExtParams::insane()) } -); -impl_block_str!( - ;Ctx; ScriptContext, - Miniscript, /// Attempt to parse an Miniscripts that don't follow the spec. /// Use this to parse scripts with repeated pubkeys, timelock mixing, malleable /// scripts, raw pubkey hashes without sig or scripts that can exceed resource limits. /// /// Use [`ExtParams`] builder to specify the types of non-sane rules to allow while parsing. - pub fn from_str_ext(s: &str, ext: &ExtParams,) -> Result, Error> - { + pub fn from_str_ext(s: &str, ext: &ExtParams) -> Result, Error> { // This checks for invalid ASCII chars let top = expression::Tree::from_str(s)?; let ms: Miniscript = expression::FromTree::from_tree(&top)?; @@ -545,31 +646,29 @@ impl_block_str!( Ok(ms) } } -); +} -impl_from_tree!( - ;Ctx; ScriptContext, - Arc>, +impl crate::expression::FromTree + for Arc> +{ fn from_tree(top: &expression::Tree) -> Result>, Error> { Ok(Arc::new(expression::FromTree::from_tree(top)?)) } -); +} -impl_from_tree!( - ;Ctx; ScriptContext, - Miniscript, +impl crate::expression::FromTree + for Miniscript +{ /// Parse an expression tree into a Miniscript. As a general rule, this /// should not be called directly; rather go through the descriptor API. fn from_tree(top: &expression::Tree) -> Result, Error> { let inner: Terminal = expression::FromTree::from_tree(top)?; Miniscript::from_ast(inner) } -); +} -impl_from_str!( - ;Ctx; ScriptContext, - Miniscript, - type Err = Error;, +impl str::FromStr for Miniscript { + type Err = Error; /// Parse a Miniscript from string and perform sanity checks /// See [Miniscript::from_str_insane] to parse scripts from string that /// do not clear the [Miniscript::sanity_check] checks. @@ -577,7 +676,7 @@ impl_from_str!( let ms = Self::from_str_ext(s, &ExtParams::sane())?; Ok(ms) } -); +} serde_string_impl_pk!(Miniscript, "a miniscript", Ctx; ScriptContext); @@ -1350,3 +1449,32 @@ mod tests { } } } + +#[cfg(bench)] +mod benches { + use test::{black_box, Bencher}; + + use super::*; + + #[bench] + pub fn parse_segwit0(bh: &mut Bencher) { + bh.iter(|| { + let tree = Miniscript::::from_str_ext( + "and_v(v:pk(E),thresh(2,j:and_v(v:sha256(H),t:or_i(v:sha256(H),v:pkh(A))),s:pk(B),s:pk(C),s:pk(D),sjtv:sha256(H)))", + &ExtParams::sane(), + ).unwrap(); + black_box(tree); + }); + } + + #[bench] + pub fn parse_segwit0_deep(bh: &mut Bencher) { + bh.iter(|| { + let tree = Miniscript::::from_str_ext( + "and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:and_v(v:pk(1),pk(2)),pk(3)),pk(4)),pk(5)),pk(6)),pk(7)),pk(8)),pk(9)),pk(10)),pk(11)),pk(12)),pk(13)),pk(14)),pk(15)),pk(16)),pk(17)),pk(18)),pk(19)),pk(20)),pk(21))", + &ExtParams::sane(), + ).unwrap(); + black_box(tree); + }); + } +} diff --git a/src/miniscript/types/correctness.rs b/src/miniscript/types/correctness.rs index 74b6b62ce..4b6365a66 100644 --- a/src/miniscript/types/correctness.rs +++ b/src/miniscript/types/correctness.rs @@ -83,6 +83,14 @@ pub struct Correctness { } impl Correctness { + /// Correctness data for the `1` combinator + pub const TRUE: Self = + Correctness { base: Base::B, input: Input::Zero, dissatisfiable: false, unit: true }; + + /// Correctness data for the `0` combinator + pub const FALSE: Self = + Correctness { base: Base::B, input: Input::Zero, dissatisfiable: true, unit: true }; + /// Check whether the `self` is a subtype of `other` argument . /// This checks whether the argument `other` has attributes which are present /// in the given `Type`. This returns `true` on same arguments @@ -113,13 +121,9 @@ impl Property for Correctness { } } - fn from_true() -> Self { - Correctness { base: Base::B, input: Input::Zero, dissatisfiable: false, unit: true } - } + fn from_true() -> Self { Self::TRUE } - fn from_false() -> Self { - Correctness { base: Base::B, input: Input::Zero, dissatisfiable: true, unit: true } - } + fn from_false() -> Self { Self::FALSE } fn from_pk_k() -> Self { Correctness { base: Base::K, input: Input::OneNonZero, dissatisfiable: true, unit: true } diff --git a/src/miniscript/types/extra_props.rs b/src/miniscript/types/extra_props.rs index 8f5c45135..9d57b2df6 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -42,7 +42,7 @@ pub struct OpLimits { impl OpLimits { /// Creates a new instance of [`OpLimits`] - pub fn new(op_static: usize, op_sat: Option, op_nsat: Option) -> Self { + pub const fn new(op_static: usize, op_sat: Option, op_nsat: Option) -> Self { OpLimits { count: op_static, sat: op_sat, nsat: op_nsat } } @@ -51,6 +51,17 @@ impl OpLimits { } impl TimelockInfo { + /// Creates a new `TimelockInfo` with all fields set to false. + pub const fn new() -> Self { + TimelockInfo { + csv_with_height: false, + csv_with_time: false, + cltv_with_height: false, + cltv_with_time: false, + contains_combination: false, + } + } + /// Returns true if the current `TimelockInfo` contains any possible unspendable paths. pub fn contains_unspendable_path(self) -> bool { self.contains_combination } @@ -133,6 +144,36 @@ pub struct ExtData { pub exec_stack_elem_count_dissat: Option, } +impl ExtData { + /// Extra data for the `0` combinator + pub const FALSE: Self = ExtData { + pk_cost: 1, + has_free_verify: false, + ops: OpLimits::new(0, None, Some(0)), + stack_elem_count_sat: None, + stack_elem_count_dissat: Some(0), + max_sat_size: None, + max_dissat_size: Some((0, 0)), + timelock_info: TimelockInfo::new(), + exec_stack_elem_count_sat: None, + exec_stack_elem_count_dissat: Some(1), + }; + + /// Extra data for the `1` combinator + pub const TRUE: Self = ExtData { + pk_cost: 1, + has_free_verify: false, + ops: OpLimits::new(0, Some(0), None), + stack_elem_count_sat: Some(0), + stack_elem_count_dissat: None, + max_sat_size: Some((0, 0)), + max_dissat_size: None, + timelock_info: TimelockInfo::new(), + exec_stack_elem_count_sat: Some(1), + exec_stack_elem_count_dissat: None, + }; +} + impl Property for ExtData { fn sanity_checks(&self) { debug_assert_eq!( @@ -145,35 +186,9 @@ impl Property for ExtData { ); } - fn from_true() -> Self { - ExtData { - pk_cost: 1, - has_free_verify: false, - ops: OpLimits::new(0, Some(0), None), - stack_elem_count_sat: Some(0), - stack_elem_count_dissat: None, - max_sat_size: Some((0, 0)), - max_dissat_size: None, - timelock_info: TimelockInfo::default(), - exec_stack_elem_count_sat: Some(1), - exec_stack_elem_count_dissat: None, - } - } + fn from_true() -> Self { Self::TRUE } - fn from_false() -> Self { - ExtData { - pk_cost: 1, - has_free_verify: false, - ops: OpLimits::new(0, None, Some(0)), - stack_elem_count_sat: None, - stack_elem_count_dissat: Some(0), - max_sat_size: None, - max_dissat_size: Some((0, 0)), - timelock_info: TimelockInfo::default(), - exec_stack_elem_count_sat: None, - exec_stack_elem_count_dissat: Some(1), - } - } + fn from_false() -> Self { Self::FALSE } fn from_pk_k() -> Self { ExtData { diff --git a/src/miniscript/types/malleability.rs b/src/miniscript/types/malleability.rs index a0bc53122..71d644d97 100644 --- a/src/miniscript/types/malleability.rs +++ b/src/miniscript/types/malleability.rs @@ -55,6 +55,13 @@ pub struct Malleability { } impl Malleability { + /// Malleability data for the `1` combinator + pub const TRUE: Self = Malleability { dissat: Dissat::None, safe: false, non_malleable: true }; + + /// Malleability data for the `0` combinator + pub const FALSE: Self = + Malleability { dissat: Dissat::Unique, safe: true, non_malleable: true }; + /// Check whether the `self` is a subtype of `other` argument . /// This checks whether the argument `other` has attributes which are present /// in the given `Type`. This returns `true` on same arguments diff --git a/src/miniscript/types/mod.rs b/src/miniscript/types/mod.rs index e9bf7697f..ae154e004 100644 --- a/src/miniscript/types/mod.rs +++ b/src/miniscript/types/mod.rs @@ -216,6 +216,12 @@ pub struct Type { } impl Type { + /// Type of the `0` combinator + pub const TRUE: Self = Type { corr: Correctness::TRUE, mall: Malleability::TRUE }; + + /// Type of the `0` combinator + pub const FALSE: Self = Type { corr: Correctness::FALSE, mall: Malleability::FALSE }; + /// Check whether the `self` is a subtype of `other` argument . /// This checks whether the argument `other` has attributes which are present /// in the given `Type`. This returns `true` on same arguments diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index 5a0b88809..1b1ced6e7 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -704,27 +704,13 @@ fn all_casts() -> [Cast; 10] { }, Cast { ext_data: types::ExtData::cast_likely, - node: |ms| { - Terminal::OrI( - Arc::new( - Miniscript::from_ast(Terminal::False).expect("False Miniscript creation"), - ), - ms, - ) - }, + node: |ms| Terminal::OrI(Arc::new(Miniscript::FALSE), ms), ast_type: types::Type::cast_likely, comp_ext_data: CompilerExtData::cast_likely, }, Cast { ext_data: types::ExtData::cast_unlikely, - node: |ms| { - Terminal::OrI( - ms, - Arc::new( - Miniscript::from_ast(Terminal::False).expect("False Miniscript creation"), - ), - ) - }, + node: |ms| Terminal::OrI(ms, Arc::new(Miniscript::FALSE)), ast_type: types::Type::cast_unlikely, comp_ext_data: CompilerExtData::cast_unlikely, }, @@ -742,14 +728,7 @@ fn all_casts() -> [Cast; 10] { }, Cast { ext_data: types::ExtData::cast_true, - node: |ms| { - Terminal::AndV( - ms, - Arc::new( - Miniscript::from_ast(Terminal::True).expect("True Miniscript creation"), - ), - ) - }, + node: |ms| Terminal::AndV(ms, Arc::new(Miniscript::TRUE)), ast_type: types::Type::cast_true, comp_ext_data: CompilerExtData::cast_true, }, diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index f4991c23a..5e235bb4b 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -930,9 +930,8 @@ impl fmt::Display for Policy { } } -impl_from_str!( - Policy, - type Err = Error;, +impl str::FromStr for Policy { + type Err = Error; fn from_str(s: &str) -> Result, Error> { expression::check_valid_chars(s)?; @@ -941,13 +940,12 @@ impl_from_str!( policy.check_timelocks()?; Ok(policy) } -); +} serde_string_impl_pk!(Policy, "a miniscript concrete policy"); #[rustfmt::skip] -impl_block_str!( - Policy, +impl Policy { /// Helper function for `from_tree` to parse subexpressions with /// names of the form x@y fn from_tree_prob(top: &expression::Tree, allow_prob: bool,) @@ -1050,14 +1048,13 @@ impl_block_str!( } .map(|res| (frag_prob, res)) } -); +} -impl_from_tree!( - Policy, +impl expression::FromTree for Policy { fn from_tree(top: &expression::Tree) -> Result, Error> { Policy::from_tree_prob(top, false).map(|(_, result)| result) } -); +} /// Creates a Huffman Tree from compiled [`Miniscript`] nodes. #[cfg(feature = "compiler")] diff --git a/src/policy/semantic.rs b/src/policy/semantic.rs index a1549c2b6..6bed0f710 100644 --- a/src/policy/semantic.rs +++ b/src/policy/semantic.rs @@ -308,21 +308,19 @@ impl fmt::Display for Policy { } } -impl_from_str!( - Policy, - type Err = Error;, +impl str::FromStr for Policy { + type Err = Error; fn from_str(s: &str) -> Result, Error> { expression::check_valid_chars(s)?; let tree = expression::Tree::from_str(s)?; expression::FromTree::from_tree(&tree) } -); +} serde_string_impl_pk!(Policy, "a miniscript semantic policy"); -impl_from_tree!( - Policy, +impl expression::FromTree for Policy { fn from_tree(top: &expression::Tree) -> Result, Error> { match (top.name, top.args.len()) { ("UNSATISFIABLE", 0) => Ok(Policy::Unsatisfiable), @@ -396,7 +394,7 @@ impl_from_tree!( _ => Err(errstr(top.name)), } } -); +} impl Policy { /// Flattens out trees of `And`s and `Or`s; eliminate `Trivial` and