From d29c2984e60e41e3872ac23b8b59e8b25fea88a2 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Tue, 25 Oct 2022 12:24:52 +0200 Subject: [PATCH] Add plan capabilities to miniscript Add a `plan` module that contains utilities to calculate the cheapest spending path given an AssetProvider (that could keys, preimages, or timelocks). Adds a `get_plan` method on the various descriptor types. Co-authored-by: Daniela Brozzoni --- src/descriptor/bare.rs | 66 +++ src/descriptor/mod.rs | 65 ++- src/descriptor/segwitv0.rs | 72 +++ src/descriptor/sh.rs | 36 ++ src/descriptor/sortedmulti.rs | 12 + src/descriptor/tr.rs | 146 +++++-- src/lib.rs | 1 + src/miniscript/mod.rs | 35 +- src/miniscript/satisfy.rs | 514 ++++++++++++++++------ src/plan.rs | 799 ++++++++++++++++++++++++++++++++++ src/util.rs | 39 +- 11 files changed, 1596 insertions(+), 189 deletions(-) create mode 100644 src/plan.rs diff --git a/src/descriptor/bare.rs b/src/descriptor/bare.rs index a8028fe60..1a54df64f 100644 --- a/src/descriptor/bare.rs +++ b/src/descriptor/bare.rs @@ -13,8 +13,11 @@ use bitcoin::script::{self, PushBytes}; use bitcoin::{Address, Network, ScriptBuf}; use super::checksum::{self, verify_checksum}; +use crate::descriptor::DefiniteDescriptorKey; use crate::expression::{self, FromTree}; use crate::miniscript::context::{ScriptContext, ScriptContextError}; +use crate::miniscript::satisfy::{Placeholder, Satisfaction, Witness}; +use crate::plan::AssetProvider; use crate::policy::{semantic, Liftable}; use crate::prelude::*; use crate::util::{varint_len, witness_to_scriptsig}; @@ -134,6 +137,30 @@ impl Bare { } } +impl Bare { + /// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction + pub fn plan_satisfaction

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + self.ms.build_template(provider) + } + + /// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction + pub fn plan_satisfaction_mall

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + self.ms.build_template_mall(provider) + } +} + impl fmt::Debug for Bare { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self.ms) @@ -311,6 +338,45 @@ impl Pkh { } } +impl Pkh { + /// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction + pub fn plan_satisfaction

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + let stack = if provider.provider_lookup_ecdsa_sig(&self.pk) { + let stack = vec![ + Placeholder::EcdsaSigPk(self.pk.clone()), + Placeholder::Pubkey(self.pk.clone(), BareCtx::pk_len(&self.pk)), + ]; + Witness::Stack(stack) + } else { + Witness::Unavailable + }; + + Satisfaction { + stack, + has_sig: true, + relative_timelock: None, + absolute_timelock: None, + } + } + + /// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction + pub fn plan_satisfaction_mall

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + self.plan_satisfaction(provider) + } +} + impl fmt::Debug for Pkh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "pkh({:?})", self.pk) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 826ca0335..417e7d1d2 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -22,7 +22,8 @@ use sync::Arc; use self::checksum::verify_checksum; use crate::miniscript::decode::Terminal; -use crate::miniscript::{Legacy, Miniscript, Segwitv0}; +use crate::miniscript::{satisfy, Legacy, Miniscript, Segwitv0}; +use crate::plan::{AssetProvider, Plan}; use crate::prelude::*; use crate::{ expression, hash256, BareCtx, Error, ForEachKey, MiniscriptKey, Satisfier, ToPublicKey, @@ -474,7 +475,7 @@ impl Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction(satisfier), Descriptor::Wsh(ref wsh) => wsh.get_satisfaction(satisfier), Descriptor::Sh(ref sh) => sh.get_satisfaction(satisfier), - Descriptor::Tr(ref tr) => tr.get_satisfaction(satisfier), + Descriptor::Tr(ref tr) => tr.get_satisfaction(&satisfier), } } @@ -491,7 +492,7 @@ impl Descriptor { Descriptor::Wpkh(ref wpkh) => wpkh.get_satisfaction_mall(satisfier), Descriptor::Wsh(ref wsh) => wsh.get_satisfaction_mall(satisfier), Descriptor::Sh(ref sh) => sh.get_satisfaction_mall(satisfier), - Descriptor::Tr(ref tr) => tr.get_satisfaction_mall(satisfier), + Descriptor::Tr(ref tr) => tr.get_satisfaction_mall(&satisfier), } } @@ -509,6 +510,64 @@ impl Descriptor { } } +impl Descriptor { + /// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction + /// + /// If the assets aren't sufficient for generating a Plan, the descriptor is returned + pub fn plan

(self, provider: &P) -> Result + where + P: AssetProvider, + { + let satisfaction = match self { + Descriptor::Bare(ref bare) => bare.plan_satisfaction(provider), + Descriptor::Pkh(ref pkh) => pkh.plan_satisfaction(provider), + Descriptor::Wpkh(ref wpkh) => wpkh.plan_satisfaction(provider), + Descriptor::Wsh(ref wsh) => wsh.plan_satisfaction(provider), + Descriptor::Sh(ref sh) => sh.plan_satisfaction(provider), + Descriptor::Tr(ref tr) => tr.plan_satisfaction(provider), + }; + + if let satisfy::Witness::Stack(stack) = satisfaction.stack { + Ok(Plan { + descriptor: self, + template: stack, + absolute_timelock: satisfaction.absolute_timelock.map(Into::into), + relative_timelock: satisfaction.relative_timelock, + }) + } else { + Err(self) + } + } + + /// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction + /// + /// If the assets aren't sufficient for generating a Plan, the descriptor is returned + pub fn plan_mall

(self, provider: &P) -> Result + where + P: AssetProvider, + { + let satisfaction = match self { + Descriptor::Bare(ref bare) => bare.plan_satisfaction_mall(provider), + Descriptor::Pkh(ref pkh) => pkh.plan_satisfaction_mall(provider), + Descriptor::Wpkh(ref wpkh) => wpkh.plan_satisfaction_mall(provider), + Descriptor::Wsh(ref wsh) => wsh.plan_satisfaction_mall(provider), + Descriptor::Sh(ref sh) => sh.plan_satisfaction_mall(provider), + Descriptor::Tr(ref tr) => tr.plan_satisfaction_mall(provider), + }; + + if let satisfy::Witness::Stack(stack) = satisfaction.stack { + Ok(Plan { + descriptor: self, + template: stack, + absolute_timelock: satisfaction.absolute_timelock.map(Into::into), + relative_timelock: satisfaction.relative_timelock, + }) + } else { + Err(self) + } + } +} + impl TranslatePk for Descriptor

where P: MiniscriptKey, diff --git a/src/descriptor/segwitv0.rs b/src/descriptor/segwitv0.rs index 4d7c4e7a3..6ef6e6684 100644 --- a/src/descriptor/segwitv0.rs +++ b/src/descriptor/segwitv0.rs @@ -11,8 +11,11 @@ use bitcoin::{Address, Network, ScriptBuf}; use super::checksum::{self, verify_checksum}; use super::SortedMultiVec; +use crate::descriptor::DefiniteDescriptorKey; use crate::expression::{self, FromTree}; use crate::miniscript::context::{ScriptContext, ScriptContextError}; +use crate::miniscript::satisfy::{Placeholder, Satisfaction, Witness}; +use crate::plan::AssetProvider; use crate::policy::{semantic, Liftable}; use crate::prelude::*; use crate::util::varint_len; @@ -191,6 +194,36 @@ impl Wsh { } } +impl Wsh { + /// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction + pub fn plan_satisfaction

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + match &self.inner { + WshInner::SortedMulti(sm) => sm.build_template(provider), + WshInner::Ms(ms) => ms.build_template(provider), + } + } + + /// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction + pub fn plan_satisfaction_mall

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + match &self.inner { + WshInner::SortedMulti(sm) => sm.build_template(provider), + WshInner::Ms(ms) => ms.build_template_mall(provider), + } + } +} + /// Wsh Inner #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub enum WshInner { @@ -418,6 +451,45 @@ impl Wpkh { } } +impl Wpkh { + /// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction + pub fn plan_satisfaction

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + let stack = if provider.provider_lookup_ecdsa_sig(&self.pk) { + let stack = vec![ + Placeholder::EcdsaSigPk(self.pk.clone()), + Placeholder::Pubkey(self.pk.clone(), Segwitv0::pk_len(&self.pk)), + ]; + Witness::Stack(stack) + } else { + Witness::Unavailable + }; + + Satisfaction { + stack, + has_sig: true, + relative_timelock: None, + absolute_timelock: None, + } + } + + /// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction + pub fn plan_satisfaction_mall

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + self.plan_satisfaction(provider) + } +} + impl fmt::Debug for Wpkh { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "wpkh({:?})", self.pk) diff --git a/src/descriptor/sh.rs b/src/descriptor/sh.rs index 1f18c641a..4b4c24637 100644 --- a/src/descriptor/sh.rs +++ b/src/descriptor/sh.rs @@ -15,8 +15,11 @@ use bitcoin::{script, Address, Network, ScriptBuf}; use super::checksum::{self, verify_checksum}; use super::{SortedMultiVec, Wpkh, Wsh}; +use crate::descriptor::DefiniteDescriptorKey; use crate::expression::{self, FromTree}; use crate::miniscript::context::ScriptContext; +use crate::miniscript::satisfy::{Placeholder, Satisfaction}; +use crate::plan::AssetProvider; use crate::policy::{semantic, Liftable}; use crate::prelude::*; use crate::util::{varint_len, witness_to_scriptsig}; @@ -418,6 +421,39 @@ impl Sh { } } +impl Sh { + /// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction + pub fn plan_satisfaction

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + 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), + } + } + + /// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction + pub fn plan_satisfaction_mall

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + match &self.inner { + ShInner::Wsh(ref wsh) => wsh.plan_satisfaction_mall(provider), + ShInner::Ms(ref ms) => ms.build_template_mall(provider), + _ => self.plan_satisfaction(provider), + } + } +} + impl ForEachKey for Sh { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool { match self.inner { diff --git a/src/descriptor/sortedmulti.rs b/src/descriptor/sortedmulti.rs index b626c1c9a..3887d8752 100644 --- a/src/descriptor/sortedmulti.rs +++ b/src/descriptor/sortedmulti.rs @@ -14,6 +14,8 @@ use bitcoin::script; 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::{ errstr, expression, policy, script_num_size, Error, ForEachKey, Miniscript, MiniscriptKey, @@ -154,6 +156,16 @@ impl SortedMultiVec { 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. diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 23c1742d0..798617d94 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -11,8 +11,11 @@ use bitcoin::{opcodes, secp256k1, Address, Network, ScriptBuf}; use sync::Arc; use super::checksum::{self, verify_checksum}; +use crate::descriptor::DefiniteDescriptorKey; use crate::expression::{self, FromTree}; +use crate::miniscript::satisfy::{Placeholder, Satisfaction, SchnorrSigType, Witness}; use crate::miniscript::Miniscript; +use crate::plan::AssetProvider; use crate::policy::semantic::Policy; use crate::policy::Liftable; use crate::prelude::*; @@ -398,21 +401,62 @@ impl Tr { /// Returns satisfying non-malleable witness and scriptSig with minimum /// weight to spend an output controlled by the given descriptor if it is /// possible to construct one using the `satisfier`. - pub fn get_satisfaction(&self, satisfier: S) -> Result<(Vec>, ScriptBuf), Error> + pub fn get_satisfaction(&self, satisfier: &S) -> Result<(Vec>, ScriptBuf), Error> where S: Satisfier, { - best_tap_spend(self, satisfier, false /* allow_mall */) + let satisfaction = best_tap_spend(self, satisfier, false /* allow_mall */) + .try_completing(satisfier) + .expect("the same satisfier should manage to complete the template"); + if let Witness::Stack(stack) = satisfaction.stack { + Ok((stack, ScriptBuf::new())) + } else { + Err(Error::CouldNotSatisfy) + } } /// Returns satisfying, possibly malleable, witness and scriptSig with /// minimum weight to spend an output controlled by the given descriptor if /// it is possible to construct one using the `satisfier`. - pub fn get_satisfaction_mall(&self, satisfier: S) -> Result<(Vec>, ScriptBuf), Error> + pub fn get_satisfaction_mall( + &self, + satisfier: &S, + ) -> Result<(Vec>, ScriptBuf), Error> where S: Satisfier, { - best_tap_spend(self, satisfier, true /* allow_mall */) + let satisfaction = best_tap_spend(self, satisfier, true /* allow_mall */) + .try_completing(satisfier) + .expect("the same satisfier should manage to complete the template"); + if let Witness::Stack(stack) = satisfaction.stack { + Ok((stack, ScriptBuf::new())) + } else { + Err(Error::CouldNotSatisfy) + } + } +} + +impl Tr { + /// Returns a plan if the provided assets are sufficient to produce a non-malleable satisfaction + pub fn plan_satisfaction

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + best_tap_spend(self, provider, false /* allow_mall */) + } + + /// Returns a plan if the provided assets are sufficient to produce a malleable satisfaction + pub fn plan_satisfaction_mall

( + &self, + provider: &P, + ) -> Satisfaction> + where + P: AssetProvider, + { + best_tap_spend(self, provider, true /* allow_mall */) } } @@ -695,62 +739,84 @@ fn control_block_len(depth: u8) -> usize { // Helper function to get a script spend satisfaction // try script spend -fn best_tap_spend( +fn best_tap_spend( desc: &Tr, - satisfier: S, + provider: &P, allow_mall: bool, -) -> Result<(Vec>, ScriptBuf), Error> +) -> Satisfaction> where Pk: ToPublicKey, - S: Satisfier, + P: AssetProvider, { let spend_info = desc.spend_info(); // First try the key spend path - if let Some(sig) = satisfier.lookup_tap_key_spend_sig() { - Ok((vec![sig.to_vec()], ScriptBuf::new())) + if let Some(size) = provider.provider_lookup_tap_key_spend_sig(&desc.internal_key) { + Satisfaction { + stack: Witness::Stack(vec![Placeholder::SchnorrSigPk( + desc.internal_key.clone(), + SchnorrSigType::KeySpend { + merkle_root: spend_info.merkle_root(), + }, + size, + )]), + has_sig: true, + absolute_timelock: None, + relative_timelock: None, + } } else { // Since we have the complete descriptor we can ignore the satisfier. We don't use the control block // map (lookup_control_block) from the satisfier here. - let (mut min_wit, mut min_wit_len) = (None, None); - for (depth, ms) in desc.iter_scripts() { - let mut wit = if allow_mall { - match ms.satisfy_malleable(&satisfier) { - Ok(wit) => wit, - Err(..) => continue, // No witness for this script in tr descriptor, look for next one + let mut min_satisfaction = Satisfaction { + stack: Witness::Unavailable, + has_sig: false, + relative_timelock: None, + absolute_timelock: None, + }; + let mut min_wit_len = None; + for (_depth, ms) in desc.iter_scripts() { + let mut satisfaction = if allow_mall { + match ms.build_template(provider) { + s @ Satisfaction { + stack: Witness::Stack(_), + .. + } => s, + _ => continue, // No witness for this script in tr descriptor, look for next one } } else { - match ms.satisfy(&satisfier) { - Ok(wit) => wit, - Err(..) => continue, // No witness for this script in tr descriptor, look for next one + match ms.build_template_mall(provider) { + s @ Satisfaction { + stack: Witness::Stack(_), + .. + } => s, + _ => continue, // No witness for this script in tr descriptor, look for next one } }; - // Compute the final witness size - // Control block len + script len + witnesssize + varint(wit.len + 2) - // The extra +2 elements are control block and script itself - let wit_size = witness_size(&wit) - + control_block_len(depth) - + ms.script_size() - + varint_len(ms.script_size()); + let wit = match satisfaction { + Satisfaction { + stack: Witness::Stack(ref mut wit), + .. + } => wit, + _ => unreachable!(), + }; + + let leaf_script = (ms.encode(), LeafVersion::TapScript); + let control_block = spend_info + .control_block(&leaf_script) + .expect("Control block must exist in script map for every known leaf"); + + wit.push(Placeholder::TapScript(leaf_script.0)); + wit.push(Placeholder::TapControlBlock(control_block)); + + let wit_size = witness_size(&wit); if min_wit_len.is_some() && Some(wit_size) > min_wit_len { continue; } else { - let leaf_script = (ms.encode(), LeafVersion::TapScript); - let control_block = spend_info - .control_block(&leaf_script) - .expect("Control block must exist in script map for every known leaf"); - wit.push(leaf_script.0.into_bytes()); // Push the leaf script - // There can be multiple control blocks for a (script, ver) pair - // Find the smallest one amongst those - wit.push(control_block.serialize()); - // Finally, save the minimum - min_wit = Some(wit); + min_satisfaction = satisfaction; min_wit_len = Some(wit_size); } } - match min_wit { - Some(wit) => Ok((wit, ScriptBuf::new())), - None => Err(Error::CouldNotSatisfy), // Could not satisfy all miniscripts inside Tr - } + + min_satisfaction } } diff --git a/src/lib.rs b/src/lib.rs index 6848b2c01..f8930e49d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,6 +123,7 @@ pub mod expression; pub mod interpreter; pub mod iter; pub mod miniscript; +pub mod plan; pub mod policy; pub mod psbt; diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index cd9c912ea..a4b0115b0 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -45,7 +45,9 @@ pub use crate::miniscript::context::ScriptContext; use crate::miniscript::decode::Terminal; use crate::miniscript::types::extra_props::ExtData; use crate::miniscript::types::Type; -use crate::{expression, Error, ForEachKey, MiniscriptKey, ToPublicKey, TranslatePk, Translator}; +use crate::{ + expression, plan, Error, ForEachKey, MiniscriptKey, ToPublicKey, TranslatePk, Translator, +}; #[cfg(test)] mod ms_tests; @@ -225,7 +227,7 @@ impl Miniscript { self._satisfy(satisfaction) } - fn _satisfy(&self, satisfaction: satisfy::Satisfaction) -> Result>, Error> + fn _satisfy(&self, satisfaction: satisfy::Satisfaction>) -> Result>, Error> where Pk: ToPublicKey, { @@ -239,6 +241,35 @@ impl Miniscript { } } } + + /// Attempt to produce a non-malleable witness template given the assets available + pub fn build_template>( + &self, + provider: &P, + ) -> satisfy::Satisfaction> + where + Pk: ToPublicKey, + { + let leaf_hash = TapLeafHash::from_script(&self.encode(), LeafVersion::TapScript); + satisfy::Satisfaction::build_template(&self.node, provider, self.ty.mall.safe, &leaf_hash) + } + + /// Attempt to produce a malleable witness template given the assets available + pub fn build_template_mall>( + &self, + provider: &P, + ) -> satisfy::Satisfaction> + where + Pk: ToPublicKey, + { + let leaf_hash = TapLeafHash::from_script(&self.encode(), LeafVersion::TapScript); + satisfy::Satisfaction::build_template_mall( + &self.node, + provider, + self.ty.mall.safe, + &leaf_hash, + ) + } } impl Miniscript { diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index f5cce8b6f..a0641b04b 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -6,15 +6,16 @@ //! scriptpubkeys. //! -use core::{cmp, i64, mem}; +use core::{cmp, fmt, i64, mem}; use bitcoin::hashes::hash160; use bitcoin::key::XOnlyPublicKey; -use bitcoin::taproot::{ControlBlock, LeafVersion, TapLeafHash}; -use bitcoin::{absolute, Sequence}; +use bitcoin::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapNodeHash}; +use bitcoin::{absolute, 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, ScriptContext, Terminal, ToPublicKey}; @@ -105,11 +106,19 @@ pub trait Satisfier { } /// Assert whether an relative locktime is satisfied + /// + /// NOTE: If a descriptor mixes time-based and height-based timelocks, the implementation of + /// this method MUST only allow timelocks of either unit, but not both. Allowing both could cause + /// miniscript to construct an invalid witness. fn check_older(&self, _: Sequence) -> bool { false } /// Assert whether a absolute locktime is satisfied + /// + /// NOTE: If a descriptor mixes time-based and height-based timelocks, the implementation of + /// this method MUST only allow timelocks of either unit, but not both. Allowing both could cause + /// miniscript to construct an invalid witness. fn check_after(&self, _: absolute::LockTime) -> bool { false } @@ -495,7 +504,6 @@ macro_rules! impl_tuple_satisfier { )* None } - fn lookup_raw_pkh_x_only_pk( &self, key_hash: &hash160::Hash, @@ -593,11 +601,156 @@ impl_tuple_satisfier!(A, B, C, D, E, F); impl_tuple_satisfier!(A, B, C, D, E, F, G); impl_tuple_satisfier!(A, B, C, D, E, F, G, H); +#[derive(Debug, Clone, PartialEq, Eq)] +/// Type of schnorr signature to produce +pub enum SchnorrSigType { + /// Key spend signature + KeySpend { + /// Merkle root to tweak the key, if present + merkle_root: Option, + }, + /// Script spend signature + ScriptSpend { + /// Leaf hash of the script + leaf_hash: TapLeafHash, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +/// Placeholder for some data in a [`Plan`] +/// +/// [`Plan`]: crate::plan::Plan +pub enum Placeholder { + /// Public key and its size + Pubkey(Pk, usize), + /// Public key hash and public key size + PubkeyHash(hash160::Hash, usize), + /// ECDSA signature given the raw pubkey + EcdsaSigPk(Pk), + /// ECDSA signature given the pubkey hash + EcdsaSigPkHash(hash160::Hash), + /// Schnorr signature and its size + SchnorrSigPk(Pk, SchnorrSigType, usize), + /// Schnorr signature given the pubkey hash, the tapleafhash, and the sig size + SchnorrSigPkHash(hash160::Hash, TapLeafHash, usize), + /// SHA-256 preimage + Sha256Preimage(Pk::Sha256), + /// HASH256 preimage + Hash256Preimage(Pk::Hash256), + /// RIPEMD160 preimage + Ripemd160Preimage(Pk::Ripemd160), + /// HASH160 preimage + Hash160Preimage(Pk::Hash160), + /// Hash dissatisfaction (32 bytes of 0x00) + HashDissatisfaction, + /// OP_1 + PushOne, + /// \ + PushZero, + /// Taproot leaf script + TapScript(ScriptBuf), + /// Taproot control block + TapControlBlock(ControlBlock), +} + +impl fmt::Display for Placeholder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Placeholder::*; + match self { + Pubkey(pk, size) => write!(f, "Pubkey(pk: {}, size: {})", pk, size), + PubkeyHash(hash, size) => write!(f, "PubkeyHash(hash: {}, size: {})", hash, size), + EcdsaSigPk(pk) => write!(f, "EcdsaSigPk(pk: {})", pk), + EcdsaSigPkHash(hash) => write!(f, "EcdsaSigPkHash(pkh: {})", hash), + SchnorrSigPk(pk, tap_leaf_hash, size) => write!( + f, + "SchnorrSig(pk: {}, tap_leaf_hash: {:?}, size: {})", + pk, tap_leaf_hash, size + ), + SchnorrSigPkHash(pkh, tap_leaf_hash, size) => write!( + f, + "SchnorrSigPkHash(pkh: {}, tap_leaf_hash: {:?}, size: {})", + pkh, tap_leaf_hash, size + ), + Sha256Preimage(hash) => write!(f, "Sha256Preimage(hash: {})", hash), + Hash256Preimage(hash) => write!(f, "Hash256Preimage(hash: {})", hash), + Ripemd160Preimage(hash) => write!(f, "Ripemd160Preimage(hash: {})", hash), + Hash160Preimage(hash) => write!(f, "Hash160Preimage(hash: {})", hash), + HashDissatisfaction => write!(f, "HashDissatisfaction"), + PushOne => write!(f, "PushOne"), + PushZero => write!(f, "PushZero"), + TapScript(script) => write!(f, "TapScript(script: {})", script), + TapControlBlock(control_block) => write!( + f, + "TapControlBlock(control_block: {})", + bitcoin::consensus::encode::serialize_hex(&control_block.serialize()) + ), + } + } +} + +impl Placeholder { + /// Replaces the placeholders with the information given by the satisfier + pub fn satisfy_self>(&self, sat: &Sat) -> Option> { + match self { + Placeholder::Pubkey(pk, size) => { + if *size == 33 { + Some(pk.to_x_only_pubkey().serialize().to_vec()) + } else { + Some(pk.to_public_key().to_bytes()) + } + } + Placeholder::PubkeyHash(pkh, size) => sat + .lookup_raw_pkh_pk(pkh) + .map(|p| p.to_public_key()) + .or(sat.lookup_raw_pkh_ecdsa_sig(pkh).map(|(p, _)| p)) + .map(|pk| { + let pk = pk.to_bytes(); + // We have to add a 1-byte OP_PUSH + debug_assert!(1 + pk.len() == *size); + pk + }), + Placeholder::Hash256Preimage(h) => sat.lookup_hash256(h).map(|p| p.to_vec()), + Placeholder::Sha256Preimage(h) => sat.lookup_sha256(h).map(|p| p.to_vec()), + Placeholder::Hash160Preimage(h) => sat.lookup_hash160(h).map(|p| p.to_vec()), + Placeholder::Ripemd160Preimage(h) => sat.lookup_ripemd160(h).map(|p| p.to_vec()), + Placeholder::EcdsaSigPk(pk) => sat.lookup_ecdsa_sig(pk).map(|s| s.to_vec()), + Placeholder::EcdsaSigPkHash(pkh) => { + sat.lookup_raw_pkh_ecdsa_sig(pkh).map(|(_, s)| s.to_vec()) + } + Placeholder::SchnorrSigPk(pk, SchnorrSigType::ScriptSpend { leaf_hash }, size) => sat + .lookup_tap_leaf_script_sig(pk, leaf_hash) + .map(|s| s.to_vec()) + .map(|s| { + debug_assert!(s.len() == *size); + s + }), + Placeholder::SchnorrSigPk(_, _, size) => { + sat.lookup_tap_key_spend_sig().map(|s| s.to_vec()).map(|s| { + debug_assert!(s.len() == *size); + s + }) + } + Placeholder::SchnorrSigPkHash(pkh, tap_leaf_hash, size) => sat + .lookup_raw_pkh_tap_leaf_script_sig(&(*pkh, *tap_leaf_hash)) + .map(|(_, s)| { + let sig = s.to_vec(); + debug_assert!(sig.len() == *size); + sig + }), + Placeholder::HashDissatisfaction => Some(vec![0; 32]), + Placeholder::PushZero => Some(vec![]), + Placeholder::PushOne => Some(vec![1]), + Placeholder::TapScript(s) => Some(s.to_bytes()), + Placeholder::TapControlBlock(cb) => Some(cb.serialize()), + } + } +} + /// A witness, if available, for a Miniscript fragment #[derive(Clone, PartialEq, Eq, Debug)] -pub enum Witness { +pub enum Witness { /// Witness Available and the value of the witness - Stack(Vec>), + Stack(Vec), /// Third party can possibly satisfy the fragment but we cannot /// Witness Unavailable Unavailable, @@ -606,13 +759,13 @@ pub enum Witness { Impossible, } -impl PartialOrd for Witness { +impl PartialOrd for Witness> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for Witness { +impl Ord for Witness> { fn cmp(&self, other: &Self) -> cmp::Ordering { match (self, other) { (Witness::Stack(v1), Witness::Stack(v2)) => { @@ -630,111 +783,128 @@ impl Ord for Witness { } } -impl Witness { +impl Witness> { /// Turn a signature into (part of) a satisfaction - fn signature, Ctx: ScriptContext>( - sat: S, + fn signature, Ctx: ScriptContext>( + sat: &S, pk: &Pk, leaf_hash: &TapLeafHash, ) -> Self { match Ctx::sig_type() { - super::context::SigType::Ecdsa => match sat.lookup_ecdsa_sig(pk) { - Some(sig) => Witness::Stack(vec![sig.to_vec()]), - // Signatures cannot be forged - None => Witness::Impossible, - }, - super::context::SigType::Schnorr => match sat.lookup_tap_leaf_script_sig(pk, leaf_hash) - { - Some(sig) => Witness::Stack(vec![sig.to_vec()]), - // Signatures cannot be forged - None => Witness::Impossible, - }, + super::context::SigType::Ecdsa => { + if sat.provider_lookup_ecdsa_sig(pk) { + Witness::Stack(vec![Placeholder::EcdsaSigPk(pk.clone())]) + } else { + // Signatures cannot be forged + Witness::Impossible + } + } + super::context::SigType::Schnorr => { + match sat.provider_lookup_tap_leaf_script_sig(pk, leaf_hash) { + Some(size) => Witness::Stack(vec![Placeholder::SchnorrSigPk( + pk.clone(), + SchnorrSigType::ScriptSpend { + leaf_hash: *leaf_hash, + }, + size, + )]), + // Signatures cannot be forged + None => Witness::Impossible, + } + } } } /// Turn a public key related to a pkh into (part of) a satisfaction - fn pkh_public_key, Ctx: ScriptContext>( - sat: S, + fn pkh_public_key, Ctx: ScriptContext>( + sat: &S, pkh: &hash160::Hash, ) -> Self { // public key hashes are assumed to be unavailable // instead of impossible since it is the same as pub-key hashes match Ctx::sig_type() { - SigType::Ecdsa => match sat.lookup_raw_pkh_pk(pkh) { - Some(pk) => Witness::Stack(vec![pk.to_bytes()]), + SigType::Ecdsa => match sat.provider_lookup_raw_pkh_pk(pkh) { + Some(pk) => Witness::Stack(vec![Placeholder::PubkeyHash(*pkh, Ctx::pk_len(&pk))]), None => Witness::Unavailable, }, - SigType::Schnorr => match sat.lookup_raw_pkh_x_only_pk(pkh) { - Some(pk) => Witness::Stack(vec![pk.serialize().to_vec()]), + SigType::Schnorr => match sat.provider_lookup_raw_pkh_x_only_pk(pkh) { + Some(pk) => Witness::Stack(vec![Placeholder::PubkeyHash(*pkh, Ctx::pk_len(&pk))]), None => Witness::Unavailable, }, } } /// Turn a key/signature pair related to a pkh into (part of) a satisfaction - fn pkh_signature, Ctx: ScriptContext>( - sat: S, + fn pkh_signature, Ctx: ScriptContext>( + sat: &S, pkh: &hash160::Hash, leaf_hash: &TapLeafHash, ) -> Self { match Ctx::sig_type() { - SigType::Ecdsa => match sat.lookup_raw_pkh_ecdsa_sig(pkh) { - Some((pk, sig)) => { - Witness::Stack(vec![sig.to_vec(), pk.to_public_key().to_bytes()]) - } - None => Witness::Impossible, - }, - SigType::Schnorr => match sat.lookup_raw_pkh_tap_leaf_script_sig(&(*pkh, *leaf_hash)) { - Some((pk, sig)) => Witness::Stack(vec![ - sig.to_vec(), - pk.to_x_only_pubkey().serialize().to_vec(), + SigType::Ecdsa => match sat.provider_lookup_raw_pkh_ecdsa_sig(pkh) { + Some(pk) => Witness::Stack(vec![ + Placeholder::EcdsaSigPkHash(*pkh), + Placeholder::PubkeyHash(*pkh, Ctx::pk_len(&pk)), ]), None => Witness::Impossible, }, + SigType::Schnorr => { + match sat.provider_lookup_raw_pkh_tap_leaf_script_sig(&(*pkh, *leaf_hash)) { + Some((pk, size)) => Witness::Stack(vec![ + Placeholder::SchnorrSigPkHash(*pkh, *leaf_hash, size), + Placeholder::PubkeyHash(*pkh, Ctx::pk_len(&pk)), + ]), + None => Witness::Impossible, + } + } } } /// Turn a hash preimage into (part of) a satisfaction - fn ripemd160_preimage>(sat: S, h: &Pk::Ripemd160) -> Self { - match sat.lookup_ripemd160(h) { - Some(pre) => Witness::Stack(vec![pre.to_vec()]), - // Note hash preimages are unavailable instead of impossible - None => Witness::Unavailable, + fn ripemd160_preimage>(sat: &S, h: &Pk::Ripemd160) -> Self { + if sat.provider_lookup_ripemd160(h) { + Witness::Stack(vec![Placeholder::Ripemd160Preimage(h.clone())]) + // Note hash preimages are unavailable instead of impossible + } else { + Witness::Unavailable } } /// Turn a hash preimage into (part of) a satisfaction - fn hash160_preimage>(sat: S, h: &Pk::Hash160) -> Self { - match sat.lookup_hash160(h) { - Some(pre) => Witness::Stack(vec![pre.to_vec()]), - // Note hash preimages are unavailable instead of impossible - None => Witness::Unavailable, + fn hash160_preimage>(sat: &S, h: &Pk::Hash160) -> Self { + if sat.provider_lookup_hash160(h) { + Witness::Stack(vec![Placeholder::Hash160Preimage(h.clone())]) + // Note hash preimages are unavailable instead of impossible + } else { + Witness::Unavailable } } /// Turn a hash preimage into (part of) a satisfaction - fn sha256_preimage>(sat: S, h: &Pk::Sha256) -> Self { - match sat.lookup_sha256(h) { - Some(pre) => Witness::Stack(vec![pre.to_vec()]), - // Note hash preimages are unavailable instead of impossible - None => Witness::Unavailable, + fn sha256_preimage>(sat: &S, h: &Pk::Sha256) -> Self { + if sat.provider_lookup_sha256(h) { + Witness::Stack(vec![Placeholder::Sha256Preimage(h.clone())]) + // Note hash preimages are unavailable instead of impossible + } else { + Witness::Unavailable } } /// Turn a hash preimage into (part of) a satisfaction - fn hash256_preimage>(sat: S, h: &Pk::Hash256) -> Self { - match sat.lookup_hash256(h) { - Some(pre) => Witness::Stack(vec![pre.to_vec()]), - // Note hash preimages are unavailable instead of impossible - None => Witness::Unavailable, + fn hash256_preimage>(sat: &S, h: &Pk::Hash256) -> Self { + if sat.provider_lookup_hash256(h) { + Witness::Stack(vec![Placeholder::Hash256Preimage(h.clone())]) + // Note hash preimages are unavailable instead of impossible + } else { + Witness::Unavailable } } } -impl Witness { +impl Witness> { /// Produce something like a 32-byte 0 push fn hash_dissatisfaction() -> Self { - Witness::Stack(vec![vec![0; 32]]) + Witness::Stack(vec![Placeholder::HashDissatisfaction]) } /// Construct a satisfaction equivalent to an empty stack @@ -744,12 +914,12 @@ impl Witness { /// Construct a satisfaction equivalent to `OP_1` fn push_1() -> Self { - Witness::Stack(vec![vec![1]]) + Witness::Stack(vec![Placeholder::PushOne]) } /// Construct a satisfaction equivalent to a single empty push fn push_0() -> Self { - Witness::Stack(vec![vec![]]) + Witness::Stack(vec![Placeholder::PushZero]) } /// Concatenate, or otherwise combine, two satisfactions @@ -767,23 +937,61 @@ impl Witness { /// A (dis)satisfaction of a Miniscript fragment #[derive(Clone, PartialEq, Eq, Debug)] -pub struct Satisfaction { +pub struct Satisfaction { /// The actual witness stack - pub stack: Witness, + pub stack: Witness, /// Whether or not this (dis)satisfaction has a signature somewhere /// in it pub has_sig: bool, - // We use AbsLockTime here as we need to compare timelocks using Ord. This is safe, - // as miniscript checks for us beforehand that the timelocks are of the same type. /// The absolute timelock used by this satisfaction pub absolute_timelock: Option, /// The relative timelock used by this satisfaction pub relative_timelock: Option, } -impl Satisfaction { +impl Satisfaction> { + pub(crate) fn build_template( + term: &Terminal, + provider: &P, + root_has_sig: bool, + leaf_hash: &TapLeafHash, + ) -> Self + where + Ctx: ScriptContext, + P: AssetProvider, + { + Self::satisfy_helper( + term, + provider, + root_has_sig, + leaf_hash, + &mut Satisfaction::minimum, + &mut Satisfaction::thresh, + ) + } + + pub(crate) fn build_template_mall( + term: &Terminal, + provider: &P, + root_has_sig: bool, + leaf_hash: &TapLeafHash, + ) -> Self + where + Ctx: ScriptContext, + P: AssetProvider, + { + Self::satisfy_helper( + term, + provider, + root_has_sig, + leaf_hash, + &mut Satisfaction::minimum_mall, + &mut Satisfaction::thresh_mall, + ) + } + // produce a non-malleable satisafaction for thesh frag - fn thresh( + fn thresh( k: usize, subs: &[Arc>], stfr: &Sat, @@ -792,10 +1000,12 @@ impl Satisfaction { min_fn: &mut F, ) -> Self where - Pk: MiniscriptKey + ToPublicKey, Ctx: ScriptContext, - Sat: Satisfier, - F: FnMut(Satisfaction, Satisfaction) -> Satisfaction, + Sat: AssetProvider, + F: FnMut( + Satisfaction>, + Satisfaction>, + ) -> Satisfaction>, { let mut sats = subs .iter() @@ -913,7 +1123,7 @@ impl Satisfaction { } // produce a possily malleable satisafaction for thesh frag - fn thresh_mall( + fn thresh_mall( k: usize, subs: &[Arc>], stfr: &Sat, @@ -922,10 +1132,12 @@ impl Satisfaction { min_fn: &mut F, ) -> Self where - Pk: MiniscriptKey + ToPublicKey, Ctx: ScriptContext, - Sat: Satisfier, - F: FnMut(Satisfaction, Satisfaction) -> Satisfaction, + Sat: AssetProvider, + F: FnMut( + Satisfaction>, + Satisfaction>, + ) -> Satisfaction>, { let mut sats = subs .iter() @@ -1070,7 +1282,7 @@ impl Satisfaction { } // produce a non-malleable satisfaction - fn satisfy_helper( + fn satisfy_helper( term: &Terminal, stfr: &Sat, root_has_sig: bool, @@ -1079,10 +1291,12 @@ impl Satisfaction { thresh_fn: &mut G, ) -> Self where - Pk: MiniscriptKey + ToPublicKey, Ctx: ScriptContext, - Sat: Satisfier, - F: FnMut(Satisfaction, Satisfaction) -> Satisfaction, + Sat: AssetProvider, + F: FnMut( + Satisfaction>, + Satisfaction>, + ) -> Satisfaction>, G: FnMut( usize, &[Arc>], @@ -1090,30 +1304,29 @@ impl Satisfaction { bool, &TapLeafHash, &mut F, - ) -> Satisfaction, + ) -> Satisfaction>, { match *term { Terminal::PkK(ref pk) => Satisfaction { - stack: Witness::signature::<_, _, Ctx>(stfr, pk, leaf_hash), + stack: Witness::signature::<_, Ctx>(stfr, pk, leaf_hash), has_sig: true, relative_timelock: None, absolute_timelock: None, }, Terminal::PkH(ref pk) => { - let wit = Witness::signature::<_, _, Ctx>(stfr, pk, leaf_hash); - let pk_bytes = match Ctx::sig_type() { - SigType::Ecdsa => pk.to_public_key().to_bytes(), - SigType::Schnorr => pk.to_x_only_pubkey().serialize().to_vec(), - }; + let wit = Witness::signature::<_, Ctx>(stfr, pk, leaf_hash); Satisfaction { - stack: Witness::combine(wit, Witness::Stack(vec![pk_bytes])), + 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), + stack: Witness::pkh_signature::<_, Ctx>(stfr, pkh, leaf_hash), has_sig: true, relative_timelock: None, absolute_timelock: None, @@ -1362,7 +1575,7 @@ impl Satisfaction { let mut sig_count = 0; let mut sigs = Vec::with_capacity(k); for pk in keys { - match Witness::signature::<_, _, Ctx>(stfr, pk, leaf_hash) { + match Witness::signature::<_, Ctx>(stfr, pk, leaf_hash) { Witness::Stack(sig) => { sigs.push(sig); sig_count += 1; @@ -1406,9 +1619,9 @@ impl Satisfaction { Terminal::MultiA(k, ref keys) => { // Collect all available signatures let mut sig_count = 0; - let mut sigs = vec![vec![vec![]]; keys.len()]; + let mut sigs = vec![vec![Placeholder::PushZero]; keys.len()]; for (i, pk) in keys.iter().rev().enumerate() { - match Witness::signature::<_, _, Ctx>(stfr, pk, leaf_hash) { + match Witness::signature::<_, Ctx>(stfr, pk, leaf_hash) { Witness::Stack(sig) => { sigs[i] = sig; sig_count += 1; @@ -1449,7 +1662,7 @@ impl Satisfaction { } // Helper function to produce a dissatisfaction - fn dissatisfy_helper( + fn dissatisfy_helper( term: &Terminal, stfr: &Sat, root_has_sig: bool, @@ -1458,10 +1671,12 @@ impl Satisfaction { thresh_fn: &mut G, ) -> Self where - Pk: MiniscriptKey + ToPublicKey, Ctx: ScriptContext, - Sat: Satisfier, - F: FnMut(Satisfaction, Satisfaction) -> Satisfaction, + Sat: AssetProvider, + F: FnMut( + Satisfaction>, + Satisfaction>, + ) -> Satisfaction>, G: FnMut( usize, &[Arc>], @@ -1469,7 +1684,7 @@ impl Satisfaction { bool, &TapLeafHash, &mut F, - ) -> Satisfaction, + ) -> Satisfaction>, { match *term { Terminal::PkK(..) => Satisfaction { @@ -1478,22 +1693,19 @@ impl Satisfaction { relative_timelock: None, absolute_timelock: None, }, - Terminal::PkH(ref pk) => { - let pk_bytes = match Ctx::sig_type() { - SigType::Ecdsa => pk.to_public_key().to_bytes(), - SigType::Schnorr => pk.to_x_only_pubkey().serialize().to_vec(), - }; - Satisfaction { - stack: Witness::combine(Witness::push_0(), Witness::Stack(vec![pk_bytes])), - has_sig: false, - relative_timelock: None, - absolute_timelock: None, - } - } + 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), + Witness::pkh_public_key::<_, Ctx>(stfr, pkh), ), has_sig: false, relative_timelock: None, @@ -1633,13 +1845,13 @@ impl Satisfaction { absolute_timelock: None, }, Terminal::Multi(k, _) => Satisfaction { - stack: Witness::Stack(vec![vec![]; k + 1]), + stack: Witness::Stack(vec![Placeholder::PushZero; k + 1]), has_sig: false, relative_timelock: None, absolute_timelock: None, }, Terminal::MultiA(_, ref pks) => Satisfaction { - stack: Witness::Stack(vec![vec![]; pks.len()]), + stack: Witness::Stack(vec![Placeholder::PushZero; pks.len()]), has_sig: false, relative_timelock: None, absolute_timelock: None, @@ -1647,45 +1859,65 @@ impl Satisfaction { } } + /// 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; + let stack = match stack { + Witness::Stack(stack) => Witness::Stack( + stack + .iter() + .map(|placeholder| placeholder.satisfy_self(stfr)) + .collect::>()?, + ), + Witness::Unavailable => Witness::Unavailable, + Witness::Impossible => Witness::Impossible, + }; + Some(Satisfaction { + stack, + has_sig: *has_sig, + relative_timelock: *relative_timelock, + absolute_timelock: *absolute_timelock, + }) + } +} + +impl Satisfaction> { /// Produce a satisfaction non-malleable satisfaction - pub(super) fn satisfy< - Pk: MiniscriptKey + ToPublicKey, - Ctx: ScriptContext, - Sat: Satisfier, - >( + pub(super) fn satisfy( term: &Terminal, stfr: &Sat, root_has_sig: bool, leaf_hash: &TapLeafHash, - ) -> Self { - Self::satisfy_helper( - term, - stfr, - root_has_sig, - leaf_hash, - &mut Satisfaction::minimum, - &mut Satisfaction::thresh, - ) + ) -> Self + where + Ctx: ScriptContext, + Pk: MiniscriptKey + ToPublicKey, + Sat: Satisfier, + { + Satisfaction::>::build_template(term, &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< - Pk: MiniscriptKey + ToPublicKey, - Ctx: ScriptContext, - Sat: Satisfier, - >( + pub(super) fn satisfy_mall( term: &Terminal, stfr: &Sat, root_has_sig: bool, leaf_hash: &TapLeafHash, - ) -> Self { - Self::satisfy_helper( - term, - stfr, - root_has_sig, - leaf_hash, - &mut Satisfaction::minimum_mall, - &mut Satisfaction::thresh_mall, - ) + ) -> Self + where + Ctx: ScriptContext, + Pk: MiniscriptKey + ToPublicKey, + Sat: Satisfier, + { + Satisfaction::>::build_template_mall(term, &stfr, root_has_sig, leaf_hash) + .try_completing(stfr) + .expect("the same satisfier should manage to complete the template") } } diff --git a/src/plan.rs b/src/plan.rs new file mode 100644 index 000000000..3cb91d129 --- /dev/null +++ b/src/plan.rs @@ -0,0 +1,799 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! A spending plan or *plan* for short is a representation of a particular spending path on a +//! descriptor. This allows us to analayze a choice of spending path without producing any +//! signatures or other witness data for it. +//! +//! To make a plan you provide the descriptor with "assets" like which keys you are able to use, hash +//! pre-images you have access to, absolute/relative timelock constraints etc. +//! +//! Once you've got a plan it can tell you its expected satisfaction weight which can be useful for +//! doing coin selection. Furthermore it provides which subset of those keys and hash pre-images you +//! will actually need as well as what locktime or sequence number you need to set. +//! +//! Once you've obtained signatures, hash pre-images etc required by the plan, it can create a +//! witness/script_sig for the input. + +use core::cmp::Ordering; +use core::iter::FromIterator; + +use bitcoin::absolute::LockTime; +use bitcoin::address::WitnessVersion; +use bitcoin::hashes::{hash160, ripemd160, sha256}; +use bitcoin::key::XOnlyPublicKey; +use bitcoin::script::PushBytesBuf; +use bitcoin::taproot::{ControlBlock, LeafVersion, TapLeafHash}; +use bitcoin::{bip32, psbt, ScriptBuf, Sequence}; + +use crate::descriptor::{self, Descriptor, DescriptorType, KeyMap}; +use crate::miniscript::hash256; +use crate::miniscript::satisfy::{Placeholder, Satisfier, SchnorrSigType}; +use crate::prelude::*; +use crate::util::witness_size; +use crate::{DefiniteDescriptorKey, DescriptorPublicKey, Error, MiniscriptKey, ToPublicKey}; + +/// Trait describing a present/missing lookup table for constructing witness templates +/// +/// This trait mirrors the [`Satisfier`] trait, with the difference that most methods just return a +/// boolean indicating the item presence. The methods looking up keys return the key +/// length, the methods looking up public key hashes return the public key, and a few other methods +/// need to return the item itself. +/// +/// This trait is automatically implemented for every type that is also a satisfier, and simply +/// proxies the queries to the satisfier and returns whether an item is available or not. +/// +/// All the methods have a default implementation that returns `false` or `None`. +pub trait AssetProvider { + /// Given a public key, look up an ECDSA signature with that key, return whether we found it + fn provider_lookup_ecdsa_sig(&self, _: &Pk) -> bool { + false + } + + /// Lookup the tap key spend sig and return its size + fn provider_lookup_tap_key_spend_sig(&self, _: &Pk) -> Option { + None + } + + /// Given a public key and a associated leaf hash, look up a schnorr signature with that key + /// and return its size + fn provider_lookup_tap_leaf_script_sig(&self, _: &Pk, _: &TapLeafHash) -> Option { + None + } + + /// Obtain a reference to the control block for a ver and script + fn provider_lookup_tap_control_block_map( + &self, + ) -> Option<&BTreeMap> { + None + } + + /// Given a raw `Pkh`, lookup corresponding [`bitcoin::PublicKey`] + fn provider_lookup_raw_pkh_pk(&self, _: &hash160::Hash) -> Option { + None + } + + /// Given a raw `Pkh`, lookup corresponding [`bitcoin::secp256k1::XOnlyPublicKey`] + fn provider_lookup_raw_pkh_x_only_pk(&self, _: &hash160::Hash) -> Option { + None + } + + /// Given a keyhash, look up the EC signature and the associated key. + /// Returns the key if a signature is found. + /// Even if signatures for public key Hashes are not available, the users + /// can use this map to provide pkh -> pk mapping which can be useful + /// for dissatisfying pkh. + fn provider_lookup_raw_pkh_ecdsa_sig(&self, _: &hash160::Hash) -> Option { + None + } + + /// Given a keyhash, look up the schnorr signature and the associated key. + /// Returns the key and sig len if a signature is found. + /// Even if signatures for public key Hashes are not available, the users + /// can use this map to provide pkh -> pk mapping which can be useful + /// for dissatisfying pkh. + fn provider_lookup_raw_pkh_tap_leaf_script_sig( + &self, + _: &(hash160::Hash, TapLeafHash), + ) -> Option<(XOnlyPublicKey, usize)> { + None + } + + /// Given a SHA256 hash, look up its preimage, return whether we found it + fn provider_lookup_sha256(&self, _: &Pk::Sha256) -> bool { + false + } + + /// Given a HASH256 hash, look up its preimage, return whether we found it + fn provider_lookup_hash256(&self, _: &Pk::Hash256) -> bool { + false + } + + /// Given a RIPEMD160 hash, look up its preimage, return whether we found it + fn provider_lookup_ripemd160(&self, _: &Pk::Ripemd160) -> bool { + false + } + + /// Given a HASH160 hash, look up its preimage, return whether we found it + fn provider_lookup_hash160(&self, _: &Pk::Hash160) -> bool { + false + } + + /// Assert whether a relative locktime is satisfied + fn check_older(&self, _: Sequence) -> bool { + false + } + + /// Assert whether an absolute locktime is satisfied + fn check_after(&self, _: LockTime) -> bool { + false + } +} + +/// Wrapper around [`Assets`] that logs every query and value returned +#[cfg(feature = "std")] +pub struct LoggerAssetProvider(Assets); + +#[cfg(feature = "std")] +macro_rules! impl_log_method { + ( $name:ident, $( <$ctx:ident: ScriptContext > )? $( $arg:ident : $ty:ty, )* -> $ret_ty:ty ) => { + fn $name $( <$ctx: ScriptContext> )? ( &self, $( $arg:$ty ),* ) -> $ret_ty { + let ret = (self.0).$name $( ::<$ctx> )*( $( $arg ),* ); + dbg!(stringify!( $name ), ( $( $arg ),* ), &ret); + + ret + } + } +} + +#[cfg(feature = "std")] +impl AssetProvider for LoggerAssetProvider { + impl_log_method!(provider_lookup_ecdsa_sig, pk: &DefiniteDescriptorKey, -> bool); + impl_log_method!(provider_lookup_tap_key_spend_sig, pk: &DefiniteDescriptorKey, -> Option); + impl_log_method!(provider_lookup_tap_leaf_script_sig, pk: &DefiniteDescriptorKey, leaf_hash: &TapLeafHash, -> Option); + impl_log_method!(provider_lookup_tap_control_block_map, -> Option<&BTreeMap>); + impl_log_method!(provider_lookup_raw_pkh_pk, hash: &hash160::Hash, -> Option); + impl_log_method!(provider_lookup_raw_pkh_x_only_pk, hash: &hash160::Hash, -> Option); + impl_log_method!(provider_lookup_raw_pkh_ecdsa_sig, hash: &hash160::Hash, -> Option); + impl_log_method!(provider_lookup_raw_pkh_tap_leaf_script_sig, hash: &(hash160::Hash, TapLeafHash), -> Option<(XOnlyPublicKey, usize)>); + impl_log_method!(provider_lookup_sha256, hash: &sha256::Hash, -> bool); + impl_log_method!(provider_lookup_hash256, hash: &hash256::Hash, -> bool); + impl_log_method!(provider_lookup_ripemd160, hash: &ripemd160::Hash, -> bool); + impl_log_method!(provider_lookup_hash160, hash: &hash160::Hash, -> bool); + impl_log_method!(check_older, s: Sequence, -> bool); + impl_log_method!(check_after, t: LockTime, -> bool); +} + +impl AssetProvider for T +where + T: Satisfier, + Pk: MiniscriptKey + ToPublicKey, +{ + fn provider_lookup_ecdsa_sig(&self, pk: &Pk) -> bool { + Satisfier::lookup_ecdsa_sig(self, pk).is_some() + } + + fn provider_lookup_tap_key_spend_sig(&self, _: &Pk) -> Option { + Satisfier::lookup_tap_key_spend_sig(self).map(|s| s.to_vec().len()) + } + + fn provider_lookup_tap_leaf_script_sig( + &self, + pk: &Pk, + leaf_hash: &TapLeafHash, + ) -> Option { + Satisfier::lookup_tap_leaf_script_sig(self, pk, leaf_hash).map(|s| s.to_vec().len()) + } + + fn provider_lookup_tap_control_block_map( + &self, + ) -> Option<&BTreeMap> { + Satisfier::lookup_tap_control_block_map(self) + } + + fn provider_lookup_raw_pkh_pk(&self, hash: &hash160::Hash) -> Option { + Satisfier::lookup_raw_pkh_pk(self, hash) + } + + fn provider_lookup_raw_pkh_x_only_pk(&self, hash: &hash160::Hash) -> Option { + Satisfier::lookup_raw_pkh_x_only_pk(self, hash) + } + + fn provider_lookup_raw_pkh_ecdsa_sig( + &self, + hash: &hash160::Hash, + ) -> Option { + Satisfier::lookup_raw_pkh_ecdsa_sig(self, hash).map(|(pk, _)| pk) + } + + fn provider_lookup_raw_pkh_tap_leaf_script_sig( + &self, + hash: &(hash160::Hash, TapLeafHash), + ) -> Option<(XOnlyPublicKey, usize)> { + Satisfier::lookup_raw_pkh_tap_leaf_script_sig(self, hash) + .map(|(pk, sig)| (pk, sig.to_vec().len())) + } + + fn provider_lookup_sha256(&self, hash: &Pk::Sha256) -> bool { + Satisfier::lookup_sha256(self, hash).is_some() + } + + fn provider_lookup_hash256(&self, hash: &Pk::Hash256) -> bool { + Satisfier::lookup_hash256(self, hash).is_some() + } + + fn provider_lookup_ripemd160(&self, hash: &Pk::Ripemd160) -> bool { + Satisfier::lookup_ripemd160(self, hash).is_some() + } + + fn provider_lookup_hash160(&self, hash: &Pk::Hash160) -> bool { + Satisfier::lookup_hash160(self, hash).is_some() + } + + fn check_older(&self, s: Sequence) -> bool { + Satisfier::check_older(self, s) + } + + fn check_after(&self, l: LockTime) -> bool { + Satisfier::check_after(self, l) + } +} + +/// Representation of a particular spending path on a descriptor. Contains the witness template +/// and the timelocks needed for satisfying the plan. +/// Calling `plan` on a Descriptor will return this structure, +/// containing the cheapest spending path possible (considering the `Assets` given) +#[derive(Debug, Clone)] +pub struct Plan { + /// This plan's witness template + pub(crate) template: Vec>, + /// The absolute timelock this plan uses + pub absolute_timelock: Option, + /// The relative timelock this plan uses + pub relative_timelock: Option, + + pub(crate) descriptor: Descriptor, +} + +impl Plan { + /// Returns the witness template + pub fn witness_template(&self) -> &Vec> { + &self.template + } + + /// Returns the witness version + pub fn witness_version(&self) -> Option { + self.descriptor.desc_type().segwit_version() + } + + /// The weight, in witness units, needed for satisfying this plan (includes both + /// the script sig weight and the witness weight) + pub fn satisfaction_weight(&self) -> usize { + self.witness_size() + self.scriptsig_size() * 4 + } + + /// The size in bytes of the script sig that satisfies this plan + pub fn scriptsig_size(&self) -> usize { + match ( + self.descriptor.desc_type().segwit_version(), + self.descriptor.desc_type(), + ) { + // Entire witness goes in the script_sig + (None, _) => witness_size(self.template.as_ref()), + // Taproot doesn't have a "wrapped" version (scriptSig len (1)) + (Some(WitnessVersion::V1), _) => 1, + // scriptSig len (1) + OP_0 (1) + OP_PUSHBYTES_20 (1) + (20) + (_, DescriptorType::ShWpkh) => 1 + 1 + 1 + 20, + // scriptSig len (1) + OP_0 (1) + OP_PUSHBYTES_32 (1) +