diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index bea539920..ef4b18882 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -1040,18 +1040,17 @@ mod tests { StdDescriptor::from_str("(\u{7f}()3").unwrap_err(); StdDescriptor::from_str("pk()").unwrap_err(); StdDescriptor::from_str("nl:0").unwrap_err(); //issue 63 - let compressed_pk = String::from(""); assert_eq!( StdDescriptor::from_str("sh(sortedmulti)") .unwrap_err() .to_string(), - "unexpected «no arguments given for sortedmulti»" + "expected threshold, found terminal", ); //issue 202 assert_eq!( - StdDescriptor::from_str(&format!("sh(sortedmulti(2,{}))", compressed_pk)) + StdDescriptor::from_str(&format!("sh(sortedmulti(2,{}))", &TEST_PK[3..69])) .unwrap_err() .to_string(), - "unexpected «higher threshold than there were keys in sortedmulti»" + "invalid threshold 2-of-1; cannot have k > n", ); //issue 202 StdDescriptor::from_str(TEST_PK).unwrap(); diff --git a/src/descriptor/sortedmulti.rs b/src/descriptor/sortedmulti.rs index 1a97790c0..11101c5bb 100644 --- a/src/descriptor/sortedmulti.rs +++ b/src/descriptor/sortedmulti.rs @@ -19,62 +19,55 @@ use crate::plan::AssetProvider; use crate::prelude::*; use crate::sync::Arc; use crate::{ - errstr, expression, policy, script_num_size, Error, ForEachKey, Miniscript, MiniscriptKey, - Satisfier, ToPublicKey, TranslateErr, Translator, + expression, policy, script_num_size, Error, ForEachKey, Miniscript, MiniscriptKey, Satisfier, + Threshold, ToPublicKey, TranslateErr, Translator, }; /// Contents of a "sortedmulti" descriptor #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct SortedMultiVec { - /// signatures required - pub k: usize, - /// public keys inside sorted Multi - pub pks: Vec, + inner: Threshold, /// The current ScriptContext for sortedmulti - pub(crate) phantom: PhantomData, + phantom: PhantomData, } impl SortedMultiVec { - /// Create a new instance of `SortedMultiVec` given a list of keys and the threshold - /// - /// Internally checks all the applicable size limits and pubkey types limitations according to the current `Ctx`. - pub fn new(k: usize, pks: Vec) -> Result { - // A sortedmulti() is only defined for <= 20 keys (it maps to CHECKMULTISIG) - if pks.len() > MAX_PUBKEYS_PER_MULTISIG { - return Err(Error::BadDescriptor("Too many public keys".to_string())); - } - + fn constructor_check(&self) -> Result<(), Error> { // Check the limits before creating a new SortedMultiVec // For example, under p2sh context the scriptlen can only be // upto 520 bytes. - let term: Terminal = Terminal::Multi(k, pks.clone()); + let term: Terminal = Terminal::Multi(self.inner.k(), self.inner.data().to_owned()); let ms = Miniscript::from_ast(term)?; - // This would check all the consensus rules for p2sh/p2wsh and // even tapscript in future - Ctx::check_local_validity(&ms)?; + Ctx::check_local_validity(&ms).map_err(From::from) + } - Ok(Self { k, pks, phantom: PhantomData }) + /// Create a new instance of `SortedMultiVec` given a list of keys and the threshold + /// + /// Internally checks all the applicable size limits and pubkey types limitations according to the current `Ctx`. + pub fn new(k: usize, pks: Vec) -> Result { + let ret = + Self { inner: Threshold::new(k, pks).map_err(Error::Threshold)?, phantom: PhantomData }; + ret.constructor_check()?; + Ok(ret) } + /// Parse an expression tree into a SortedMultiVec pub fn from_tree(tree: &expression::Tree) -> Result where Pk: FromStr, ::Err: fmt::Display, { - if tree.args.is_empty() { - return Err(errstr("no arguments given for sortedmulti")); - } - let k = expression::parse_num(tree.args[0].name)?; - if k > (tree.args.len() - 1) as u32 { - return Err(errstr("higher threshold than there were keys in sortedmulti")); - } - let pks: Result, _> = tree.args[1..] - .iter() - .map(|sub| expression::terminal(sub, Pk::from_str)) - .collect(); - - pks.map(|pks| SortedMultiVec::new(k as usize, pks))? + let ret = Self { + inner: tree + .to_null_threshold() + .map_err(Error::ParseThreshold)? + .translate_by_index(|i| expression::terminal(&tree.args[i + 1], Pk::from_str))?, + phantom: PhantomData, + }; + ret.constructor_check()?; + Ok(ret) } /// This will panic if fpk returns an uncompressed key when @@ -88,15 +81,31 @@ impl SortedMultiVec { T: Translator, Q: MiniscriptKey, { - let pks: Result, _> = self.pks.iter().map(|pk| t.pk(pk)).collect(); - let res = SortedMultiVec::new(self.k, pks?).map_err(TranslateErr::OuterError)?; - Ok(res) + let ret = SortedMultiVec { + inner: self.inner.translate_ref(|pk| t.pk(pk))?, + phantom: PhantomData, + }; + ret.constructor_check().map_err(TranslateErr::OuterError)?; + Ok(ret) } + + /// The threshold value for the multisig. + pub fn k(&self) -> usize { self.inner.k() } + + /// The number of keys in the multisig. + pub fn n(&self) -> usize { self.inner.n() } + + /// Accessor for the public keys in the multisig. + /// + /// The keys in this structure might **not** be sorted. In general, they cannot be + /// sorted until they are converted to consensus-encoded public keys, which may not + /// be possible (for example for BIP32 paths with unfilled wildcards). + pub fn pks(&self) -> &[Pk] { self.inner.data() } } impl ForEachKey for SortedMultiVec { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool { - self.pks.iter().all(pred) + self.pks().iter().all(pred) } } @@ -104,7 +113,7 @@ impl SortedMultiVec { /// utility function to sanity a sorted multi vec pub fn sanity_check(&self) -> Result<(), Error> { let ms: Miniscript = - Miniscript::from_ast(Terminal::Multi(self.k, self.pks.clone())) + Miniscript::from_ast(Terminal::Multi(self.k(), self.pks().to_owned())) .expect("Must typecheck"); // '?' for doing From conversion ms.sanity_check()?; @@ -118,7 +127,7 @@ impl SortedMultiVec { where Pk: ToPublicKey, { - let mut pks = self.pks.clone(); + let mut pks = self.pks().to_owned(); // Sort pubkeys lexicographically according to BIP 67 pks.sort_by(|a, b| { a.to_public_key() @@ -127,7 +136,7 @@ impl SortedMultiVec { .partial_cmp(&b.to_public_key().inner.serialize()) .unwrap() }); - Terminal::Multi(self.k, pks) + Terminal::Multi(self.k(), pks) } /// Encode as a Bitcoin script @@ -169,10 +178,10 @@ impl SortedMultiVec { /// to instead call the corresponding function on a `Descriptor`, which /// will handle the segwit/non-segwit technicalities for you. pub fn script_size(&self) -> usize { - script_num_size(self.k) + script_num_size(self.k()) + 1 - + script_num_size(self.pks.len()) - + self.pks.iter().map(|pk| Ctx::pk_len(pk)).sum::() + + script_num_size(self.n()) + + self.pks().iter().map(|pk| Ctx::pk_len(pk)).sum::() } /// Maximum number of witness elements used to satisfy the Miniscript @@ -183,7 +192,7 @@ impl SortedMultiVec { /// This function may panic on malformed `Miniscript` objects which do /// not correspond to semantically sane Scripts. (Such scripts should be /// rejected at parse time. Any exceptions are bugs.) - pub fn max_satisfaction_witness_elements(&self) -> usize { 2 + self.k } + pub fn max_satisfaction_witness_elements(&self) -> usize { 2 + self.k() } /// Maximum size, in bytes, of a satisfying witness. /// In general, it is not recommended to use this function directly, but @@ -193,19 +202,16 @@ impl SortedMultiVec { /// All signatures are assumed to be 73 bytes in size, including the /// length prefix (segwit) or push opcode (pre-segwit) and sighash /// postfix. - pub fn max_satisfaction_size(&self) -> usize { 1 + 73 * self.k } + pub fn max_satisfaction_size(&self) -> usize { 1 + 73 * self.k() } } impl policy::Liftable for SortedMultiVec { fn lift(&self) -> Result, Error> { - let ret = policy::semantic::Policy::Thresh( - self.k, - self.pks - .iter() - .map(|k| Arc::new(policy::semantic::Policy::Key(k.clone()))) - .collect(), - ); - Ok(ret) + Ok(policy::semantic::Policy::Thresh( + self.inner + .map_ref(|pk| Arc::new(policy::semantic::Policy::Key(pk.clone()))) + .forget_maximum(), + )) } } @@ -215,11 +221,7 @@ impl fmt::Debug for SortedMultiVec fmt::Display for SortedMultiVec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "sortedmulti({}", self.k)?; - for k in &self.pks { - write!(f, ",{}", k)?; - } - f.write_str(")") + fmt::Display::fmt(&self.inner.display("sortedmulti", true), f) } } @@ -249,7 +251,7 @@ mod tests { let error = res.expect_err("constructor should err"); match error { - Error::BadDescriptor(_) => {} // ok + Error::Threshold(_) => {} // ok other => panic!("unexpected error: {:?}", other), } } diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 0bce1ecf0..c9ab851d1 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -23,7 +23,7 @@ use crate::policy::Liftable; use crate::prelude::*; use crate::util::{varint_len, witness_size}; use crate::{ - errstr, Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, ScriptContext, Tap, + errstr, Error, ForEachKey, FromStrKey, MiniscriptKey, Satisfier, ScriptContext, Tap, Threshold, ToPublicKey, TranslateErr, TranslatePk, Translator, }; @@ -617,8 +617,7 @@ impl Liftable for TapTree { fn lift_helper(s: &TapTree) -> Result, Error> { match *s { TapTree::Tree { ref left, ref right, height: _ } => Ok(Policy::Thresh( - 1, - vec![Arc::new(lift_helper(left)?), Arc::new(lift_helper(right)?)], + Threshold::or(Arc::new(lift_helper(left)?), Arc::new(lift_helper(right)?)), )), TapTree::Leaf(ref leaf) => leaf.lift(), } @@ -632,13 +631,10 @@ impl Liftable for TapTree { impl Liftable for Tr { fn lift(&self) -> Result, Error> { match &self.tree { - Some(root) => Ok(Policy::Thresh( - 1, - vec![ - Arc::new(Policy::Key(self.internal_key.clone())), - Arc::new(root.lift()?), - ], - )), + Some(root) => Ok(Policy::Thresh(Threshold::or( + Arc::new(Policy::Key(self.internal_key.clone())), + Arc::new(root.lift()?), + ))), None => Ok(Policy::Key(self.internal_key.clone())), } } diff --git a/src/expression/error.rs b/src/expression/error.rs new file mode 100644 index 000000000..f82358647 --- /dev/null +++ b/src/expression/error.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Expression-related errors + +use core::fmt; + +use crate::prelude::*; +use crate::ThresholdError; + +/// Error parsing a threshold expression. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ParseThresholdError { + /// Expression had no children, not even a threshold value. + NoChildren, + /// The threshold value appeared to be a sub-expression rather than a number. + KNotTerminal, + /// 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), + /// Threshold parameters were invalid. + Threshold(ThresholdError), +} + +impl fmt::Display for ParseThresholdError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use 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), + Threshold(ref e) => e.fmt(f), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ParseThresholdError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use ParseThresholdError::*; + + match *self { + NoChildren => None, + KNotTerminal => None, + ParseK(..) => None, + Threshold(ref e) => Some(e), + } + } +} diff --git a/src/expression.rs b/src/expression/mod.rs similarity index 86% rename from src/expression.rs rename to src/expression/mod.rs index 3db95a44a..06c4fa6bb 100644 --- a/src/expression.rs +++ b/src/expression/mod.rs @@ -2,11 +2,15 @@ //! # Function-like Expression Language //! + +mod error; + use core::fmt; use core::str::FromStr; +pub use self::error::ParseThresholdError; use crate::prelude::*; -use crate::{errstr, Error, MAX_RECURSION_DEPTH}; +use crate::{errstr, Error, Threshold, MAX_RECURSION_DEPTH}; /// Allowed characters are descriptor strings. pub const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "; @@ -185,6 +189,35 @@ impl<'a> Tree<'a> { Err(errstr(rem)) } } + + /// Parses an expression tree as a threshold (a term with at least one child, + /// the first of which is a positive integer k). + /// + /// This sanity-checks that the threshold is well-formed (begins with a valid + /// threshold value, etc.) but does not parse the children of the threshold. + /// Instead it returns a threshold holding the empty type `()`, which is + /// constructed without any allocations, and expects the caller to convert + /// this to the "real" threshold type by calling [`Threshold::translate`]. + /// + /// (An alternate API which does the conversion inline turned out to be + /// too messy; it needs to take a closure, have multiple generic parameters, + /// and be able to return multiple error types.) + pub fn to_null_threshold( + &self, + ) -> Result, ParseThresholdError> { + // First, special case "no arguments" so we can index the first argument without panics. + if self.args.is_empty() { + return Err(ParseThresholdError::NoChildren); + } + + if !self.args[0].args.is_empty() { + return Err(ParseThresholdError::KNotTerminal); + } + + let k = parse_num(self.args[0].name) + .map_err(|e| ParseThresholdError::ParseK(e.to_string()))? as usize; + Threshold::new(k, vec![(); self.args.len() - 1]).map_err(ParseThresholdError::Threshold) + } } /// Filter out non-ASCII because we byte-index strings all over the diff --git a/src/lib.rs b/src/lib.rs index 6c644e6e4..0eb8782d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,6 +143,7 @@ use bitcoin::{script, Opcode}; pub use crate::blanket_traits::FromStrKey; pub use crate::descriptor::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey}; +pub use crate::expression::ParseThresholdError; pub use crate::interpreter::Interpreter; pub use crate::miniscript::analyzable::{AnalysisError, ExtParams}; pub use crate::miniscript::context::{BareCtx, Legacy, ScriptContext, Segwitv0, SigType, Tap}; @@ -152,6 +153,7 @@ pub use crate::miniscript::{hash256, Miniscript}; use crate::prelude::*; pub use crate::primitives::absolute_locktime::{AbsLockTime, AbsLockTimeError}; pub use crate::primitives::relative_locktime::{RelLockTime, RelLockTimeError}; +pub use crate::primitives::threshold::{Threshold, ThresholdError}; /// Public key trait which can be converted to Hash type pub trait MiniscriptKey: Clone + Eq + Ord + fmt::Debug + fmt::Display + hash::Hash { @@ -492,6 +494,10 @@ pub enum Error { AbsoluteLockTime(AbsLockTimeError), /// Invalid absolute locktime RelativeLockTime(RelLockTimeError), + /// Invalid threshold. + Threshold(ThresholdError), + /// Invalid threshold. + ParseThreshold(ParseThresholdError), } // https://github.com/sipa/miniscript/pull/5 for discussion on this number @@ -549,6 +555,8 @@ impl fmt::Display for Error { Error::MultipathDescLenMismatch => write!(f, "At least two BIP389 key expressions in the descriptor contain tuples of derivation indexes of different lengths"), Error::AbsoluteLockTime(ref e) => e.fmt(f), Error::RelativeLockTime(ref e) => e.fmt(f), + Error::Threshold(ref e) => e.fmt(f), + Error::ParseThreshold(ref e) => e.fmt(f), } } } @@ -595,6 +603,8 @@ impl error::Error for Error { PubKeyCtxError(e, _) => Some(e), AbsoluteLockTime(e) => Some(e), RelativeLockTime(e) => Some(e), + Threshold(e) => Some(e), + ParseThreshold(e) => Some(e), } } } diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 5b8a33192..db7274f6b 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -25,7 +25,7 @@ use crate::miniscript::{Miniscript, ScriptContext}; use crate::sync::Arc; #[cfg(all(not(feature = "std"), not(test)))] use crate::Vec; -use crate::{Error, MiniscriptKey, Terminal}; +use crate::{Error, MiniscriptKey, Terminal, Threshold}; /// Policy entailment algorithm maximum number of terminals allowed. const ENTAILMENT_MAX_TERMINALS: usize = 20; @@ -138,35 +138,40 @@ impl Liftable for Terminal { | Terminal::NonZero(ref sub) | Terminal::ZeroNotEqual(ref sub) => sub.node.lift()?, Terminal::AndV(ref left, ref right) | Terminal::AndB(ref left, ref right) => { - Semantic::Thresh(2, vec![Arc::new(left.node.lift()?), Arc::new(right.node.lift()?)]) + Semantic::Thresh(Threshold::and( + Arc::new(left.node.lift()?), + Arc::new(right.node.lift()?), + )) } - Terminal::AndOr(ref a, ref b, ref c) => Semantic::Thresh( - 1, - vec![ - Arc::new(Semantic::Thresh( - 2, - vec![Arc::new(a.node.lift()?), Arc::new(b.node.lift()?)], - )), - Arc::new(c.node.lift()?), - ], - ), + Terminal::AndOr(ref a, ref b, ref c) => Semantic::Thresh(Threshold::or( + Arc::new(Semantic::Thresh(Threshold::and( + Arc::new(a.node.lift()?), + Arc::new(b.node.lift()?), + ))), + Arc::new(c.node.lift()?), + )), Terminal::OrB(ref left, ref right) | Terminal::OrD(ref left, ref right) | Terminal::OrC(ref left, ref right) - | Terminal::OrI(ref left, ref right) => { - Semantic::Thresh(1, vec![Arc::new(left.node.lift()?), Arc::new(right.node.lift()?)]) - } + | Terminal::OrI(ref left, ref right) => Semantic::Thresh(Threshold::or( + Arc::new(left.node.lift()?), + Arc::new(right.node.lift()?), + )), Terminal::Thresh(k, ref subs) => { let semantic_subs: Result>, Error> = subs.iter().map(|s| s.node.lift()).collect(); let semantic_subs = semantic_subs?.into_iter().map(Arc::new).collect(); - Semantic::Thresh(k, semantic_subs) + // unwrap to be removed in a later commit + Semantic::Thresh(Threshold::new(k, semantic_subs).unwrap()) } Terminal::Multi(k, ref keys) | Terminal::MultiA(k, ref keys) => Semantic::Thresh( - k, - keys.iter() - .map(|k| Arc::new(Semantic::Key(k.clone()))) - .collect(), + Threshold::new( + k, + keys.iter() + .map(|k| Arc::new(Semantic::Key(k.clone()))) + .collect(), + ) + .unwrap(), // unwrap to be removed in a later commit ), } .normalized(); @@ -210,19 +215,19 @@ impl Liftable for Concrete { let semantic_subs: Result>, Error> = subs.iter().map(Liftable::lift).collect(); let semantic_subs = semantic_subs?.into_iter().map(Arc::new).collect(); - Semantic::Thresh(2, semantic_subs) + Semantic::Thresh(Threshold::new(2, semantic_subs).unwrap()) } Concrete::Or(ref subs) => { let semantic_subs: Result>, Error> = subs.iter().map(|(_p, sub)| sub.lift()).collect(); let semantic_subs = semantic_subs?.into_iter().map(Arc::new).collect(); - Semantic::Thresh(1, semantic_subs) + Semantic::Thresh(Threshold::new(1, semantic_subs).unwrap()) } Concrete::Thresh(k, ref subs) => { let semantic_subs: Result>, Error> = subs.iter().map(Liftable::lift).collect(); let semantic_subs = semantic_subs?.into_iter().map(Arc::new).collect(); - Semantic::Thresh(k, semantic_subs) + Semantic::Thresh(Threshold::new(k, semantic_subs).unwrap()) } } .normalized(); @@ -358,19 +363,13 @@ mod tests { .parse() .unwrap(); assert_eq!( - Semantic::Thresh( - 1, - vec![ - Arc::new(Semantic::Thresh( - 2, - vec![ - Arc::new(Semantic::Key(key_a)), - Arc::new(Semantic::Older(RelLockTime::from_height(42))) - ] - )), - Arc::new(Semantic::Key(key_b)) - ] - ), + Semantic::Thresh(Threshold::or( + Arc::new(Semantic::Thresh(Threshold::and( + Arc::new(Semantic::Key(key_a)), + Arc::new(Semantic::Older(RelLockTime::from_height(42))) + ))), + Arc::new(Semantic::Key(key_b)) + )), ms_str.lift().unwrap() ); } diff --git a/src/policy/semantic.rs b/src/policy/semantic.rs index 73002f05e..cd7d61002 100644 --- a/src/policy/semantic.rs +++ b/src/policy/semantic.rs @@ -17,7 +17,7 @@ use crate::prelude::*; use crate::sync::Arc; use crate::{ errstr, expression, AbsLockTime, Error, ForEachKey, FromStrKey, MiniscriptKey, RelLockTime, - Translator, + Threshold, Translator, }; /// Abstract policy which corresponds to the semantics of a miniscript and @@ -47,7 +47,7 @@ pub enum Policy { /// A HASH160 whose preimage must be provided to satisfy the descriptor. Hash160(Pk::Hash160), /// A set of descriptors, satisfactions must be provided for `k` of them. - Thresh(usize, Vec>>), + Thresh(Threshold>, 0>), } impl ForEachKey for Policy { @@ -110,8 +110,6 @@ impl Policy { let mut translated = vec![]; for data in self.post_order_iter() { - let child_n = |n| Arc::clone(&translated[data.child_indices[n]]); - let new_policy = match data.node { Unsatisfiable => Unsatisfiable, Trivial => Trivial, @@ -122,7 +120,9 @@ impl Policy { Hash160(ref h) => t.hash160(h).map(Hash160)?, Older(ref n) => Older(*n), After(ref n) => After(*n), - Thresh(ref k, ref subs) => Thresh(*k, (0..subs.len()).map(child_n).collect()), + Thresh(ref thresh) => { + Thresh(thresh.map_from_post_order_iter(&data.child_indices, &translated)) + } }; translated.push(Arc::new(new_policy)); } @@ -174,7 +174,7 @@ impl Policy { let n_terminals_for_child_n = |n| n_terminals[data.child_indices[n]]; let num = match data.node { - Thresh(_k, subs) => (0..subs.len()).map(n_terminals_for_child_n).sum(), + Thresh(thresh) => (0..thresh.n()).map(n_terminals_for_child_n).sum(), Trivial | Unsatisfiable => 0, _leaf => 1, }; @@ -190,7 +190,7 @@ impl Policy { fn first_constraint(&self) -> Policy { debug_assert!(self.clone().normalized() == self.clone()); match self { - &Policy::Thresh(_k, ref subs) => subs[0].first_constraint(), + Policy::Thresh(ref thresh) => thresh.data()[0].first_constraint(), first => first.clone(), } } @@ -206,24 +206,20 @@ impl Policy { panic!("should be unreachable") } - let ret = match self { - Policy::Thresh(k, subs) => { - let mut ret_subs = vec![]; - for sub in subs { - ret_subs.push(sub.as_ref().clone().satisfy_constraint(witness, available)); - } - let ret_subs = ret_subs.into_iter().map(Arc::new).collect(); - Policy::Thresh(k, ret_subs) - } - ref leaf if leaf == witness => { - if available { - Policy::Trivial - } else { - Policy::Unsatisfiable + let ret = + match self { + Policy::Thresh(thresh) => Policy::Thresh(thresh.map(|sub| { + Arc::new(sub.as_ref().clone().satisfy_constraint(witness, available)) + })), + ref leaf if leaf == witness => { + if available { + Policy::Trivial + } else { + Policy::Unsatisfiable + } } - } - x => x, - }; + x => x, + }; ret.normalized() } } @@ -240,22 +236,14 @@ impl fmt::Debug for Policy { Policy::Hash256(ref h) => write!(f, "hash256({})", h), Policy::Ripemd160(ref h) => write!(f, "ripemd160({})", h), Policy::Hash160(ref h) => write!(f, "hash160({})", h), - Policy::Thresh(k, ref subs) => { - if k == subs.len() { - write!(f, "and(")?; - } else if k == 1 { - write!(f, "or(")?; + Policy::Thresh(ref thresh) => { + if thresh.k() == thresh.n() { + thresh.debug("and", false).fmt(f) + } else if thresh.k() == 1 { + thresh.debug("or", false).fmt(f) } else { - write!(f, "thresh({},", k)?; + thresh.debug("thresh", true).fmt(f) } - for (i, sub) in subs.iter().enumerate() { - if i == 0 { - write!(f, "{}", sub)?; - } else { - write!(f, ",{}", sub)?; - } - } - f.write_str(")") } } } @@ -273,22 +261,14 @@ impl fmt::Display for Policy { Policy::Hash256(ref h) => write!(f, "hash256({})", h), Policy::Ripemd160(ref h) => write!(f, "ripemd160({})", h), Policy::Hash160(ref h) => write!(f, "hash160({})", h), - Policy::Thresh(k, ref subs) => { - if k == subs.len() { - write!(f, "and(")?; - } else if k == 1 { - write!(f, "or(")?; + Policy::Thresh(ref thresh) => { + if thresh.k() == thresh.n() { + thresh.display("and", false).fmt(f) + } else if thresh.k() == 1 { + thresh.display("or", false).fmt(f) } else { - write!(f, "thresh({},", k)?; - } - for (i, sub) in subs.iter().enumerate() { - if i == 0 { - write!(f, "{}", sub)?; - } else { - write!(f, ",{}", sub)?; - } + thresh.display("thresh", true).fmt(f) } - f.write_str(")") } } } @@ -342,7 +322,7 @@ impl expression::FromTree for Policy { for arg in &top.args { subs.push(Arc::new(Policy::from_tree(arg)?)); } - Ok(Policy::Thresh(nsubs, subs)) + Ok(Policy::Thresh(Threshold::new(nsubs, subs).map_err(Error::Threshold)?)) } ("or", nsubs) => { if nsubs < 2 { @@ -352,34 +332,21 @@ impl expression::FromTree for Policy { for arg in &top.args { subs.push(Arc::new(Policy::from_tree(arg)?)); } - Ok(Policy::Thresh(1, subs)) + Ok(Policy::Thresh(Threshold::new(1, subs).map_err(Error::Threshold)?)) } - ("thresh", nsubs) => { - if nsubs == 0 || nsubs == 1 { - // thresh() and thresh(k) are err - return Err(errstr("thresh without args")); - } - if !top.args[0].args.is_empty() { - return Err(errstr(top.args[0].args[0].name)); - } - - let thresh = expression::parse_num(top.args[0].name)?; + ("thresh", _) => { + let thresh = top.to_null_threshold().map_err(Error::ParseThreshold)?; // thresh(1) and thresh(n) are disallowed in semantic policies - if thresh <= 1 || thresh >= (nsubs as u32 - 1) { + 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 >= (nsubs as u32) { - return Err(errstr(top.args[0].name)); - } - let mut subs = Vec::with_capacity(top.args.len() - 1); - for arg in &top.args[1..] { - subs.push(Arc::new(Policy::from_tree(arg)?)); - } - Ok(Policy::Thresh(thresh as usize, subs)) + thresh + .translate_by_index(|i| Policy::from_tree(&top.args[1 + i]).map(Arc::new)) + .map(Policy::Thresh) } _ => Err(errstr(top.name)), } @@ -391,11 +358,11 @@ impl Policy { /// `Unsatisfiable`s. Does not reorder any branches; use `.sort`. pub fn normalized(self) -> Policy { match self { - Policy::Thresh(k, subs) => { - let mut ret_subs = Vec::with_capacity(subs.len()); + Policy::Thresh(thresh) => { + let mut ret_subs = Vec::with_capacity(thresh.n()); - let subs: Vec<_> = subs - .into_iter() + let subs: Vec<_> = thresh + .iter() .map(|sub| Arc::new(sub.as_ref().clone().normalized())) .collect(); let trivial_count = subs @@ -408,7 +375,7 @@ impl Policy { .count(); let n = subs.len() - unsatisfied_count - trivial_count; // remove all true/false - let m = k.saturating_sub(trivial_count); // satisfy all trivial + let m = thresh.k().saturating_sub(trivial_count); // satisfy all trivial let is_and = m == n; let is_or = m == 1; @@ -416,15 +383,19 @@ impl Policy { for sub in subs { match sub.as_ref() { Policy::Trivial | Policy::Unsatisfiable => {} - Policy::Thresh(ref k, ref subs) => { + Policy::Thresh(ref subthresh) => { match (is_and, is_or) { (true, true) => { // means m = n = 1, thresh(1,X) type thing. - ret_subs.push(Arc::new(Policy::Thresh(*k, subs.to_vec()))); + ret_subs.push(Arc::new(Policy::Thresh(subthresh.clone()))); } - (true, false) if *k == subs.len() => ret_subs.extend(subs.to_vec()), // and case - (false, true) if *k == 1 => ret_subs.extend(subs.to_vec()), // or case - _ => ret_subs.push(Arc::new(Policy::Thresh(*k, subs.to_vec()))), + (true, false) if subthresh.k() == subthresh.n() => { + ret_subs.extend(subthresh.iter().cloned()) + } // and case + (false, true) if subthresh.k() == 1 => { + ret_subs.extend(subthresh.iter().cloned()) + } // or case + _ => ret_subs.push(Arc::new(Policy::Thresh(subthresh.clone()))), } } x => ret_subs.push(Arc::new(x.clone())), @@ -440,11 +411,14 @@ impl Policy { // Only one strong reference because we created the Arc when pushing to ret_subs. Arc::try_unwrap(policy).unwrap() } else if is_and { - Policy::Thresh(ret_subs.len(), ret_subs) + // unwrap ok since ret_subs is nonempty + Policy::Thresh(Threshold::new(ret_subs.len(), ret_subs).unwrap()) } else if is_or { - Policy::Thresh(1, ret_subs) + // unwrap ok since ret_subs is nonempty + Policy::Thresh(Threshold::new(1, ret_subs).unwrap()) } else { - Policy::Thresh(m, ret_subs) + // unwrap ok since ret_subs is nonempty and we made sure m <= ret_subs.len + Policy::Thresh(Threshold::new(m, ret_subs).unwrap()) } } x => x, @@ -510,8 +484,6 @@ impl Policy { let mut at_age = vec![]; for data in Arc::new(self).post_order_iter() { - let child_n = |n| Arc::clone(&at_age[data.child_indices[n]]); - let new_policy = match data.node.as_ref() { Older(ref t) => { if relative::LockTime::from(*t).is_implied_by(age) { @@ -520,7 +492,9 @@ impl Policy { Some(Unsatisfiable) } } - Thresh(k, ref subs) => Some(Thresh(*k, (0..subs.len()).map(child_n).collect())), + Thresh(ref thresh) => { + Some(Thresh(thresh.map_from_post_order_iter(&data.child_indices, &at_age))) + } _ => None, }; match new_policy { @@ -542,8 +516,6 @@ impl Policy { let mut at_age = vec![]; for data in Arc::new(self).post_order_iter() { - let child_n = |n| Arc::clone(&at_age[data.child_indices[n]]); - let new_policy = match data.node.as_ref() { After(t) => { if absolute::LockTime::from(*t).is_implied_by(n) { @@ -552,7 +524,9 @@ impl Policy { Some(Unsatisfiable) } } - Thresh(k, ref subs) => Some(Thresh(*k, (0..subs.len()).map(child_n).collect())), + Thresh(ref thresh) => { + Some(Thresh(thresh.map_from_post_order_iter(&data.child_indices, &at_age))) + } _ => None, }; match new_policy { @@ -593,16 +567,16 @@ impl Policy { Trivial | After(..) | Older(..) | Sha256(..) | Hash256(..) | Ripemd160(..) | Hash160(..) => Some(0), Key(..) => Some(1), - Thresh(k, ref subs) => { - let mut sublens = (0..subs.len()) + Thresh(ref thresh) => { + let mut sublens = (0..thresh.n()) .filter_map(minimum_n_keys_for_child_n) .collect::>(); - if sublens.len() < *k { + if sublens.len() < thresh.k() { // Not enough branches are satisfiable None } else { sublens.sort_unstable(); - Some(sublens[0..*k].iter().cloned().sum::()) + Some(sublens[0..thresh.k()].iter().cloned().sum::()) } } }; @@ -624,13 +598,12 @@ impl Policy { let mut sorted = vec![]; for data in Arc::new(self).post_order_iter() { - let child_n = |n| Arc::clone(&sorted[data.child_indices[n]]); - let new_policy = match data.node.as_ref() { - Thresh(k, ref subs) => { - let mut subs = (0..subs.len()).map(child_n).collect::>(); - subs.sort(); - Some(Thresh(*k, subs)) + Thresh(ref thresh) => { + let mut new_thresh = + thresh.map_from_post_order_iter(&data.child_indices, &sorted); + new_thresh.data_mut().sort(); + Some(Thresh(new_thresh)) } _ => None, }; @@ -653,7 +626,7 @@ impl<'a, Pk: MiniscriptKey> TreeLike for &'a Policy { match *self { Unsatisfiable | Trivial | Key(_) | After(_) | Older(_) | Sha256(_) | Hash256(_) | Ripemd160(_) | Hash160(_) => Tree::Nullary, - Thresh(_, ref subs) => Tree::Nary(subs.iter().map(Arc::as_ref).collect()), + Thresh(ref thresh) => Tree::Nary(thresh.iter().map(Arc::as_ref).collect()), } } } @@ -665,7 +638,7 @@ impl TreeLike for Arc> { match self.as_ref() { Unsatisfiable | Trivial | Key(_) | After(_) | Older(_) | Sha256(_) | Hash256(_) | Ripemd160(_) | Hash160(_) => Tree::Nullary, - Thresh(_, ref subs) => Tree::Nary(subs.iter().map(Arc::clone).collect()), + Thresh(ref thresh) => Tree::Nary(thresh.iter().map(Arc::clone).collect()), } } } @@ -740,13 +713,10 @@ mod tests { let policy = StringPolicy::from_str("or(pk(),older(1000))").unwrap(); assert_eq!( policy, - Policy::Thresh( - 1, - vec![ - Policy::Key("".to_owned()).into(), - Policy::Older(RelLockTime::from_height(1000)).into(), - ] - ) + Policy::Thresh(Threshold::or( + Policy::Key("".to_owned()).into(), + Policy::Older(RelLockTime::from_height(1000)).into(), + )) ); assert_eq!(policy.relative_timelocks(), vec![1000]); assert_eq!(policy.absolute_timelocks(), vec![]); @@ -771,13 +741,10 @@ mod tests { let policy = StringPolicy::from_str("or(pk(),UNSATISFIABLE)").unwrap(); assert_eq!( policy, - Policy::Thresh( - 1, - vec![ - Policy::Key("".to_owned()).into(), - Policy::Unsatisfiable.into() - ] - ) + Policy::Thresh(Threshold::or( + Policy::Key("".to_owned()).into(), + Policy::Unsatisfiable.into() + )) ); assert_eq!(policy.relative_timelocks(), vec![]); assert_eq!(policy.absolute_timelocks(), vec![]); @@ -787,13 +754,10 @@ mod tests { let policy = StringPolicy::from_str("and(pk(),UNSATISFIABLE)").unwrap(); assert_eq!( policy, - Policy::Thresh( - 2, - vec![ - Policy::Key("".to_owned()).into(), - Policy::Unsatisfiable.into() - ] - ) + Policy::Thresh(Threshold::and( + Policy::Key("".to_owned()).into(), + Policy::Unsatisfiable.into() + )) ); assert_eq!(policy.relative_timelocks(), vec![]); assert_eq!(policy.absolute_timelocks(), vec![]); @@ -809,14 +773,17 @@ mod tests { assert_eq!( policy, Policy::Thresh( - 2, - vec![ - Policy::Older(RelLockTime::from_height(1000)).into(), - Policy::Older(RelLockTime::from_height(10000)).into(), - Policy::Older(RelLockTime::from_height(1000)).into(), - Policy::Older(RelLockTime::from_height(2000)).into(), - Policy::Older(RelLockTime::from_height(2000)).into(), - ] + Threshold::new( + 2, + vec![ + Policy::Older(RelLockTime::from_height(1000)).into(), + Policy::Older(RelLockTime::from_height(10000)).into(), + Policy::Older(RelLockTime::from_height(1000)).into(), + Policy::Older(RelLockTime::from_height(2000)).into(), + Policy::Older(RelLockTime::from_height(2000)).into(), + ] + ) + .unwrap() ) ); assert_eq!( @@ -833,14 +800,17 @@ mod tests { assert_eq!( policy, Policy::Thresh( - 2, - vec![ - Policy::Older(RelLockTime::from_height(1000)).into(), - Policy::Older(RelLockTime::from_height(10000)).into(), - Policy::Older(RelLockTime::from_height(1000)).into(), - Policy::Unsatisfiable.into(), - Policy::Unsatisfiable.into(), - ] + Threshold::new( + 2, + vec![ + Policy::Older(RelLockTime::from_height(1000)).into(), + Policy::Older(RelLockTime::from_height(10000)).into(), + Policy::Older(RelLockTime::from_height(1000)).into(), + Policy::Unsatisfiable.into(), + Policy::Unsatisfiable.into(), + ] + ) + .unwrap() ) ); assert_eq!( @@ -945,7 +915,8 @@ mod tests { "or(and(older(4096),thresh(2,pk(A),pk(B),pk(C))),thresh(11,pk(F1),pk(F2),pk(F3),pk(F4),pk(F5),pk(F6),pk(F7),pk(F8),pk(F9),pk(F10),pk(F11),pk(F12),pk(F13),pk(F14)))").unwrap(); // Very bad idea to add master key,pk but let's have it have 50M blocks let master_key = StringPolicy::from_str("and(older(50000000),pk(master))").unwrap(); - let new_liquid_pol = Policy::Thresh(1, vec![liquid_pol.clone().into(), master_key.into()]); + let new_liquid_pol = + Policy::Thresh(Threshold::or(liquid_pol.clone().into(), master_key.into())); assert!(liquid_pol.clone().entails(new_liquid_pol.clone()).unwrap()); assert!(!new_liquid_pol.entails(liquid_pol.clone()).unwrap()); diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index 84406f182..919e6c8ba 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -13,3 +13,4 @@ pub mod absolute_locktime; pub mod relative_locktime; +pub mod threshold; diff --git a/src/primitives/threshold.rs b/src/primitives/threshold.rs new file mode 100644 index 000000000..daf440327 --- /dev/null +++ b/src/primitives/threshold.rs @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Thresholds +//! +//! Miniscript + +#[cfg(all(not(feature = "std"), not(test)))] +use alloc::{vec, vec::Vec}; +use core::{cmp, fmt, iter}; +#[cfg(any(feature = "std", test))] +use std::vec; + +/// Error parsing an absolute locktime. +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ThresholdError { + k: usize, + n: usize, + max: Option, +} + +impl fmt::Display for ThresholdError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.n == 0 { + f.write_str("thresholds in Miniscript must be nonempty") + } else if self.k == 0 { + f.write_str("thresholds in Miniscript must have k > 0") + } else if self.k > self.n { + write!(f, "invalid threshold {}-of-{}; cannot have k > n", self.k, self.n) + } else { + debug_assert!(self.max.is_some()); + let max = self.max.unwrap(); + debug_assert!(self.n > max); + write!(f, "invalid threshold {}-of-{}; maximum size is {}", self.k, self.n, max) + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ThresholdError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } +} + +/// Structure representing a k-of-n threshold collection of some arbitrary +/// object `T`. +/// +/// If the constant parameter `MAX` is nonzero, it represents a cap on the +/// `n` value; if `n` exceeds `MAX` then an error is returned on construction. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Threshold { + k: usize, + inner: Vec, +} + +impl Threshold { + /// Constructs a threshold directly from a threshold value and collection. + pub fn new(k: usize, inner: Vec) -> Result { + if k == 0 || k > inner.len() || (MAX > 0 && inner.len() > MAX) { + Err(ThresholdError { k, n: inner.len(), max: (MAX > 0).then(|| MAX) }) + } else { + Ok(Threshold { k, inner }) + } + } + + /// Constructs a threshold from a threshold value and an iterator that yields collection + /// elements. + pub fn from_iter>(k: usize, iter: I) -> Result { + let min_size = cmp::max(k, iter.size_hint().0); + // Do an early return if our minimum size exceeds the max. + if MAX > 0 && min_size > MAX { + let n = iter.count(); + return Err(ThresholdError { k, n, max: (MAX > 0).then(|| MAX) }); + } + + let mut inner = Vec::with_capacity(min_size); + iter.for_each(|x| inner.push(x)); + Self::new(k, inner) + } + + /// Constructor for an "or" represented as a 1-of-2 threshold. + pub fn or(left: T, right: T) -> Self { + debug_assert!(MAX == 0 || MAX > 1); + Threshold { k: 1, inner: vec![left, right] } + } + + /// Constructor for an "and" represented as a 2-of-2 threshold. + pub fn and(left: T, right: T) -> Self { + debug_assert!(MAX == 0 || MAX > 1); + Threshold { k: 2, inner: vec![left, right] } + } + + /// Whether this threshold is a 1-of-n. + pub fn is_or(&self) -> bool { self.k == 1 } + + /// Whether this threshold is a n-of-n. + pub fn is_and(&self) -> bool { self.k == self.inner.len() } + + /// Changes the type-system-enforced maximum value of the threshold. + pub fn set_maximum(self) -> Result, ThresholdError> { + Threshold::new(self.k, self.inner) + } + + /// Forgets the type-system-enforced maximum value of the threshold. + pub fn forget_maximum(self) -> Threshold { Threshold { k: self.k, inner: self.inner } } + + /// Constructs a threshold from an existing threshold by applying a mapping function to + /// each individual item. + pub fn map U>(self, mapfn: F) -> Threshold { + Threshold { k: self.k, inner: self.inner.into_iter().map(mapfn).collect() } + } + + /// Like [`Self::map`] but takes a reference to the threshold rather than taking ownership. + pub fn map_ref U>(&self, mapfn: F) -> Threshold { + Threshold { k: self.k, inner: self.inner.iter().map(mapfn).collect() } + } + + /// Like [`Self::map`] except that the mapping function may return an error. + pub fn translate(self, translatefn: F) -> Result, FuncError> + where + F: FnMut(T) -> Result, + { + let k = self.k; + self.inner + .into_iter() + .map(translatefn) + .collect::, _>>() + .map(|inner| Threshold { k, inner }) + } + + /// Like [`Self::translate`] but takes a reference to the threshold rather than taking ownership. + pub fn translate_ref( + &self, + translatefn: F, + ) -> Result, FuncError> + where + F: FnMut(&T) -> Result, + { + let k = self.k; + self.inner + .iter() + .map(translatefn) + .collect::, _>>() + .map(|inner| Threshold { k, inner }) + } + + /// Like [`Self::translate_ref`] but passes indices to the closure rather than internal data. + /// + /// This is useful in situations where the data to be translated exists outside of the + /// threshold itself, and the threshold data is irrelevant. In particular it is commonly + /// paired with [`crate::expression::Tree::to_null_threshold`]. + /// + /// If the data to be translated comes from a post-order iterator, you may instead want + /// [`Self::map_from_post_order_iter`]. + pub fn translate_by_index( + &self, + translatefn: F, + ) -> Result, FuncError> + where + F: FnMut(usize) -> Result, + { + let k = self.k; + (0..self.inner.len()) + .map(translatefn) + .collect::, _>>() + .map(|inner| Threshold { k, inner }) + } + + /// Construct a threshold from an existing threshold which has been processed in some way. + /// + /// It is a common pattern in this library to transform data structures by + /// running a post-order iterator over them, putting processed elements into + /// a vector to be later referenced by their parents. + /// + /// This function encapsulates that pattern by taking the child-index vector of + /// the`PostOrderIterItem`, under consideration, and the vector of processed + /// elements. + pub fn map_from_post_order_iter( + &self, + child_indices: &[usize], + processed: &[U], + ) -> Threshold { + debug_assert_eq!( + self.inner.len(), + child_indices.len(), + "internal consistency error translating threshold by post-order iterator" + ); + let mut processed_inner = Vec::with_capacity(self.inner.len()); + processed_inner.extend(child_indices.iter().copied().map(|n| processed[n].clone())); + Threshold { k: self.k, inner: processed_inner } + } + + /// Accessor for the number of elements in the threshold. + // non-const because Vec::len is not const + pub fn n(&self) -> usize { self.inner.len() } + + /// Accessor for the threshold value. + pub const fn k(&self) -> usize { self.k } + + /// Accessor for the underlying data. + pub fn data(&self) -> &[T] { &self.inner } + + /// Mutable accessor for the underlying data. + /// + /// This returns access to the underlying data as a mutable slice, which allows you + /// to modify individual elements. To change the number of elements, you must + /// destructure the threshold with [`Self::k`] and [`Self::into_data`] and + /// reconstruct it (and on reconstruction, deal with any errors caused by your + /// tinkering with the threshold values). + pub fn data_mut(&mut self) -> &mut [T] { &mut self.inner } + + /// Accessor for the underlying data. + pub fn into_data(self) -> Vec { self.inner } + + /// Passthrough to an iterator on the underlying vector. + pub fn iter(&self) -> core::slice::Iter { self.inner.iter() } +} + +impl Threshold { + /// Constructor for an "or" represented as a 1-of-n threshold. + /// + /// # Panics + /// + /// Panics if the passed vector is empty. + pub fn or_n(inner: Vec) -> Self { + assert_ne!(inner.len(), 0); + Threshold { k: 1, inner } + } + + /// Constructor for an "and" represented as a n-of-n threshold. + /// + /// # Panics + /// + /// Panics if the passed vector is empty. + pub fn and_n(inner: Vec) -> Self { + assert_ne!(inner.len(), 0); + Threshold { k: inner.len(), inner } + } +} + +impl iter::IntoIterator for Threshold { + type Item = T; + type IntoIter = vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { self.inner.into_iter() } +} + +impl Threshold { + /// Produces an object which can [`fmt::Display`] the threshold. + pub fn display<'s>(&'s self, name: &'s str, show_k: bool) -> impl fmt::Display + 's { + ThreshDisplay { name, thresh: self, show_k } + } +} + +impl Threshold { + /// Produces an object which can [`fmt::Debug`] the threshold. + pub fn debug<'s>(&'s self, name: &'s str, show_k: bool) -> impl fmt::Debug + 's { + ThreshDisplay { name, thresh: self, show_k } + } +} + +struct ThreshDisplay<'t, 's, T, const MAX: usize> { + name: &'s str, + thresh: &'t Threshold, + show_k: bool, +} + +impl<'t, 's, T, const MAX: usize> fmt::Display for ThreshDisplay<'t, 's, T, MAX> +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use core::fmt::Write; + + f.write_str(self.name)?; + f.write_char('(')?; + let inners = if self.show_k { + write!(f, "{}", self.thresh.k)?; + &self.thresh.inner[0..] + } else { + write!(f, "{}", self.thresh.inner[0])?; + &self.thresh.inner[1..] + }; + for inner in inners { + write!(f, ",{}", inner)?; + } + f.write_char(')') + } +} + +impl<'t, 's, T, const MAX: usize> fmt::Debug for ThreshDisplay<'t, 's, T, MAX> +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use core::fmt::Write; + + f.write_str(self.name)?; + f.write_char('(')?; + let inners = if self.show_k { + write!(f, "{}", self.thresh.k)?; + &self.thresh.inner[0..] + } else { + write!(f, "{:?}", self.thresh.inner[0])?; + &self.thresh.inner[1..] + }; + for inner in inners { + write!(f, ",{:?}", inner)?; + } + f.write_char(')') + } +} diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index 61027d6ad..500534d3c 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -563,7 +563,7 @@ pub trait PsbtExt { /// /// Based on the sighash /// flag specified in the [`Psbt`] sighash field. If the input sighash flag psbt field is `None` - /// the [`sighash::TapSighashType::Default`](bitcoin::sighash::TapSighashType::Default) is chosen + /// the [`sighash::TapSighashType::Default`] is chosen /// for for taproot spends, otherwise [`EcdsaSighashType::All`](bitcoin::sighash::EcdsaSighashType::All) is chosen. /// If the utxo at `idx` is a taproot output, returns a [`PsbtSighashMsg::TapSighash`] variant. /// If the utxo at `idx` is a pre-taproot segwit output, returns a [`PsbtSighashMsg::SegwitV0Sighash`] variant.