From 26dde9e7bc2535074e5a9e65afd66692f45570c3 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 11 Aug 2025 14:44:53 +0000 Subject: [PATCH 1/6] rename miniscript/satisfy.rs to miniscript/satisfy/mod.rs This is a very large file with lots of different things in it and poor privacy boundaries. We want to break it up into modules. Start by creating a new directory. --- src/miniscript/{satisfy.rs => satisfy/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/miniscript/{satisfy.rs => satisfy/mod.rs} (100%) diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy/mod.rs similarity index 100% rename from src/miniscript/satisfy.rs rename to src/miniscript/satisfy/mod.rs From c37f9823a0e21899aa6449e45b2c52b6bfcd7d17 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 11 Aug 2025 13:34:57 +0000 Subject: [PATCH 2/6] miniscript/satisfy: use Miniscript instead of Terminal in API I would like to make the satisfaction logic non-recursive. We have a post order iterator on Miniscript but not on Terminal. So we must use Miniscript in our API functions. (This also is more conceptually correct.) --- src/miniscript/mod.rs | 8 +- src/miniscript/satisfy/mod.rs | 177 ++++++++++------------------------ 2 files changed, 55 insertions(+), 130 deletions(-) diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index fea98b5bb..4adc9fadf 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -440,7 +440,7 @@ impl Miniscript { { // Only satisfactions for default versions (0xc0) are allowed. let satisfaction = satisfy::Satisfaction::satisfy( - &self.node, + self, &satisfier, self.ty.mall.safe, &self.leaf_hash_internal(), @@ -458,7 +458,7 @@ impl Miniscript { Pk: ToPublicKey, { let satisfaction = satisfy::Satisfaction::satisfy_mall( - &self.node, + self, &satisfier, self.ty.mall.safe, &self.leaf_hash_internal(), @@ -487,7 +487,7 @@ impl Miniscript { Pk: ToPublicKey, { satisfy::Satisfaction::build_template( - &self.node, + self, provider, self.ty.mall.safe, &self.leaf_hash_internal(), @@ -503,7 +503,7 @@ impl Miniscript { Pk: ToPublicKey, { satisfy::Satisfaction::build_template_mall( - &self.node, + self, provider, self.ty.mall.safe, &self.leaf_hash_internal(), diff --git a/src/miniscript/satisfy/mod.rs b/src/miniscript/satisfy/mod.rs index 027aabd7f..f26305d41 100644 --- a/src/miniscript/satisfy/mod.rs +++ b/src/miniscript/satisfy/mod.rs @@ -922,7 +922,7 @@ impl Satisfaction> { } pub(crate) fn build_template( - term: &Terminal, + node: &Miniscript, provider: &P, root_has_sig: bool, leaf_hash: &TapLeafHash, @@ -932,7 +932,7 @@ impl Satisfaction> { P: AssetProvider, { Self::satisfy_helper( - term, + node, provider, root_has_sig, leaf_hash, @@ -942,7 +942,7 @@ impl Satisfaction> { } pub(crate) fn build_template_mall( - term: &Terminal, + node: &Miniscript, provider: &P, root_has_sig: bool, leaf_hash: &TapLeafHash, @@ -952,7 +952,7 @@ impl Satisfaction> { P: AssetProvider, { Self::satisfy_helper( - term, + node, provider, root_has_sig, leaf_hash, @@ -980,28 +980,14 @@ impl Satisfaction> { let mut sats = thresh .iter() .map(|s| { - Self::satisfy_helper( - &s.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - &mut Self::thresh, - ) + Self::satisfy_helper(s, stfr, root_has_sig, leaf_hash, min_fn, &mut Self::thresh) }) .collect::>(); // Start with the to-return stack set to all dissatisfactions let mut ret_stack = thresh .iter() .map(|s| { - Self::dissatisfy_helper( - &s.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - &mut Self::thresh, - ) + Self::dissatisfy_helper(s, stfr, root_has_sig, leaf_hash, min_fn, &mut Self::thresh) }) .collect::>(); @@ -1099,7 +1085,7 @@ impl Satisfaction> { .iter() .map(|s| { Self::satisfy_helper( - &s.node, + s, stfr, root_has_sig, leaf_hash, @@ -1113,7 +1099,7 @@ impl Satisfaction> { .iter() .map(|s| { Self::dissatisfy_helper( - &s.node, + s, stfr, root_has_sig, leaf_hash, @@ -1228,7 +1214,7 @@ impl Satisfaction> { // produce a non-malleable satisfaction fn satisfy_helper( - term: &Terminal, + node: &Miniscript, stfr: &Sat, root_has_sig: bool, leaf_hash: &TapLeafHash, @@ -1250,7 +1236,7 @@ impl Satisfaction> { &mut F, ) -> Satisfaction>, { - match *term { + match *node.as_inner() { Terminal::PkK(ref pk) => Satisfaction { stack: Witness::signature::<_, Ctx>(stfr, pk, leaf_hash), has_sig: true, @@ -1347,17 +1333,11 @@ impl Satisfaction> { | Terminal::Verify(ref sub) | Terminal::NonZero(ref sub) | Terminal::ZeroNotEqual(ref sub) => { - Self::satisfy_helper(&sub.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) + Self::satisfy_helper(sub, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) } Terminal::DupIf(ref sub) => { - let sat = Self::satisfy_helper( - &sub.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ); + let sat = + Self::satisfy_helper(sub, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); Satisfaction { stack: Witness::combine(sat.stack, Witness::push_1()), has_sig: sat.has_sig, @@ -1367,50 +1347,32 @@ impl Satisfaction> { } Terminal::AndV(ref l, ref r) | Terminal::AndB(ref l, ref r) => { let l_sat = - Self::satisfy_helper(&l.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + Self::satisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); let r_sat = - Self::satisfy_helper(&r.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + Self::satisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); l_sat.concatenate_rev(r_sat) } Terminal::AndOr(ref a, ref b, ref c) => { let a_sat = - Self::satisfy_helper(&a.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let a_nsat = Self::dissatisfy_helper( - &a.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ); + Self::satisfy_helper(a, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let a_nsat = + Self::dissatisfy_helper(a, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); let b_sat = - Self::satisfy_helper(&b.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + Self::satisfy_helper(b, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); let c_sat = - Self::satisfy_helper(&c.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + Self::satisfy_helper(c, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); min_fn(a_sat.concatenate_rev(b_sat), a_nsat.concatenate_rev(c_sat)) } Terminal::OrB(ref l, ref r) => { let l_sat = - Self::satisfy_helper(&l.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + Self::satisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); let r_sat = - Self::satisfy_helper(&r.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let l_nsat = Self::dissatisfy_helper( - &l.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ); - let r_nsat = Self::dissatisfy_helper( - &r.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ); + Self::satisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let l_nsat = + Self::dissatisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let r_nsat = + Self::dissatisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); assert!(!l_nsat.has_sig); assert!(!r_nsat.has_sig); @@ -1422,17 +1384,11 @@ impl Satisfaction> { } Terminal::OrD(ref l, ref r) | Terminal::OrC(ref l, ref r) => { let l_sat = - Self::satisfy_helper(&l.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + Self::satisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); let r_sat = - Self::satisfy_helper(&r.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let l_nsat = Self::dissatisfy_helper( - &l.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ); + Self::satisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let l_nsat = + Self::dissatisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); assert!(!l_nsat.has_sig); @@ -1440,9 +1396,9 @@ impl Satisfaction> { } Terminal::OrI(ref l, ref r) => { let l_sat = - Self::satisfy_helper(&l.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + Self::satisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); let r_sat = - Self::satisfy_helper(&r.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + Self::satisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); min_fn( Satisfaction { stack: Witness::combine(l_sat.stack, Witness::push_1()), @@ -1465,7 +1421,7 @@ impl Satisfaction> { .iter() .map(|s| { Self::satisfy_helper( - &s.node, + s, stfr, root_has_sig, leaf_hash, @@ -1571,7 +1527,7 @@ impl Satisfaction> { // Helper function to produce a dissatisfaction fn dissatisfy_helper( - term: &Terminal, + node: &Miniscript, stfr: &Sat, root_has_sig: bool, leaf_hash: &TapLeafHash, @@ -1593,7 +1549,7 @@ impl Satisfaction> { &mut F, ) -> Satisfaction>, { - match *term { + match *node.as_inner() { Terminal::PkK(..) => Satisfaction { stack: Witness::push_0(), has_sig: false, @@ -1647,7 +1603,7 @@ impl Satisfaction> { | Terminal::Swap(ref sub) | Terminal::Check(ref sub) | Terminal::ZeroNotEqual(ref sub) => { - Self::dissatisfy_helper(&sub.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) + Self::dissatisfy_helper(sub, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) } Terminal::DupIf(_) | Terminal::NonZero(_) => Satisfaction { stack: Witness::push_0(), @@ -1657,9 +1613,9 @@ impl Satisfaction> { }, Terminal::AndV(ref v, ref other) => { let vsat = - Self::satisfy_helper(&v.node, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + Self::satisfy_helper(v, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); let odissat = Self::dissatisfy_helper( - &other.node, + other, stfr, root_has_sig, leaf_hash, @@ -1672,33 +1628,15 @@ impl Satisfaction> { | Terminal::OrB(ref l, ref r) | Terminal::OrD(ref l, ref r) | Terminal::AndOr(ref l, _, ref r) => { - let lnsat = Self::dissatisfy_helper( - &l.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ); - let rnsat = Self::dissatisfy_helper( - &r.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ); + let lnsat = + Self::dissatisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); + let rnsat = + Self::dissatisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); lnsat.concatenate_rev(rnsat) } Terminal::OrI(ref l, ref r) => { - let lnsat = Self::dissatisfy_helper( - &l.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ); + let lnsat = + Self::dissatisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); let dissat_1 = Satisfaction { stack: Witness::combine(lnsat.stack, Witness::push_1()), has_sig: lnsat.has_sig, @@ -1706,14 +1644,8 @@ impl Satisfaction> { absolute_timelock: None, }; - let rnsat = Self::dissatisfy_helper( - &r.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ); + let rnsat = + Self::dissatisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); let dissat_2 = Satisfaction { stack: Witness::combine(rnsat.stack, Witness::push_0()), has_sig: rnsat.has_sig, @@ -1727,14 +1659,7 @@ impl Satisfaction> { Terminal::Thresh(ref thresh) => thresh .iter() .map(|s| { - Self::dissatisfy_helper( - &s.node, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ) + Self::dissatisfy_helper(s, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) }) .fold(Satisfaction::empty(), Satisfaction::concatenate_rev), Terminal::Multi(ref thresh) => Satisfaction { @@ -1777,7 +1702,7 @@ impl Satisfaction> { impl Satisfaction> { /// Produce a satisfaction non-malleable satisfaction pub(super) fn satisfy( - term: &Terminal, + node: &Miniscript, stfr: &Sat, root_has_sig: bool, leaf_hash: &TapLeafHash, @@ -1787,14 +1712,14 @@ impl Satisfaction> { Pk: MiniscriptKey + ToPublicKey, Sat: Satisfier, { - Satisfaction::>::build_template(term, &stfr, root_has_sig, leaf_hash) + Satisfaction::>::build_template(node, &stfr, root_has_sig, leaf_hash) .try_completing(stfr) .expect("the same satisfier should manage to complete the template") } /// Produce a satisfaction(possibly malleable) pub(super) fn satisfy_mall( - term: &Terminal, + node: &Miniscript, stfr: &Sat, root_has_sig: bool, leaf_hash: &TapLeafHash, @@ -1804,7 +1729,7 @@ impl Satisfaction> { Pk: MiniscriptKey + ToPublicKey, Sat: Satisfier, { - Satisfaction::>::build_template_mall(term, &stfr, root_has_sig, leaf_hash) + Satisfaction::>::build_template_mall(node, &stfr, root_has_sig, leaf_hash) .try_completing(stfr) .expect("the same satisfier should manage to complete the template") } From c074871de0b82b5511151a6cf14c094151200365 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 11 Aug 2025 14:52:05 +0000 Subject: [PATCH 3/6] miniscript/satisfy: move pk_k logic into helper function For every fragment, we are going to create a short helper function which computes the satisfaction and dissatisfaction for that fragment. We create these together because in general satisfactions and dissatisfactions are defined in terms of each other: * Several satisfactions (e.g. `or_b`) require dissatisfactions to create * Satisfaction of `and_v` requires the satisfaction of its V child to create When we un-recursify these functions we will therefore need to compute both satisfactions and dissatisfactions together. (The result will be less efficient than the recursive version, since it will compute both sats and dissats for every fragment rather than computing just the data that's needed. But it should be easier to follow.) The next commit will do this for several more fragments. But I am doing it for pk_k in a commit by itself so you can see the process. --- src/miniscript/satisfy/mod.rs | 16 ++++---------- src/miniscript/satisfy/sat_dissat.rs | 33 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 src/miniscript/satisfy/sat_dissat.rs diff --git a/src/miniscript/satisfy/mod.rs b/src/miniscript/satisfy/mod.rs index f26305d41..711dfca21 100644 --- a/src/miniscript/satisfy/mod.rs +++ b/src/miniscript/satisfy/mod.rs @@ -6,6 +6,8 @@ //! scriptpubkeys. //! +mod sat_dissat; + use core::{cmp, fmt, mem}; use bitcoin::hashes::hash160; @@ -1237,12 +1239,7 @@ impl Satisfaction> { ) -> Satisfaction>, { match *node.as_inner() { - Terminal::PkK(ref pk) => Satisfaction { - stack: Witness::signature::<_, Ctx>(stfr, pk, leaf_hash), - has_sig: true, - relative_timelock: None, - absolute_timelock: None, - }, + Terminal::PkK(ref pk) => Self::pk_k::<_, Ctx>(stfr, pk, leaf_hash).1, Terminal::PkH(ref pk) => { let wit = Witness::signature::<_, Ctx>(stfr, pk, leaf_hash); Satisfaction { @@ -1550,12 +1547,7 @@ impl Satisfaction> { ) -> Satisfaction>, { match *node.as_inner() { - Terminal::PkK(..) => Satisfaction { - stack: Witness::push_0(), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, + Terminal::PkK(ref pk) => Self::pk_k::<_, Ctx>(stfr, pk, leaf_hash).0, Terminal::PkH(ref pk) => Satisfaction { stack: Witness::combine( Witness::push_0(), diff --git a/src/miniscript/satisfy/sat_dissat.rs b/src/miniscript/satisfy/sat_dissat.rs new file mode 100644 index 000000000..a241112a3 --- /dev/null +++ b/src/miniscript/satisfy/sat_dissat.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Satisfactions and dissatisfactions for individual Miniscript fragments. + +use bitcoin::TapLeafHash; + +use super::{Placeholder, Satisfaction, Witness}; +use crate::plan::AssetProvider; +use crate::{MiniscriptKey, ScriptContext, ToPublicKey}; + +impl Satisfaction> { + /// The (dissatisfaction, satisfaction) pair for a `pk_k` fragment. + pub(super) fn pk_k(stfr: &S, pk: &Pk, leaf_hash: &TapLeafHash) -> (Self, Self) + where + S: AssetProvider, + Ctx: ScriptContext, + { + ( + Self { + stack: Witness::push_0(), + has_sig: false, + relative_timelock: None, + absolute_timelock: None, + }, + Self { + stack: Witness::signature::<_, Ctx>(stfr, pk, leaf_hash), + has_sig: true, + relative_timelock: None, + absolute_timelock: None, + }, + ) + } +} From b575a842de08c8b6a89039ae1a49168c02075fab Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 11 Aug 2025 14:55:10 +0000 Subject: [PATCH 4/6] miniscript/satisfy: move the rest of the terminals into their own module Probably this should just be squashed into the previous commit. --- src/miniscript/satisfy/mod.rs | 150 ++++------------------- src/miniscript/satisfy/sat_dissat.rs | 170 +++++++++++++++++++++++++-- 2 files changed, 184 insertions(+), 136 deletions(-) diff --git a/src/miniscript/satisfy/mod.rs b/src/miniscript/satisfy/mod.rs index 711dfca21..e62e1df98 100644 --- a/src/miniscript/satisfy/mod.rs +++ b/src/miniscript/satisfy/mod.rs @@ -859,7 +859,7 @@ impl Witness> { fn hash_dissatisfaction() -> Self { Witness::Stack(vec![Placeholder::HashDissatisfaction]) } /// Construct a satisfaction equivalent to an empty stack - fn empty() -> Self { Witness::Stack(vec![]) } + const fn empty() -> Self { Witness::Stack(vec![]) } /// Construct a satisfaction equivalent to `OP_1` fn push_1() -> Self { Witness::Stack(vec![Placeholder::PushOne]) } @@ -1239,91 +1239,17 @@ impl Satisfaction> { ) -> Satisfaction>, { match *node.as_inner() { + Terminal::False => Self::IMPOSSIBLE, + Terminal::True => Self::TRIVIAL, Terminal::PkK(ref pk) => Self::pk_k::<_, Ctx>(stfr, pk, leaf_hash).1, - Terminal::PkH(ref pk) => { - let wit = Witness::signature::<_, Ctx>(stfr, pk, leaf_hash); - Satisfaction { - stack: Witness::combine( - wit, - Witness::Stack(vec![Placeholder::Pubkey(pk.clone(), Ctx::pk_len(pk))]), - ), - has_sig: true, - relative_timelock: None, - absolute_timelock: None, - } - } - Terminal::RawPkH(ref pkh) => Satisfaction { - stack: Witness::pkh_signature::<_, Ctx>(stfr, pkh, leaf_hash), - has_sig: true, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::After(t) => { - let (stack, absolute_timelock) = if stfr.check_after(t.into()) { - (Witness::empty(), Some(t)) - } else if root_has_sig { - // If the root terminal has signature, the - // signature covers the nLockTime and nSequence - // values. The sender of the transaction should - // take care that it signs the value such that the - // timelock is not met - (Witness::Impossible, None) - } else { - (Witness::Unavailable, None) - }; - Satisfaction { stack, has_sig: false, relative_timelock: None, absolute_timelock } - } - Terminal::Older(t) => { - let (stack, relative_timelock) = if stfr.check_older(t.into()) { - (Witness::empty(), Some(t)) - } else if root_has_sig { - // If the root terminal has signature, the - // signature covers the nLockTime and nSequence - // values. The sender of the transaction should - // take care that it signs the value such that the - // timelock is not met - (Witness::Impossible, None) - } else { - (Witness::Unavailable, None) - }; - Satisfaction { stack, has_sig: false, relative_timelock, absolute_timelock: None } - } - Terminal::Ripemd160(ref h) => Satisfaction { - stack: Witness::ripemd160_preimage(stfr, h), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::Hash160(ref h) => Satisfaction { - stack: Witness::hash160_preimage(stfr, h), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::Sha256(ref h) => Satisfaction { - stack: Witness::sha256_preimage(stfr, h), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::Hash256(ref h) => Satisfaction { - stack: Witness::hash256_preimage(stfr, h), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::True => Satisfaction { - stack: Witness::empty(), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::False => Satisfaction { - stack: Witness::Impossible, - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, + Terminal::PkH(ref pk) => Self::pk_h::<_, Ctx>(stfr, pk, leaf_hash).1, + Terminal::RawPkH(ref pkh) => Self::raw_pk_h::<_, Ctx>(stfr, pkh, leaf_hash).1, + Terminal::After(t) => Self::after(stfr, t, root_has_sig).1, + Terminal::Older(t) => Self::older(stfr, t, root_has_sig).1, + Terminal::Ripemd160(ref h) => Self::ripemd160(stfr, h).1, + Terminal::Hash160(ref h) => Self::hash160(stfr, h).1, + Terminal::Sha256(ref h) => Self::sha256(stfr, h).1, + Terminal::Hash256(ref h) => Self::hash256(stfr, h).1, Terminal::Alt(ref sub) | Terminal::Swap(ref sub) | Terminal::Check(ref sub) @@ -1547,50 +1473,18 @@ impl Satisfaction> { ) -> Satisfaction>, { match *node.as_inner() { + Terminal::False => Self::TRIVIAL, + Terminal::True => Self::IMPOSSIBLE, Terminal::PkK(ref pk) => Self::pk_k::<_, Ctx>(stfr, pk, leaf_hash).0, - Terminal::PkH(ref pk) => Satisfaction { - stack: Witness::combine( - Witness::push_0(), - Witness::Stack(vec![Placeholder::Pubkey(pk.clone(), Ctx::pk_len(pk))]), - ), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::RawPkH(ref pkh) => Satisfaction { - stack: Witness::combine( - Witness::push_0(), - Witness::pkh_public_key::<_, Ctx>(stfr, pkh), - ), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::False => Satisfaction { - stack: Witness::empty(), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::True - | Terminal::Older(_) - | Terminal::After(_) - | Terminal::Verify(_) - | Terminal::OrC(..) => Satisfaction { - stack: Witness::Impossible, - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::Sha256(_) - | Terminal::Hash256(_) - | Terminal::Ripemd160(_) - | Terminal::Hash160(_) => Satisfaction { - stack: Witness::hash_dissatisfaction(), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, + Terminal::PkH(ref pk) => Self::pk_h::<_, Ctx>(stfr, pk, leaf_hash).0, + Terminal::RawPkH(ref pkh) => Self::raw_pk_h::<_, Ctx>(stfr, pkh, leaf_hash).0, + Terminal::After(t) => Self::after(stfr, t, root_has_sig).0, + Terminal::Older(t) => Self::older(stfr, t, root_has_sig).0, + Terminal::Ripemd160(ref h) => Self::ripemd160(stfr, h).0, + Terminal::Hash160(ref h) => Self::hash160(stfr, h).0, + Terminal::Sha256(ref h) => Self::sha256(stfr, h).0, + Terminal::Hash256(ref h) => Self::hash256(stfr, h).0, + Terminal::Verify(_) | Terminal::OrC(..) => Self::IMPOSSIBLE, Terminal::Alt(ref sub) | Terminal::Swap(ref sub) | Terminal::Check(ref sub) diff --git a/src/miniscript/satisfy/sat_dissat.rs b/src/miniscript/satisfy/sat_dissat.rs index a241112a3..9f0ce8e3e 100644 --- a/src/miniscript/satisfy/sat_dissat.rs +++ b/src/miniscript/satisfy/sat_dissat.rs @@ -2,32 +2,186 @@ //! Satisfactions and dissatisfactions for individual Miniscript fragments. +use bitcoin::hashes::hash160; use bitcoin::TapLeafHash; use super::{Placeholder, Satisfaction, Witness}; use crate::plan::AssetProvider; -use crate::{MiniscriptKey, ScriptContext, ToPublicKey}; +use crate::{AbsLockTime, MiniscriptKey, RelLockTime, ScriptContext, ToPublicKey}; impl Satisfaction> { + pub(super) const IMPOSSIBLE: Self = Self { + stack: Witness::Impossible, + has_sig: false, + relative_timelock: None, + absolute_timelock: None, + }; + + pub(super) const TRIVIAL: Self = Self { + stack: Witness::empty(), + has_sig: false, + relative_timelock: None, + absolute_timelock: None, + }; + + /// Constant that can't be `const` due to Rust limitations + fn push_0() -> Self { Self { stack: Witness::push_0(), ..Self::TRIVIAL } } + /// The (dissatisfaction, satisfaction) pair for a `pk_k` fragment. pub(super) fn pk_k(stfr: &S, pk: &Pk, leaf_hash: &TapLeafHash) -> (Self, Self) where S: AssetProvider, Ctx: ScriptContext, { + ( + Self::push_0(), + Self { + stack: Witness::signature::<_, Ctx>(stfr, pk, leaf_hash), + has_sig: true, + ..Self::TRIVIAL + }, + ) + } + + /// The (dissatisfaction, satisfaction) pair for a `pk_h` fragment. + pub(super) fn pk_h(stfr: &S, pk: &Pk, leaf_hash: &TapLeafHash) -> (Self, Self) + where + S: AssetProvider, + Ctx: ScriptContext, + { + let wit = Witness::signature::<_, Ctx>(stfr, pk, leaf_hash); ( Self { - stack: Witness::push_0(), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, + stack: Witness::combine( + Witness::push_0(), + Witness::Stack(vec![Placeholder::Pubkey(pk.clone(), Ctx::pk_len(pk))]), + ), + ..Self::TRIVIAL }, Self { - stack: Witness::signature::<_, Ctx>(stfr, pk, leaf_hash), + stack: Witness::combine( + wit, + Witness::Stack(vec![Placeholder::Pubkey(pk.clone(), Ctx::pk_len(pk))]), + ), + has_sig: true, + ..Self::TRIVIAL + }, + ) + } + + /// The (dissatisfaction, satisfaction) pair for a `pk_h` fragment. + pub(super) fn raw_pk_h( + stfr: &S, + pkh: &hash160::Hash, + leaf_hash: &TapLeafHash, + ) -> (Self, Self) + where + S: AssetProvider, + Ctx: ScriptContext, + { + ( + Self { + stack: Witness::combine( + Witness::push_0(), + Witness::pkh_public_key::<_, Ctx>(stfr, pkh), + ), + ..Self::TRIVIAL + }, + Self { + stack: Witness::pkh_signature::<_, Ctx>(stfr, pkh, leaf_hash), has_sig: true, - relative_timelock: None, - absolute_timelock: None, + ..Self::TRIVIAL }, ) } + + /// The (dissatisfaction, satisfaction) pair for an `after` fragment. + pub(super) fn after(stfr: &S, t: AbsLockTime, root_has_sig: bool) -> (Self, Self) + where + S: AssetProvider, + { + let (stack, absolute_timelock) = if stfr.check_after(t.into()) { + (Witness::empty(), Some(t)) + } else if root_has_sig { + // If the root terminal has signature, the + // signature covers the nLockTime and nSequence + // values. The sender of the transaction should + // take care that it signs the value such that the + // timelock is not met + (Witness::Impossible, None) + } else { + (Witness::Unavailable, None) + }; + ( + Self::IMPOSSIBLE, + Self { stack, has_sig: false, relative_timelock: None, absolute_timelock }, + ) + } + + /// The (dissatisfaction, satisfaction) pair for an `older` fragment. + pub(super) fn older(stfr: &S, t: RelLockTime, root_has_sig: bool) -> (Self, Self) + where + S: AssetProvider, + { + let (stack, relative_timelock) = if stfr.check_older(t.into()) { + (Witness::empty(), Some(t)) + } else if root_has_sig { + // If the root terminal has signature, the + // signature covers the nLockTime and nSequence + // values. The sender of the transaction should + // take care that it signs the value such that the + // timelock is not met + (Witness::Impossible, None) + } else { + (Witness::Unavailable, None) + }; + ( + Self::IMPOSSIBLE, + Self { stack, has_sig: false, relative_timelock, absolute_timelock: None }, + ) + } + + /// The (dissatisfaction, satisfaction) pair for a `ripemd160` fragment. + pub(super) fn ripemd160(stfr: &S, h: &Pk::Ripemd160) -> (Self, Self) + where + S: AssetProvider, + { + ( + Self { stack: Witness::hash_dissatisfaction(), ..Self::TRIVIAL }, + Self { stack: Witness::ripemd160_preimage(stfr, h), ..Self::TRIVIAL }, + ) + } + + /// The (dissatisfaction, satisfaction) pair for a `hash160` fragment. + pub(super) fn hash160(stfr: &S, h: &Pk::Hash160) -> (Self, Self) + where + S: AssetProvider, + { + ( + Self { stack: Witness::hash_dissatisfaction(), ..Self::TRIVIAL }, + Self { stack: Witness::hash160_preimage(stfr, h), ..Self::TRIVIAL }, + ) + } + + /// The (dissatisfaction, satisfaction) pair for a `ripemd256` fragment. + pub(super) fn sha256(stfr: &S, h: &Pk::Sha256) -> (Self, Self) + where + S: AssetProvider, + { + ( + Self { stack: Witness::hash_dissatisfaction(), ..Self::TRIVIAL }, + Self { stack: Witness::sha256_preimage(stfr, h), ..Self::TRIVIAL }, + ) + } + + /// The (dissatisfaction, satisfaction) pair for a `ripemd256` fragment. + pub(super) fn hash256(stfr: &S, h: &Pk::Hash256) -> (Self, Self) + where + S: AssetProvider, + { + ( + Self { stack: Witness::hash_dissatisfaction(), ..Self::TRIVIAL }, + Self { stack: Witness::hash256_preimage(stfr, h), ..Self::TRIVIAL }, + ) + } } From ffb6cee3dd4bb9546925e9b3e157a3fe74f43a8d Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 11 Aug 2025 23:56:49 +0000 Subject: [PATCH 5/6] miniscript/satisfy: move multi/multi_a into submodule Again, candidate for squash. --- src/miniscript/satisfy/mod.rs | 61 +------------- src/miniscript/satisfy/sat_dissat.rs | 116 ++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 59 deletions(-) diff --git a/src/miniscript/satisfy/mod.rs b/src/miniscript/satisfy/mod.rs index e62e1df98..0c4c7a810 100644 --- a/src/miniscript/satisfy/mod.rs +++ b/src/miniscript/satisfy/mod.rs @@ -1357,52 +1357,7 @@ impl Satisfaction> { thresh_fn(thresh, stfr, root_has_sig, leaf_hash, min_fn) } } - Terminal::Multi(ref thresh) => { - // Collect all available signatures - let mut sig_count = 0; - let mut sigs = Vec::with_capacity(thresh.k()); - for pk in thresh.data() { - match Witness::signature::<_, Ctx>(stfr, pk, leaf_hash) { - Witness::Stack(sig) => { - sigs.push(sig); - sig_count += 1; - } - Witness::Impossible => {} - Witness::Unavailable => unreachable!( - "Signature satisfaction without witness must be impossible" - ), - } - } - - if sig_count < thresh.k() { - Satisfaction { - stack: Witness::Impossible, - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - } - } else { - // Throw away the most expensive ones - for _ in 0..sig_count - thresh.k() { - let max_idx = sigs - .iter() - .enumerate() - .max_by_key(|&(_, v)| v.len()) - .unwrap() - .0; - sigs[max_idx] = vec![]; - } - - Satisfaction { - stack: sigs.into_iter().fold(Witness::push_0(), |acc, sig| { - Witness::combine(acc, Witness::Stack(sig)) - }), - has_sig: true, - relative_timelock: None, - absolute_timelock: None, - } - } - } + Terminal::Multi(ref thresh) => Self::multi::<_, Ctx>(stfr, thresh, leaf_hash).1, Terminal::MultiA(ref thresh) => { // Collect all available signatures let mut sig_count = 0; @@ -1548,18 +1503,8 @@ impl Satisfaction> { Self::dissatisfy_helper(s, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) }) .fold(Satisfaction::empty(), Satisfaction::concatenate_rev), - Terminal::Multi(ref thresh) => Satisfaction { - stack: Witness::Stack(vec![Placeholder::PushZero; thresh.k() + 1]), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::MultiA(ref thresh) => Satisfaction { - stack: Witness::Stack(vec![Placeholder::PushZero; thresh.n()]), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, + Terminal::Multi(ref thresh) => Self::multi::<_, Ctx>(stfr, thresh, leaf_hash).0, + Terminal::MultiA(ref thresh) => Self::multi_a::<_, Ctx>(stfr, thresh, leaf_hash).0, } } diff --git a/src/miniscript/satisfy/sat_dissat.rs b/src/miniscript/satisfy/sat_dissat.rs index 9f0ce8e3e..8caa24305 100644 --- a/src/miniscript/satisfy/sat_dissat.rs +++ b/src/miniscript/satisfy/sat_dissat.rs @@ -6,8 +6,10 @@ use bitcoin::hashes::hash160; use bitcoin::TapLeafHash; use super::{Placeholder, Satisfaction, Witness}; +use crate::miniscript::limits::{MAX_PUBKEYS_IN_CHECKSIGADD, MAX_PUBKEYS_PER_MULTISIG}; use crate::plan::AssetProvider; -use crate::{AbsLockTime, MiniscriptKey, RelLockTime, ScriptContext, ToPublicKey}; +use crate::prelude::*; +use crate::{AbsLockTime, MiniscriptKey, RelLockTime, ScriptContext, Threshold, ToPublicKey}; impl Satisfaction> { pub(super) const IMPOSSIBLE: Self = Self { @@ -95,6 +97,118 @@ impl Satisfaction> { ) } + /// The (dissatisfaction, satisfaction) pair for a `multi` fragment. + pub(super) fn multi( + stfr: &S, + thresh: &Threshold, + leaf_hash: &TapLeafHash, + ) -> (Self, Self) + where + S: AssetProvider, + Ctx: ScriptContext, + { + let dissat = Self { + stack: Witness::Stack(vec![Placeholder::PushZero; thresh.k() + 1]), + ..Self::TRIVIAL + }; + + // Collect all available signatures + let mut sig_count = 0; + let mut sigs = Vec::with_capacity(thresh.k()); + for pk in thresh.data() { + match Witness::signature::<_, Ctx>(stfr, pk, leaf_hash) { + Witness::Stack(sig) => { + sigs.push(sig); + sig_count += 1; + } + Witness::Impossible => {} + Witness::Unavailable => { + unreachable!("Signature satisfaction without witness must be impossible") + } + } + } + + if sig_count < thresh.k() { + (dissat, Self::IMPOSSIBLE) + } else { + // Throw away the most expensive ones + for _ in 0..sig_count - thresh.k() { + let max_idx = sigs + .iter() + .enumerate() + .max_by_key(|&(_, v)| v.len()) + .unwrap() + .0; + sigs[max_idx] = vec![]; + } + + ( + dissat, + Self { + stack: sigs.into_iter().fold(Witness::push_0(), |acc, sig| { + Witness::combine(acc, Witness::Stack(sig)) + }), + has_sig: true, + ..Self::TRIVIAL + }, + ) + } + } + + /// The (dissatisfaction, satisfaction) pair for a `multi` fragment. + pub(super) fn multi_a( + stfr: &S, + thresh: &Threshold, + leaf_hash: &TapLeafHash, + ) -> (Self, Self) + where + S: AssetProvider, + Ctx: ScriptContext, + { + let dissat = Self { + stack: Witness::Stack(vec![Placeholder::PushZero; thresh.n()]), + ..Self::TRIVIAL + }; + + // Collect all available signatures + let mut sig_count = 0; + let mut sigs = vec![vec![Placeholder::PushZero]; thresh.n()]; + for (i, pk) in thresh.iter().rev().enumerate() { + match Witness::signature::<_, Ctx>(stfr, pk, leaf_hash) { + Witness::Stack(sig) => { + sigs[i] = sig; + sig_count += 1; + // This a privacy issue, we are only selecting the first available + // sigs. Incase pk at pos 1 is not selected, we know we did not have access to it + // bitcoin core also implements the same logic for MULTISIG, so I am not bothering + // permuting the sigs for now + if sig_count == thresh.k() { + break; + } + } + Witness::Impossible => {} + Witness::Unavailable => { + unreachable!("Signature satisfaction without witness must be impossible") + } + } + } + + if sig_count < thresh.k() { + (dissat, Self::IMPOSSIBLE) + } else { + ( + dissat, + Self { + stack: sigs.into_iter().fold(Witness::empty(), |acc, sig| { + Witness::combine(acc, Witness::Stack(sig)) + }), + has_sig: true, + ..Self::TRIVIAL + }, + ) + } + } + /// The (dissatisfaction, satisfaction) pair for an `after` fragment. pub(super) fn after(stfr: &S, t: AbsLockTime, root_has_sig: bool) -> (Self, Self) where From 2b9c724d7d0bbdd5ac7a5b6f99ed7078b94b63a8 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Mon, 11 Aug 2025 23:08:17 +0000 Subject: [PATCH 6/6] miniscript/satisfy: rewrite satifier to be non-recursive This rewrites the main satisfy function, which I apologize for, and it's not even a simple code move because I combined satisfy_ and dissatisfy_helper, and had to change the code to pop from the stack rather than doing recursive calls. Furthermore, I suspect that this code will be slower in some cases, though unlikelily in a way that anybody would ever notice, because it computes both the satisfaction and dissatisfaction for every single node, not just the ones that are needed. (Note: it was always the case that, when satisfying, that a satisfaction was attempted for every single node, and that a dissatisfaction would be attempted for most nodes contained in any disjunction. So probably satisfaction is not meaningfully slowed, and importantly, we aren't making any more calls to the satisfier object, which might be an API to some slow USB device.) HOWEVER, the resulting code is much smaller and cleaner. I found a bug, I think (noted with a FIXME to add a test). I deleted over twice as much code as I added, and in particular eliminated tons of extremely noisy generics related to mall/non-mall function pointers. (The next commits will remove even more generics.) --- src/miniscript/satisfy/mod.rs | 403 +-------------------------- src/miniscript/satisfy/sat_dissat.rs | 212 ++++++++++++-- 2 files changed, 207 insertions(+), 408 deletions(-) diff --git a/src/miniscript/satisfy/mod.rs b/src/miniscript/satisfy/mod.rs index 0c4c7a810..0d30eacdb 100644 --- a/src/miniscript/satisfy/mod.rs +++ b/src/miniscript/satisfy/mod.rs @@ -14,16 +14,12 @@ use bitcoin::hashes::hash160; use bitcoin::key::XOnlyPublicKey; use bitcoin::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapNodeHash}; use bitcoin::{absolute, relative, ScriptBuf, Sequence}; -use sync::Arc; use super::context::SigType; use crate::plan::AssetProvider; use crate::prelude::*; use crate::util::witness_size; -use crate::{ - AbsLockTime, Miniscript, MiniscriptKey, RelLockTime, ScriptContext, Terminal, Threshold, - ToPublicKey, -}; +use crate::{AbsLockTime, Miniscript, MiniscriptKey, RelLockTime, ScriptContext, ToPublicKey}; /// Type alias for 32 byte Preimage. pub type Preimage32 = [u8; 32]; @@ -933,14 +929,7 @@ impl Satisfaction> { Ctx: ScriptContext, P: AssetProvider, { - Self::satisfy_helper( - node, - provider, - root_has_sig, - leaf_hash, - &mut Satisfaction::minimum, - &mut Satisfaction::thresh, - ) + Self::dissat_sat(node, provider, false, root_has_sig, leaf_hash).1 } pub(crate) fn build_template_mall( @@ -953,50 +942,18 @@ impl Satisfaction> { Ctx: ScriptContext, P: AssetProvider, { - Self::satisfy_helper( - node, - provider, - root_has_sig, - leaf_hash, - &mut Satisfaction::minimum_mall, - &mut Satisfaction::thresh_mall, - ) + Self::dissat_sat(node, provider, true, root_has_sig, leaf_hash).1 } // produce a non-malleable satisafaction for thesh frag - fn thresh( - thresh: &Threshold>, 0>, - stfr: &Sat, - root_has_sig: bool, - leaf_hash: &TapLeafHash, - min_fn: &mut F, - ) -> Self - where - Ctx: ScriptContext, - Sat: AssetProvider, - F: FnMut( - Satisfaction>, - Satisfaction>, - ) -> Satisfaction>, - { - let mut sats = thresh - .iter() - .map(|s| { - Self::satisfy_helper(s, stfr, root_has_sig, leaf_hash, min_fn, &mut Self::thresh) - }) - .collect::>(); + fn thresh(k: usize, n: usize, dissats: Vec, mut sats: Vec) -> Self { // Start with the to-return stack set to all dissatisfactions - let mut ret_stack = thresh - .iter() - .map(|s| { - Self::dissatisfy_helper(s, stfr, root_has_sig, leaf_hash, min_fn, &mut Self::thresh) - }) - .collect::>(); + let mut ret_stack = dissats; // Sort everything by (sat cost - dissat cost), except that // satisfactions without signatures beat satisfactions with // signatures - let mut sat_indices = (0..thresh.n()).collect::>(); + let mut sat_indices = (0..n).collect::>(); sat_indices.sort_by_key(|&i| { let stack_weight = match (&sats[i].stack, &ret_stack[i].stack) { (&Witness::Unavailable, _) | (&Witness::Impossible, _) => i64::MAX, @@ -1015,7 +972,7 @@ impl Satisfaction> { (is_impossible, sats[i].has_sig, stack_weight) }); - for i in 0..thresh.k() { + for i in 0..k { mem::swap(&mut ret_stack[sat_indices[i]], &mut sats[sat_indices[i]]); } @@ -1024,7 +981,7 @@ impl Satisfaction> { // then the threshold branch is impossible to satisfy // For example, the fragment thresh(2, hash, 0, 0, 0) // is has an impossible witness - if sats[sat_indices[thresh.k() - 1]].stack == Witness::Impossible { + if sats[sat_indices[k - 1]].stack == Witness::Impossible { Satisfaction { stack: Witness::Impossible, // If the witness is impossible, we don't care about the @@ -1043,8 +1000,7 @@ impl Satisfaction> { // For example, the fragment thresh(2, hash, hash, 0, 0) // is uniquely satisfyiable because there is no satisfaction // for the 0 fragment - else if !sats[sat_indices[thresh.k()]].has_sig - && sats[sat_indices[thresh.k()]].stack != Witness::Impossible + else if !sats[sat_indices[k]].has_sig && sats[sat_indices[k]].stack != Witness::Impossible { // All arguments should be `d`, so dissatisfactions have no // signatures; and in this branch we assume too many weak @@ -1068,53 +1024,14 @@ impl Satisfaction> { } // produce a possibly malleable satisafaction for thesh frag - fn thresh_mall( - thresh: &Threshold>, 0>, - stfr: &Sat, - root_has_sig: bool, - leaf_hash: &TapLeafHash, - min_fn: &mut F, - ) -> Self - where - Ctx: ScriptContext, - Sat: AssetProvider, - F: FnMut( - Satisfaction>, - Satisfaction>, - ) -> Satisfaction>, - { - let mut sats = thresh - .iter() - .map(|s| { - Self::satisfy_helper( - s, - stfr, - root_has_sig, - leaf_hash, - min_fn, - &mut Self::thresh_mall, - ) - }) - .collect::>(); + fn thresh_mall(k: usize, n: usize, dissats: Vec, mut sats: Vec) -> Self { // Start with the to-return stack set to all dissatisfactions - let mut ret_stack = thresh - .iter() - .map(|s| { - Self::dissatisfy_helper( - s, - stfr, - root_has_sig, - leaf_hash, - min_fn, - &mut Self::thresh_mall, - ) - }) - .collect::>(); + let mut ret_stack = dissats; // Sort everything by (sat cost - dissat cost), except that // satisfactions without signatures beat satisfactions with // signatures - let mut sat_indices = (0..thresh.n()).collect::>(); + let mut sat_indices = (0..n).collect::>(); sat_indices.sort_by_key(|&i| { // For malleable satifactions, directly choose smallest weights match (&sats[i].stack, &ret_stack[i].stack) { @@ -1128,7 +1045,7 @@ impl Satisfaction> { }); // swap the satisfactions - for i in 0..thresh.k() { + for i in 0..k { mem::swap(&mut ret_stack[sat_indices[i]], &mut sats[sat_indices[i]]); } @@ -1214,300 +1131,6 @@ impl Satisfaction> { } } - // produce a non-malleable satisfaction - fn satisfy_helper( - node: &Miniscript, - stfr: &Sat, - root_has_sig: bool, - leaf_hash: &TapLeafHash, - min_fn: &mut F, - thresh_fn: &mut G, - ) -> Self - where - Ctx: ScriptContext, - Sat: AssetProvider, - F: FnMut( - Satisfaction>, - Satisfaction>, - ) -> Satisfaction>, - G: FnMut( - &Threshold>, 0>, - &Sat, - bool, - &TapLeafHash, - &mut F, - ) -> Satisfaction>, - { - match *node.as_inner() { - Terminal::False => Self::IMPOSSIBLE, - Terminal::True => Self::TRIVIAL, - Terminal::PkK(ref pk) => Self::pk_k::<_, Ctx>(stfr, pk, leaf_hash).1, - Terminal::PkH(ref pk) => Self::pk_h::<_, Ctx>(stfr, pk, leaf_hash).1, - Terminal::RawPkH(ref pkh) => Self::raw_pk_h::<_, Ctx>(stfr, pkh, leaf_hash).1, - Terminal::After(t) => Self::after(stfr, t, root_has_sig).1, - Terminal::Older(t) => Self::older(stfr, t, root_has_sig).1, - Terminal::Ripemd160(ref h) => Self::ripemd160(stfr, h).1, - Terminal::Hash160(ref h) => Self::hash160(stfr, h).1, - Terminal::Sha256(ref h) => Self::sha256(stfr, h).1, - Terminal::Hash256(ref h) => Self::hash256(stfr, h).1, - Terminal::Alt(ref sub) - | Terminal::Swap(ref sub) - | Terminal::Check(ref sub) - | Terminal::Verify(ref sub) - | Terminal::NonZero(ref sub) - | Terminal::ZeroNotEqual(ref sub) => { - Self::satisfy_helper(sub, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) - } - Terminal::DupIf(ref sub) => { - let sat = - Self::satisfy_helper(sub, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - Satisfaction { - stack: Witness::combine(sat.stack, Witness::push_1()), - has_sig: sat.has_sig, - relative_timelock: sat.relative_timelock, - absolute_timelock: sat.absolute_timelock, - } - } - Terminal::AndV(ref l, ref r) | Terminal::AndB(ref l, ref r) => { - let l_sat = - Self::satisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let r_sat = - Self::satisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - l_sat.concatenate_rev(r_sat) - } - Terminal::AndOr(ref a, ref b, ref c) => { - let a_sat = - Self::satisfy_helper(a, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let a_nsat = - Self::dissatisfy_helper(a, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let b_sat = - Self::satisfy_helper(b, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let c_sat = - Self::satisfy_helper(c, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - - min_fn(a_sat.concatenate_rev(b_sat), a_nsat.concatenate_rev(c_sat)) - } - Terminal::OrB(ref l, ref r) => { - let l_sat = - Self::satisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let r_sat = - Self::satisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let l_nsat = - Self::dissatisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let r_nsat = - Self::dissatisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - - assert!(!l_nsat.has_sig); - assert!(!r_nsat.has_sig); - - min_fn( - Satisfaction::concatenate_rev(l_nsat, r_sat), - Satisfaction::concatenate_rev(l_sat, r_nsat), - ) - } - Terminal::OrD(ref l, ref r) | Terminal::OrC(ref l, ref r) => { - let l_sat = - Self::satisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let r_sat = - Self::satisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let l_nsat = - Self::dissatisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - - assert!(!l_nsat.has_sig); - - min_fn(l_sat, Satisfaction::concatenate_rev(l_nsat, r_sat)) - } - Terminal::OrI(ref l, ref r) => { - let l_sat = - Self::satisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let r_sat = - Self::satisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - min_fn( - Satisfaction { - stack: Witness::combine(l_sat.stack, Witness::push_1()), - has_sig: l_sat.has_sig, - relative_timelock: l_sat.relative_timelock, - absolute_timelock: l_sat.absolute_timelock, - }, - Satisfaction { - stack: Witness::combine(r_sat.stack, Witness::push_0()), - has_sig: r_sat.has_sig, - relative_timelock: r_sat.relative_timelock, - absolute_timelock: r_sat.absolute_timelock, - }, - ) - } - Terminal::Thresh(ref thresh) => { - if thresh.k() == thresh.n() { - // this is just an and - thresh - .iter() - .map(|s| { - Self::satisfy_helper( - s, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ) - }) - .fold(Satisfaction::empty(), Satisfaction::concatenate_rev) - } else { - thresh_fn(thresh, stfr, root_has_sig, leaf_hash, min_fn) - } - } - Terminal::Multi(ref thresh) => Self::multi::<_, Ctx>(stfr, thresh, leaf_hash).1, - Terminal::MultiA(ref thresh) => { - // Collect all available signatures - let mut sig_count = 0; - let mut sigs = vec![vec![Placeholder::PushZero]; thresh.n()]; - for (i, pk) in thresh.iter().rev().enumerate() { - match Witness::signature::<_, Ctx>(stfr, pk, leaf_hash) { - Witness::Stack(sig) => { - sigs[i] = sig; - sig_count += 1; - // This a privacy issue, we are only selecting the first available - // sigs. Incase pk at pos 1 is not selected, we know we did not have access to it - // bitcoin core also implements the same logic for MULTISIG, so I am not bothering - // permuting the sigs for now - if sig_count == thresh.k() { - break; - } - } - Witness::Impossible => {} - Witness::Unavailable => unreachable!( - "Signature satisfaction without witness must be impossible" - ), - } - } - - if sig_count < thresh.k() { - Satisfaction { - stack: Witness::Impossible, - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - } - } else { - Satisfaction { - stack: sigs.into_iter().fold(Witness::empty(), |acc, sig| { - Witness::combine(acc, Witness::Stack(sig)) - }), - has_sig: true, - relative_timelock: None, - absolute_timelock: None, - } - } - } - } - } - - // Helper function to produce a dissatisfaction - fn dissatisfy_helper( - node: &Miniscript, - stfr: &Sat, - root_has_sig: bool, - leaf_hash: &TapLeafHash, - min_fn: &mut F, - thresh_fn: &mut G, - ) -> Self - where - Ctx: ScriptContext, - Sat: AssetProvider, - F: FnMut( - Satisfaction>, - Satisfaction>, - ) -> Satisfaction>, - G: FnMut( - &Threshold>, 0>, - &Sat, - bool, - &TapLeafHash, - &mut F, - ) -> Satisfaction>, - { - match *node.as_inner() { - Terminal::False => Self::TRIVIAL, - Terminal::True => Self::IMPOSSIBLE, - Terminal::PkK(ref pk) => Self::pk_k::<_, Ctx>(stfr, pk, leaf_hash).0, - Terminal::PkH(ref pk) => Self::pk_h::<_, Ctx>(stfr, pk, leaf_hash).0, - Terminal::RawPkH(ref pkh) => Self::raw_pk_h::<_, Ctx>(stfr, pkh, leaf_hash).0, - Terminal::After(t) => Self::after(stfr, t, root_has_sig).0, - Terminal::Older(t) => Self::older(stfr, t, root_has_sig).0, - Terminal::Ripemd160(ref h) => Self::ripemd160(stfr, h).0, - Terminal::Hash160(ref h) => Self::hash160(stfr, h).0, - Terminal::Sha256(ref h) => Self::sha256(stfr, h).0, - Terminal::Hash256(ref h) => Self::hash256(stfr, h).0, - Terminal::Verify(_) | Terminal::OrC(..) => Self::IMPOSSIBLE, - Terminal::Alt(ref sub) - | Terminal::Swap(ref sub) - | Terminal::Check(ref sub) - | Terminal::ZeroNotEqual(ref sub) => { - Self::dissatisfy_helper(sub, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) - } - Terminal::DupIf(_) | Terminal::NonZero(_) => Satisfaction { - stack: Witness::push_0(), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - }, - Terminal::AndV(ref v, ref other) => { - let vsat = - Self::satisfy_helper(v, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let odissat = Self::dissatisfy_helper( - other, - stfr, - root_has_sig, - leaf_hash, - min_fn, - thresh_fn, - ); - vsat.concatenate_rev(odissat) - } - Terminal::AndB(ref l, ref r) - | Terminal::OrB(ref l, ref r) - | Terminal::OrD(ref l, ref r) - | Terminal::AndOr(ref l, _, ref r) => { - let lnsat = - Self::dissatisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let rnsat = - Self::dissatisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - lnsat.concatenate_rev(rnsat) - } - Terminal::OrI(ref l, ref r) => { - let lnsat = - Self::dissatisfy_helper(l, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let dissat_1 = Satisfaction { - stack: Witness::combine(lnsat.stack, Witness::push_1()), - has_sig: lnsat.has_sig, - relative_timelock: None, - absolute_timelock: None, - }; - - let rnsat = - Self::dissatisfy_helper(r, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn); - let dissat_2 = Satisfaction { - stack: Witness::combine(rnsat.stack, Witness::push_0()), - has_sig: rnsat.has_sig, - relative_timelock: None, - absolute_timelock: None, - }; - - // Dissatisfactions don't need to non-malleable. Use minimum_mall always - Satisfaction::minimum_mall(dissat_1, dissat_2) - } - Terminal::Thresh(ref thresh) => thresh - .iter() - .map(|s| { - Self::dissatisfy_helper(s, stfr, root_has_sig, leaf_hash, min_fn, thresh_fn) - }) - .fold(Satisfaction::empty(), Satisfaction::concatenate_rev), - Terminal::Multi(ref thresh) => Self::multi::<_, Ctx>(stfr, thresh, leaf_hash).0, - Terminal::MultiA(ref thresh) => Self::multi_a::<_, Ctx>(stfr, thresh, leaf_hash).0, - } - } - /// Try creating the final witness using a [`Satisfier`] pub fn try_completing>(&self, stfr: &Sat) -> Option>> { let Satisfaction { stack, has_sig, relative_timelock, absolute_timelock } = self; diff --git a/src/miniscript/satisfy/sat_dissat.rs b/src/miniscript/satisfy/sat_dissat.rs index 8caa24305..29453a1db 100644 --- a/src/miniscript/satisfy/sat_dissat.rs +++ b/src/miniscript/satisfy/sat_dissat.rs @@ -6,20 +6,24 @@ use bitcoin::hashes::hash160; use bitcoin::TapLeafHash; use super::{Placeholder, Satisfaction, Witness}; +use crate::iter::TreeLike; use crate::miniscript::limits::{MAX_PUBKEYS_IN_CHECKSIGADD, MAX_PUBKEYS_PER_MULTISIG}; use crate::plan::AssetProvider; use crate::prelude::*; -use crate::{AbsLockTime, MiniscriptKey, RelLockTime, ScriptContext, Threshold, ToPublicKey}; +use crate::{ + AbsLockTime, Miniscript, MiniscriptKey, RelLockTime, ScriptContext, Terminal, Threshold, + ToPublicKey, +}; impl Satisfaction> { - pub(super) const IMPOSSIBLE: Self = Self { + const IMPOSSIBLE: Self = Self { stack: Witness::Impossible, has_sig: false, relative_timelock: None, absolute_timelock: None, }; - pub(super) const TRIVIAL: Self = Self { + const TRIVIAL: Self = Self { stack: Witness::empty(), has_sig: false, relative_timelock: None, @@ -30,7 +34,7 @@ impl Satisfaction> { fn push_0() -> Self { Self { stack: Witness::push_0(), ..Self::TRIVIAL } } /// The (dissatisfaction, satisfaction) pair for a `pk_k` fragment. - pub(super) fn pk_k(stfr: &S, pk: &Pk, leaf_hash: &TapLeafHash) -> (Self, Self) + fn pk_k(stfr: &S, pk: &Pk, leaf_hash: &TapLeafHash) -> (Self, Self) where S: AssetProvider, Ctx: ScriptContext, @@ -46,7 +50,7 @@ impl Satisfaction> { } /// The (dissatisfaction, satisfaction) pair for a `pk_h` fragment. - pub(super) fn pk_h(stfr: &S, pk: &Pk, leaf_hash: &TapLeafHash) -> (Self, Self) + fn pk_h(stfr: &S, pk: &Pk, leaf_hash: &TapLeafHash) -> (Self, Self) where S: AssetProvider, Ctx: ScriptContext, @@ -72,11 +76,7 @@ impl Satisfaction> { } /// The (dissatisfaction, satisfaction) pair for a `pk_h` fragment. - pub(super) fn raw_pk_h( - stfr: &S, - pkh: &hash160::Hash, - leaf_hash: &TapLeafHash, - ) -> (Self, Self) + fn raw_pk_h(stfr: &S, pkh: &hash160::Hash, leaf_hash: &TapLeafHash) -> (Self, Self) where S: AssetProvider, Ctx: ScriptContext, @@ -98,7 +98,7 @@ impl Satisfaction> { } /// The (dissatisfaction, satisfaction) pair for a `multi` fragment. - pub(super) fn multi( + fn multi( stfr: &S, thresh: &Threshold, leaf_hash: &TapLeafHash, @@ -156,7 +156,7 @@ impl Satisfaction> { } /// The (dissatisfaction, satisfaction) pair for a `multi` fragment. - pub(super) fn multi_a( + fn multi_a( stfr: &S, thresh: &Threshold, leaf_hash: &TapLeafHash, @@ -210,7 +210,7 @@ impl Satisfaction> { } /// The (dissatisfaction, satisfaction) pair for an `after` fragment. - pub(super) fn after(stfr: &S, t: AbsLockTime, root_has_sig: bool) -> (Self, Self) + fn after(stfr: &S, t: AbsLockTime, root_has_sig: bool) -> (Self, Self) where S: AssetProvider, { @@ -233,7 +233,7 @@ impl Satisfaction> { } /// The (dissatisfaction, satisfaction) pair for an `older` fragment. - pub(super) fn older(stfr: &S, t: RelLockTime, root_has_sig: bool) -> (Self, Self) + fn older(stfr: &S, t: RelLockTime, root_has_sig: bool) -> (Self, Self) where S: AssetProvider, { @@ -256,7 +256,7 @@ impl Satisfaction> { } /// The (dissatisfaction, satisfaction) pair for a `ripemd160` fragment. - pub(super) fn ripemd160(stfr: &S, h: &Pk::Ripemd160) -> (Self, Self) + fn ripemd160(stfr: &S, h: &Pk::Ripemd160) -> (Self, Self) where S: AssetProvider, { @@ -267,7 +267,7 @@ impl Satisfaction> { } /// The (dissatisfaction, satisfaction) pair for a `hash160` fragment. - pub(super) fn hash160(stfr: &S, h: &Pk::Hash160) -> (Self, Self) + fn hash160(stfr: &S, h: &Pk::Hash160) -> (Self, Self) where S: AssetProvider, { @@ -278,7 +278,7 @@ impl Satisfaction> { } /// The (dissatisfaction, satisfaction) pair for a `ripemd256` fragment. - pub(super) fn sha256(stfr: &S, h: &Pk::Sha256) -> (Self, Self) + fn sha256(stfr: &S, h: &Pk::Sha256) -> (Self, Self) where S: AssetProvider, { @@ -289,7 +289,7 @@ impl Satisfaction> { } /// The (dissatisfaction, satisfaction) pair for a `ripemd256` fragment. - pub(super) fn hash256(stfr: &S, h: &Pk::Hash256) -> (Self, Self) + fn hash256(stfr: &S, h: &Pk::Hash256) -> (Self, Self) where S: AssetProvider, { @@ -298,4 +298,180 @@ impl Satisfaction> { Self { stack: Witness::hash256_preimage(stfr, h), ..Self::TRIVIAL }, ) } + + /// Compute the dissatisfaction and the satisfaction for the given node, by querying + /// the satisfier `stfr`. + /// + /// If the `malleable` flag is set to true, more efficient satisfactions may be found, + /// but which a 3rd party may be able to replace with less efficient versions. (This + /// flag does not affect dissatisfactions.) + pub(super) fn dissat_sat( + node: &Miniscript, + stfr: &Sat, + malleable: bool, + root_has_sig: bool, + leaf_hash: &TapLeafHash, + ) -> (Self, Self) + where + Ctx: ScriptContext, + Sat: AssetProvider, + { + let min_fn = if malleable { + Self::minimum_mall + } else { + Self::minimum + }; + let thresh_fn = if malleable { + Self::thresh_mall + } else { + Self::thresh + }; + + let mut stack = vec![]; + for item in node.post_order_iter() { + let new_dissat_sat = match *item.node.as_inner() { + Terminal::False => (Self::TRIVIAL, Self::IMPOSSIBLE), + Terminal::True => (Self::IMPOSSIBLE, Self::TRIVIAL), + Terminal::PkK(ref pk) => Self::pk_k::<_, Ctx>(stfr, pk, leaf_hash), + Terminal::PkH(ref pk) => Self::pk_h::<_, Ctx>(stfr, pk, leaf_hash), + Terminal::RawPkH(ref pkh) => Self::raw_pk_h::<_, Ctx>(stfr, pkh, leaf_hash), + Terminal::Multi(ref thresh) => Self::multi::<_, Ctx>(stfr, thresh, leaf_hash), + Terminal::MultiA(ref thresh) => Self::multi_a::<_, Ctx>(stfr, thresh, leaf_hash), + Terminal::After(t) => Self::after(stfr, t, root_has_sig), + Terminal::Older(t) => Self::older(stfr, t, root_has_sig), + Terminal::Ripemd160(ref h) => Self::ripemd160(stfr, h), + Terminal::Hash160(ref h) => Self::hash160(stfr, h), + Terminal::Sha256(ref h) => Self::sha256(stfr, h), + Terminal::Hash256(ref h) => Self::hash256(stfr, h), + // These four wrappers have no effect on either satisfaction nor dissatisfaction + Terminal::Alt(_) + | Terminal::Swap(_) + | Terminal::Check(_) + | Terminal::ZeroNotEqual(_) => stack.pop().unwrap(), + Terminal::DupIf(_) => { + let (_, sub) = stack.pop().unwrap(); + ( + Self::push_0(), + Self { stack: Witness::combine(sub.stack, Witness::push_1()), ..sub }, + ) + } + Terminal::Verify(_) => { + let (_, sub) = stack.pop().unwrap(); + (Self::IMPOSSIBLE, sub) + } + Terminal::NonZero(_) => { + let (_, sub) = stack.pop().unwrap(); + (Self::IMPOSSIBLE, sub) + } + Terminal::AndB(_, _) => { + let (r_dis, r_sat) = stack.pop().unwrap(); + let (l_dis, l_sat) = stack.pop().unwrap(); + (l_dis.concatenate_rev(r_dis), l_sat.concatenate_rev(r_sat)) + } + Terminal::AndV(_, _) => { + let (r_dis, r_sat) = stack.pop().unwrap(); + let (_, l_sat) = stack.pop().unwrap(); + // Left child is a `v` and must be satisfied for both sat and dissat. + (l_sat.clone().concatenate_rev(r_dis), l_sat.concatenate_rev(r_sat)) + } + Terminal::AndOr(_, _, _) => { + let (c_dis, c_sat) = stack.pop().unwrap(); + let (_, b_sat) = stack.pop().unwrap(); + let (a_dis, a_sat) = stack.pop().unwrap(); + + ( + a_dis.clone().concatenate_rev(c_dis), + min_fn(a_sat.concatenate_rev(b_sat), a_dis.concatenate_rev(c_sat)), + ) + } + Terminal::OrB(_, _) => { + let (r_dis, r_sat) = stack.pop().unwrap(); + let (l_dis, l_sat) = stack.pop().unwrap(); + assert!(!l_dis.has_sig); + assert!(!r_dis.has_sig); + + ( + l_dis.clone().concatenate_rev(r_dis.clone()), + min_fn( + Self::concatenate_rev(l_dis, r_sat), + Self::concatenate_rev(l_sat, r_dis), + ), + ) + } + Terminal::OrC(_, _) => { + let (_, r_sat) = stack.pop().unwrap(); + let (l_dis, l_sat) = stack.pop().unwrap(); + assert!(!l_dis.has_sig); + + (Self::IMPOSSIBLE, min_fn(l_sat, Self::concatenate_rev(l_dis, r_sat))) + } + Terminal::OrD(_, _) => { + let (r_dis, r_sat) = stack.pop().unwrap(); + let (l_dis, l_sat) = stack.pop().unwrap(); + assert!(!l_dis.has_sig); + + ( + l_dis.clone().concatenate_rev(r_dis), + min_fn(l_sat, Self::concatenate_rev(l_dis, r_sat)), + ) + } + Terminal::OrI(_, _) => { + let (r_dis, r_sat) = stack.pop().unwrap(); + let (l_dis, l_sat) = stack.pop().unwrap(); + // FIXME existing code sets timelocks to None instead of propagating them, + // in the dissat case. This dates to the original plan module and was not + // comment on. https://github.com/rust-bitcoin/rust-miniscript/pull/592 + // Was likely a mistake. Need to find a test case that distinguishes before + // merging. + ( + min_fn( + Self { + stack: Witness::combine(l_dis.stack, Witness::push_1()), + ..l_dis + }, + Self { + stack: Witness::combine(r_dis.stack, Witness::push_0()), + ..r_dis + }, + ), + min_fn( + Self { + stack: Witness::combine(l_sat.stack, Witness::push_1()), + ..l_sat + }, + Self { + stack: Witness::combine(r_sat.stack, Witness::push_0()), + ..r_sat + }, + ), + ) + } + Terminal::Thresh(ref thresh) => { + let (dissats, sats): (Vec<_>, Vec<_>) = + stack.drain(stack.len() - thresh.n()..).unzip(); + + // Dissatisfaction of a threshold is just the dissatisfaction of its children. + let dissat = dissats + .iter() + .cloned() + .fold(Self::empty(), Self::concatenate_rev); + + // But satisfaction is a bit harder. + let sat = if thresh.k() == thresh.n() { + // this is just an and + sats.into_iter().fold(Self::empty(), Self::concatenate_rev) + } else { + thresh_fn(thresh.k(), thresh.n(), dissats, sats) + }; + + (dissat, sat) + } + }; + + stack.push(new_dissat_sat); + } + + assert_eq!(stack.len(), 1); + stack.pop().unwrap() + } }