diff --git a/bitcoind-tests/tests/test_cpp.rs b/bitcoind-tests/tests/test_cpp.rs index 7efea41a5..06254765c 100644 --- a/bitcoind-tests/tests/test_cpp.rs +++ b/bitcoind-tests/tests/test_cpp.rs @@ -147,10 +147,7 @@ pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { for i in 0..psbts.len() { let wsh_derived = desc_vec[i].derived_descriptor(&secp); let ms = if let Descriptor::Wsh(wsh) = &wsh_derived { - match wsh.as_inner() { - miniscript::descriptor::WshInner::Ms(ms) => ms, - _ => unreachable!(), - } + wsh.as_inner() } else { unreachable!("Only Wsh descriptors are supported"); }; diff --git a/bitcoind-tests/tests/test_desc.rs b/bitcoind-tests/tests/test_desc.rs index 7aafba691..94fcc084d 100644 --- a/bitcoind-tests/tests/test_desc.rs +++ b/bitcoind-tests/tests/test_desc.rs @@ -225,29 +225,15 @@ pub fn test_desc_satisfy( Descriptor::Pkh(pk) => find_sk_single_key(*pk.as_inner(), testdata), Descriptor::Wpkh(pk) => find_sk_single_key(*pk.as_inner(), testdata), Descriptor::Sh(sh) => match sh.as_inner() { - miniscript::descriptor::ShInner::Wsh(wsh) => match wsh.as_inner() { - miniscript::descriptor::WshInner::SortedMulti(ref smv) => { - let ms = Miniscript::from_ast(smv.sorted_node()).unwrap(); - find_sks_ms(&ms, testdata) - } - miniscript::descriptor::WshInner::Ms(ref ms) => find_sks_ms(ms, testdata), - }, + miniscript::descriptor::ShInner::Wsh(wsh) => { + find_sks_ms(wsh.as_inner(), testdata) + } miniscript::descriptor::ShInner::Wpkh(pk) => { find_sk_single_key(*pk.as_inner(), testdata) } - miniscript::descriptor::ShInner::SortedMulti(smv) => { - let ms = Miniscript::from_ast(smv.sorted_node()).unwrap(); - find_sks_ms(&ms, testdata) - } miniscript::descriptor::ShInner::Ms(ms) => find_sks_ms(ms, testdata), }, - Descriptor::Wsh(wsh) => match wsh.as_inner() { - miniscript::descriptor::WshInner::SortedMulti(ref smv) => { - let ms = Miniscript::from_ast(smv.sorted_node()).unwrap(); - find_sks_ms(&ms, testdata) - } - miniscript::descriptor::WshInner::Ms(ref ms) => find_sks_ms(ms, testdata), - }, + Descriptor::Wsh(wsh) => find_sks_ms(wsh.as_inner(), testdata), Descriptor::Tr(_tr) => unreachable!("Tr checked earlier"), }; let msg = psbt diff --git a/src/descriptor/iter.rs b/src/descriptor/iter.rs index 3e5552b4c..43e4896ee 100644 --- a/src/descriptor/iter.rs +++ b/src/descriptor/iter.rs @@ -14,7 +14,6 @@ pub struct PkIter<'desc, Pk: MiniscriptKey> { ms_iter_legacy: Option>, ms_iter_segwit: Option>, ms_iter_taproot: Option>, - sorted_multi: Option>, } impl<'desc, Pk: MiniscriptKey> PkIter<'desc, Pk> { @@ -26,7 +25,6 @@ impl<'desc, Pk: MiniscriptKey> PkIter<'desc, Pk> { ms_iter_legacy: None, ms_iter_segwit: None, ms_iter_taproot: None, - sorted_multi: None, } } @@ -38,7 +36,6 @@ impl<'desc, Pk: MiniscriptKey> PkIter<'desc, Pk> { ms_iter_legacy: None, ms_iter_segwit: None, ms_iter_taproot: None, - sorted_multi: None, } } @@ -50,7 +47,6 @@ impl<'desc, Pk: MiniscriptKey> PkIter<'desc, Pk> { ms_iter_legacy: Some(ms.iter_pk()), ms_iter_segwit: None, ms_iter_taproot: None, - sorted_multi: None, } } @@ -62,19 +58,6 @@ impl<'desc, Pk: MiniscriptKey> PkIter<'desc, Pk> { ms_iter_legacy: None, ms_iter_segwit: Some(ms.iter_pk()), ms_iter_taproot: None, - sorted_multi: None, - } - } - - pub(super) fn from_sortedmulti(sm: &'desc [Pk]) -> Self { - Self { - single_key: None, - taptree_iter: None, - ms_iter_bare: None, - ms_iter_legacy: None, - ms_iter_segwit: None, - ms_iter_taproot: None, - sorted_multi: Some(sm.iter()), } } @@ -86,7 +69,6 @@ impl<'desc, Pk: MiniscriptKey> PkIter<'desc, Pk> { ms_iter_legacy: None, ms_iter_segwit: None, ms_iter_taproot: None, - sorted_multi: None, } } } @@ -118,9 +100,7 @@ impl<'desc, Pk: MiniscriptKey> Iterator for PkIter<'desc, Pk> { // Finally run through the train of other iterators. self.ms_iter_bare.as_mut().and_then(Iterator::next).or_else( || self.ms_iter_legacy.as_mut().and_then(Iterator::next).or_else( - || self.ms_iter_segwit.as_mut().and_then(Iterator::next).or_else( - || self.sorted_multi.as_mut().and_then(Iterator::next).cloned() - ) + || self.ms_iter_segwit.as_mut().and_then(Iterator::next) ) ) } diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 2040257ee..23d2e8830 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -36,15 +36,13 @@ mod bare; mod iter; mod segwitv0; mod sh; -mod sortedmulti; mod tr; // Descriptor Exports pub use self::bare::{Bare, Pkh}; pub use self::iter::PkIter; -pub use self::segwitv0::{Wpkh, Wsh, WshInner}; +pub use self::segwitv0::{Wpkh, Wsh}; pub use self::sh::{Sh, ShInner}; -pub use self::sortedmulti::SortedMultiVec; pub use self::tr::{ TapTree, TapTreeDepthError, TapTreeIter, TapTreeIterItem, Tr, TrSpendInfo, TrSpendInfoIter, TrSpendInfoIterItem, @@ -256,18 +254,11 @@ impl Descriptor { Descriptor::Pkh(ref pk) => PkIter::from_key(pk.as_inner().clone()), Descriptor::Wpkh(ref pk) => PkIter::from_key(pk.as_inner().clone()), Descriptor::Sh(ref sh) => match *sh.as_inner() { - ShInner::Wsh(ref wsh) => match wsh.as_inner() { - WshInner::SortedMulti(ref sorted) => PkIter::from_sortedmulti(sorted.pks()), - WshInner::Ms(ref ms) => PkIter::from_miniscript_segwit(ms), - }, + ShInner::Wsh(ref wsh) => PkIter::from_miniscript_segwit(wsh.as_inner()), ShInner::Wpkh(ref pk) => PkIter::from_key(pk.as_inner().clone()), - ShInner::SortedMulti(ref sorted) => PkIter::from_sortedmulti(sorted.pks()), ShInner::Ms(ref ms) => PkIter::from_miniscript_legacy(ms), }, - Descriptor::Wsh(ref wsh) => match wsh.as_inner() { - WshInner::SortedMulti(ref sorted) => PkIter::from_sortedmulti(sorted.pks()), - WshInner::Ms(ref ms) => PkIter::from_miniscript_segwit(ms), - }, + Descriptor::Wsh(ref wsh) => PkIter::from_miniscript_segwit(wsh.as_inner()), Descriptor::Tr(ref tr) => PkIter::from_tr(tr), } } @@ -313,18 +304,29 @@ impl Descriptor { Descriptor::Pkh(ref _pkh) => DescriptorType::Pkh, Descriptor::Wpkh(ref _wpkh) => DescriptorType::Wpkh, Descriptor::Sh(ref sh) => match sh.as_inner() { - ShInner::Wsh(ref wsh) => match wsh.as_inner() { - WshInner::SortedMulti(ref _smv) => DescriptorType::ShWshSortedMulti, - WshInner::Ms(ref _ms) => DescriptorType::ShWsh, - }, + ShInner::Wsh(ref wsh) => { + if let Terminal::SortedMulti(..) = wsh.as_inner().node { + DescriptorType::ShWshSortedMulti + } else { + DescriptorType::ShWsh + } + } ShInner::Wpkh(ref _wpkh) => DescriptorType::ShWpkh, - ShInner::SortedMulti(ref _smv) => DescriptorType::ShSortedMulti, - ShInner::Ms(ref _ms) => DescriptorType::Sh, - }, - Descriptor::Wsh(ref wsh) => match wsh.as_inner() { - WshInner::SortedMulti(ref _smv) => DescriptorType::WshSortedMulti, - WshInner::Ms(ref _ms) => DescriptorType::Wsh, + ShInner::Ms(ref ms) => { + if let Terminal::SortedMulti(..) = ms.node { + DescriptorType::ShSortedMulti + } else { + DescriptorType::Sh + } + } }, + Descriptor::Wsh(ref wsh) => { + if let Terminal::SortedMulti(..) = wsh.as_inner().node { + DescriptorType::WshSortedMulti + } else { + DescriptorType::Wsh + } + } Descriptor::Tr(ref _tr) => DescriptorType::Tr, } } @@ -1208,6 +1210,7 @@ mod tests { use super::{checksum, *}; use crate::hex_script; + use crate::miniscript::context::ScriptContextError; #[cfg(feature = "compiler")] use crate::policy; @@ -1257,7 +1260,7 @@ mod tests { StdDescriptor::from_str("sh(sortedmulti)") .unwrap_err() .to_string(), - "sortedmulti must have at least 1 children, but found 0" + "expected threshold, found terminal" ); //issue 202 assert_eq!( StdDescriptor::from_str(&format!("sh(sortedmulti(2,{}))", &TEST_PK[3..69])) @@ -2884,4 +2887,26 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; let definite: Result, _> = desc.try_into(); assert!(matches!(definite, Err(NonDefiniteKeyError::Wildcard))); } + + #[test] + fn too_many_pubkeys_for_p2sh() { + // Arbitrary 65-byte public key (66 with length prefix). + let pk = PublicKey::from_str( + "0400232a2acfc9b43fa89f1b4f608fde335d330d7114f70ea42bfb4a41db368a3e3be6934a4097dd25728438ef73debb1f2ffdb07fec0f18049df13bdc5285dc5b", + ) + .unwrap(); + + // This is legal for CHECKMULTISIG, but the 8 keys consume the whole 520 bytes + // allowed by P2SH, meaning that the full script goes over the limit. + let thresh = Threshold::new(2, vec![pk; 8]).expect("the thresh is ok.."); + let script = Miniscript::<_, Legacy>::sortedmulti(thresh).encode(); + let res = Miniscript::<_, Legacy>::decode(&script); + + let error = res.expect_err("decoding should err"); + + match error { + Error::ContextError(ScriptContextError::MaxRedeemScriptSizeExceeded { .. }) => {} // ok + other => panic!("unexpected error: {:?}", other), + } + } } diff --git a/src/descriptor/segwitv0.rs b/src/descriptor/segwitv0.rs index a78867fba..b1398ba68 100644 --- a/src/descriptor/segwitv0.rs +++ b/src/descriptor/segwitv0.rs @@ -10,7 +10,6 @@ use core::fmt; use bitcoin::{Address, Network, ScriptBuf, Weight}; -use super::SortedMultiVec; use crate::descriptor::{write_descriptor, DefiniteDescriptorKey}; use crate::expression::{self, FromTree}; use crate::miniscript::context::{ScriptContext, ScriptContextError}; @@ -21,35 +20,33 @@ use crate::policy::{semantic, Liftable}; use crate::prelude::*; use crate::util::varint_len; use crate::{ - Error, ForEachKey, FromStrKey, Miniscript, MiniscriptKey, Satisfier, Segwitv0, Threshold, - ToPublicKey, TranslateErr, Translator, + Error, ForEachKey, FromStrKey, Miniscript, MiniscriptKey, Satisfier, Segwitv0, Terminal, + Threshold, ToPublicKey, TranslateErr, Translator, }; /// A Segwitv0 wsh descriptor #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct Wsh { /// underlying miniscript - inner: WshInner, + ms: Miniscript, } impl Wsh { - /// Get the Inner - pub fn into_inner(self) -> WshInner { self.inner } + /// Get the inner Miniscript + pub fn into_inner(self) -> Miniscript { self.ms } - /// Get a reference to inner - pub fn as_inner(&self) -> &WshInner { &self.inner } + /// Get a reference to inner Miniscript + pub fn as_inner(&self) -> &Miniscript { &self.ms } /// Create a new wsh descriptor pub fn new(ms: Miniscript) -> Result { // do the top-level checks Segwitv0::top_level_checks(&ms)?; - Ok(Self { inner: WshInner::Ms(ms) }) + Ok(Self { ms }) } /// Create a new sortedmulti wsh descriptor pub fn new_sortedmulti(thresh: Threshold) -> Result { - // The context checks will be carried out inside new function for - // sortedMultiVec - Ok(Self { inner: WshInner::SortedMulti(SortedMultiVec::new(thresh)?) }) + Ok(Self { ms: Miniscript::sortedmulti(thresh) }) } /// Get the descriptor without the checksum @@ -58,10 +55,7 @@ impl Wsh { /// Checks whether the descriptor is safe. pub fn sanity_check(&self) -> Result<(), Error> { - match self.inner { - WshInner::SortedMulti(ref smv) => smv.sanity_check()?, - WshInner::Ms(ref ms) => ms.sanity_check()?, - } + self.ms.sanity_check()?; Ok(()) } @@ -74,18 +68,11 @@ impl Wsh { /// # Errors /// When the descriptor is impossible to safisfy (ex: sh(OP_FALSE)). pub fn max_weight_to_satisfy(&self) -> Result { - let (redeem_script_size, max_sat_elems, max_sat_size) = match self.inner { - WshInner::SortedMulti(ref smv) => ( - smv.script_size(), - smv.max_satisfaction_witness_elements(), - smv.max_satisfaction_size(), - ), - WshInner::Ms(ref ms) => ( - ms.script_size(), - ms.max_satisfaction_witness_elements()?, - ms.max_satisfaction_size()?, - ), - }; + let (redeem_script_size, max_sat_elems, max_sat_size) = ( + self.ms.script_size(), + self.ms.max_satisfaction_witness_elements()?, + self.ms.max_satisfaction_size()?, + ); // stack size varint difference between non-satisfied (0) and satisfied // `max_sat_elems` is inclusive of the "witness script" (redeem script) let stack_varint_diff = varint_len(max_sat_elems) - varint_len(0); @@ -110,18 +97,11 @@ impl Wsh { note = "Use max_weight_to_satisfy instead. The method to count bytes was redesigned and the results will differ from max_weight_to_satisfy. For more details check rust-bitcoin/rust-miniscript#476." )] pub fn max_satisfaction_weight(&self) -> Result { - let (script_size, max_sat_elems, max_sat_size) = match self.inner { - WshInner::SortedMulti(ref smv) => ( - smv.script_size(), - smv.max_satisfaction_witness_elements(), - smv.max_satisfaction_size(), - ), - WshInner::Ms(ref ms) => ( - ms.script_size(), - ms.max_satisfaction_witness_elements()?, - ms.max_satisfaction_size()?, - ), - }; + let (script_size, max_sat_elems, max_sat_size) = ( + self.ms.script_size(), + self.ms.max_satisfaction_witness_elements()?, + self.ms.max_satisfaction_size()?, + ); Ok(4 + // scriptSig length byte varint_len(script_size) + script_size + @@ -134,11 +114,7 @@ impl Wsh { where T: Translator, { - let inner = match self.inner { - WshInner::SortedMulti(ref smv) => WshInner::SortedMulti(smv.translate_pk(t)?), - WshInner::Ms(ref ms) => WshInner::Ms(ms.translate_pk(t)?), - }; - Ok(Wsh { inner }) + Ok(Wsh { ms: self.ms.translate_pk(t)? }) } } @@ -148,19 +124,11 @@ impl Wsh { /// Obtains the corresponding script pubkey for this descriptor. pub fn address(&self, network: Network) -> Address { - match self.inner { - WshInner::SortedMulti(ref smv) => Address::p2wsh(&smv.encode(), network), - WshInner::Ms(ref ms) => Address::p2wsh(&ms.encode(), network), - } + Address::p2wsh(&self.ms.encode(), network) } /// Obtains the underlying miniscript for this descriptor. - pub fn inner_script(&self) -> ScriptBuf { - match self.inner { - WshInner::SortedMulti(ref smv) => smv.encode(), - WshInner::Ms(ref ms) => ms.encode(), - } - } + pub fn inner_script(&self) -> ScriptBuf { self.ms.encode() } /// Obtains the pre bip-340 signature script code for this descriptor. pub fn ecdsa_sighash_script_code(&self) -> ScriptBuf { self.inner_script() } @@ -172,10 +140,7 @@ impl Wsh { where S: Satisfier, { - let mut witness = match self.inner { - WshInner::SortedMulti(ref smv) => smv.satisfy(satisfier)?, - WshInner::Ms(ref ms) => ms.satisfy(satisfier)?, - }; + let mut witness = self.ms.satisfy(satisfier)?; let witness_script = self.inner_script(); witness.push(witness_script.into_bytes()); let script_sig = ScriptBuf::new(); @@ -189,10 +154,7 @@ impl Wsh { where S: Satisfier, { - let mut witness = match self.inner { - WshInner::SortedMulti(ref smv) => smv.satisfy(satisfier)?, - WshInner::Ms(ref ms) => ms.satisfy_malleable(satisfier)?, - }; + let mut witness = self.ms.satisfy_malleable(satisfier)?; witness.push(self.inner_script().into_bytes()); let script_sig = ScriptBuf::new(); Ok((witness, script_sig)) @@ -208,10 +170,7 @@ impl Wsh { where P: AssetProvider, { - match &self.inner { - WshInner::SortedMulti(sm) => sm.build_template(provider), - WshInner::Ms(ms) => ms.build_template(provider), - } + self.ms.build_template(provider) } /// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction @@ -222,29 +181,16 @@ impl Wsh { where P: AssetProvider, { - match &self.inner { - WshInner::SortedMulti(sm) => sm.build_template(provider), - WshInner::Ms(ms) => ms.build_template_mall(provider), + if let Terminal::SortedMulti(..) = self.ms.node { + self.ms.build_template(provider) + } else { + self.ms.build_template_mall(provider) } } } -/// Wsh Inner -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub enum WshInner { - /// Sorted Multi - SortedMulti(SortedMultiVec), - /// Wsh Miniscript - Ms(Miniscript), -} - impl Liftable for Wsh { - fn lift(&self) -> Result, Error> { - match self.inner { - WshInner::SortedMulti(ref smv) => smv.lift(), - WshInner::Ms(ref ms) => ms.lift(), - } - } + fn lift(&self) -> Result, Error> { self.ms.lift() } } impl crate::expression::FromTree for Wsh { @@ -254,30 +200,19 @@ impl crate::expression::FromTree for Wsh { .map_err(From::from) .map_err(Error::Parse)?; - if top.name() == "sortedmulti" { - return Ok(Wsh { inner: WshInner::SortedMulti(SortedMultiVec::from_tree(top)?) }); - } let sub = Miniscript::from_tree(top)?; Segwitv0::top_level_checks(&sub)?; - Ok(Wsh { inner: WshInner::Ms(sub) }) + Ok(Wsh { ms: sub }) } } impl fmt::Debug for Wsh { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.inner { - WshInner::SortedMulti(ref smv) => write!(f, "wsh({:?})", smv), - WshInner::Ms(ref ms) => write!(f, "wsh({:?})", ms), - } - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "wsh({:?})", self.ms) } } impl fmt::Display for Wsh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.inner { - WshInner::SortedMulti(ref smv) => write_descriptor!(f, "wsh({})", smv), - WshInner::Ms(ref ms) => write_descriptor!(f, "wsh({})", ms), - } + write_descriptor!(f, "wsh({})", self.ms) } } @@ -291,10 +226,7 @@ impl core::str::FromStr for Wsh { impl ForEachKey for Wsh { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool { - match self.inner { - WshInner::SortedMulti(ref smv) => smv.for_each_key(pred), - WshInner::Ms(ref ms) => ms.for_each_key(pred), - } + self.ms.for_each_key(pred) } } diff --git a/src/descriptor/sh.rs b/src/descriptor/sh.rs index 4acb5fa73..3c235b777 100644 --- a/src/descriptor/sh.rs +++ b/src/descriptor/sh.rs @@ -13,7 +13,7 @@ use core::fmt; use bitcoin::script::PushBytes; use bitcoin::{script, Address, Network, ScriptBuf, Weight}; -use super::{SortedMultiVec, Wpkh, Wsh}; +use super::{Wpkh, Wsh}; use crate::descriptor::{write_descriptor, DefiniteDescriptorKey}; use crate::expression::{self, FromTree}; use crate::miniscript::context::ScriptContext; @@ -42,8 +42,6 @@ pub enum ShInner { Wsh(Wsh), /// Nested Wpkh Wpkh(Wpkh), - /// Inner Sorted Multi - SortedMulti(SortedMultiVec), /// p2sh miniscript Ms(Miniscript), } @@ -53,7 +51,6 @@ impl Liftable for Sh { match self.inner { ShInner::Wsh(ref wsh) => wsh.lift(), ShInner::Wpkh(ref pk) => Ok(semantic::Policy::Key(pk.as_inner().clone())), - ShInner::SortedMulti(ref smv) => smv.lift(), ShInner::Ms(ref ms) => ms.lift(), } } @@ -64,7 +61,6 @@ impl fmt::Debug for Sh { match self.inner { ShInner::Wsh(ref wsh_inner) => write!(f, "sh({:?})", wsh_inner), ShInner::Wpkh(ref pk) => write!(f, "sh({:?})", pk), - ShInner::SortedMulti(ref smv) => write!(f, "sh({:?})", smv), ShInner::Ms(ref ms) => write!(f, "sh({:?})", ms), } } @@ -75,7 +71,6 @@ impl fmt::Display for Sh { match self.inner { ShInner::Wsh(ref wsh) => write_descriptor!(f, "sh({:#})", wsh), ShInner::Wpkh(ref pk) => write_descriptor!(f, "sh({:#})", pk), - ShInner::SortedMulti(ref smv) => write_descriptor!(f, "sh({})", smv), ShInner::Ms(ref ms) => write_descriptor!(f, "sh({})", ms), } } @@ -91,7 +86,6 @@ impl crate::expression::FromTree for Sh { let inner = match top.name() { "wsh" => ShInner::Wsh(Wsh::from_tree(top)?), "wpkh" => ShInner::Wpkh(Wpkh::from_tree(top)?), - "sortedmulti" => ShInner::SortedMulti(SortedMultiVec::from_tree(top)?), _ => { let sub = Miniscript::from_tree(top)?; Legacy::top_level_checks(&sub)?; @@ -127,9 +121,7 @@ impl Sh { /// Create a new p2sh sortedmulti descriptor with threshold `k` /// and Vec of `pks`. pub fn new_sortedmulti(thresh: Threshold) -> Result { - // The context checks will be carried out inside new function for - // sortedMultiVec - Ok(Self { inner: ShInner::SortedMulti(SortedMultiVec::new(thresh)?) }) + Ok(Self { inner: ShInner::Ms(Miniscript::sortedmulti(thresh)) }) } /// Create a new p2sh wrapped wsh descriptor with the raw miniscript @@ -145,7 +137,6 @@ impl Sh { match self.inner { ShInner::Wsh(ref wsh) => wsh.sanity_check()?, ShInner::Wpkh(ref wpkh) => wpkh.sanity_check()?, - ShInner::SortedMulti(ref smv) => smv.sanity_check()?, ShInner::Ms(ref ms) => ms.sanity_check()?, } Ok(()) @@ -191,12 +182,6 @@ impl Sh { let witness_size = wsh.max_weight_to_satisfy()?; (scriptsig_size, witness_size) } - ShInner::SortedMulti(ref smv) => { - let ss = smv.script_size(); - let ps = push_opcode_size(ss); - let scriptsig_size = ps + ss + smv.max_satisfaction_size(); - (scriptsig_size, Weight::ZERO) - } // add weighted script sig, len byte stays the same ShInner::Wpkh(ref wpkh) => { // scriptSig: OP_22 > @@ -240,12 +225,6 @@ impl Sh { Ok(match self.inner { // add weighted script sig, len byte stays the same ShInner::Wsh(ref wsh) => 4 * 35 + wsh.max_satisfaction_weight()?, - ShInner::SortedMulti(ref smv) => { - let ss = smv.script_size(); - let ps = push_opcode_size(ss); - let scriptsig_len = ps + ss + smv.max_satisfaction_size(); - 4 * (varint_len(scriptsig_len) + scriptsig_len) - } // add weighted script sig, len byte stays the same ShInner::Wpkh(ref wpkh) => 4 * 23 + wpkh.max_satisfaction_weight(), ShInner::Ms(ref ms) => { @@ -265,7 +244,6 @@ impl Sh { let inner = match self.inner { ShInner::Wsh(ref wsh) => ShInner::Wsh(wsh.translate_pk(t)?), ShInner::Wpkh(ref wpkh) => ShInner::Wpkh(wpkh.translate_pk(t)?), - ShInner::SortedMulti(ref smv) => ShInner::SortedMulti(smv.translate_pk(t)?), ShInner::Ms(ref ms) => ShInner::Ms(ms.translate_pk(t)?), }; Ok(Sh { inner }) @@ -278,7 +256,6 @@ impl Sh { match self.inner { ShInner::Wsh(ref wsh) => wsh.script_pubkey().to_p2sh(), ShInner::Wpkh(ref wpkh) => wpkh.script_pubkey().to_p2sh(), - ShInner::SortedMulti(ref smv) => smv.encode().to_p2sh(), ShInner::Ms(ref ms) => ms.encode().to_p2sh(), } } @@ -296,7 +273,6 @@ impl Sh { let script = match self.inner { ShInner::Wsh(ref wsh) => wsh.script_pubkey(), ShInner::Wpkh(ref wpkh) => wpkh.script_pubkey(), - ShInner::SortedMulti(ref smv) => smv.encode(), ShInner::Ms(ref ms) => ms.encode(), }; let address = Address::p2sh(&script, network)?; @@ -309,7 +285,6 @@ impl Sh { match self.inner { ShInner::Wsh(ref wsh) => wsh.inner_script(), ShInner::Wpkh(ref wpkh) => wpkh.script_pubkey(), - ShInner::SortedMulti(ref smv) => smv.encode(), ShInner::Ms(ref ms) => ms.encode(), } } @@ -320,7 +295,6 @@ impl Sh { // - For P2WSH witness program, if the witnessScript does not contain any `OP_CODESEPARATOR`, // the `scriptCode` is the `witnessScript` serialized as scripts inside CTxOut. ShInner::Wsh(ref wsh) => wsh.ecdsa_sighash_script_code(), - ShInner::SortedMulti(ref smv) => smv.encode(), ShInner::Wpkh(ref wpkh) => wpkh.ecdsa_sighash_script_code(), // For "legacy" P2SH outputs, it is defined as the txo's redeemScript. ShInner::Ms(ref ms) => ms.encode(), @@ -349,7 +323,7 @@ impl Sh { <&PushBytes>::try_from(redeem_script.as_bytes()).expect("Script not too large"); script::Builder::new().push_slice(push_bytes).into_script() } - ShInner::SortedMulti(..) | ShInner::Ms(..) => ScriptBuf::new(), + ShInner::Ms(..) => ScriptBuf::new(), } } @@ -370,13 +344,6 @@ impl Sh { let (witness, _) = wpkh.get_satisfaction(satisfier)?; Ok((witness, script_sig)) } - ShInner::SortedMulti(ref smv) => { - let mut script_witness = smv.satisfy(satisfier)?; - script_witness.push(smv.encode().into_bytes()); - let script_sig = witness_to_scriptsig(&script_witness); - let witness = vec![]; - Ok((witness, script_sig)) - } ShInner::Ms(ref ms) => { let mut script_witness = ms.satisfy(satisfier)?; script_witness.push(ms.encode().into_bytes()); @@ -424,7 +391,6 @@ impl Sh { match &self.inner { ShInner::Wsh(ref wsh) => wsh.plan_satisfaction(provider), ShInner::Wpkh(ref wpkh) => wpkh.plan_satisfaction(provider), - ShInner::SortedMulti(ref smv) => smv.build_template(provider), ShInner::Ms(ref ms) => ms.build_template(provider), } } @@ -449,7 +415,6 @@ impl ForEachKey for Sh { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool { match self.inner { ShInner::Wsh(ref wsh) => wsh.for_each_key(pred), - ShInner::SortedMulti(ref smv) => smv.for_each_key(pred), ShInner::Wpkh(ref wpkh) => wpkh.for_each_key(pred), ShInner::Ms(ref ms) => ms.for_each_key(pred), } diff --git a/src/descriptor/sortedmulti.rs b/src/descriptor/sortedmulti.rs deleted file mode 100644 index f57af806f..000000000 --- a/src/descriptor/sortedmulti.rs +++ /dev/null @@ -1,243 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -//! # Sorted Multi -//! -//! Implementation of sorted multi primitive for descriptors -//! - -use core::fmt; -use core::marker::PhantomData; - -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; -use crate::miniscript::satisfy::{Placeholder, Satisfaction}; -use crate::plan::AssetProvider; -use crate::prelude::*; -use crate::sync::Arc; -use crate::{ - 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 { - inner: Threshold, - /// The current ScriptContext for sortedmulti - phantom: PhantomData, -} - -impl SortedMultiVec { - fn constructor_check(mut self) -> Result { - let ms = Miniscript::::multi(self.inner); - // Check the limits before creating a new SortedMultiVec - // For example, under p2sh context the scriptlen can only be - // upto 520 bytes. - Ctx::check_local_validity(&ms)?; - if let Terminal::Multi(inner) = ms.node { - self.inner = inner; - Ok(self) - } else { - unreachable!() - } - } - - /// 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(thresh: Threshold) -> Result { - let ret = Self { inner: thresh, phantom: PhantomData }; - ret.constructor_check() - } - - /// Parse an expression tree into a SortedMultiVec - pub fn from_tree(tree: expression::TreeIterItem) -> Result - where - Pk: FromStrKey, - { - tree.verify_toplevel("sortedmulti", 1..) - .map_err(From::from) - .map_err(Error::Parse)?; - - let ret = Self { - inner: tree - .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse))?, - phantom: PhantomData, - }; - ret.constructor_check() - } - - /// This will panic if fpk returns an uncompressed key when - /// converting to a Segwit descriptor. To prevent this panic, ensure - /// fpk returns an error in this case instead. - pub fn translate_pk( - &self, - t: &mut T, - ) -> Result, TranslateErr> - where - T: Translator, - { - let ret = SortedMultiVec { - inner: self.inner.translate_ref(|pk| t.pk(pk))?, - phantom: PhantomData, - }; - ret.constructor_check().map_err(TranslateErr::OuterError) - } - - /// 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) - } -} - -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.inner.clone())).expect("Must typecheck"); - ms.sanity_check().map_err(From::from) - } -} - -impl SortedMultiVec { - /// Create Terminal::Multi containing sorted pubkeys - pub fn sorted_node(&self) -> Terminal - where - Pk: ToPublicKey, - { - Terminal::Multi(self.inner.clone().into_sorted_bip67()) - } - - /// Encode as a Bitcoin script - pub fn encode(&self) -> script::ScriptBuf - where - Pk: ToPublicKey, - { - self.sorted_node() - .encode(script::Builder::new()) - .into_script() - } - - /// Attempt to produce a satisfying witness for the - /// witness script represented by the parse tree - pub fn satisfy(&self, satisfier: S) -> Result>, Error> - where - Pk: ToPublicKey, - S: Satisfier, - { - let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck"); - ms.satisfy(satisfier) - } - - /// Attempt to produce a witness template given the assets available - pub fn build_template

(&self, provider: &P) -> Satisfaction> - where - Pk: ToPublicKey, - P: AssetProvider, - { - let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck"); - ms.build_template(provider) - } - - /// Size, in bytes of the script-pubkey. If this Miniscript is used outside - /// of segwit (e.g. in a bare or P2SH descriptor), this quantity should be - /// multiplied by 4 to compute the weight. - /// - /// In general, it is not recommended to use this function directly, but - /// 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()) - + 1 - + 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 - /// fragment, including the witness script itself. Used to estimate - /// the weight of the `VarInt` that specifies this number in a serialized - /// transaction. - /// - /// 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() } - - /// Maximum size, in bytes, of a satisfying witness. - /// In general, it is not recommended to use this function directly, but - /// to instead call the corresponding function on a `Descriptor`, which - /// will handle the segwit/non-segwit technicalities for you. - /// - /// 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() } -} - -impl policy::Liftable for SortedMultiVec { - fn lift(&self) -> Result, Error> { - Ok(policy::semantic::Policy::Thresh( - self.inner - .map_ref(|pk| Arc::new(policy::semantic::Policy::Key(pk.clone()))) - .forget_maximum(), - )) - } -} - -impl fmt::Debug for SortedMultiVec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) } -} - -impl fmt::Display for SortedMultiVec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.inner.display("sortedmulti", true), f) - } -} - -#[cfg(test)] -mod tests { - use core::str::FromStr as _; - - use bitcoin::PublicKey; - - use super::*; - use crate::miniscript::context::{Legacy, ScriptContextError}; - - #[test] - fn too_many_pubkeys_for_p2sh() { - // Arbitrary 65-byte public key (66 with length prefix). - let pk = PublicKey::from_str( - "0400232a2acfc9b43fa89f1b4f608fde335d330d7114f70ea42bfb4a41db368a3e3be6934a4097dd25728438ef73debb1f2ffdb07fec0f18049df13bdc5285dc5b", - ) - .unwrap(); - - // This is legal for CHECKMULTISIG, but the 8 keys consume the whole 520 bytes - // allowed by P2SH, meaning that the full script goes over the limit. - let thresh = Threshold::new(2, vec![pk; 8]).expect("the thresh is ok.."); - let res: Result, Error> = SortedMultiVec::new(thresh); - let error = res.expect_err("constructor should err"); - - match error { - Error::ContextError(ScriptContextError::MaxRedeemScriptSizeExceeded { .. }) => {} // ok - other => panic!("unexpected error: {:?}", other), - } - } -} diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index ca88adfc9..5aed49fb0 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -909,7 +909,9 @@ where } } } - Terminal::Multi(ref thresh) if node_state.n_evaluated == 0 => { + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) + if node_state.n_evaluated == 0 => + { let len = self.stack.len(); if len < thresh.k() + 1 { return Some(Err(Error::InsufficientSignaturesMultiSig)); @@ -956,7 +958,7 @@ where } } } - Terminal::Multi(ref thresh) => { + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { if node_state.n_satisfied == thresh.k() { //multi-sig bug: Pop extra 0 if let Some(stack::Element::Dissatisfied) = self.stack.pop() { diff --git a/src/iter/mod.rs b/src/iter/mod.rs index 82ce9a25f..90b701213 100644 --- a/src/iter/mod.rs +++ b/src/iter/mod.rs @@ -27,8 +27,8 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Miniscript Tree::Nullary, + | Ripemd160(..) | Hash160(..) | True | False | Multi(..) | SortedMulti(..) + | MultiA(..) | SortedMultiA(..) => Tree::Nullary, Alt(ref sub) | Swap(ref sub) | Check(ref sub) @@ -58,8 +58,8 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Arc Tree::Nullary, + | Ripemd160(..) | Hash160(..) | True | False | Multi(..) | SortedMulti(..) + | MultiA(..) | SortedMultiA(..) => Tree::Nullary, Alt(ref sub) | Swap(ref sub) | Check(ref sub) @@ -89,8 +89,8 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Terminal Tree::Nullary, + | Ripemd160(..) | Hash160(..) | True | False | Multi(..) | SortedMulti(..) + | MultiA(..) | SortedMultiA(..) => Tree::Nullary, Alt(ref sub) | Swap(ref sub) | Check(ref sub) diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index 3dc53bc77..078cfac58 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -152,10 +152,17 @@ impl Terminal { .push_int(thresh.k() as i64) .push_opcode(opcodes::all::OP_EQUAL) } - Terminal::Multi(ref thresh) => { + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { debug_assert!(Ctx::sig_type() == SigType::Ecdsa); + let sorted; + let iter = if let Terminal::SortedMulti(thresh) = self { + sorted = thresh.clone().into_sorted_bip67(); + sorted.iter() + } else { + thresh.iter() + }; builder = builder.push_int(thresh.k() as i64); - for pk in thresh.data() { + for pk in iter { builder = builder.push_key(&pk.to_public_key()); } builder diff --git a/src/miniscript/context.rs b/src/miniscript/context.rs index 3801d35f5..bd3f0a929 100644 --- a/src/miniscript/context.rs +++ b/src/miniscript/context.rs @@ -384,7 +384,7 @@ impl ScriptContext for Legacy { // 1. Check the node first, throw an error on the language itself let node_checked = match ms.node { Terminal::PkK(ref pk) => Self::check_pk(pk), - Terminal::Multi(ref thresh) => { + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { for pk in thresh.iter() { Self::check_pk(pk)?; } @@ -490,7 +490,7 @@ impl ScriptContext for Segwitv0 { // 1. Check the node first, throw an error on the language itself let node_checked = match ms.node { Terminal::PkK(ref pk) => Self::check_pk(pk), - Terminal::Multi(ref thresh) => { + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { for pk in thresh.iter() { Self::check_pk(pk)?; } @@ -609,7 +609,9 @@ impl ScriptContext for Tap { } Ok(()) } - Terminal::Multi(..) => Err(ScriptContextError::TaprootMultiDisabled), + Terminal::Multi(..) | Terminal::SortedMulti(..) => { + Err(ScriptContextError::TaprootMultiDisabled) + } _ => Ok(()), }; // 2. After fragment and param check, validate the script size finally @@ -714,7 +716,7 @@ impl ScriptContext for BareCtx { // 1. Check the node first, throw an error on the language itself let node_checked = match ms.node { Terminal::PkK(ref key) => Self::check_pk(key), - Terminal::Multi(ref thresh) => { + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { for pk in thresh.iter() { Self::check_pk(pk)?; } @@ -763,7 +765,9 @@ impl ScriptContext for BareCtx { Terminal::PkK(_pk) | Terminal::PkH(_pk) => Ok(()), _ => Err(Error::NonStandardBareScript), }, - Terminal::Multi(ref thresh) if thresh.n() <= 3 => Ok(()), + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) if thresh.n() <= 3 => { + Ok(()) + } _ => Err(Error::NonStandardBareScript), } } diff --git a/src/miniscript/decode.rs b/src/miniscript/decode.rs index 772495793..a8c0b8491 100644 --- a/src/miniscript/decode.rs +++ b/src/miniscript/decode.rs @@ -153,6 +153,8 @@ pub enum Terminal { Thresh(Threshold>, 0>), /// `k ()* n CHECKMULTISIG` Multi(Threshold), + /// `k ()* n CHECKMULTISIG` + SortedMulti(Threshold), /// ` CHECKSIG ( CHECKSIGADD)*(n-1) k NUMEQUAL` MultiA(Threshold), /// ` CHECKSIG ( CHECKSIGADD)*(n-1) k NUMEQUAL` @@ -214,6 +216,7 @@ impl Clone for Terminal { Terminal::Thresh(thresh.map_ref(|child| Arc::new(Miniscript::clone(child)))) } Terminal::Multi(ref thresh) => Terminal::Multi(thresh.clone()), + Terminal::SortedMulti(ref thresh) => Terminal::SortedMulti(thresh.clone()), Terminal::MultiA(ref thresh) => Terminal::MultiA(thresh.clone()), Terminal::SortedMultiA(ref thresh) => Terminal::SortedMultiA(thresh.clone()), } @@ -234,6 +237,9 @@ impl PartialEq for Terminal { (Terminal::Ripemd160(h1), Terminal::Ripemd160(h2)) if h1 != h2 => return false, (Terminal::Hash160(h1), Terminal::Hash160(h2)) if h1 != h2 => return false, (Terminal::Multi(th1), Terminal::Multi(th2)) if th1 != th2 => return false, + (Terminal::SortedMulti(th1), Terminal::SortedMulti(th2)) if th1 != th2 => { + return false + } (Terminal::MultiA(th1), Terminal::MultiA(th2)) if th1 != th2 => return false, (Terminal::SortedMultiA(th1), Terminal::SortedMultiA(th2)) if th1 != th2 => { return false @@ -269,7 +275,7 @@ impl core::hash::Hash for Terminal th.hash(hasher), + Terminal::Multi(th) | Terminal::SortedMulti(th) => th.hash(hasher), Terminal::MultiA(th) | Terminal::SortedMultiA(th) => th.hash(hasher), _ => {} } diff --git a/src/miniscript/display.rs b/src/miniscript/display.rs index c0da63160..3a1ac1ebd 100644 --- a/src/miniscript/display.rs +++ b/src/miniscript/display.rs @@ -127,6 +127,9 @@ impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for DisplayNode<'a, Pk, Terminal::Multi(ref thresh) => { Tree::Nary(NaryChildren::Keys(thresh.k(), thresh.data())) } + Terminal::SortedMulti(ref thresh) => { + Tree::Nary(NaryChildren::Keys(thresh.k(), thresh.data())) + } Terminal::MultiA(ref thresh) => { Tree::Nary(NaryChildren::Keys(thresh.k(), thresh.data())) } @@ -274,6 +277,7 @@ impl Terminal { Terminal::OrI(..) => "or_i", Terminal::Thresh(..) => "thresh", Terminal::Multi(..) => "multi", + Terminal::SortedMulti(..) => "sortedmulti", Terminal::MultiA(..) => "multi_a", Terminal::SortedMultiA(..) => "sortedmulti_a", } diff --git a/src/miniscript/iter.rs b/src/miniscript/iter.rs index 2bfcb7008..0ef2272af 100644 --- a/src/miniscript/iter.rs +++ b/src/miniscript/iter.rs @@ -29,7 +29,13 @@ impl Miniscript { /// them. pub fn branches(&self) -> Vec<&Miniscript> { match self.node { - Terminal::PkK(_) | Terminal::PkH(_) | Terminal::RawPkH(_) | Terminal::Multi(_) => { + Terminal::PkK(_) + | Terminal::PkH(_) + | Terminal::RawPkH(_) + | Terminal::Multi(_) + | Terminal::SortedMulti(_) + | Terminal::MultiA(_) + | Terminal::SortedMultiA(_) => { vec![] } @@ -96,6 +102,7 @@ impl Miniscript { match (&self.node, n) { (Terminal::PkK(key), 0) | (Terminal::PkH(key), 0) => Some(key.clone()), (Terminal::Multi(thresh), _) => thresh.data().get(n).cloned(), + (Terminal::SortedMulti(thresh), _) => thresh.data().get(n).cloned(), (Terminal::MultiA(thresh), _) => thresh.data().get(n).cloned(), (Terminal::SortedMultiA(thresh), _) => thresh.data().get(n).cloned(), _ => None, diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 38289f2aa..4aff8784f 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -125,6 +125,7 @@ mod private { Terminal::Thresh(thresh.map_ref(|_| stack.pop().unwrap())) } Terminal::Multi(ref thresh) => Terminal::Multi(thresh.clone()), + Terminal::SortedMulti(ref thresh) => Terminal::SortedMulti(thresh.clone()), Terminal::MultiA(ref thresh) => Terminal::MultiA(thresh.clone()), Terminal::SortedMultiA(ref thresh) => Terminal::SortedMultiA(thresh.clone()), }; @@ -282,7 +283,18 @@ mod private { } } - // non-const because Thresh::n is not because Vec::len is only const in 1.87 + // non-const because Thresh::n is not because Vec::len is not (needs Rust 1.87) + /// The `sortedmulti` combinator. + pub fn sortedmulti(thresh: crate::Threshold) -> Self { + Self { + ty: types::Type::sortedmulti(), + ext: types::extra_props::ExtData::sortedmulti(&thresh), + node: Terminal::SortedMulti(thresh), + phantom: PhantomData, + } + } + + // non-const because Thresh::n is not because Vec::len is not /// The `multi` combinator. pub fn multi_a(thresh: crate::Threshold) -> Self { Self { @@ -389,7 +401,7 @@ impl Miniscript { + thresh.n() // ADD - 1 // no ADD on first element } - Terminal::Multi(ref thresh) => { + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { script_num_size(thresh.k()) + 1 + script_num_size(thresh.n()) @@ -648,7 +660,9 @@ impl ForEachKey for Miniscript { + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) + if !thresh.iter().all(&mut pred) => + { return false; } Terminal::MultiA(ref thresh) | Terminal::SortedMultiA(ref thresh) @@ -732,6 +746,9 @@ impl Miniscript { Terminal::Thresh(thresh.map_ref(|_| translated.pop().unwrap())) } Terminal::Multi(ref thresh) => Terminal::Multi(thresh.translate_ref(|k| t.pk(k))?), + Terminal::SortedMulti(ref thresh) => { + Terminal::SortedMulti(thresh.translate_ref(|k| t.pk(k))?) + } Terminal::MultiA(ref thresh) => { Terminal::MultiA(thresh.translate_ref(|k| t.pk(k))?) } @@ -788,6 +805,7 @@ impl Miniscript { Terminal::Thresh(thresh.map_ref(|_| stack.pop().unwrap())) } Terminal::Multi(ref thresh) => Terminal::Multi(thresh.clone()), + Terminal::SortedMulti(ref thresh) => Terminal::SortedMulti(thresh.clone()), Terminal::MultiA(ref thresh) => Terminal::MultiA(thresh.clone()), Terminal::SortedMultiA(ref thresh) => Terminal::SortedMultiA(thresh.clone()), }; @@ -883,7 +901,7 @@ impl FromTree for Miniscript { .map_err(From::from) .map_err(Error::Parse)?; - if matches!(parent_name, "multi" | "multi_a" | "sortedmulti_a") { + if matches!(parent_name, "multi" | "sortedmulti" | "multi_a" | "sortedmulti_a") { continue; } if parent_name == "thresh" && node.is_first_child() { @@ -981,6 +999,10 @@ impl FromTree for Miniscript { .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) .map(Terminal::Multi) .and_then(Miniscript::from_ast), + "sortedmulti" => node + .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) + .map(Terminal::SortedMulti) + .and_then(Miniscript::from_ast), "multi_a" => node .verify_threshold(|sub| sub.verify_terminal("public_key").map_err(Error::Parse)) .map(Terminal::MultiA) diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index cc8c687a4..8a9f50a9b 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -1478,7 +1478,7 @@ impl Satisfaction> { thresh_fn(thresh, stfr, root_has_sig, leaf_hash, min_fn) } } - Terminal::Multi(ref thresh) => { + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { // Collect all available signatures let mut sig_count = 0; let mut sigs = Vec::with_capacity(thresh.k()); @@ -1744,7 +1744,7 @@ impl Satisfaction> { ) }) .fold(Satisfaction::empty(), Satisfaction::concatenate_rev), - Terminal::Multi(ref thresh) => Satisfaction { + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => Satisfaction { stack: Witness::Stack(vec![Placeholder::PushZero; thresh.k() + 1]), has_sig: false, relative_timelock: None, diff --git a/src/miniscript/types/correctness.rs b/src/miniscript/types/correctness.rs index 638aa4468..5db001567 100644 --- a/src/miniscript/types/correctness.rs +++ b/src/miniscript/types/correctness.rs @@ -152,6 +152,11 @@ impl Correctness { Correctness { base: Base::B, input: Input::AnyNonZero, dissatisfiable: true, unit: true } } + /// Constructor for the correctness properties of the `sortedmulti` fragment. + pub const fn sortedmulti() -> Self { + Correctness { base: Base::B, input: Input::AnyNonZero, dissatisfiable: true, unit: true } + } + /// Constructor for the correctness properties of the `multi_a` fragment. pub const fn multi_a() -> Self { Correctness { base: Base::B, input: Input::Any, dissatisfiable: true, unit: true } diff --git a/src/miniscript/types/extra_props.rs b/src/miniscript/types/extra_props.rs index 7ad83f30f..204b4bddf 100644 --- a/src/miniscript/types/extra_props.rs +++ b/src/miniscript/types/extra_props.rs @@ -310,6 +310,13 @@ impl ExtData { } } + /// Extra properties for the `sortedmulti` fragment. + pub fn sortedmulti( + thresh: &crate::Threshold, + ) -> Self { + Self::multi(thresh) + } + /// Extra properties for the `multi_a` fragment. pub fn multi_a(k: usize, n: usize) -> Self { let num_cost = match (k > 16, n > 16) { @@ -949,6 +956,7 @@ impl ExtData { Terminal::PkH(ref k) => Self::pk_h::<_, Ctx>(Some(k)), Terminal::RawPkH(..) => Self::pk_h::(None), Terminal::Multi(ref thresh) => Self::multi(thresh), + Terminal::SortedMulti(ref thresh) => Self::sortedmulti(thresh), Terminal::MultiA(ref thresh) => Self::multi_a(thresh.k(), thresh.n()), Terminal::SortedMultiA(ref thresh) => Self::multi_a(thresh.k(), thresh.n()), Terminal::After(t) => Self::after(t), diff --git a/src/miniscript/types/malleability.rs b/src/miniscript/types/malleability.rs index 70a6f25f4..93f32b3a6 100644 --- a/src/miniscript/types/malleability.rs +++ b/src/miniscript/types/malleability.rs @@ -104,6 +104,11 @@ impl Malleability { Malleability { dissat: Dissat::Unique, safe: true, non_malleable: true } } + /// Constructor for the malleabilitiy properties of the `sortedmulti` fragment. + pub const fn sortedmulti() -> Self { + Malleability { dissat: Dissat::Unique, safe: true, non_malleable: true } + } + /// Constructor for the malleabilitiy properties of the `multi_a` fragment. pub const fn multi_a() -> Self { Malleability { dissat: Dissat::Unique, safe: true, non_malleable: true } diff --git a/src/miniscript/types/mod.rs b/src/miniscript/types/mod.rs index 6ebf59900..674603edf 100644 --- a/src/miniscript/types/mod.rs +++ b/src/miniscript/types/mod.rs @@ -229,6 +229,11 @@ impl Type { Type { corr: Correctness::multi(), mall: Malleability::multi() } } + /// Constructor for the type of the `sortedmulti` fragment. + pub const fn sortedmulti() -> Self { + Type { corr: Correctness::sortedmulti(), mall: Malleability::sortedmulti() } + } + /// Constructor for the type of the `multi_a` fragment. pub const fn multi_a() -> Self { Type { corr: Correctness::multi_a(), mall: Malleability::multi_a() } @@ -464,6 +469,7 @@ impl Type { Terminal::PkK(..) => Ok(Self::pk_k()), Terminal::PkH(..) | Terminal::RawPkH(..) => Ok(Self::pk_h()), Terminal::Multi(..) => Ok(Self::multi()), + Terminal::SortedMulti(..) => Ok(Self::sortedmulti()), Terminal::MultiA(..) => Ok(Self::multi_a()), Terminal::SortedMultiA(..) => Ok(Self::sortedmulti_a()), Terminal::After(_) => Ok(Self::time()), diff --git a/src/plan.rs b/src/plan.rs index d53e9ebee..b76d4a816 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -416,9 +416,7 @@ impl Plan { input.redeem_script = Some(wsh.inner_script().to_p2wsh()); } descriptor::ShInner::Wpkh(..) => input.redeem_script = Some(sh.inner_script()), - descriptor::ShInner::SortedMulti(_) | descriptor::ShInner::Ms(_) => { - input.redeem_script = Some(sh.inner_script()) - } + descriptor::ShInner::Ms(_) => input.redeem_script = Some(sh.inner_script()), }, Descriptor::Wsh(wsh) => input.witness_script = Some(wsh.inner_script()), Descriptor::Tr(_) => unreachable!("Tr is dealt with separately"), diff --git a/src/policy/compiler.rs b/src/policy/compiler.rs index 4027cb248..24b0f0ae1 100644 --- a/src/policy/compiler.rs +++ b/src/policy/compiler.rs @@ -230,6 +230,14 @@ impl CompilerExtData { } } + fn sortedmulti(k: usize, _n: usize) -> Self { + CompilerExtData { + branch_prob: None, + sat_cost: 1.0 + 73.0 * k as f64, + dissat_cost: Some(1.0 * (k + 1) as f64), + } + } + fn multi_a(k: usize, n: usize) -> Self { CompilerExtData { branch_prob: None, @@ -465,6 +473,7 @@ impl CompilerExtData { Terminal::PkK(..) => Self::pk_k::(), Terminal::PkH(..) | Terminal::RawPkH(..) => Self::pk_h::(), Terminal::Multi(ref thresh) => Self::multi(thresh.k(), thresh.n()), + Terminal::SortedMulti(ref thresh) => Self::sortedmulti(thresh.k(), thresh.n()), Terminal::MultiA(ref thresh) => Self::multi_a(thresh.k(), thresh.n()), Terminal::SortedMultiA(ref thresh) => Self::sortedmulti_a(thresh.k(), thresh.n()), Terminal::After(_) => Self::time(), diff --git a/src/policy/mod.rs b/src/policy/mod.rs index 455449ae1..073c3aa40 100644 --- a/src/policy/mod.rs +++ b/src/policy/mod.rs @@ -156,11 +156,13 @@ impl Liftable for Miniscript Terminal::Thresh(ref thresh) => { Arc::new(Semantic::Thresh(thresh.map_ref(|_| stack.pop().unwrap()))) } - Terminal::Multi(ref thresh) => Arc::new(Semantic::Thresh( - thresh - .map_ref(|key| Arc::new(Semantic::Key(key.clone()))) - .forget_maximum(), - )), + Terminal::Multi(ref thresh) | Terminal::SortedMulti(ref thresh) => { + Arc::new(Semantic::Thresh( + thresh + .map_ref(|key| Arc::new(Semantic::Key(key.clone()))) + .forget_maximum(), + )) + } Terminal::MultiA(ref thresh) | Terminal::SortedMultiA(ref thresh) => { Arc::new(Semantic::Thresh( thresh diff --git a/src/psbt/mod.rs b/src/psbt/mod.rs index a0833ad2e..554ca9b36 100644 --- a/src/psbt/mod.rs +++ b/src/psbt/mod.rs @@ -1164,9 +1164,7 @@ fn update_item_with_descriptor_helper( *item.redeem_script() = Some(wsh.inner_script().to_p2wsh()); } descriptor::ShInner::Wpkh(..) => *item.redeem_script() = Some(sh.inner_script()), - descriptor::ShInner::SortedMulti(_) | descriptor::ShInner::Ms(_) => { - *item.redeem_script() = Some(sh.inner_script()) - } + descriptor::ShInner::Ms(_) => *item.redeem_script() = Some(sh.inner_script()), }, Descriptor::Wsh(wsh) => *item.witness_script() = Some(wsh.inner_script()), Descriptor::Tr(_) => unreachable!("Tr is dealt with separately"),