diff --git a/src/descriptor/bare.rs b/src/descriptor/bare.rs index 3ff49cc39..3cb0da61e 100644 --- a/src/descriptor/bare.rs +++ b/src/descriptor/bare.rs @@ -370,11 +370,10 @@ impl Liftable for Pkh { impl FromTree for Pkh { fn from_tree(top: &expression::Tree) -> Result { - let top = top - .verify_toplevel("pkh", 1..=1) - .map_err(From::from) + let pk = top + .verify_terminal_parent("pkh", "public key") .map_err(Error::Parse)?; - Ok(Pkh::new(expression::terminal(top, |pk| Pk::from_str(pk))?)?) + Pkh::new(pk).map_err(Error::ContextError) } } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 72d2f57a2..ce7654958 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -27,8 +27,8 @@ use crate::miniscript::{satisfy, Legacy, Miniscript, Segwitv0}; use crate::plan::{AssetProvider, Plan}; use crate::prelude::*; use crate::{ - expression, hash256, BareCtx, Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, - ToPublicKey, TranslateErr, Translator, + expression, hash256, BareCtx, Error, ForEachKey, FromStrKey, MiniscriptKey, ParseError, + Satisfier, ToPublicKey, TranslateErr, Translator, }; mod bare; @@ -715,8 +715,9 @@ impl Descriptor { Some(sk), ), Err(_) => ( - DescriptorPublicKey::from_str(s) - .map_err(|e| Error::Unexpected(e.to_string()))?, + // try to parse as a public key if parsing as a secret key failed + s.parse() + .map_err(|e| Error::Parse(ParseError::box_from_str(e)))?, None, ), }; @@ -741,37 +742,34 @@ impl Descriptor { } fn sha256(&mut self, sha256: &String) -> Result { - let hash = - sha256::Hash::from_str(sha256).map_err(|e| Error::Unexpected(e.to_string()))?; - Ok(hash) + sha256 + .parse() + .map_err(|e| Error::Parse(ParseError::box_from_str(e))) } fn hash256(&mut self, hash256: &String) -> Result { - let hash = hash256::Hash::from_str(hash256) - .map_err(|e| Error::Unexpected(e.to_string()))?; - Ok(hash) + hash256 + .parse() + .map_err(|e| Error::Parse(ParseError::box_from_str(e))) } fn ripemd160(&mut self, ripemd160: &String) -> Result { - let hash = ripemd160::Hash::from_str(ripemd160) - .map_err(|e| Error::Unexpected(e.to_string()))?; - Ok(hash) + ripemd160 + .parse() + .map_err(|e| Error::Parse(ParseError::box_from_str(e))) } fn hash160(&mut self, hash160: &String) -> Result { - let hash = hash160::Hash::from_str(hash160) - .map_err(|e| Error::Unexpected(e.to_string()))?; - Ok(hash) + hash160 + .parse() + .map_err(|e| Error::Parse(ParseError::box_from_str(e))) } } let descriptor = Descriptor::::from_str(s)?; - let descriptor = descriptor.translate_pk(&mut keymap_pk).map_err(|e| { - Error::Unexpected( - e.expect_translator_err("No Outer context errors") - .to_string(), - ) - })?; + let descriptor = descriptor + .translate_pk(&mut keymap_pk) + .map_err(|e| e.expect_translator_err("No Outer context errors"))?; Ok((descriptor, keymap_pk.0)) } diff --git a/src/descriptor/segwitv0.rs b/src/descriptor/segwitv0.rs index bfb33b935..6679f062a 100644 --- a/src/descriptor/segwitv0.rs +++ b/src/descriptor/segwitv0.rs @@ -484,11 +484,10 @@ impl Liftable for Wpkh { impl crate::expression::FromTree for Wpkh { fn from_tree(top: &expression::Tree) -> Result { - let top = top - .verify_toplevel("wpkh", 1..=1) - .map_err(From::from) + let pk = top + .verify_terminal_parent("wpkh", "public key") .map_err(Error::Parse)?; - Ok(Wpkh::new(expression::terminal(top, |pk| Pk::from_str(pk))?)?) + Wpkh::new(pk).map_err(Error::ContextError) } } diff --git a/src/descriptor/sortedmulti.rs b/src/descriptor/sortedmulti.rs index 1aa96c094..fe0be4244 100644 --- a/src/descriptor/sortedmulti.rs +++ b/src/descriptor/sortedmulti.rs @@ -7,10 +7,10 @@ use core::fmt; use core::marker::PhantomData; -use core::str::FromStr; use bitcoin::script; +use crate::blanket_traits::FromStrKey; use crate::miniscript::context::ScriptContext; use crate::miniscript::decode::Terminal; use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG; @@ -61,8 +61,7 @@ impl SortedMultiVec { /// Parse an expression tree into a SortedMultiVec pub fn from_tree(tree: &expression::Tree) -> Result where - Pk: FromStr, - ::Err: fmt::Display, + Pk: FromStrKey, { tree.verify_toplevel("sortedmulti", 1..) .map_err(From::from) @@ -72,7 +71,8 @@ impl SortedMultiVec { inner: tree .to_null_threshold() .map_err(Error::ParseThreshold)? - .translate_by_index(|i| expression::terminal(&tree.args[i + 1], Pk::from_str))?, + .translate_by_index(|i| tree.args[i + 1].verify_terminal("public key")) + .map_err(Error::Parse)?, phantom: PhantomData, }; ret.constructor_check() @@ -230,6 +230,8 @@ impl fmt::Display for SortedMultiVec crate::expression::FromTree for Tr { } } else if item.index == 1 { // First child of tr, which must be the internal key - if !item.node.args.is_empty() { - return Err(Error::Parse(ParseError::Tree( - ParseTreeError::IncorrectNumberOfChildren { - description: "internal key", - n_children: item.node.args.len(), - minimum: Some(0), - maximum: Some(0), - }, - ))); - } - internal_key = Some( - Pk::from_str(item.node.name) - .map_err(ParseError::box_from_str) - .map_err(Error::Parse)?, - ); + internal_key = item + .node + .verify_terminal("internal key") + .map_err(Error::Parse) + .map(Some)?; } else { // From here on we are into the taptree. if item.n_children_yielded == 0 { diff --git a/src/error.rs b/src/error.rs index f66cd42f1..f4b3b1d8c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,16 +8,25 @@ use core::fmt; use std::error; use crate::blanket_traits::StaticDebugAndDisplay; +use crate::primitives::absolute_locktime::AbsLockTimeError; +use crate::primitives::relative_locktime::RelLockTimeError; use crate::Box; + /// An error parsing a Miniscript object (policy, descriptor or miniscript) /// from a string. #[derive(Debug)] pub enum ParseError { + /// Invalid absolute locktime + AbsoluteLockTime(AbsLockTimeError), + /// Invalid absolute locktime + RelativeLockTime(RelLockTimeError), /// Failed to parse a public key or hash. /// /// Note that the error information is lost for nostd compatibility reasons. See /// . FromStr(Box), + /// Failed to parse a number. + Num(crate::ParseNumError), /// Error parsing a string into an expression tree. Tree(crate::ParseTreeError), } @@ -29,6 +38,10 @@ impl ParseError { } } +impl From for ParseError { + fn from(e: crate::ParseNumError) -> Self { Self::Num(e) } +} + impl From for ParseError { fn from(e: crate::ParseTreeError) -> Self { Self::Tree(e) } } @@ -36,7 +49,10 @@ impl From for ParseError { impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { + ParseError::AbsoluteLockTime(ref e) => e.fmt(f), + ParseError::RelativeLockTime(ref e) => e.fmt(f), ParseError::FromStr(ref e) => e.fmt(f), + ParseError::Num(ref e) => e.fmt(f), ParseError::Tree(ref e) => e.fmt(f), } } @@ -46,7 +62,10 @@ impl fmt::Display for ParseError { impl error::Error for ParseError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { + ParseError::AbsoluteLockTime(ref e) => Some(e), + ParseError::RelativeLockTime(ref e) => Some(e), ParseError::FromStr(..) => None, + ParseError::Num(ref e) => Some(e), ParseError::Tree(ref e) => Some(e), } } diff --git a/src/expression/error.rs b/src/expression/error.rs index b9292a445..aec2f6539 100644 --- a/src/expression/error.rs +++ b/src/expression/error.rs @@ -2,7 +2,7 @@ //! Expression-related errors -use core::fmt; +use core::{fmt, num}; use crate::descriptor::checksum; use crate::prelude::*; @@ -76,6 +76,13 @@ pub enum ParseTreeError { /// The position of the opening curly brace. pos: usize, }, + /// Multiple separators (':' or '@') appeared in a node name. + MultipleSeparators { + /// The separator in question. + separator: char, + /// The position of the second separator. + pos: usize, + }, /// Data occurred after the final ). TrailingCharacter { /// The first trailing character. @@ -83,6 +90,11 @@ pub enum ParseTreeError { /// Its byte-index into the string. pos: usize, }, + /// A node's name was not recognized. + UnknownName { + /// The name that was not recognized. + name: String, + }, } impl From for ParseTreeError { @@ -144,9 +156,17 @@ impl fmt::Display for ParseTreeError { }?; write!(f, ", but found {}", n_children) } + ParseTreeError::MultipleSeparators { separator, pos } => { + write!( + f, + "separator '{}' occured multiple times (second time at position {})", + separator, pos + ) + } ParseTreeError::TrailingCharacter { ch, pos } => { write!(f, "trailing data `{}...` (position {})", ch, pos) } + ParseTreeError::UnknownName { name } => write!(f, "unrecognized name '{}'", name), } } } @@ -163,7 +183,39 @@ impl std::error::Error for ParseTreeError { | ParseTreeError::IllegalCurlyBrace { .. } | ParseTreeError::IncorrectName { .. } | ParseTreeError::IncorrectNumberOfChildren { .. } - | ParseTreeError::TrailingCharacter { .. } => None, + | ParseTreeError::MultipleSeparators { .. } + | ParseTreeError::TrailingCharacter { .. } + | ParseTreeError::UnknownName { .. } => None, + } + } +} + +/// Error parsing a number. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ParseNumError { + /// Failed to parse the number at all. + StdParse(num::ParseIntError), + /// Number had a leading zero, + or -. + InvalidLeadingDigit(char), +} + +impl fmt::Display for ParseNumError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ParseNumError::StdParse(ref e) => e.fmt(f), + ParseNumError::InvalidLeadingDigit(ch) => { + write!(f, "numbers must start with 1-9, not {}", ch) + } + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseNumError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + ParseNumError::StdParse(ref e) => Some(e), + ParseNumError::InvalidLeadingDigit(..) => None, } } } @@ -175,10 +227,12 @@ pub enum ParseThresholdError { NoChildren, /// The threshold value appeared to be a sub-expression rather than a number. KNotTerminal, + /// A 1-of-n threshold was used in a context it was not allowed. + IllegalOr, + /// A n-of-n threshold was used in a context it was not allowed. + IllegalAnd, /// Failed to parse the threshold value. - // FIXME this should be a more specific type. Will be handled in a later PR - // that rewrites the expression parsing logic. - ParseK(String), + ParseK(ParseNumError), /// Threshold parameters were invalid. Threshold(ThresholdError), } @@ -190,7 +244,13 @@ impl fmt::Display for ParseThresholdError { match *self { NoChildren => f.write_str("expected threshold, found terminal"), KNotTerminal => f.write_str("expected positive integer, found expression"), - ParseK(ref x) => write!(f, "failed to parse threshold value {}", x), + IllegalOr => f.write_str( + "1-of-n thresholds not allowed here; please use an 'or' fragment instead", + ), + IllegalAnd => f.write_str( + "n-of-n thresholds not allowed here; please use an 'and' fragment instead", + ), + ParseK(ref x) => write!(f, "failed to parse threshold value: {}", x), Threshold(ref e) => e.fmt(f), } } @@ -202,9 +262,8 @@ impl std::error::Error for ParseThresholdError { use ParseThresholdError::*; match *self { - NoChildren => None, - KNotTerminal => None, - ParseK(..) => None, + NoChildren | KNotTerminal | IllegalOr | IllegalAnd => None, + ParseK(ref e) => Some(e), Threshold(ref e) => Some(e), } } diff --git a/src/expression/mod.rs b/src/expression/mod.rs index 14978def2..8377da767 100644 --- a/src/expression/mod.rs +++ b/src/expression/mod.rs @@ -5,14 +5,15 @@ mod error; +use core::ops; use core::str::FromStr; -use core::{fmt, ops}; -pub use self::error::{ParseThresholdError, ParseTreeError}; +pub use self::error::{ParseNumError, ParseThresholdError, ParseTreeError}; +use crate::blanket_traits::StaticDebugAndDisplay; use crate::descriptor::checksum::verify_checksum; use crate::iter::{self, TreeLike}; use crate::prelude::*; -use crate::{errstr, Error, Threshold, MAX_RECURSION_DEPTH}; +use crate::{AbsLockTime, Error, ParseError, RelLockTime, Threshold, MAX_RECURSION_DEPTH}; /// Allowed characters are descriptor strings. pub const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "; @@ -78,6 +79,25 @@ pub trait FromTree: Sized { } impl<'a> Tree<'a> { + /// Split the name by a separating character. + /// + /// If the separator is present, returns the prefix before the separator and + /// the suffix after the separator. Otherwise returns the whole name. + /// + /// If the separator occurs multiple times, returns an error. + pub fn name_separated(&self, separator: char) -> Result<(Option<&str>, &str), ParseTreeError> { + let mut name_split = self.name.splitn(3, separator); + match (name_split.next(), name_split.next(), name_split.next()) { + (None, _, _) => unreachable!("'split' always yields at least one element"), + (Some(_), None, _) => Ok((None, self.name)), + (Some(prefix), Some(name), None) => Ok((Some(prefix), name)), + (Some(_), Some(_), Some(suffix)) => Err(ParseTreeError::MultipleSeparators { + separator, + pos: self.children_pos - suffix.len() - 1, + }), + } + } + /// Check that a tree node has the given number of children. /// /// The `description` argument is only used to populate the error return, @@ -137,6 +157,88 @@ impl<'a> Tree<'a> { } } + /// Check that a tree node has a single terminal child which is an absolute locktime. + /// + /// Returns an error assuming that the node is named "after". + /// + /// If so, parse the locktime from a string and return it. + pub fn verify_after(&self) -> Result { + self.verify_n_children("after", 1..=1) + .map_err(ParseError::Tree)?; + self.args[0] + .verify_n_children("absolute locktime", 0..=0) + .map_err(ParseError::Tree)?; + parse_num(self.args[0].name) + .map_err(ParseError::Num) + .and_then(|n| AbsLockTime::from_consensus(n).map_err(ParseError::AbsoluteLockTime)) + } + + /// Check that a tree node has a single terminal child which is a relative locktime. + /// + /// Returns an error assuming that the node is named "older". + /// + /// If so, parse the locktime from a string and return it. + pub fn verify_older(&self) -> Result { + self.verify_n_children("older", 1..=1) + .map_err(ParseError::Tree)?; + self.args[0] + .verify_n_children("relative locktime", 0..=0) + .map_err(ParseError::Tree)?; + parse_num(self.args[0].name) + .map_err(ParseError::Num) + .and_then(|n| RelLockTime::from_consensus(n).map_err(ParseError::RelativeLockTime)) + } + + /// Check that a tree node is a terminal (has no children). + /// + /// If so, parse the terminal from a string and return it. + /// + /// The `description` and `inner_description` arguments are only used to + /// populate the error return, and is not validated in any way. + pub fn verify_terminal(&self, description: &'static str) -> Result + where + T: FromStr, + T::Err: StaticDebugAndDisplay, + { + self.verify_n_children(description, 0..=0) + .map_err(ParseError::Tree)?; + T::from_str(self.name).map_err(ParseError::box_from_str) + } + + /// Check that a tree node has exactly one child, which is a terminal. + /// + /// If so, parse the terminal child from a string and return it. + /// + /// The `description` and `inner_description` arguments are only used to + /// populate the error return, and is not validated in any way. + pub fn verify_terminal_parent( + &self, + description: &'static str, + inner_description: &'static str, + ) -> Result + where + T: FromStr, + T::Err: StaticDebugAndDisplay, + { + self.verify_n_children(description, 1..=1) + .map_err(ParseError::Tree)?; + self.args[0].verify_terminal(inner_description) + } + + /// Check that a tree node has exactly two children. + /// + /// If so, return them. + /// + /// The `description` argument is only used to populate the error return, + /// and is not validated in any way. + pub fn verify_binary( + &self, + description: &'static str, + ) -> Result<(&Self, &Self), ParseTreeError> { + self.verify_n_children(description, 2..=2)?; + Ok((&self.args[0], &self.args[1])) + } + /// Check that a tree has no curly-brace children in it. pub fn verify_no_curly_braces(&self) -> Result<(), ParseTreeError> { for tree in self.pre_order_iter() { @@ -326,64 +428,23 @@ impl<'a> Tree<'a> { return Err(ParseThresholdError::KNotTerminal); } - let k = parse_num(self.args[0].name) - .map_err(|e| ParseThresholdError::ParseK(e.to_string()))? as usize; + let k = parse_num(self.args[0].name).map_err(ParseThresholdError::ParseK)? as usize; Threshold::new(k, vec![(); self.args.len() - 1]).map_err(ParseThresholdError::Threshold) } } /// Parse a string as a u32, for timelocks or thresholds -pub fn parse_num(s: &str) -> Result { - if s.len() > 1 { - let ch = s.chars().next().unwrap(); +pub fn parse_num(s: &str) -> Result { + if s == "0" { + // Special-case 0 since it is the only number which may start with a leading zero. + return Ok(0); + } + if let Some(ch) = s.chars().next() { if !('1'..='9').contains(&ch) { - return Err(Error::Unexpected("Number must start with a digit 1-9".to_string())); + return Err(ParseNumError::InvalidLeadingDigit(ch)); } } - u32::from_str(s).map_err(|_| errstr(s)) -} - -/// Attempts to parse a terminal expression -pub fn terminal(term: &Tree, convert: F) -> Result -where - F: FnOnce(&str) -> Result, - Err: fmt::Display, -{ - if term.args.is_empty() { - convert(term.name).map_err(|e| Error::Unexpected(e.to_string())) - } else { - Err(errstr(term.name)) - } -} - -/// Attempts to parse an expression with exactly one child -pub fn unary(term: &Tree, convert: F) -> Result -where - L: FromTree, - F: FnOnce(L) -> T, -{ - if term.args.len() == 1 { - let left = FromTree::from_tree(&term.args[0])?; - Ok(convert(left)) - } else { - Err(errstr(term.name)) - } -} - -/// Attempts to parse an expression with exactly two children -pub fn binary(term: &Tree, convert: F) -> Result -where - L: FromTree, - R: FromTree, - F: FnOnce(L, R) -> T, -{ - if term.args.len() == 2 { - let left = FromTree::from_tree(&term.args[0])?; - let right = FromTree::from_tree(&term.args[1])?; - Ok(convert(left, right)) - } else { - Err(errstr(term.name)) - } + u32::from_str(s).map_err(ParseNumError::StdParse) } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index fde7ac66b..8115afe13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,7 +137,7 @@ use bitcoin::{script, Opcode}; pub use crate::blanket_traits::FromStrKey; pub use crate::descriptor::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey}; pub use crate::error::ParseError; -pub use crate::expression::{ParseThresholdError, ParseTreeError}; +pub use crate::expression::{ParseNumError, ParseThresholdError, ParseTreeError}; pub use crate::interpreter::Interpreter; pub use crate::miniscript::analyzable::{AnalysisError, ExtParams}; pub use crate::miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0, SigType, Tap}; @@ -427,10 +427,6 @@ pub enum Error { UnexpectedStart, /// Got something we were not expecting Unexpected(String), - /// Name of a fragment contained `:` multiple times - MultiColon(String), - /// Name of a fragment contained `@` but we were not parsing an OR - AtOutsideOr(String), /// Encountered a wrapping character that we don't recognize UnknownWrapper(char), /// Parsed a miniscript and the result was not of type T @@ -449,8 +445,6 @@ pub enum Error { /// Compiler related errors CompilerError(crate::policy::compiler::CompilerError), /// Errors related to policy - SemanticPolicy(policy::semantic::PolicyError), - /// Errors related to policy ConcretePolicy(policy::concrete::PolicyError), /// Errors related to lifting LiftError(policy::LiftError), @@ -502,8 +496,6 @@ impl fmt::Display for Error { Error::AddrP2shError(ref e) => fmt::Display::fmt(e, f), Error::UnexpectedStart => f.write_str("unexpected start of script"), Error::Unexpected(ref s) => write!(f, "unexpected «{}»", s), - Error::MultiColon(ref s) => write!(f, "«{}» has multiple instances of «:»", s), - Error::AtOutsideOr(ref s) => write!(f, "«{}» contains «@» in non-or() context", s), Error::UnknownWrapper(ch) => write!(f, "unknown wrapper «{}:»", ch), Error::NonTopLevel(ref s) => write!(f, "non-T miniscript: {}", s), Error::Trailing(ref s) => write!(f, "trailing tokens: {}", s), @@ -514,7 +506,6 @@ impl fmt::Display for Error { Error::ContextError(ref e) => fmt::Display::fmt(e, f), #[cfg(feature = "compiler")] Error::CompilerError(ref e) => fmt::Display::fmt(e, f), - Error::SemanticPolicy(ref e) => fmt::Display::fmt(e, f), Error::ConcretePolicy(ref e) => fmt::Display::fmt(e, f), Error::LiftError(ref e) => fmt::Display::fmt(e, f), Error::MaxRecursiveDepthExceeded => write!( @@ -556,8 +547,6 @@ impl std::error::Error for Error { | InvalidPush(_) | UnexpectedStart | Unexpected(_) - | MultiColon(_) - | AtOutsideOr(_) | UnknownWrapper(_) | NonTopLevel(_) | Trailing(_) @@ -577,7 +566,6 @@ impl std::error::Error for Error { #[cfg(feature = "compiler")] CompilerError(e) => Some(e), ConcretePolicy(e) => Some(e), - SemanticPolicy(e) => Some(e), LiftError(e) => Some(e), ContextError(e) => Some(e), AnalysisError(e) => Some(e), @@ -632,8 +620,6 @@ impl From for Error { fn from(e: crate::policy::compiler::CompilerError) -> Error { Error::CompilerError(e) } } -fn errstr(s: &str) -> Error { Error::Unexpected(s.to_owned()) } - /// The size of an encoding of a number in Script pub fn script_num_size(n: usize) -> usize { match n { diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index 08ddd71d3..e5c20a5f6 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -7,9 +7,7 @@ //! encoding in Bitcoin script, as well as a datatype. Full details //! are given on the Miniscript website. -use core::str::FromStr; - -use bitcoin::hashes::{hash160, Hash}; +use bitcoin::hashes::Hash; use bitcoin::{absolute, opcodes, script}; use sync::Arc; @@ -17,10 +15,7 @@ use crate::miniscript::context::SigType; use crate::miniscript::ScriptContext; use crate::prelude::*; use crate::util::MsKeyBuilder; -use crate::{ - expression, AbsLockTime, Error, FromStrKey, Miniscript, MiniscriptKey, RelLockTime, Terminal, - ToPublicKey, -}; +use crate::{expression, Error, FromStrKey, Miniscript, MiniscriptKey, Terminal, ToPublicKey}; impl crate::expression::FromTree for Arc> { fn from_tree(top: &expression::Tree) -> Result>, Error> { @@ -30,79 +25,136 @@ impl crate::expression::FromTree for Arc crate::expression::FromTree for Terminal { fn from_tree(top: &expression::Tree) -> Result, Error> { - 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)) + let binary = + |node: &expression::Tree, name, termfn: fn(_, _) -> Self| -> Result { + node.verify_binary(name) + .map_err(From::from) + .map_err(Error::Parse) + .and_then(|(x, y)| { + let x = Arc::>::from_tree(x)?; + let y = Arc::>::from_tree(y)?; + Ok(termfn(x, y)) + }) + }; + + let (frag_wrap, frag_name) = top + .name_separated(':') + .map_err(From::from) + .map_err(Error::Parse)?; + // "pk" and "pkh" are aliases for "c:pk_k" and "c:pk_h" respectively. + let unwrapped = match frag_name { + "expr_raw_pkh" => top + .verify_terminal_parent("expr_raw_pkh", "public key hash") + .map(Terminal::RawPkH) + .map_err(Error::Parse), + "pk" => top + .verify_terminal_parent("pk", "public key") + .map(Terminal::PkK) + .map_err(Error::Parse) + .and_then(|term| Miniscript::from_ast(term)) + .map(|ms| Terminal::Check(Arc::new(ms))), + "pkh" => top + .verify_terminal_parent("pkh", "public key") + .map(Terminal::PkH) + .map_err(Error::Parse) + .and_then(|term| Miniscript::from_ast(term)) + .map(|ms| Terminal::Check(Arc::new(ms))), + "pk_k" => top + .verify_terminal_parent("pk_k", "public key") + .map(Terminal::PkK) + .map_err(Error::Parse), + "pk_h" => top + .verify_terminal_parent("pk_h", "public key") + .map(Terminal::PkH) + .map_err(Error::Parse), + "after" => top + .verify_after() + .map_err(Error::Parse) + .map(Terminal::After), + "older" => top + .verify_older() + .map_err(Error::Parse) + .map(Terminal::Older), + "sha256" => top + .verify_terminal_parent("sha256", "hash") + .map(Terminal::Sha256) + .map_err(Error::Parse), + "hash256" => top + .verify_terminal_parent("hash256", "hash") + .map(Terminal::Hash256) + .map_err(Error::Parse), + "ripemd160" => top + .verify_terminal_parent("ripemd160", "hash") + .map(Terminal::Ripemd160) + .map_err(Error::Parse), + "hash160" => top + .verify_terminal_parent("hash160", "hash") + .map(Terminal::Hash160) + .map_err(Error::Parse), + "1" => { + top.verify_n_children("1", 0..=0) + .map_err(From::from) + .map_err(Error::Parse)?; + Ok(Terminal::True) + } + "0" => { + top.verify_n_children("0", 0..=0) + .map_err(From::from) + .map_err(Error::Parse)?; + Ok(Terminal::False) } - ("pk_h", 1) => { - expression::terminal(&top.args[0], |x| Pk::from_str(x).map(Terminal::PkH)) + "and_v" => binary(top, "and_v", Terminal::AndV), + "and_b" => binary(top, "and_b", Terminal::AndB), + "and_n" => { + binary(top, "and_n", |x, y| Terminal::AndOr(x, y, Arc::new(Miniscript::FALSE))) } - ("after", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x) - .and_then(|x| AbsLockTime::from_consensus(x).map_err(Error::AbsoluteLockTime)) - .map(Terminal::After) - }), - ("older", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x) - .and_then(|x| RelLockTime::from_consensus(x).map_err(Error::RelativeLockTime)) - .map(Terminal::Older) - }), - ("sha256", 1) => expression::terminal(&top.args[0], |x| { - Pk::Sha256::from_str(x).map(Terminal::Sha256) - }), - ("hash256", 1) => expression::terminal(&top.args[0], |x| { - Pk::Hash256::from_str(x).map(Terminal::Hash256) - }), - ("ripemd160", 1) => expression::terminal(&top.args[0], |x| { - Pk::Ripemd160::from_str(x).map(Terminal::Ripemd160) - }), - ("hash160", 1) => expression::terminal(&top.args[0], |x| { - Pk::Hash160::from_str(x).map(Terminal::Hash160) - }), - ("1", 0) => Ok(Terminal::True), - ("0", 0) => Ok(Terminal::False), - ("and_v", 2) => expression::binary(top, Terminal::AndV), - ("and_b", 2) => expression::binary(top, Terminal::AndB), - ("and_n", 2) => Ok(Terminal::AndOr( - expression::FromTree::from_tree(&top.args[0])?, - expression::FromTree::from_tree(&top.args[1])?, - Arc::new(Miniscript::FALSE), - )), - ("andor", 3) => Ok(Terminal::AndOr( - expression::FromTree::from_tree(&top.args[0])?, - expression::FromTree::from_tree(&top.args[1])?, - expression::FromTree::from_tree(&top.args[2])?, - )), - ("or_b", 2) => expression::binary(top, Terminal::OrB), - ("or_d", 2) => expression::binary(top, Terminal::OrD), - ("or_c", 2) => expression::binary(top, Terminal::OrC), - ("or_i", 2) => expression::binary(top, Terminal::OrI), - ("thresh", _) => top + "andor" => { + top.verify_n_children("andor", 3..=3) + .map_err(From::from) + .map_err(Error::Parse)?; + let x = Arc::>::from_tree(&top.args[0])?; + let y = Arc::>::from_tree(&top.args[1])?; + let z = Arc::>::from_tree(&top.args[2])?; + Ok(Terminal::AndOr(x, y, z)) + } + "or_b" => binary(top, "or_b", Terminal::OrB), + "or_d" => binary(top, "or_d", Terminal::OrD), + "or_c" => binary(top, "or_c", Terminal::OrC), + "or_i" => binary(top, "or_i", Terminal::OrI), + "thresh" => top .to_null_threshold() .map_err(Error::ParseThreshold)? .translate_by_index(|i| Miniscript::from_tree(&top.args[1 + i]).map(Arc::new)) .map(Terminal::Thresh), - ("multi", _) => top + "multi" => top .to_null_threshold() .map_err(Error::ParseThreshold)? - .translate_by_index(|i| expression::terminal(&top.args[1 + i], Pk::from_str)) + .translate_by_index(|i| { + top.args[1 + i] + .verify_terminal("public key") + .map_err(Error::Parse) + }) .map(Terminal::Multi), - ("multi_a", _) => top + "multi_a" => top .to_null_threshold() .map_err(Error::ParseThreshold)? - .translate_by_index(|i| expression::terminal(&top.args[1 + i], Pk::from_str)) + .translate_by_index(|i| { + top.args[1 + i] + .verify_terminal("public key") + .map_err(Error::Parse) + }) .map(Terminal::MultiA), - _ => Err(Error::Unexpected(format!( - "{}({} args) while parsing Miniscript", - top.name, - top.args.len(), - ))), + x => Err(Error::Parse(crate::ParseError::Tree(crate::ParseTreeError::UnknownName { + name: x.to_owned(), + }))), }?; - let ms = super::wrap_into_miniscript(unwrapped, frag_wrap)?; + + if frag_wrap == Some("") { + return Err(Error::Parse(crate::ParseError::Tree( + crate::ParseTreeError::UnknownName { name: top.name.to_owned() }, + ))); + } + let ms = super::wrap_into_miniscript(unwrapped, frag_wrap.unwrap_or(""))?; Ok(ms.node) } } diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 763df9a31..b47d19e71 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -670,69 +670,13 @@ impl 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, + frag_wrap: &str, ) -> Result, Error> where Pk: MiniscriptKey, diff --git a/src/policy/concrete.rs b/src/policy/concrete.rs index deebe289c..3d202f2fc 100644 --- a/src/policy/concrete.rs +++ b/src/policy/concrete.rs @@ -27,8 +27,7 @@ use crate::sync::Arc; #[cfg(all(doc, not(feature = "compiler")))] use crate::Descriptor; use crate::{ - errstr, AbsLockTime, Error, ForEachKey, FromStrKey, MiniscriptKey, RelLockTime, Threshold, - Translator, + AbsLockTime, Error, ForEachKey, FromStrKey, MiniscriptKey, RelLockTime, Threshold, Translator, }; /// Maximum TapLeafs allowed in a compiled TapTree @@ -248,7 +247,7 @@ impl Policy { leaf_compilations.push((OrdF64(prob), compilation)); } if !leaf_compilations.is_empty() { - let tap_tree = with_huffman_tree::(leaf_compilations).unwrap(); + let tap_tree = with_huffman_tree::(leaf_compilations); Some(tap_tree) } else { // no policies remaining once the extracted key is skipped @@ -310,7 +309,7 @@ impl Policy { .collect(); if !leaf_compilations.is_empty() { - let tap_tree = with_huffman_tree::(leaf_compilations).unwrap(); + let tap_tree = with_huffman_tree::(leaf_compilations); Some(tap_tree) } else { // no policies remaining once the extracted key is skipped @@ -851,85 +850,89 @@ impl Policy { top: &expression::Tree, allow_prob: bool, ) -> Result<(usize, Policy), Error> { - let frag_prob; - let frag_name; - let mut name_split = top.name.split('@'); - match (name_split.next(), name_split.next(), name_split.next()) { - (None, _, _) => { - frag_prob = 1; - frag_name = ""; - } - (Some(name), None, _) => { - frag_prob = 1; - frag_name = name; - } - (Some(prob), Some(name), None) => { - if !allow_prob { - return Err(Error::AtOutsideOr(top.name.to_owned())); - } - frag_prob = expression::parse_num(prob)? as usize; - frag_name = name; + // When 'allow_prob' is true we parse '@' signs out of node names. + let (frag_prob, frag_name) = if allow_prob { + top.name_separated('@') + .map_err(From::from) + .map_err(Error::Parse)? + } else { + (None, top.name) + }; + + let frag_prob = match frag_prob { + None => 1, + Some(s) => expression::parse_num(s) + .map_err(From::from) + .map_err(Error::Parse)? as usize, + }; + + match frag_name { + "UNSATISFIABLE" => { + top.verify_n_children("UNSATISFIABLE", 0..=0) + .map_err(From::from) + .map_err(Error::Parse)?; + Ok(Policy::Unsatisfiable) } - (Some(_), Some(_), Some(_)) => { - return Err(Error::MultiColon(top.name.to_owned())); + "TRIVIAL" => { + top.verify_n_children("TRIVIAL", 0..=0) + .map_err(From::from) + .map_err(Error::Parse)?; + Ok(Policy::Trivial) } - } - match (frag_name, top.args.len() as u32) { - ("UNSATISFIABLE", 0) => Ok(Policy::Unsatisfiable), - ("TRIVIAL", 0) => Ok(Policy::Trivial), - ("pk", 1) => expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Policy::Key)), - ("after", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x) - .and_then(|x| AbsLockTime::from_consensus(x).map_err(Error::AbsoluteLockTime)) - .map(Policy::After) - }), - ("older", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x) - .and_then(|x| RelLockTime::from_consensus(x).map_err(Error::RelativeLockTime)) - .map(Policy::Older) - }), - ("sha256", 1) => expression::terminal(&top.args[0], |x| { - ::from_str(x).map(Policy::Sha256) - }), - ("hash256", 1) => expression::terminal(&top.args[0], |x| { - ::from_str(x).map(Policy::Hash256) - }), - ("ripemd160", 1) => expression::terminal(&top.args[0], |x| { - ::from_str(x).map(Policy::Ripemd160) - }), - ("hash160", 1) => expression::terminal(&top.args[0], |x| { - ::from_str(x).map(Policy::Hash160) - }), - ("and", _) => { - if top.args.len() != 2 { - return Err(Error::ConcretePolicy(PolicyError::NonBinaryArgAnd)); - } - let mut subs = Vec::with_capacity(top.args.len()); - for arg in &top.args { - subs.push(Arc::new(Policy::from_tree(arg)?)); - } + "pk" => top + .verify_terminal_parent("pk", "public key") + .map(Policy::Key) + .map_err(Error::Parse), + "after" => top.verify_after().map_err(Error::Parse).map(Policy::After), + "older" => top.verify_older().map_err(Error::Parse).map(Policy::Older), + "sha256" => top + .verify_terminal_parent("sha256", "hash") + .map(Policy::Sha256) + .map_err(Error::Parse), + "hash256" => top + .verify_terminal_parent("hash256", "hash") + .map(Policy::Hash256) + .map_err(Error::Parse), + "ripemd160" => top + .verify_terminal_parent("ripemd160", "hash") + .map(Policy::Ripemd160) + .map_err(Error::Parse), + "hash160" => top + .verify_terminal_parent("hash160", "hash") + .map(Policy::Hash160) + .map_err(Error::Parse), + "and" => { + top.verify_n_children("and", 2..=2) + .map_err(From::from) + .map_err(Error::Parse)?; + let subs = top + .args + .iter() + .map(|arg| Self::from_tree(arg).map(Arc::new)) + .collect::>()?; Ok(Policy::And(subs)) } - ("or", _) => { - if top.args.len() != 2 { - return Err(Error::ConcretePolicy(PolicyError::NonBinaryArgOr)); - } - let mut subs = Vec::with_capacity(top.args.len()); - for arg in &top.args { - subs.push(Policy::from_tree_prob(arg, true)?); - } - Ok(Policy::Or( - subs.into_iter() - .map(|(prob, sub)| (prob, Arc::new(sub))) - .collect(), - )) + "or" => { + top.verify_n_children("or", 2..=2) + .map_err(From::from) + .map_err(Error::Parse)?; + let subs = top + .args + .iter() + .map(|arg| { + Self::from_tree_prob(arg, true).map(|(prob, sub)| (prob, Arc::new(sub))) + }) + .collect::>()?; + Ok(Policy::Or(subs)) } - ("thresh", _) => top + "thresh" => top .to_null_threshold() .map_err(Error::ParseThreshold)? .translate_by_index(|i| Policy::from_tree(&top.args[1 + i]).map(Arc::new)) .map(Policy::Thresh), - _ => Err(errstr(top.name)), + x => Err(Error::Parse(crate::ParseError::Tree(crate::ParseTreeError::UnknownName { + name: x.to_owned(), + }))), } .map(|res| (frag_prob, res)) } @@ -943,30 +946,25 @@ impl expression::FromTree for Policy { /// Creates a Huffman Tree from compiled [`Miniscript`] nodes. #[cfg(feature = "compiler")] -fn with_huffman_tree( - ms: Vec<(OrdF64, Miniscript)>, -) -> Result, Error> { +fn with_huffman_tree(ms: Vec<(OrdF64, Miniscript)>) -> TapTree { let mut node_weights = BinaryHeap::<(Reverse, TapTree)>::new(); for (prob, script) in ms { node_weights.push((Reverse(prob), TapTree::Leaf(Arc::new(script)))); } - if node_weights.is_empty() { - return Err(errstr("Empty Miniscript compilation")); - } + assert_ne!(node_weights.len(), 0, "empty Miniscript compilation"); while node_weights.len() > 1 { - let (p1, s1) = node_weights.pop().expect("len must atleast be two"); - let (p2, s2) = node_weights.pop().expect("len must atleast be two"); + let (p1, s1) = node_weights.pop().expect("len must at least be two"); + let (p2, s2) = node_weights.pop().expect("len must at least be two"); let p = (p1.0).0 + (p2.0).0; node_weights.push((Reverse(OrdF64(p)), TapTree::combine(s1, s2))); } debug_assert!(node_weights.len() == 1); - let node = node_weights + node_weights .pop() .expect("huffman tree algorithm is broken") - .1; - Ok(node) + .1 } /// Enumerates a [`Policy::Thresh(k, ..n..)`] into `n` different thresh's. diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 974516dac..0b2d0c10a 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -308,11 +308,11 @@ mod tests { ); assert_eq!( ConcretePol::from_str("and(pk())").unwrap_err().to_string(), - "And policy fragment must take 2 arguments" + "and must have 2 children, but found 1" ); assert_eq!( ConcretePol::from_str("or(pk())").unwrap_err().to_string(), - "Or policy fragment must take 2 arguments" + "or must have 2 children, but found 1" ); // these weird "unexpected" wrapping of errors will go away in a later PR // which rewrites the expression parser @@ -320,14 +320,14 @@ mod tests { ConcretePol::from_str("thresh(3,after(0),pk(),pk())") .unwrap_err() .to_string(), - "unexpected «absolute locktimes in Miniscript have a minimum value of 1»", + "absolute locktimes in Miniscript have a minimum value of 1", ); assert_eq!( ConcretePol::from_str("thresh(2,older(2147483650),pk(),pk())") .unwrap_err() .to_string(), - "unexpected «locktime value 2147483650 is not a valid BIP68 relative locktime»" + "locktime value 2147483650 is not a valid BIP68 relative locktime" ); } diff --git a/src/policy/semantic.rs b/src/policy/semantic.rs index 2eca31350..865770bff 100644 --- a/src/policy/semantic.rs +++ b/src/policy/semantic.rs @@ -5,10 +5,7 @@ //! We use the terms "semantic" and "abstract" interchangeably because //! "abstract" is a reserved keyword in Rust. -use core::str::FromStr; use core::{fmt, str}; -#[cfg(feature = "std")] -use std::error; use bitcoin::{absolute, relative}; @@ -17,8 +14,8 @@ use crate::iter::{Tree, TreeLike}; use crate::prelude::*; use crate::sync::Arc; use crate::{ - errstr, expression, AbsLockTime, Error, ForEachKey, FromStrKey, MiniscriptKey, RelLockTime, - Threshold, Translator, + expression, AbsLockTime, Error, ForEachKey, FromStrKey, MiniscriptKey, RelLockTime, Threshold, + Translator, }; /// Abstract policy which corresponds to the semantics of a miniscript and @@ -51,44 +48,6 @@ pub enum Policy { Thresh(Threshold>, 0>), } -/// Detailed error type for concrete policies. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] -pub enum PolicyError { - /// Semantic Policy Error: `And` `Or` fragments must take args: `k > 1`. - InsufficientArgsforAnd, - /// Semantic policy error: `And` `Or` fragments must take args: `k > 1`. - InsufficientArgsforOr, - /// Entailment max terminals exceeded. - EntailmentMaxTerminals, -} - -impl fmt::Display for PolicyError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - PolicyError::InsufficientArgsforAnd => { - f.write_str("Semantic Policy 'And' fragment must have at least 2 args ") - } - PolicyError::InsufficientArgsforOr => { - f.write_str("Semantic Policy 'Or' fragment must have at least 2 args ") - } - PolicyError::EntailmentMaxTerminals => { - write!(f, "Policy entailment only supports {} terminals", ENTAILMENT_MAX_TERMINALS) - } - } - } -} - -#[cfg(feature = "std")] -impl error::Error for PolicyError { - fn cause(&self) -> Option<&dyn error::Error> { - match self { - PolicyError::InsufficientArgsforAnd - | PolicyError::InsufficientArgsforOr - | PolicyError::EntailmentMaxTerminals => None, - } - } -} - impl ForEachKey for Policy { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool { self.pre_order_iter().all(|policy| match policy { @@ -177,17 +136,20 @@ impl Policy { /// /// This implementation will run slowly for larger policies but should be /// sufficient for most practical policies. + /// + /// Returns None for very large policies for which entailment cannot + /// be practically computed. // This algorithm has a naive implementation. It is possible to optimize this // by memoizing and maintaining a hashmap. - pub fn entails(self, other: Policy) -> Result { + pub fn entails(self, other: Policy) -> Option { if self.n_terminals() > ENTAILMENT_MAX_TERMINALS { - return Err(PolicyError::EntailmentMaxTerminals); + return None; } match (self, other) { - (Policy::Unsatisfiable, _) => Ok(true), - (Policy::Trivial, Policy::Trivial) => Ok(true), - (Policy::Trivial, _) => Ok(false), - (_, Policy::Unsatisfiable) => Ok(false), + (Policy::Unsatisfiable, _) => Some(true), + (Policy::Trivial, Policy::Trivial) => Some(true), + (Policy::Trivial, _) => Some(false), + (_, Policy::Unsatisfiable) => Some(false), (a, b) => { let (a_norm, b_norm) = (a.normalized(), b.normalized()); let first_constraint = a_norm.first_constraint(); @@ -199,7 +161,7 @@ impl Policy { a_norm.satisfy_constraint(&first_constraint, false), b_norm.satisfy_constraint(&first_constraint, false), ); - Ok(Policy::entails(a1, b1)? && Policy::entails(a2, b2)?) + Some(Policy::entails(a1, b1)? && Policy::entails(a2, b2)?) } } } @@ -323,67 +285,81 @@ serde_string_impl_pk!(Policy, "a miniscript semantic 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), - ("TRIVIAL", 0) => Ok(Policy::Trivial), - ("pk", 1) => expression::terminal(&top.args[0], |pk| Pk::from_str(pk).map(Policy::Key)), - ("after", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x) - .and_then(|x| AbsLockTime::from_consensus(x).map_err(Error::AbsoluteLockTime)) - .map(Policy::After) - }), - ("older", 1) => expression::terminal(&top.args[0], |x| { - expression::parse_num(x) - .and_then(|x| RelLockTime::from_consensus(x).map_err(Error::RelativeLockTime)) - .map(Policy::Older) - }), - ("sha256", 1) => { - expression::terminal(&top.args[0], |x| Pk::Sha256::from_str(x).map(Policy::Sha256)) + match top.name { + "UNSATISFIABLE" => { + top.verify_n_children("UNSATISFIABLE", 0..=0) + .map_err(From::from) + .map_err(Error::Parse)?; + Ok(Policy::Unsatisfiable) } - ("hash256", 1) => expression::terminal(&top.args[0], |x| { - Pk::Hash256::from_str(x).map(Policy::Hash256) - }), - ("ripemd160", 1) => expression::terminal(&top.args[0], |x| { - Pk::Ripemd160::from_str(x).map(Policy::Ripemd160) - }), - ("hash160", 1) => expression::terminal(&top.args[0], |x| { - Pk::Hash160::from_str(x).map(Policy::Hash160) - }), - ("and", nsubs) => { - if nsubs < 2 { - return Err(Error::SemanticPolicy(PolicyError::InsufficientArgsforAnd)); - } - let mut subs = Vec::with_capacity(nsubs); - for arg in &top.args { - subs.push(Arc::new(Policy::from_tree(arg)?)); - } - Ok(Policy::Thresh(Threshold::new(nsubs, subs).map_err(Error::Threshold)?)) + "TRIVIAL" => { + top.verify_n_children("TRIVIAL", 0..=0) + .map_err(From::from) + .map_err(Error::Parse)?; + Ok(Policy::Trivial) } - ("or", nsubs) => { - if nsubs < 2 { - return Err(Error::SemanticPolicy(PolicyError::InsufficientArgsforOr)); - } - let mut subs = Vec::with_capacity(nsubs); - for arg in &top.args { - subs.push(Arc::new(Policy::from_tree(arg)?)); - } + "pk" => top + .verify_terminal_parent("pk", "public key") + .map(Policy::Key) + .map_err(Error::Parse), + "after" => top.verify_after().map_err(Error::Parse).map(Policy::After), + "older" => top.verify_older().map_err(Error::Parse).map(Policy::Older), + "sha256" => top + .verify_terminal_parent("sha256", "hash") + .map(Policy::Sha256) + .map_err(Error::Parse), + "hash256" => top + .verify_terminal_parent("hash256", "hash") + .map(Policy::Hash256) + .map_err(Error::Parse), + "ripemd160" => top + .verify_terminal_parent("ripemd160", "hash") + .map(Policy::Ripemd160) + .map_err(Error::Parse), + "hash160" => top + .verify_terminal_parent("hash160", "hash") + .map(Policy::Hash160) + .map_err(Error::Parse), + "and" => { + top.verify_n_children("and", 2..) + .map_err(From::from) + .map_err(Error::Parse)?; + let subs = top + .args + .iter() + .map(|arg| Self::from_tree(arg).map(Arc::new)) + .collect::, Error>>()?; + Ok(Policy::Thresh(Threshold::new(subs.len(), subs).map_err(Error::Threshold)?)) + } + "or" => { + top.verify_n_children("or", 2..) + .map_err(From::from) + .map_err(Error::Parse)?; + let subs = top + .args + .iter() + .map(|arg| Self::from_tree(arg).map(Arc::new)) + .collect::, Error>>()?; Ok(Policy::Thresh(Threshold::new(1, subs).map_err(Error::Threshold)?)) } - ("thresh", _) => { + "thresh" => { let thresh = top.to_null_threshold().map_err(Error::ParseThreshold)?; // thresh(1) and thresh(n) are disallowed in semantic policies - if thresh.is_or() || thresh.is_and() { - return Err(errstr( - "Semantic Policy thresh cannot have k = 1 or k = n, use `and`/`or` instead", - )); + if thresh.is_or() { + return Err(Error::ParseThreshold(crate::ParseThresholdError::IllegalOr)); + } + if thresh.is_and() { + return Err(Error::ParseThreshold(crate::ParseThresholdError::IllegalAnd)); } thresh .translate_by_index(|i| Policy::from_tree(&top.args[1 + i]).map(Arc::new)) .map(Policy::Thresh) } - _ => Err(errstr(top.name)), + x => Err(Error::Parse(crate::ParseError::Tree(crate::ParseTreeError::UnknownName { + name: x.to_owned(), + }))), } } } @@ -683,6 +659,8 @@ impl<'a, Pk: MiniscriptKey> TreeLike for &'a Arc> { #[cfg(test)] mod tests { + use core::str::FromStr as _; + use bitcoin::PublicKey; use super::*;