From 7ca9ba178799c4d21d3cd389e0a9b54280815051 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Mon, 17 Oct 2022 12:18:55 +0100 Subject: [PATCH 1/6] Add relative and absolute timelock in Satisfaction The Satisfaction struct now contains `relative_timelock` and `absolute_timelock`, which represent the needed timelocks for that particular spending path. This is useful for the plan module. Co-authored-by: Daniela Brozzoni --- src/miniscript/satisfy.rs | 177 +++++++++++++++++++++++++++++++++----- 1 file changed, 156 insertions(+), 21 deletions(-) diff --git a/src/miniscript/satisfy.rs b/src/miniscript/satisfy.rs index 98009f085..f5cce8b6f 100644 --- a/src/miniscript/satisfy.rs +++ b/src/miniscript/satisfy.rs @@ -17,7 +17,7 @@ use sync::Arc; use super::context::SigType; use crate::prelude::*; use crate::util::witness_size; -use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey}; +use crate::{AbsLockTime, Miniscript, MiniscriptKey, ScriptContext, Terminal, ToPublicKey}; /// Type alias for 32 byte Preimage. pub type Preimage32 = [u8; 32]; @@ -773,6 +773,12 @@ pub struct Satisfaction { /// 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 { @@ -855,8 +861,10 @@ impl Satisfaction { Satisfaction { stack: Witness::Impossible, // If the witness is impossible, we don't care about the - // has_sig flag + // has_sig flag, nor about the timelocks has_sig: false, + relative_timelock: None, + absolute_timelock: None, } } // We are now guaranteed that all elements in `k` satisfactions @@ -882,11 +890,21 @@ impl Satisfaction { Satisfaction { stack: Witness::Unavailable, has_sig: false, + relative_timelock: None, + absolute_timelock: None, } } else { // Otherwise flatten everything out Satisfaction { has_sig: ret_stack.iter().any(|sat| sat.has_sig), + relative_timelock: ret_stack + .iter() + .filter_map(|sat| sat.relative_timelock) + .max(), + absolute_timelock: ret_stack + .iter() + .filter_map(|sat| sat.absolute_timelock) + .max(), stack: ret_stack.into_iter().fold(Witness::empty(), |acc, next| { Witness::combine(next.stack, acc) }), @@ -962,6 +980,14 @@ impl Satisfaction { // no non-malleability checks needed Satisfaction { has_sig: ret_stack.iter().any(|sat| sat.has_sig), + relative_timelock: ret_stack + .iter() + .filter_map(|sat| sat.relative_timelock) + .max(), + absolute_timelock: ret_stack + .iter() + .filter_map(|sat| sat.absolute_timelock) + .max(), stack: ret_stack.into_iter().fold(Witness::empty(), |acc, next| { Witness::combine(next.stack, acc) }), @@ -983,6 +1009,8 @@ impl Satisfaction { (false, false) => Satisfaction { stack: Witness::Unavailable, has_sig: false, + relative_timelock: None, + absolute_timelock: None, }, // If only one has a signature, take the one that doesn't; a // third party could malleate by removing the signature, but @@ -990,17 +1018,29 @@ impl Satisfaction { (false, true) => Satisfaction { stack: sat1.stack, has_sig: false, + relative_timelock: sat1.relative_timelock, + absolute_timelock: sat1.absolute_timelock, }, (true, false) => Satisfaction { stack: sat2.stack, has_sig: false, + relative_timelock: sat2.relative_timelock, + absolute_timelock: sat2.absolute_timelock, }, // If both have a signature associated with them, choose the // cheaper one (where "cheaper" is defined such that available // things are cheaper than unavailable ones) + (true, true) if sat1.stack < sat2.stack => Satisfaction { + stack: sat1.stack, + has_sig: true, + relative_timelock: sat1.relative_timelock, + absolute_timelock: sat1.absolute_timelock, + }, (true, true) => Satisfaction { - stack: cmp::min(sat1.stack, sat2.stack), + stack: sat2.stack, has_sig: true, + relative_timelock: sat2.relative_timelock, + absolute_timelock: sat2.absolute_timelock, }, } } @@ -1014,11 +1054,18 @@ impl Satisfaction { (_, &Witness::Impossible) | (_, &Witness::Unavailable) => return sat1, _ => {} } + let (stack, absolute_timelock, relative_timelock) = if sat1.stack < sat2.stack { + (sat1.stack, sat1.absolute_timelock, sat1.relative_timelock) + } else { + (sat2.stack, sat2.absolute_timelock, sat2.relative_timelock) + }; Satisfaction { - stack: cmp::min(sat1.stack, sat2.stack), + stack, // The fragment is has_sig only if both of the // fragments are has_sig has_sig: sat1.has_sig && sat2.has_sig, + relative_timelock, + absolute_timelock, } } @@ -1049,6 +1096,8 @@ impl Satisfaction { Terminal::PkK(ref pk) => Satisfaction { 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); @@ -1059,66 +1108,91 @@ impl Satisfaction { Satisfaction { stack: Witness::combine(wit, Witness::Stack(vec![pk_bytes])), 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) => Satisfaction { - stack: if stfr.check_after(t.into()) { - Witness::empty() + 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 + (Witness::Impossible, None) } else { - Witness::Unavailable - }, - has_sig: false, - }, - Terminal::Older(t) => Satisfaction { - stack: if stfr.check_older(t) { - Witness::empty() + (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) { + (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 + (Witness::Impossible, None) } else { - Witness::Unavailable - }, - - has_sig: false, - }, + (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::Alt(ref sub) | Terminal::Swap(ref sub) @@ -1140,6 +1214,8 @@ impl Satisfaction { 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) => { @@ -1150,6 +1226,8 @@ impl Satisfaction { Satisfaction { stack: Witness::combine(r_sat.stack, l_sat.stack), has_sig: l_sat.has_sig || r_sat.has_sig, + relative_timelock: cmp::max(l_sat.relative_timelock, r_sat.relative_timelock), + absolute_timelock: cmp::max(l_sat.absolute_timelock, r_sat.absolute_timelock), } } Terminal::AndOr(ref a, ref b, ref c) => { @@ -1172,10 +1250,21 @@ impl Satisfaction { Satisfaction { stack: Witness::combine(b_sat.stack, a_sat.stack), has_sig: a_sat.has_sig || b_sat.has_sig, + relative_timelock: cmp::max( + a_sat.relative_timelock, + b_sat.relative_timelock, + ), + absolute_timelock: cmp::max( + a_sat.absolute_timelock, + b_sat.absolute_timelock, + ), }, Satisfaction { stack: Witness::combine(c_sat.stack, a_nsat.stack), has_sig: a_nsat.has_sig || c_sat.has_sig, + // timelocks can't be dissatisfied, so here we ignore a_nsat and only consider c_sat + relative_timelock: c_sat.relative_timelock, + absolute_timelock: c_sat.absolute_timelock, }, ) } @@ -1208,10 +1297,14 @@ impl Satisfaction { Satisfaction { stack: Witness::combine(r_sat.stack, l_nsat.stack), has_sig: r_sat.has_sig, + relative_timelock: r_sat.relative_timelock, + absolute_timelock: r_sat.absolute_timelock, }, Satisfaction { stack: Witness::combine(r_nsat.stack, l_sat.stack), has_sig: l_sat.has_sig, + relative_timelock: l_sat.relative_timelock, + absolute_timelock: l_sat.absolute_timelock, }, ) } @@ -1236,6 +1329,8 @@ impl Satisfaction { Satisfaction { stack: Witness::combine(r_sat.stack, l_nsat.stack), has_sig: r_sat.has_sig, + relative_timelock: r_sat.relative_timelock, + absolute_timelock: r_sat.absolute_timelock, }, ) } @@ -1248,10 +1343,14 @@ impl Satisfaction { 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, }, ) } @@ -1279,6 +1378,8 @@ impl Satisfaction { Satisfaction { stack: Witness::Impossible, has_sig: false, + relative_timelock: None, + absolute_timelock: None, } } else { // Throw away the most expensive ones @@ -1297,6 +1398,8 @@ impl Satisfaction { Witness::combine(acc, Witness::Stack(sig)) }), has_sig: true, + relative_timelock: None, + absolute_timelock: None, } } } @@ -1328,6 +1431,8 @@ impl Satisfaction { Satisfaction { stack: Witness::Impossible, has_sig: false, + relative_timelock: None, + absolute_timelock: None, } } else { Satisfaction { @@ -1335,6 +1440,8 @@ impl Satisfaction { Witness::combine(acc, Witness::Stack(sig)) }), has_sig: true, + relative_timelock: None, + absolute_timelock: None, } } } @@ -1368,6 +1475,8 @@ impl Satisfaction { Terminal::PkK(..) => Satisfaction { stack: Witness::push_0(), has_sig: false, + relative_timelock: None, + absolute_timelock: None, }, Terminal::PkH(ref pk) => { let pk_bytes = match Ctx::sig_type() { @@ -1377,6 +1486,8 @@ impl Satisfaction { Satisfaction { stack: Witness::combine(Witness::push_0(), Witness::Stack(vec![pk_bytes])), has_sig: false, + relative_timelock: None, + absolute_timelock: None, } } Terminal::RawPkH(ref pkh) => Satisfaction { @@ -1385,10 +1496,14 @@ impl Satisfaction { 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(_) @@ -1397,6 +1512,8 @@ impl Satisfaction { | Terminal::OrC(..) => Satisfaction { stack: Witness::Impossible, has_sig: false, + relative_timelock: None, + absolute_timelock: None, }, Terminal::Sha256(_) | Terminal::Hash256(_) @@ -1404,6 +1521,8 @@ impl Satisfaction { | Terminal::Hash160(_) => Satisfaction { stack: Witness::hash_dissatisfaction(), has_sig: false, + relative_timelock: None, + absolute_timelock: None, }, Terminal::Alt(ref sub) | Terminal::Swap(ref sub) @@ -1414,6 +1533,8 @@ impl Satisfaction { 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 = @@ -1429,6 +1550,8 @@ impl Satisfaction { Satisfaction { stack: Witness::combine(odissat.stack, vsat.stack), has_sig: vsat.has_sig || odissat.has_sig, + relative_timelock: None, + absolute_timelock: None, } } Terminal::AndB(ref l, ref r) @@ -1454,6 +1577,8 @@ impl Satisfaction { Satisfaction { stack: Witness::combine(rnsat.stack, lnsat.stack), has_sig: rnsat.has_sig || lnsat.has_sig, + relative_timelock: None, + absolute_timelock: None, } } Terminal::OrI(ref l, ref r) => { @@ -1468,6 +1593,8 @@ impl Satisfaction { 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( @@ -1481,6 +1608,8 @@ impl Satisfaction { 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 @@ -1500,14 +1629,20 @@ impl Satisfaction { Witness::combine(nsat.stack, acc) }), has_sig: false, + relative_timelock: None, + absolute_timelock: None, }, Terminal::Multi(k, _) => Satisfaction { stack: Witness::Stack(vec![vec![]; k + 1]), has_sig: false, + relative_timelock: None, + absolute_timelock: None, }, Terminal::MultiA(_, ref pks) => Satisfaction { stack: Witness::Stack(vec![vec![]; pks.len()]), has_sig: false, + relative_timelock: None, + absolute_timelock: None, }, } } From 448fbd8d3a72ef0a2314697a644a6870adea712a Mon Sep 17 00:00:00 2001 From: Daniela Brozzoni Date: Mon, 26 Jun 2023 19:10:42 +0200 Subject: [PATCH 2/6] Add full_derivation_paths on DescriptorPublicKey --- src/descriptor/key.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/descriptor/key.rs b/src/descriptor/key.rs index 83308e0dc..f39c5f05f 100644 --- a/src/descriptor/key.rs +++ b/src/descriptor/key.rs @@ -592,6 +592,33 @@ impl DescriptorPublicKey { } } + /// Returns a vector containing the full derivation paths from the master key. + /// The vector will contain just one element for single keys, and multiple elements + /// for multipath extended keys. + /// + /// For wildcard keys this will return the path up to the wildcard, so you + /// can get full paths by appending one additional derivation step, according + /// to the wildcard type (hardened or normal). + pub fn full_derivation_paths(&self) -> Vec { + match self { + DescriptorPublicKey::MultiXPub(xpub) => { + let origin_path = if let Some((_, ref path)) = xpub.origin { + path.clone() + } else { + bip32::DerivationPath::from(vec![]) + }; + xpub.derivation_paths + .paths() + .into_iter() + .map(|p| origin_path.extend(p)) + .collect() + } + _ => vec![self + .full_derivation_path() + .expect("Must be Some for non-multipath keys")], + } + } + /// Whether or not the key has a wildcard #[deprecated(note = "use has_wildcard instead")] pub fn is_deriveable(&self) -> bool { @@ -1075,6 +1102,12 @@ impl DefiniteDescriptorKey { self.0.full_derivation_path() } + /// Full paths from the master key. The vector will contain just one path for single + /// keys, and multiple ones for multipath extended keys + pub fn full_derivation_paths(&self) -> Vec { + self.0.full_derivation_paths() + } + /// Reference to the underlying `DescriptorPublicKey` pub fn as_descriptor_public_key(&self) -> &DescriptorPublicKey { &self.0 @@ -1476,6 +1509,14 @@ mod test { let desc_key = DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/<0';1>/8h/*'").unwrap(); assert!(desc_key.full_derivation_path().is_none()); assert!(desc_key.is_multipath()); + // But you can get all the derivation paths + assert_eq!( + desc_key.full_derivation_paths(), + vec![ + bip32::DerivationPath::from_str("m/0'/1'/9478'/0'/8'").unwrap(), + bip32::DerivationPath::from_str("m/0'/1'/9478'/1/8'").unwrap(), + ], + ); assert_eq!(desc_key.into_single_keys(), vec![DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/0'/8h/*'").unwrap(), DescriptorPublicKey::from_str("[abcdef00/0'/1']tpubDBrgjcxBxnXyL575sHdkpKohWu5qHKoQ7TJXKNrYznh5fVEGBv89hA8ENW7A8MFVpFUSvgLqc4Nj1WZcpePX6rrxviVtPowvMuGF5rdT2Vi/9478'/1/8h/*'").unwrap()]); // All the same but with extended private keys instead of xpubs. From fc20eb074855554667bd77d9f7184fdb1f63e401 Mon Sep 17 00:00:00 2001 From: sanket1729 Date: Wed, 28 Jun 2023 00:59:02 -0700 Subject: [PATCH 3/6] Fix test_cpp --- bitcoind-tests/tests/test_cpp.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/bitcoind-tests/tests/test_cpp.rs b/bitcoind-tests/tests/test_cpp.rs index 8e682386d..27adc0d4e 100644 --- a/bitcoind-tests/tests/test_cpp.rs +++ b/bitcoind-tests/tests/test_cpp.rs @@ -11,28 +11,24 @@ use std::path::Path; use bitcoin::hashes::{sha256d, Hash}; use bitcoin::psbt::Psbt; -use bitcoin::secp256k1::{self, Secp256k1}; -use bitcoin::{psbt, Amount, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid}; +use bitcoin::{psbt, secp256k1, Amount, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid}; use bitcoind::bitcoincore_rpc::{json, Client, RpcApi}; use miniscript::bitcoin::absolute; use miniscript::psbt::PsbtExt; -use miniscript::{bitcoin, Descriptor}; +use miniscript::{bitcoin, DefiniteDescriptorKey, Descriptor}; mod setup; use setup::test_util::{self, PubData, TestData}; // parse ~30 miniscripts from file -pub(crate) fn parse_miniscripts( - secp: &Secp256k1, - pubdata: &PubData, -) -> Vec> { +pub(crate) fn parse_miniscripts(pubdata: &PubData) -> Vec> { // File must exist in current path before this produces output let mut desc_vec = vec![]; // Consumes the iterator, returns an (Optional) String for line in read_lines("tests/data/random_ms.txt") { let ms = test_util::parse_insane_ms(&line.unwrap(), pubdata); let wsh = Descriptor::new_wsh(ms).unwrap(); - desc_vec.push(wsh.derived_descriptor(secp, 0).unwrap()); + desc_vec.push(wsh.at_derivation_index(0).unwrap()); } desc_vec } @@ -71,7 +67,7 @@ fn get_vout(cl: &Client, txid: Txid, value: u64) -> (OutPoint, TxOut) { pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { let secp = secp256k1::Secp256k1::new(); - let desc_vec = parse_miniscripts(&secp, &testdata.pubdata); + let desc_vec = parse_miniscripts(&testdata.pubdata); let sks = &testdata.secretdata.sks; let pks = &testdata.pubdata.pks; // Generate some blocks @@ -152,6 +148,7 @@ pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { input.witness_utxo = Some(witness_utxo); input.witness_script = Some(desc.explicit_script().unwrap()); psbt.inputs.push(input); + psbt.update_input_with_descriptor(0, &desc).unwrap(); psbt.outputs.push(psbt::Output::default()); psbts.push(psbt); } @@ -160,7 +157,8 @@ pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { // Sign the transactions with all keys // AKA the signer role of psbt for i in 0..psbts.len() { - let ms = if let Descriptor::Wsh(wsh) = &desc_vec[i] { + let wsh_derived = desc_vec[i].derived_descriptor(&secp).unwrap(); + let ms = if let Descriptor::Wsh(wsh) = &wsh_derived { match wsh.as_inner() { miniscript::descriptor::WshInner::Ms(ms) => ms, _ => unreachable!(), From d29c2984e60e41e3872ac23b8b59e8b25fea88a2 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Tue, 25 Oct 2022 12:24:52 +0200 Subject: [PATCH 4/6] 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) +