From ebcc7e4d3408220ed165d635d8b60ed498837569 Mon Sep 17 00:00:00 2001 From: Harshil Jani Date: Mon, 25 Sep 2023 18:15:34 +0530 Subject: [PATCH 1/6] Adding `all_assets` method for miniscript Using `all_assets` method, you can get all possible asset for a given node of Miniscript AST. Eg : multi(2,A,B,C) will give possible combinations as AB, AC, BC. This API will allow the developers to figure out all the possible spend paths for a given descriptor. Signed-off-by: Harshil Jani --- src/miniscript/astelem.rs | 211 +++++++++++++++++++++++++++++++++++++- src/miniscript/mod.rs | 146 +++++++++++++++++++++++++- src/plan.rs | 2 +- 3 files changed, 355 insertions(+), 4 deletions(-) diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index b272830e1..db6a3918a 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -17,10 +17,12 @@ use sync::Arc; use crate::miniscript::context::SigType; use crate::miniscript::types::{self, Property}; use crate::miniscript::ScriptContext; +use crate::plan::Assets; use crate::prelude::*; use crate::util::MsKeyBuilder; use crate::{ - errstr, expression, AbsLockTime, Error, Miniscript, MiniscriptKey, Terminal, ToPublicKey, + errstr, expression, AbsLockTime, DescriptorPublicKey, Error, Miniscript, MiniscriptKey, + Terminal, ToPublicKey, }; impl Terminal { @@ -593,3 +595,210 @@ impl Terminal { } } } + +impl Terminal { + /// Retrieve the assets associated with the type of miniscript element. + pub fn all_assets(&self) -> Vec { + match self { + Terminal::True => vec![Assets::new()], + Terminal::False => Vec::new(), + Terminal::PkK(k) => { + let mut asset = Assets::new(); + asset = asset.add(k.clone()); + vec![asset] + } + Terminal::PkH(k) => { + let mut asset = Assets::new(); + asset = asset.add(k.clone()); + vec![asset] + } + Terminal::RawPkH(k) => { + let mut asset = Assets::new(); + asset = asset.add(k.clone()); + vec![asset] + } + Terminal::After(k) => { + let mut asset = Assets::new(); + asset.absolute_timelock = Some(k.clone().into()); + vec![asset] + } + Terminal::Older(k) => { + let mut asset = Assets::new(); + asset.relative_timelock = Some(k.clone()); + vec![asset] + } + Terminal::Sha256(k) => { + let mut asset = Assets::new(); + asset = asset.add(k.clone()); + vec![asset] + } + Terminal::Hash256(k) => { + let mut asset = Assets::new(); + asset = asset.add(k.clone()); + vec![asset] + } + Terminal::Ripemd160(k) => { + let mut asset = Assets::new(); + asset = asset.add(k.clone()); + vec![asset] + } + Terminal::Hash160(k) => { + let mut asset = Assets::new(); + asset = asset.add(k.clone()); + vec![asset] + } + Terminal::Alt(k) => k.all_assets(), + Terminal::Swap(k) => k.all_assets(), + Terminal::Check(k) => k.all_assets(), + Terminal::DupIf(k) => k.all_assets(), + Terminal::Verify(k) => k.all_assets(), + Terminal::NonZero(k) => k.all_assets(), + Terminal::ZeroNotEqual(k) => k.all_assets(), + Terminal::AndB(left, right) | Terminal::AndV(left, right) => { + let a = left.all_assets(); + let b = right.all_assets(); + let result: Vec = a + .into_iter() + .flat_map(|x| { + b.clone().into_iter().map(move |y| { + let mut new_asset = Assets::new(); + new_asset = new_asset.add(x.clone()); + new_asset = new_asset.add(y.clone()); + new_asset + }) + }) + .collect(); + result + } + Terminal::AndOr(a, b, c) => { + let a = a.all_assets(); + let b = b.all_assets(); + let mut c = c.all_assets(); + let and: Vec = a + .into_iter() + .flat_map(|x| { + b.clone().into_iter().map(move |y| { + let mut new_asset = Assets::new(); + new_asset = new_asset.add(x.clone()); + new_asset = new_asset.add(y.clone()); + new_asset + }) + }) + .collect(); + c.extend(and); + c + } + Terminal::OrB(left, right) + | Terminal::OrC(left, right) + | Terminal::OrD(left, right) + | Terminal::OrI(left, right) => { + let mut a = left.all_assets(); + let b = right.all_assets(); + a.extend(b); + a + } + Terminal::Thresh(k, ms) => { + // In order to understand working of below code consider k of n as 2 of 3 thresh policy + // Eg : thresh(2,ms(A),ms(B),ms(C)) Here ms(A),ms(B) and ms(C) are miniscript policies + // k = 2 + // ms = [ms(A),ms(B),ms(C)]; + // We would consider the possible combinations of k policies into the ms_v + // here k=2 so all possible combinations of 2. + // ms_v = [[ms(A),ms(B)],[ms(A),ms(C)],[ms(B),ms(C)]] + // Between each set of combination we would need to do an OR + // (i.e ms_v[0] OR ms_v[1] OR ms_v[3]) + // Now inside of each policy combination we need to have AND + // Eg : ms_v[0] = [ms(A),ms(B)] so here -> ms(A) AND ms(B) + + let ms_v = Self::get_ms_combination_thresh(*k, ms); + let mut result = Vec::new(); + for ms in ms_v { + // AND between each miniscript policy + let mut and: Vec = Vec::new(); + if let Some(first_assets) = ms.first() { + and = first_assets.all_assets().clone(); + } + for i in ms.iter().skip(1) { + let i_assets = i.all_assets(); + and = and + .iter() + .flat_map(|x| { + i_assets.iter().map(move |y| { + let mut new_asset = x.clone(); + new_asset = new_asset.add(y.clone()); + new_asset + }) + }) + .collect(); + } + // OR of possible combinations of k miniscript policies. + result.extend(and.clone()); + } + result + } + Terminal::Multi(k, dpk_v) | Terminal::MultiA(k, dpk_v) => { + Self::get_asset_combination(*k, dpk_v) + } + } + } + + // Helper to get all possible pairs of K of N assets + fn get_asset_combination(k: usize, dpk_v: &Vec) -> Vec { + let mut all_assets: Vec = Vec::new(); + let current_assets = Assets::new(); + Self::combine_assets(k, dpk_v, 0, current_assets, &mut all_assets); + all_assets + } + + // Combine K of N assets + fn combine_assets( + k: usize, + dpk_v: &[DescriptorPublicKey], + index: usize, + current_assets: Assets, + all_assets: &mut Vec, + ) { + if k == 0 { + all_assets.push(current_assets); + return; + } + if index >= dpk_v.len() { + return; + } + Self::combine_assets(k, dpk_v, index + 1, current_assets.clone(), all_assets); + let mut new_asset = current_assets; + new_asset = new_asset.add(dpk_v[index].clone()); + println!("{:#?}", new_asset); + Self::combine_assets(k - 1, dpk_v, index + 1, new_asset, all_assets) + } + + // Helper to get all combinations of K policies of N for thresh + fn get_ms_combination_thresh( + k: usize, + ms: &Vec>>, + ) -> Vec>>> { + let mut result = Vec::new(); + let mut current_combination = Vec::new(); + Self::combine_ms(0, &mut current_combination, &mut result, ms, k); + result + } + + // combine K policies of N for thresh + fn combine_ms( + start: usize, + current_combination: &mut Vec>>, + result: &mut Vec>>>, + ms: &Vec>>, + k: usize, + ) { + if current_combination.len() == k { + result.push(current_combination.clone()); + return; + } + for i in start..ms.len() { + current_combination.push(ms[i].clone()); + Self::combine_ms(i + 1, current_combination, result, ms, k); + current_combination.truncate(current_combination.len() - 1); + } + } +} diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index fa75a712c..f0fc90bdd 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -22,8 +22,9 @@ use bitcoin::taproot::{LeafVersion, TapLeafHash}; use self::analyzable::ExtParams; pub use self::context::{BareCtx, Legacy, Segwitv0, Tap}; use crate::iter::TreeLike; +use crate::plan::Assets; use crate::prelude::*; -use crate::{script_num_size, TranslateErr}; +use crate::{script_num_size, DescriptorPublicKey, TranslateErr}; pub mod analyzable; pub mod astelem; @@ -385,6 +386,11 @@ impl fmt::Display for Miniscript fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.node) } } +impl Miniscript { + /// Get all possible asset for a given node of Miniscript AST + pub fn all_assets(&self) -> Vec { self.node.all_assets() } +} + impl ForEachKey for Miniscript { fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool { for ms in self.pre_order_iter() { @@ -610,10 +616,11 @@ mod tests { use super::{Miniscript, ScriptContext, Segwitv0, Tap}; use crate::miniscript::types::{self, ExtData, Property, Type}; use crate::miniscript::Terminal; + use crate::plan::Assets; use crate::policy::Liftable; use crate::prelude::*; use crate::test_utils::{StrKeyTranslator, StrXOnlyKeyTranslator}; - use crate::{hex_script, ExtParams, Satisfier, ToPublicKey, TranslatePk}; + use crate::{hex_script, DescriptorPublicKey, ExtParams, Satisfier, ToPublicKey, TranslatePk}; type Segwitv0Script = Miniscript; type Tapscript = Miniscript; @@ -1351,4 +1358,139 @@ mod tests { assert_eq!(template.relative_timelock, relative_timelock, "{}", ms_str); } } + + #[test] + fn test_all_assets_and() { + let keys = vec![ + "02638737cb676ca8851ac3e2c155e16cf9186d8d576e5670d76d49f8840113d078", + "02b33eeea5cd309376cf82914dce386f26459a07354add732069b90abd907674cb", + "02722c78fed469dd77df4a2c92c5bf4ddfa583fad30a1b7993488530d2d097393c", + ]; + + let ms = Miniscript::::from_str(&format!( + "and_v(v:pk({}),pk({}))", + keys[0], keys[1] + )) + .unwrap(); + + // Getting the assets from the all_assets method + let assets = ms.all_assets(); + + let mut expected_asset = Assets::new(); + expected_asset = expected_asset.add(DescriptorPublicKey::from_str(keys[0]).unwrap()); // A + expected_asset = expected_asset.add(DescriptorPublicKey::from_str(keys[1]).unwrap()); // B + + assert_eq!(assets, vec![expected_asset]); + } + + #[test] + fn test_all_assets_multi() { + let keys = vec![ + "02638737cb676ca8851ac3e2c155e16cf9186d8d576e5670d76d49f8840113d078", + "02b33eeea5cd309376cf82914dce386f26459a07354add732069b90abd907674cb", + "02722c78fed469dd77df4a2c92c5bf4ddfa583fad30a1b7993488530d2d097393c", + ]; + + let ms = Miniscript::::from_str(&format!( + "multi(2,{},{},{})", + keys[0], keys[1], keys[2] + )) + .unwrap(); + + // Getting the assets from the all_assets method + let assets = ms.all_assets(); + + let mut expected_assets: Vec = Vec::new(); + + let mut assets_1 = Assets::new(); + assets_1 = assets_1.add(DescriptorPublicKey::from_str(keys[0]).unwrap()); // A + assets_1 = assets_1.add(DescriptorPublicKey::from_str(keys[1]).unwrap()); // B + expected_assets.push(assets_1); + + let mut assets_2 = Assets::new(); + assets_2 = assets_2.add(DescriptorPublicKey::from_str(keys[0]).unwrap()); // A + assets_2 = assets_2.add(DescriptorPublicKey::from_str(keys[2]).unwrap()); // C + expected_assets.push(assets_2); + + let mut assets_3 = Assets::new(); + assets_3 = assets_3.add(DescriptorPublicKey::from_str(keys[1]).unwrap()); // B + assets_3 = assets_3.add(DescriptorPublicKey::from_str(keys[2]).unwrap()); // C + expected_assets.push(assets_3); + + for expected_asset in &expected_assets { + assert!(assets.contains(expected_asset)); + } + } + + #[test] + fn test_all_assets_or() { + let keys = vec![ + "02638737cb676ca8851ac3e2c155e16cf9186d8d576e5670d76d49f8840113d078", + "02b33eeea5cd309376cf82914dce386f26459a07354add732069b90abd907674cb", + ]; + + let ms = Miniscript::::from_str(&format!( + "or_b(pk({}),s:pk({}))", + keys[0], keys[1] + )) + .unwrap(); + + // Getting the assets from the all_assets method + let assets = ms.all_assets(); + + let mut expected_assets: Vec = Vec::new(); + + let mut asset1 = Assets::new(); + asset1 = asset1.add(DescriptorPublicKey::from_str(keys[0]).unwrap()); // A + expected_assets.push(asset1); + + let mut asset2 = Assets::new(); + asset2 = asset2.add(DescriptorPublicKey::from_str(keys[1]).unwrap()); // B + expected_assets.push(asset2); + + // Check that all received assets are as expected. + for expected_asset in &expected_assets { + assert!(assets.contains(expected_asset)); + } + } + + #[test] + fn test_all_assets_thresh() { + let keys = vec![ + "02638737cb676ca8851ac3e2c155e16cf9186d8d576e5670d76d49f8840113d078", + "02b33eeea5cd309376cf82914dce386f26459a07354add732069b90abd907674cb", + "02722c78fed469dd77df4a2c92c5bf4ddfa583fad30a1b7993488530d2d097393c", + ]; + + let ms = Miniscript::::from_str(&format!( + "thresh(2,pk({}),a:pk({}),a:pk({}))", + keys[0], keys[1], keys[2] + )) + .unwrap(); + + // Getting the assets from the all_assets method + let assets = ms.all_assets(); + + let mut expected_assets: Vec = Vec::new(); + + let mut assets_1 = Assets::new(); + assets_1 = assets_1.add(DescriptorPublicKey::from_str(keys[0]).unwrap()); // A + assets_1 = assets_1.add(DescriptorPublicKey::from_str(keys[1]).unwrap()); // B + expected_assets.push(assets_1); + + let mut assets_2 = Assets::new(); + assets_2 = assets_2.add(DescriptorPublicKey::from_str(keys[0]).unwrap()); // A + assets_2 = assets_2.add(DescriptorPublicKey::from_str(keys[2]).unwrap()); // C + expected_assets.push(assets_2); + + let mut assets_3 = Assets::new(); + assets_3 = assets_3.add(DescriptorPublicKey::from_str(keys[1]).unwrap()); // B + assets_3 = assets_3.add(DescriptorPublicKey::from_str(keys[2]).unwrap()); // C + expected_assets.push(assets_3); + + // Check that all received assets are as expected. + for expected_asset in &expected_assets { + assert!(assets.contains(expected_asset)); + } + } } diff --git a/src/plan.rs b/src/plan.rs index 32e9aa75d..a2cfe1df9 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -504,7 +504,7 @@ impl TaprootAvailableLeaves { } /// The Assets we can use to satisfy a particular spending path -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone, PartialEq)] pub struct Assets { /// Keys the user can sign for, and how. A pair `(fingerprint, derivation_path)` is /// provided, meaning that the user can sign using the key with `fingerprint`, From 200433c98444e69a774eae96d0cd7cbf14cb2583 Mon Sep 17 00:00:00 2001 From: Harshil Jani Date: Mon, 25 Sep 2023 18:25:20 +0530 Subject: [PATCH 2/6] Wrap `all_assets` for desc and add `count_assets` method This commit wraps the all_assets function for all possible descriptor types. It also adds `count_assets` function which can count the total possible ways to obtain an asset for a given descriptor. This API will help developer to count the possible ways. In-fact this will also help in protecting that if some descriptor is very large and has huge number of possible spend paths then it could simply be dropped off and we can protect the network from traffic. Signed-off-by: Harshil Jani --- src/descriptor/mod.rs | 192 +++++++++++++++++++++++++++++++++++++- src/descriptor/tr.rs | 33 ++++++- src/lib.rs | 4 + src/miniscript/astelem.rs | 155 +++++++++++++++++++++++------- src/miniscript/mod.rs | 3 + src/util.rs | 33 ++++++- 6 files changed, 383 insertions(+), 37 deletions(-) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 616bb37cc..dbafd5a03 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -23,8 +23,9 @@ use sync::Arc; use self::checksum::verify_checksum; use crate::miniscript::decode::Terminal; use crate::miniscript::{satisfy, Legacy, Miniscript, Segwitv0}; -use crate::plan::{AssetProvider, Plan}; +use crate::plan::{AssetProvider, Assets, Plan}; use crate::prelude::*; +use crate::util::get_asset_combination; use crate::{ expression, hash256, BareCtx, Error, ForEachKey, MiniscriptKey, Satisfier, ToPublicKey, TranslateErr, TranslatePk, Translator, @@ -546,6 +547,124 @@ impl Descriptor { } } +impl Descriptor { + /// Count total possible assets for a given descriptor. + pub fn count_assets(&self) -> u64 { + match self { + Descriptor::Bare(k) => k.as_inner().count_assets(), + Descriptor::Pkh(_) => 1, + Descriptor::Wpkh(_) => 1, + Descriptor::Sh(k) => match k.as_inner() { + ShInner::Wsh(k) => match k.as_inner() { + WshInner::SortedMulti(k) => { + let n = k.pks.len() as u64; + let k = k.k as u64; + Self::k_of_n(k, n) + } + WshInner::Ms(k) => k.count_assets(), + }, + ShInner::Wpkh(_) => 1, + ShInner::SortedMulti(k) => { + let n = k.clone().pks.len() as u64; + let k = k.clone().k as u64; + Self::k_of_n(k, n) + } + ShInner::Ms(k) => k.count_assets(), + }, + Descriptor::Wsh(k) => match k.as_inner() { + WshInner::SortedMulti(k) => { + let n = k.clone().pks.len() as u64; + let k = k.clone().k as u64; + Self::k_of_n(k, n) + } + WshInner::Ms(k) => k.count_assets(), + }, + Descriptor::Tr(k) => { + let s = k.tap_tree().clone().unwrap(); + match s { + TapTree::Tree { left, right, height: _ } => { + let a = left.count_assets(); + let b = right.count_assets(); + a + b + } + TapTree::Leaf(k) => k.count_assets(), + } + } + } + } + + /// Get all possible assets for a given descriptor + pub fn all_assets(&self) -> Result, Error> { + match self { + Descriptor::Bare(k) => Ok(k.as_inner().all_assets()), + Descriptor::Pkh(k) => { + let mut asset = Assets::new(); + asset = asset.add(k.as_inner().clone()); + Ok(vec![asset]) + } + Descriptor::Wpkh(k) => { + let mut asset = Assets::new(); + asset = asset.add(k.as_inner().clone()); + Ok(vec![asset]) + } + Descriptor::Sh(k) => match k.as_inner() { + ShInner::Wsh(k) => match k.as_inner() { + WshInner::SortedMulti(k) => { + let dpk_v = k.clone().pks; + let k = k.clone().k; + Ok(get_asset_combination(k, &dpk_v)) + } + WshInner::Ms(k) => Ok(k.all_assets()), + }, + ShInner::Wpkh(k) => { + let mut asset = Assets::new(); + asset = asset.add(k.as_inner().clone()); + Ok(vec![asset]) + } + ShInner::SortedMulti(k) => { + let dpk_v = k.clone().pks; + let k = k.clone().k; + Ok(get_asset_combination(k, &dpk_v)) + } + ShInner::Ms(k) => Ok(k.all_assets()), + }, + Descriptor::Wsh(k) => match k.as_inner() { + WshInner::SortedMulti(k) => { + let dpk_v = k.clone().pks; + let k = k.clone().k; + Ok(get_asset_combination(k, &dpk_v)) + } + WshInner::Ms(k) => { + println!("{}", k); + let a = k.all_assets(); + println!("{:#?}", a); + Ok(a) + } + }, + Descriptor::Tr(k) => { + let s = k.tap_tree().clone().unwrap(); + match s { + TapTree::Tree { left, right, height: _ } => { + let mut a = left.all_assets(); + let b = right.all_assets(); + a.extend(b); + Ok(a) + } + TapTree::Leaf(k) => Ok(k.all_assets()), + } + } + } + } + + // ways to select k things out of n + fn k_of_n(k: u64, n: u64) -> u64 { + if k == 0 || k == n { + return 1; + } + Self::k_of_n(k - 1, n - 1) + Self::k_of_n(k - 1, n) + } +} + impl TranslatePk for Descriptor

where P: MiniscriptKey, @@ -2041,4 +2160,75 @@ pk(03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8))"; Desc::from_str(&format!("tr({},pk({}))", x_only_key, uncomp_key)).unwrap_err(); Desc::from_str(&format!("tr({},pk({}))", x_only_key, x_only_key)).unwrap(); } + + #[test] + fn test_all_assets_bare() { + let descriptor = Descriptor::::from_str( + "pk(0237b1c59ab055a8d3de40eaeb215c7b1922664b5ac049d849fb3346f81431e77f)", + ) + .unwrap(); + + // Getting the assets from the all_assets method + let assets = descriptor.all_assets().unwrap(); + + let mut expected_asset = Assets::new(); + expected_asset = expected_asset.add( + DescriptorPublicKey::from_str( + "0237b1c59ab055a8d3de40eaeb215c7b1922664b5ac049d849fb3346f81431e77f", + ) + .unwrap(), + ); + assert_eq!(assets, vec![expected_asset]); + } + + #[test] + fn test_all_assets_sh_sortedmulti() { + let keys = vec![ + "0360eabc52e04f70c89e944f379895cdff28fed60849afe7736815c091765afb3c", + "03a80a24196e66ccf6bca6e6f96633bb629ec702acd76b074de10922b0cf41cc81", + ]; + + let descriptor = Descriptor::::from_str(&format!( + "sh(sortedmulti(1,{},{}))", + keys[0], keys[1] + )) + .unwrap(); + + let assets = descriptor.all_assets().unwrap(); + + let mut expected_assets: Vec = Vec::new(); + + let mut asset1 = Assets::new(); + asset1 = asset1.add(DescriptorPublicKey::from_str(keys[0]).unwrap()); + expected_assets.push(asset1); + + let mut asset2 = Assets::new(); + asset2 = asset2.add(DescriptorPublicKey::from_str(keys[1]).unwrap()); + expected_assets.push(asset2); + + for expected_asset in &expected_assets { + assert!(assets.contains(expected_asset)); + } + } + + #[test] + fn test_all_assets_taproot() { + let x_only_key = bitcoin::key::XOnlyPublicKey::from_str( + "015e4cb53458bf813db8c79968e76e10d13ed6426a23fa71c2f41ba021c2a7ab", + ) + .unwrap(); + let descriptor = + Descriptor::from_str(&format!("tr({},pk({}))", x_only_key, x_only_key)).unwrap(); + let assets = descriptor.all_assets().unwrap(); + let mut expected_assets: Vec = Vec::new(); + let mut asset_1 = Assets::new(); + asset_1 = asset_1.add( + DescriptorPublicKey::from_str( + "015e4cb53458bf813db8c79968e76e10d13ed6426a23fa71c2f41ba021c2a7ab", + ) + .unwrap(), + ); + expected_assets.push(asset_1); + assert_eq!(assets, expected_assets); + } } diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 48deb63e0..66622c821 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -15,14 +15,14 @@ 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::plan::{AssetProvider, Assets}; use crate::policy::semantic::Policy; use crate::policy::Liftable; use crate::prelude::*; use crate::util::{varint_len, witness_size}; use crate::{ - errstr, Error, ForEachKey, MiniscriptKey, Satisfier, ScriptContext, Tap, ToPublicKey, - TranslateErr, TranslatePk, Translator, + errstr, DescriptorPublicKey, Error, ForEachKey, MiniscriptKey, Satisfier, ScriptContext, Tap, + ToPublicKey, TranslateErr, TranslatePk, Translator, }; /// A Taproot Tree representation. @@ -153,6 +153,33 @@ impl TapTree { } } +impl TapTree { + /// Get all possible assets for Taptree + pub fn all_assets(&self) -> Vec { + match self { + TapTree::Tree { left, right, height: _ } => { + let mut a = left.all_assets(); + let b = right.all_assets(); + a.extend(b); + a + } + TapTree::Leaf(k) => k.all_assets(), + } + } + + /// Get total possible assets for TapTree + pub fn count_assets(&self) -> u64 { + match self { + TapTree::Tree { left, right, height: _ } => { + let a = left.count_assets(); + let b = right.count_assets(); + a + b + } + TapTree::Leaf(k) => k.count_assets(), + } + } +} + impl fmt::Display for TapTree { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/src/lib.rs b/src/lib.rs index 7b798915e..f74922ae3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -501,6 +501,8 @@ pub enum Error { /// At least two BIP389 key expressions in the descriptor contain tuples of /// derivation indexes of different lengths. MultipathDescLenMismatch, + /// Cannot get assets for this large descriptor. Exceeds 1000 assets. + MaxAssetThresholdExceeded, } // https://github.com/sipa/miniscript/pull/5 for discussion on this number @@ -577,6 +579,7 @@ impl fmt::Display for Error { Error::TrNoScriptCode => write!(f, "No script code for Tr descriptors"), Error::TrNoExplicitScript => write!(f, "No script code for Tr descriptors"), Error::MultipathDescLenMismatch => write!(f, "At least two BIP389 key expressions in the descriptor contain tuples of derivation indexes of different lengths"), + Error::MaxAssetThresholdExceeded => write!(f,"Cannot plan descriptors having more than 1000 possible spend paths."), } } } @@ -619,6 +622,7 @@ impl error::Error for Error { | TrNoScriptCode | TrNoExplicitScript | MultipathDescLenMismatch => None, + MaxAssetThresholdExceeded => None, Script(e) => Some(e), AddrError(e) => Some(e), BadPubkey(e) => Some(e), diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index db6a3918a..5b4483063 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -19,7 +19,7 @@ use crate::miniscript::types::{self, Property}; use crate::miniscript::ScriptContext; use crate::plan::Assets; use crate::prelude::*; -use crate::util::MsKeyBuilder; +use crate::util::{get_asset_combination, MsKeyBuilder}; use crate::{ errstr, expression, AbsLockTime, DescriptorPublicKey, Error, Miniscript, MiniscriptKey, Terminal, ToPublicKey, @@ -597,6 +597,93 @@ impl Terminal { } impl Terminal { + /// Count total possible assets + pub fn count_assets(&self) -> u64 { + match self { + Terminal::True => 0, + Terminal::False => 0, + Terminal::PkK(_) => 1, + Terminal::PkH(_) => 1, + Terminal::RawPkH(_) => 1, + // What happens to timelocks ? for both the assets and the count. + Terminal::After(_) => 0, + Terminal::Older(_) => 0, + Terminal::Sha256(_) => 1, + Terminal::Hash256(_) => 1, + Terminal::Ripemd160(_) => 1, + Terminal::Hash160(_) => 1, + Terminal::Alt(k) => k.count_assets(), + Terminal::Swap(k) => k.count_assets(), + Terminal::Check(k) => k.count_assets(), + Terminal::DupIf(k) => k.count_assets(), + Terminal::Verify(k) => k.count_assets(), + Terminal::NonZero(k) => k.count_assets(), + Terminal::ZeroNotEqual(k) => k.count_assets(), + Terminal::AndV(left, right) => { + let left_count = left.count_assets(); + let right_count = right.count_assets(); + left_count * right_count + } + Terminal::AndB(left, right) => { + let left_count = left.count_assets(); + let right_count = right.count_assets(); + left_count * right_count + } + Terminal::AndOr(a, b, c) => { + let a = a.count_assets(); + let b = b.count_assets(); + let c = c.count_assets(); + (a * b) + c + } + Terminal::OrB(left, right) => { + let left_count = left.count_assets(); + let right_count = right.count_assets(); + left_count + right_count + } + Terminal::OrD(left, right) => { + let left_count = left.count_assets(); + let right_count = right.count_assets(); + left_count + right_count + } + Terminal::OrC(left, right) => { + let left_count = left.count_assets(); + let right_count = right.count_assets(); + left_count + right_count + } + Terminal::OrI(left, right) => { + let left_count = left.count_assets(); + let right_count = right.count_assets(); + left_count + right_count + } + Terminal::Thresh(k, ms_v) => { + // k = 2, n = ms_v.len() + // ms_v = [ms(A),ms(B),ms(C)]; + // Assume count array as [5,7,8] and k=2 + // get_combinations_product gives [5*7,5*8,7*8] = [35,40,56] + let mut count_array = Vec::new(); + for ms in ms_v { + count_array.push(ms.count_assets()); + } + let products = Self::get_combinations_product(&count_array, *k as u64); + let mut total_count: u64 = 0; + for product in products { + total_count += product; + } + total_count + } + Terminal::Multi(k, dpk) => { + let k: u64 = *k as u64; + let n: u64 = dpk.len() as u64; + Self::k_of_n(k, n) + } + Terminal::MultiA(k, dpk) => { + let k: u64 = *k as u64; + let n: u64 = dpk.len() as u64; + Self::k_of_n(k, n) + } + } + } + /// Retrieve the assets associated with the type of miniscript element. pub fn all_assets(&self) -> Vec { match self { @@ -737,41 +824,11 @@ impl Terminal { result } Terminal::Multi(k, dpk_v) | Terminal::MultiA(k, dpk_v) => { - Self::get_asset_combination(*k, dpk_v) + get_asset_combination(*k, dpk_v) } } } - // Helper to get all possible pairs of K of N assets - fn get_asset_combination(k: usize, dpk_v: &Vec) -> Vec { - let mut all_assets: Vec = Vec::new(); - let current_assets = Assets::new(); - Self::combine_assets(k, dpk_v, 0, current_assets, &mut all_assets); - all_assets - } - - // Combine K of N assets - fn combine_assets( - k: usize, - dpk_v: &[DescriptorPublicKey], - index: usize, - current_assets: Assets, - all_assets: &mut Vec, - ) { - if k == 0 { - all_assets.push(current_assets); - return; - } - if index >= dpk_v.len() { - return; - } - Self::combine_assets(k, dpk_v, index + 1, current_assets.clone(), all_assets); - let mut new_asset = current_assets; - new_asset = new_asset.add(dpk_v[index].clone()); - println!("{:#?}", new_asset); - Self::combine_assets(k - 1, dpk_v, index + 1, new_asset, all_assets) - } - // Helper to get all combinations of K policies of N for thresh fn get_ms_combination_thresh( k: usize, @@ -801,4 +858,38 @@ impl Terminal { current_combination.truncate(current_combination.len() - 1); } } + + // Do product of K combinations + fn get_combinations_product(values: &[u64], k: u64) -> Vec { + let mut products = Vec::new(); + let n = values.len(); + + if k == 0 { + return vec![1]; // Empty combination has a product of 1 + } + + // Using bitwise operations to generate combinations + let max_combinations = 1u32 << n; + for combination_bits in 1..max_combinations { + if combination_bits.count_ones() as usize == k as usize { + let mut product = 1; + for i in 0..n { + if combination_bits & (1u32 << i) != 0 { + product *= values[i]; + } + } + products.push(product); + } + } + + products + } + + // ways to select k things out of n + fn k_of_n(k: u64, n: u64) -> u64 { + if k == 0 || k == n { + return 1; + } + Self::k_of_n(k - 1, n - 1) + Self::k_of_n(k, n - 1) + } } diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index f0fc90bdd..815fd4501 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -389,6 +389,9 @@ impl fmt::Display for Miniscript impl Miniscript { /// Get all possible asset for a given node of Miniscript AST pub fn all_assets(&self) -> Vec { self.node.all_assets() } + + /// Get the total number of assets possible + pub fn count_assets(&self) -> u64 { self.node.count_assets() } } impl ForEachKey for Miniscript { diff --git a/src/util.rs b/src/util.rs index 11643b643..bb6107170 100644 --- a/src/util.rs +++ b/src/util.rs @@ -8,8 +8,9 @@ use bitcoin::PubkeyHash; use crate::miniscript::context; use crate::miniscript::satisfy::Placeholder; +use crate::plan::Assets; use crate::prelude::*; -use crate::{MiniscriptKey, ScriptContext, ToPublicKey}; +use crate::{DescriptorPublicKey, MiniscriptKey, ScriptContext, ToPublicKey}; pub(crate) fn varint_len(n: usize) -> usize { bitcoin::VarInt(n as u64).len() } pub(crate) trait ItemSize { @@ -101,3 +102,33 @@ impl MsKeyBuilder for script::Builder { } } } + +// Helper to get all possible pairs of K of N assets +pub fn get_asset_combination(k: usize, dpk_v: &Vec) -> Vec { + let mut all_assets: Vec = Vec::new(); + let current_assets = Assets::new(); + combine_assets(k, dpk_v, 0, current_assets, &mut all_assets); + all_assets +} + +// Combine K of N assets +pub fn combine_assets( + k: usize, + dpk_v: &[DescriptorPublicKey], + index: usize, + current_assets: Assets, + all_assets: &mut Vec, +) { + if k == 0 { + all_assets.push(current_assets); + return; + } + if index >= dpk_v.len() { + return; + } + combine_assets(k, dpk_v, index + 1, current_assets.clone(), all_assets); + let mut new_asset = current_assets; + new_asset = new_asset.add(dpk_v[index].clone()); + println!("{:#?}", new_asset); + combine_assets(k - 1, dpk_v, index + 1, new_asset, all_assets) +} From 50a65caf6e4697888194dde583fac7cecbc2b727 Mon Sep 17 00:00:00 2001 From: Harshil Jani Date: Mon, 25 Sep 2023 18:27:41 +0530 Subject: [PATCH 3/6] Consolidated repetitive functions into `util.rs` There were helper functions to compute nCk (k combinations of n given things) and product of K different combinations. Those were duplicated in mod.rs of descriptor and astelem.rs of miniscript. This commit de-duplicates them and store them in util.rs Signed-off-by: Harshil Jani --- src/descriptor/mod.rs | 22 ++++------- src/miniscript/astelem.rs | 83 +++++---------------------------------- src/util.rs | 36 ++++++++++++++++- 3 files changed, 52 insertions(+), 89 deletions(-) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index dbafd5a03..2ccc63dce 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -25,7 +25,7 @@ use crate::miniscript::decode::Terminal; use crate::miniscript::{satisfy, Legacy, Miniscript, Segwitv0}; use crate::plan::{AssetProvider, Assets, Plan}; use crate::prelude::*; -use crate::util::get_asset_combination; +use crate::util::{asset_combination, k_of_n}; use crate::{ expression, hash256, BareCtx, Error, ForEachKey, MiniscriptKey, Satisfier, ToPublicKey, TranslateErr, TranslatePk, Translator, @@ -559,7 +559,7 @@ impl Descriptor { WshInner::SortedMulti(k) => { let n = k.pks.len() as u64; let k = k.k as u64; - Self::k_of_n(k, n) + k_of_n(k, n) } WshInner::Ms(k) => k.count_assets(), }, @@ -567,7 +567,7 @@ impl Descriptor { ShInner::SortedMulti(k) => { let n = k.clone().pks.len() as u64; let k = k.clone().k as u64; - Self::k_of_n(k, n) + k_of_n(k, n) } ShInner::Ms(k) => k.count_assets(), }, @@ -575,7 +575,7 @@ impl Descriptor { WshInner::SortedMulti(k) => { let n = k.clone().pks.len() as u64; let k = k.clone().k as u64; - Self::k_of_n(k, n) + k_of_n(k, n) } WshInner::Ms(k) => k.count_assets(), }, @@ -612,7 +612,7 @@ impl Descriptor { WshInner::SortedMulti(k) => { let dpk_v = k.clone().pks; let k = k.clone().k; - Ok(get_asset_combination(k, &dpk_v)) + Ok(asset_combination(k, &dpk_v)) } WshInner::Ms(k) => Ok(k.all_assets()), }, @@ -624,7 +624,7 @@ impl Descriptor { ShInner::SortedMulti(k) => { let dpk_v = k.clone().pks; let k = k.clone().k; - Ok(get_asset_combination(k, &dpk_v)) + Ok(asset_combination(k, &dpk_v)) } ShInner::Ms(k) => Ok(k.all_assets()), }, @@ -632,7 +632,7 @@ impl Descriptor { WshInner::SortedMulti(k) => { let dpk_v = k.clone().pks; let k = k.clone().k; - Ok(get_asset_combination(k, &dpk_v)) + Ok(asset_combination(k, &dpk_v)) } WshInner::Ms(k) => { println!("{}", k); @@ -655,14 +655,6 @@ impl Descriptor { } } } - - // ways to select k things out of n - fn k_of_n(k: u64, n: u64) -> u64 { - if k == 0 || k == n { - return 1; - } - Self::k_of_n(k - 1, n - 1) + Self::k_of_n(k - 1, n) - } } impl TranslatePk for Descriptor

diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index 5b4483063..7d1d23840 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -19,7 +19,7 @@ use crate::miniscript::types::{self, Property}; use crate::miniscript::ScriptContext; use crate::plan::Assets; use crate::prelude::*; -use crate::util::{get_asset_combination, MsKeyBuilder}; +use crate::util::{asset_combination, get_combinations_product, k_of_n, MsKeyBuilder}; use crate::{ errstr, expression, AbsLockTime, DescriptorPublicKey, Error, Miniscript, MiniscriptKey, Terminal, ToPublicKey, @@ -605,7 +605,6 @@ impl Terminal { Terminal::PkK(_) => 1, Terminal::PkH(_) => 1, Terminal::RawPkH(_) => 1, - // What happens to timelocks ? for both the assets and the count. Terminal::After(_) => 0, Terminal::Older(_) => 0, Terminal::Sha256(_) => 1, @@ -619,12 +618,7 @@ impl Terminal { Terminal::Verify(k) => k.count_assets(), Terminal::NonZero(k) => k.count_assets(), Terminal::ZeroNotEqual(k) => k.count_assets(), - Terminal::AndV(left, right) => { - let left_count = left.count_assets(); - let right_count = right.count_assets(); - left_count * right_count - } - Terminal::AndB(left, right) => { + Terminal::AndV(left, right) | Terminal::AndB(left, right) => { let left_count = left.count_assets(); let right_count = right.count_assets(); left_count * right_count @@ -635,51 +629,30 @@ impl Terminal { let c = c.count_assets(); (a * b) + c } - Terminal::OrB(left, right) => { - let left_count = left.count_assets(); - let right_count = right.count_assets(); - left_count + right_count - } - Terminal::OrD(left, right) => { - let left_count = left.count_assets(); - let right_count = right.count_assets(); - left_count + right_count - } - Terminal::OrC(left, right) => { - let left_count = left.count_assets(); - let right_count = right.count_assets(); - left_count + right_count - } - Terminal::OrI(left, right) => { + Terminal::OrB(left, right) + | Terminal::OrC(left, right) + | Terminal::OrD(left, right) + | Terminal::OrI(left, right) => { let left_count = left.count_assets(); let right_count = right.count_assets(); left_count + right_count } Terminal::Thresh(k, ms_v) => { - // k = 2, n = ms_v.len() - // ms_v = [ms(A),ms(B),ms(C)]; - // Assume count array as [5,7,8] and k=2 - // get_combinations_product gives [5*7,5*8,7*8] = [35,40,56] let mut count_array = Vec::new(); for ms in ms_v { count_array.push(ms.count_assets()); } - let products = Self::get_combinations_product(&count_array, *k as u64); + let products = get_combinations_product(&count_array, *k as u64); let mut total_count: u64 = 0; for product in products { total_count += product; } total_count } - Terminal::Multi(k, dpk) => { + Terminal::Multi(k, dpk) | Terminal::MultiA(k, dpk) => { let k: u64 = *k as u64; let n: u64 = dpk.len() as u64; - Self::k_of_n(k, n) - } - Terminal::MultiA(k, dpk) => { - let k: u64 = *k as u64; - let n: u64 = dpk.len() as u64; - Self::k_of_n(k, n) + k_of_n(k, n) } } } @@ -823,9 +796,7 @@ impl Terminal { } result } - Terminal::Multi(k, dpk_v) | Terminal::MultiA(k, dpk_v) => { - get_asset_combination(*k, dpk_v) - } + Terminal::Multi(k, dpk_v) | Terminal::MultiA(k, dpk_v) => asset_combination(*k, dpk_v), } } @@ -858,38 +829,4 @@ impl Terminal { current_combination.truncate(current_combination.len() - 1); } } - - // Do product of K combinations - fn get_combinations_product(values: &[u64], k: u64) -> Vec { - let mut products = Vec::new(); - let n = values.len(); - - if k == 0 { - return vec![1]; // Empty combination has a product of 1 - } - - // Using bitwise operations to generate combinations - let max_combinations = 1u32 << n; - for combination_bits in 1..max_combinations { - if combination_bits.count_ones() as usize == k as usize { - let mut product = 1; - for i in 0..n { - if combination_bits & (1u32 << i) != 0 { - product *= values[i]; - } - } - products.push(product); - } - } - - products - } - - // ways to select k things out of n - fn k_of_n(k: u64, n: u64) -> u64 { - if k == 0 || k == n { - return 1; - } - Self::k_of_n(k - 1, n - 1) + Self::k_of_n(k, n - 1) - } } diff --git a/src/util.rs b/src/util.rs index bb6107170..e2346fb05 100644 --- a/src/util.rs +++ b/src/util.rs @@ -104,7 +104,7 @@ impl MsKeyBuilder for script::Builder { } // Helper to get all possible pairs of K of N assets -pub fn get_asset_combination(k: usize, dpk_v: &Vec) -> Vec { +pub fn asset_combination(k: usize, dpk_v: &Vec) -> Vec { let mut all_assets: Vec = Vec::new(); let current_assets = Assets::new(); combine_assets(k, dpk_v, 0, current_assets, &mut all_assets); @@ -132,3 +132,37 @@ pub fn combine_assets( println!("{:#?}", new_asset); combine_assets(k - 1, dpk_v, index + 1, new_asset, all_assets) } + +// Do product of K combinations +pub fn get_combinations_product(values: &[u64], k: u64) -> Vec { + let mut products = Vec::new(); + let n = values.len(); + + if k == 0 { + return vec![1]; // Empty combination has a product of 1 + } + + // Using bitwise operations to generate combinations + let max_combinations = 1u32 << n; + for combination_bits in 1..max_combinations { + if combination_bits.count_ones() as usize == k as usize { + let mut product = 1; + for i in 0..n { + if combination_bits & (1u32 << i) != 0 { + product *= values[i]; + } + } + products.push(product); + } + } + + products +} + +// ways to select k things out of n +pub fn k_of_n(k: u64, n: u64) -> u64 { + if k == 0 || k == n { + return 1; + } + k_of_n(k - 1, n - 1) + k_of_n(k, n - 1) +} From aed89687ff9f9e0f127448ca851cc3b8ee93b5a8 Mon Sep 17 00:00:00 2001 From: Harshil Jani Date: Tue, 1 Aug 2023 07:52:18 +0530 Subject: [PATCH 4/6] Setting Maximum threshold for obtaining plan For the planning module we are considering that total possible ways to spend should always be less than 1000. This protects the network from any DDoS Attack if in case you receive a very large descriptor enough that it has 1000 possible spend paths. Signed-off-by: Harshil Jani --- src/descriptor/mod.rs | 24 ++++++++++++++---------- src/descriptor/tr.rs | 2 +- src/lib.rs | 3 +++ src/miniscript/astelem.rs | 12 ++++++------ src/miniscript/mod.rs | 2 +- src/util.rs | 32 ++++++++++++++++++++++---------- 6 files changed, 47 insertions(+), 28 deletions(-) diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 2ccc63dce..cf2887f80 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -549,7 +549,7 @@ impl Descriptor { impl Descriptor { /// Count total possible assets for a given descriptor. - pub fn count_assets(&self) -> u64 { + pub fn count_assets(&self) -> u32 { match self { Descriptor::Bare(k) => k.as_inner().count_assets(), Descriptor::Pkh(_) => 1, @@ -557,25 +557,25 @@ impl Descriptor { Descriptor::Sh(k) => match k.as_inner() { ShInner::Wsh(k) => match k.as_inner() { WshInner::SortedMulti(k) => { - let n = k.pks.len() as u64; - let k = k.k as u64; - k_of_n(k, n) + let n = k.pks.len() as u32; + let k = k.k as u32; + k_of_n(k, n).unwrap() } WshInner::Ms(k) => k.count_assets(), }, ShInner::Wpkh(_) => 1, ShInner::SortedMulti(k) => { - let n = k.clone().pks.len() as u64; - let k = k.clone().k as u64; - k_of_n(k, n) + let n = k.clone().pks.len() as u32; + let k = k.clone().k as u32; + k_of_n(k, n).unwrap() } ShInner::Ms(k) => k.count_assets(), }, Descriptor::Wsh(k) => match k.as_inner() { WshInner::SortedMulti(k) => { - let n = k.clone().pks.len() as u64; - let k = k.clone().k as u64; - k_of_n(k, n) + let n = k.clone().pks.len() as u32; + let k = k.clone().k as u32; + k_of_n(k, n).unwrap() } WshInner::Ms(k) => k.count_assets(), }, @@ -595,6 +595,10 @@ impl Descriptor { /// Get all possible assets for a given descriptor pub fn all_assets(&self) -> Result, Error> { + let threshold = self.count_assets(); + if threshold >= 1000 { + return Err(Error::MaxAssetThresholdExceeded); + } match self { Descriptor::Bare(k) => Ok(k.as_inner().all_assets()), Descriptor::Pkh(k) => { diff --git a/src/descriptor/tr.rs b/src/descriptor/tr.rs index 66622c821..20be1bb92 100644 --- a/src/descriptor/tr.rs +++ b/src/descriptor/tr.rs @@ -168,7 +168,7 @@ impl TapTree { } /// Get total possible assets for TapTree - pub fn count_assets(&self) -> u64 { + pub fn count_assets(&self) -> u32 { match self { TapTree::Tree { left, right, height: _ } => { let a = left.count_assets(); diff --git a/src/lib.rs b/src/lib.rs index f74922ae3..14edbf90e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -509,6 +509,9 @@ pub enum Error { const MAX_RECURSION_DEPTH: u32 = 402; // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki const MAX_SCRIPT_SIZE: u32 = 10000; +// For the planning module we are considering that total possible ways to spend +// should be less than 1000 +const MAX_ASSET_THRESHOLD: u32 = 1000; impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/miniscript/astelem.rs b/src/miniscript/astelem.rs index 7d1d23840..566f2d651 100644 --- a/src/miniscript/astelem.rs +++ b/src/miniscript/astelem.rs @@ -598,7 +598,7 @@ impl Terminal { impl Terminal { /// Count total possible assets - pub fn count_assets(&self) -> u64 { + pub fn count_assets(&self) -> u32 { match self { Terminal::True => 0, Terminal::False => 0, @@ -642,17 +642,17 @@ impl Terminal { for ms in ms_v { count_array.push(ms.count_assets()); } - let products = get_combinations_product(&count_array, *k as u64); - let mut total_count: u64 = 0; + let products = get_combinations_product(&count_array, *k as u32); + let mut total_count: u32 = 0; for product in products { total_count += product; } total_count } Terminal::Multi(k, dpk) | Terminal::MultiA(k, dpk) => { - let k: u64 = *k as u64; - let n: u64 = dpk.len() as u64; - k_of_n(k, n) + let k: u32 = *k as u32; + let n: u32 = dpk.len() as u32; + k_of_n(k, n).unwrap() } } } diff --git a/src/miniscript/mod.rs b/src/miniscript/mod.rs index 815fd4501..d6bbb07ae 100644 --- a/src/miniscript/mod.rs +++ b/src/miniscript/mod.rs @@ -391,7 +391,7 @@ impl Miniscript { pub fn all_assets(&self) -> Vec { self.node.all_assets() } /// Get the total number of assets possible - pub fn count_assets(&self) -> u64 { self.node.count_assets() } + pub fn count_assets(&self) -> u32 { self.node.count_assets() } } impl ForEachKey for Miniscript { diff --git a/src/util.rs b/src/util.rs index e2346fb05..d963c42e2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -10,7 +10,9 @@ use crate::miniscript::context; use crate::miniscript::satisfy::Placeholder; use crate::plan::Assets; use crate::prelude::*; -use crate::{DescriptorPublicKey, MiniscriptKey, ScriptContext, ToPublicKey}; +use crate::{ + DescriptorPublicKey, Error, MiniscriptKey, ScriptContext, ToPublicKey, MAX_ASSET_THRESHOLD, +}; pub(crate) fn varint_len(n: usize) -> usize { bitcoin::VarInt(n as u64).len() } pub(crate) trait ItemSize { @@ -56,7 +58,7 @@ pub(crate) fn witness_to_scriptsig(witness: &[Vec]) -> ScriptBuf { } else { let push = <&PushBytes>::try_from(wit.as_slice()) .expect("All pushes in miniscript are <73 bytes"); - b = b.push_slice(push) + b = b.push_slice(push); } } b.into_script() @@ -129,12 +131,11 @@ pub fn combine_assets( combine_assets(k, dpk_v, index + 1, current_assets.clone(), all_assets); let mut new_asset = current_assets; new_asset = new_asset.add(dpk_v[index].clone()); - println!("{:#?}", new_asset); combine_assets(k - 1, dpk_v, index + 1, new_asset, all_assets) } // Do product of K combinations -pub fn get_combinations_product(values: &[u64], k: u64) -> Vec { +pub fn get_combinations_product(values: &[u32], k: u32) -> Vec { let mut products = Vec::new(); let n = values.len(); @@ -145,10 +146,10 @@ pub fn get_combinations_product(values: &[u64], k: u64) -> Vec { // Using bitwise operations to generate combinations let max_combinations = 1u32 << n; for combination_bits in 1..max_combinations { - if combination_bits.count_ones() as usize == k as usize { + if (combination_bits.count_ones() as usize) == (k as usize) { let mut product = 1; for i in 0..n { - if combination_bits & (1u32 << i) != 0 { + if (combination_bits & (1u32 << i)) != 0 { product *= values[i]; } } @@ -160,9 +161,20 @@ pub fn get_combinations_product(values: &[u64], k: u64) -> Vec { } // ways to select k things out of n -pub fn k_of_n(k: u64, n: u64) -> u64 { - if k == 0 || k == n { - return 1; +pub fn k_of_n(k: u32, n: u32) -> Result { + let mut k = k; + if k > n - k { + k = n - k; } - k_of_n(k - 1, n - 1) + k_of_n(k, n - 1) + + let mut result = 1; + for i in 0..k { + result *= n - i; + result /= i + 1; + if result > MAX_ASSET_THRESHOLD.into() { + return Err(Error::MaxAssetThresholdExceeded); + } + } + + Ok(result) } From 599ac6f43b8269c0900a6b276f04fac5c62b7e91 Mon Sep 17 00:00:00 2001 From: Harshil Jani Date: Mon, 25 Sep 2023 18:30:35 +0530 Subject: [PATCH 5/6] Adding new example with asset planning API This commit adds a new example file which explains how we can use planner API with the taproot descriptor and compute different possible spend path and actually spend them. This would help developers to understand and learn new planning functionality. Signed-off-by: Harshil Jani --- Cargo.toml | 4 + examples/plan_spend.rs | 241 +++++++++++++++++++++++++++++++++++++++++ src/descriptor/mod.rs | 2 - 3 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 examples/plan_spend.rs diff --git a/Cargo.toml b/Cargo.toml index 680293395..4d99bd1d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,3 +64,7 @@ required-features = ["std", "base64"] [workspace] members = ["bitcoind-tests", "fuzz"] exclude = ["embedded"] + +[[example]] +name = "plan_spend" +required-features = ["std", "base64"] diff --git a/examples/plan_spend.rs b/examples/plan_spend.rs new file mode 100644 index 000000000..77f5f6921 --- /dev/null +++ b/examples/plan_spend.rs @@ -0,0 +1,241 @@ +use std::collections::BTreeMap; +use std::str::FromStr; + +use bitcoin::absolute::Height; +use bitcoin::blockdata::locktime::absolute; +use bitcoin::key::TapTweak; +use bitcoin::psbt::{self, Psbt}; +use bitcoin::sighash::SighashCache; +use bitcoin::{taproot, PrivateKey, ScriptBuf}; +use miniscript::bitcoin::consensus::encode::deserialize; +use miniscript::bitcoin::hashes::hex::FromHex; +use miniscript::bitcoin::{ + self, base64, secp256k1, Address, Network, OutPoint, Sequence, Transaction, TxIn, TxOut, +}; +use miniscript::psbt::{PsbtExt, PsbtInputExt}; +use miniscript::{Descriptor, DescriptorPublicKey}; +use secp256k1::Secp256k1; + +fn main() { + // Defining the descriptor keys required. + let secp256k1 = secp256k1::Secp256k1::new(); + let keys = vec![ + "036a7ae441409bd40af1b8efba7dbd34b822b9a72566eff10b889b8de13659e343", + "03b6c8a1a901edf3c5f1cb0e3ffe1f20393435a5d467f435e2858c9ab43d3ca78c", + "03500a2b48b0f66c8183cc0d6645ab21cc19c7fad8a33ff04d41c3ece54b0bc1c5", + "033ad2d191da4f39512adbaac320cae1f12f298386a4e9d43fd98dec7cf5db2ac9", + "023fc33527afab09fa97135f2180bcd22ce637b1d2fbcb2db748b1f2c33f45b2b4", + ]; + + // The taproot descriptor combines different spending paths and conditions, allowing the funds to be spent using + // different methods depending on the desired conditions. + + // tr({A},{{pkh({B}),{{multi_a(1,{C},{D}),and_v(v:pk({E}),after(10))}}}}) represents a taproot spending policy. + // Let's break it down: + // + // * Key Spend Path + // {A} represents the public key for the taproot key spending path. + // + // * Script Spend Paths + // {B} represents the public key for the pay-to-pubkey-hash (P2PKH) spending path. + // The multi_a(1,{C},{D}) construct represents a 1-of-2 multi-signature script condition. + // It requires at least one signature from {C} and {D} to spend funds using the script spend path. + // The and_v(v:pk({E}),after(10)) construct represents a combination of a P2PK script condition and a time lock. + // It requires a valid signature from {E} and enforces a time lock of 10 blocks on spending funds. + + // By constructing transactions using this taproot descriptor and signing them appropriately, + // you can create flexible spending policies that enable different spending paths and conditions depending on the + // transaction's inputs and outputs. + let s = format!( + "tr({},{{pkh({}),{{multi_a(1,{},{}),and_v(v:pk({}),after(10))}}}})", + keys[0], // Key A + keys[1], // Key B + keys[2], // Key C + keys[3], // Key D + keys[4], // Key E + ); + + let descriptor = Descriptor::from_str(&s).expect("parse descriptor string"); + assert!(descriptor.sanity_check().is_ok()); + + println!("Descriptor pubkey script: {}", descriptor.script_pubkey()); + println!("Descriptor address: {}", descriptor.address(Network::Regtest).unwrap()); + + let master_private_key_str = "KxQqtbUnMugSEbKHG3saknvVYux1cgFjFqWzMfwnFhLm8QrGq26v"; + let master_private_key = + PrivateKey::from_str(master_private_key_str).expect("Can't create private key"); + println!("Master public key: {}", master_private_key.public_key(&secp256k1)); + + let backup1_private_key_str = "Kwb9oFfPNt6D3Fa9DCF5emRvLyJ3UUvCHnVxp4xf7bWDxWmeVdeH"; + let backup1_private = + PrivateKey::from_str(backup1_private_key_str).expect("Can't create private key"); + + println!("Backup1 public key: {}", backup1_private.public_key(&secp256k1)); + + let backup2_private_key_str = "cPJFWUKk8sdL7pcDKrmNiWUyqgovimmhaaZ8WwsByDaJ45qLREkh"; + let backup2_private = + PrivateKey::from_str(backup2_private_key_str).expect("Can't create private key"); + + println!("Backup2 public key: {}", backup2_private.public_key(&secp256k1)); + + let backup3_private_key_str = "cT5cH9UVm81W5QAf5KABXb23RKNSMbMzMx85y6R2mF42L94YwKX6"; + let _backup3_private = + PrivateKey::from_str(backup3_private_key_str).expect("Can't create private key"); + + println!("Backup3 public key: {}", _backup3_private.public_key(&secp256k1)); + + // Create a spending transaction + let spend_tx = Transaction { + version: 2, + lock_time: absolute::LockTime::Blocks(Height::ZERO), + input: vec![], + output: vec![], + }; + + let hex_tx = "020000000001018ff27041f3d738f5f84fd5ee62f1c5b36afebfb15f6da0c9d1382ddd0eaaa23c0000000000feffffff02b3884703010000001600142ca3b4e53f17991582d47b15a053b3201891df5200e1f5050000000022512061763f4288d086c0347c4e3c387ce22ab9372cecada6c326e77efd57e9a5ea460247304402207b820860a9d425833f729775880b0ed59dd12b64b9a3d1ab677e27e4d6b370700220576003163f8420fe0b9dc8df726cff22cbc191104a2d4ae4f9dfedb087fcec72012103817e1da42a7701df4db94db8576f0e3605f3ab3701608b7e56f92321e4d8999100000000"; + let depo_tx: Transaction = deserialize(&Vec::::from_hex(hex_tx).unwrap()).unwrap(); + + let receiver = Address::from_str("bcrt1qsdks5za4t6sevaph6tz9ddfjzvhkdkxe9tfrcy").unwrap(); + + let amount = 100000000; + + let (outpoint, witness_utxo) = get_vout(&depo_tx, descriptor.script_pubkey()); + + let all_assets = Descriptor::::from_str(&s) + .unwrap() + .all_assets() + .unwrap(); + + for asset in all_assets { + // Creating a PSBT Object + let mut psbt = Psbt { + unsigned_tx: spend_tx.clone(), + unknown: BTreeMap::new(), + proprietary: BTreeMap::new(), + xpub: BTreeMap::new(), + version: 0, + inputs: vec![], + outputs: vec![], + }; + + // Defining the Transaction Input + let mut txin = TxIn::default(); + txin.previous_output = outpoint; + txin.sequence = Sequence::from_height(26); //Sequence::MAX; // + psbt.unsigned_tx.input.push(txin); + + // Defining the Transaction Output + psbt.unsigned_tx.output.push(TxOut { + script_pubkey: receiver.payload.script_pubkey(), + value: amount / 5 - 500, + }); + + psbt.unsigned_tx + .output + .push(TxOut { script_pubkey: descriptor.script_pubkey(), value: amount * 4 / 5 }); + + // Consider that out of all the keys required to sign the descriptor spend path we only have some handful of assets. + // We can plan the PSBT with only few assets(keys or hashes) if that are enough for satisfying any policy. + // + // Here for example assume that we only have two keys available. + // Key A and Key B (as seen from the descriptor above) + // We have to add the keys to `Asset` and then obtain plan with only available signatures if the descriptor can be satisfied. + + // Obtain the Plan based on available Assets + let plan = descriptor.clone().plan(&asset).unwrap(); + + // Creating PSBT Input + let mut input = psbt::Input::default(); + plan.update_psbt_input(&mut input); + + // Update the PSBT input from the result which we have obtained from the Plan. + input.update_with_descriptor_unchecked(&descriptor).unwrap(); + input.witness_utxo = Some(witness_utxo.clone()); + + // Push the PSBT Input and declare an PSBT Output Structure + psbt.inputs.push(input); + psbt.outputs.push(psbt::Output::default()); + + // Use private keys to sign + let key_a = master_private_key.inner; + let key_b = backup1_private.inner; + + // Taproot script can be signed when we have either Key spend or Script spend or both. + // Here you can try to verify by commenting one of the spend path or try signing with both. + sign_taproot_psbt(&key_a, &mut psbt, &secp256k1); // Key Spend - With Key A + sign_taproot_psbt(&key_b, &mut psbt, &secp256k1); // Script Spend - With Key B + + // Serializing and finalizing the PSBT Transaction + let serialized = psbt.serialize(); + println!("{}", base64::encode(&serialized)); + psbt.finalize_mut(&secp256k1).unwrap(); + + let tx = psbt.extract_tx(); + println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); + } +} + +// Siging the Taproot PSBT Transaction +fn sign_taproot_psbt( + secret_key: &secp256k1::SecretKey, + psbt: &mut psbt::Psbt, + secp256k1: &Secp256k1, +) { + // Creating signing entitites required + let hash_ty = bitcoin::sighash::TapSighashType::Default; + let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx); + + // Defining Keypair for given private key + let keypair = secp256k1::KeyPair::from_seckey_slice(&secp256k1, secret_key.as_ref()).unwrap(); + + // Checking if leaf hash exist or not. + // For Key Spend -> Leaf Hash is None + // For Script Spend -> Leaf Hash is Some(_) + // Convert this leaf_hash tree to a full map. + let (leaf_hashes, (_, _)) = &psbt.inputs[0].tap_key_origins[&keypair.x_only_public_key().0]; + let leaf_hash = if !leaf_hashes.is_empty() { + Some(leaf_hashes[0]) + } else { + None + }; + + let keypair = match leaf_hash { + None => keypair + .tap_tweak(&secp256k1, psbt.inputs[0].tap_merkle_root) + .to_inner(), // tweak for key spend + Some(_) => keypair, // no tweak for script spend + }; + + // Construct the message to input for schnorr signature + let msg = psbt + .sighash_msg(0, &mut sighash_cache, leaf_hash) + .unwrap() + .to_secp_msg(); + let sig = secp256k1.sign_schnorr(&msg, &keypair); + let (pk, _parity) = keypair.x_only_public_key(); + assert!(secp256k1.verify_schnorr(&sig, &msg, &pk).is_ok()); + + // Create final signature with corresponding hash type + let final_signature1 = taproot::Signature { hash_ty, sig }; + + if let Some(lh) = leaf_hash { + // Script Spend + psbt.inputs[0] + .tap_script_sigs + .insert((pk, lh), final_signature1); + } else { + // Key Spend + psbt.inputs[0].tap_key_sig = Some(final_signature1); + println!("{:#?}", psbt); + } +} + +// Find the Outpoint by spk +fn get_vout(tx: &Transaction, spk: ScriptBuf) -> (OutPoint, TxOut) { + for (i, txout) in tx.clone().output.into_iter().enumerate() { + if spk == txout.script_pubkey { + return (OutPoint::new(tx.txid(), i as u32), txout); + } + } + panic!("Only call get vout on functions which have the expected outpoint"); +} diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index cf2887f80..e9c48e1c5 100644 --- a/src/descriptor/mod.rs +++ b/src/descriptor/mod.rs @@ -639,9 +639,7 @@ impl Descriptor { Ok(asset_combination(k, &dpk_v)) } WshInner::Ms(k) => { - println!("{}", k); let a = k.all_assets(); - println!("{:#?}", a); Ok(a) } }, From d7c28c2bd574a3d9d1b621bbd387f12034ee97b9 Mon Sep 17 00:00:00 2001 From: Harshil Jani Date: Mon, 25 Sep 2023 18:32:50 +0530 Subject: [PATCH 6/6] Updating `psbt_sign_finalize.rs` with Plan API Recently we have had plan API into the rust-miniscript so this example is being updated for better use of plan API with more clear explanation Signed-off-by: Harshil Jani --- examples/psbt_sign_finalize.rs | 211 +++++++++++++++++++-------------- 1 file changed, 125 insertions(+), 86 deletions(-) diff --git a/examples/psbt_sign_finalize.rs b/examples/psbt_sign_finalize.rs index 08abd5abe..b90016802 100644 --- a/examples/psbt_sign_finalize.rs +++ b/examples/psbt_sign_finalize.rs @@ -1,31 +1,53 @@ -// SPDX-License-Identifier: CC0-1.0 - use std::collections::BTreeMap; use std::str::FromStr; +use bitcoin::sighash::SighashCache; +use bitcoin::PrivateKey; use miniscript::bitcoin::consensus::encode::deserialize; use miniscript::bitcoin::hashes::hex::FromHex; -use miniscript::bitcoin::psbt::{self, Psbt}; -use miniscript::bitcoin::sighash::SighashCache; +use miniscript::bitcoin::psbt::PartiallySignedTransaction as Psbt; use miniscript::bitcoin::{ - self, base64, secp256k1, Address, Network, OutPoint, PrivateKey, Script, Sequence, Transaction, - TxIn, TxOut, + self, base64, psbt, secp256k1, Address, Network, OutPoint, Script, Sequence, Transaction, TxIn, + TxOut, }; use miniscript::psbt::{PsbtExt, PsbtInputExt}; -use miniscript::Descriptor; +use miniscript::{Descriptor, DescriptorPublicKey}; fn main() { + // Defining the descriptor keys let secp256k1 = secp256k1::Secp256k1::new(); + let keys = vec![ + "027a3565454fe1b749bccaef22aff72843a9c3efefd7b16ac54537a0c23f0ec0de", + "032d672a1a91cc39d154d366cd231983661b0785c7f27bc338447565844f4a6813", + "03417129311ed34c242c012cd0a3e0b9bca0065f742d0dfb63c78083ea6a02d4d9", + "025a687659658baeabdfc415164528065be7bcaade19342241941e556557f01e28", + ]; + // The wsh descriptor indicates a Witness Script Hash, meaning the descriptor is for a SegWit script. + // wsh(or(pk(A),thresh(1,pkh(B),pkh(C),pkh(D)))) + + // Let's break it down: + // t:or_c specifies an "or" construct, which means the script can be satisfied by one of the given conditions: + // pk(A) OR thresh(1,pkh(B),pkh(C),pkh(D)) + // Inside threshold condition atleast 1 out of all given conditions should satisfy. + + // By constructing transactions using this wsh descriptor and signing them appropriately, + // you can create flexible spending policies that enable different spending paths and conditions depending on the + // transaction's inputs and outputs. + let s = format!( + "wsh(t:or_c(pk({}),v:thresh(1,pkh({}),a:pkh({}),a:pkh({}))))", + keys[0], // key A + keys[1], // key B + keys[2], // key C + keys[3], // key D + ); + let descriptor = Descriptor::from_str(&s).expect("parse descriptor string"); - let s = "wsh(t:or_c(pk(027a3565454fe1b749bccaef22aff72843a9c3efefd7b16ac54537a0c23f0ec0de),v:thresh(1,pkh(032d672a1a91cc39d154d366cd231983661b0785c7f27bc338447565844f4a6813),a:pkh(03417129311ed34c242c012cd0a3e0b9bca0065f742d0dfb63c78083ea6a02d4d9),a:pkh(025a687659658baeabdfc415164528065be7bcaade19342241941e556557f01e28))))#7hut9ukn"; - let bridge_descriptor = Descriptor::from_str(&s).unwrap(); - //let bridge_descriptor = Descriptor::::from_str(&s).expect("parse descriptor string"); - assert!(bridge_descriptor.sanity_check().is_ok()); - println!("Bridge pubkey script: {}", bridge_descriptor.script_pubkey()); - println!("Bridge address: {}", bridge_descriptor.address(Network::Regtest).unwrap()); + assert!(descriptor.sanity_check().is_ok()); + println!("descriptor pubkey script: {}", descriptor.script_pubkey()); + println!("descriptor address: {}", descriptor.address(Network::Regtest).unwrap()); println!( "Weight for witness satisfaction cost {}", - bridge_descriptor.max_weight_to_satisfy().unwrap() + descriptor.max_weight_to_satisfy().unwrap() ); let master_private_key_str = "cQhdvB3McbBJdx78VSSumqoHQiSXs75qwLptqwxSQBNBMDxafvaw"; @@ -51,6 +73,7 @@ fn main() { println!("Backup3 public key: {}", _backup3_private.public_key(&secp256k1)); + // Create a spending transaction let spend_tx = Transaction { version: 2, lock_time: bitcoin::absolute::LockTime::from_consensus(5000), @@ -58,17 +81,6 @@ fn main() { output: vec![], }; - // Spend one input and spend one output for simplicity. - let mut psbt = Psbt { - unsigned_tx: spend_tx, - unknown: BTreeMap::new(), - proprietary: BTreeMap::new(), - xpub: BTreeMap::new(), - version: 0, - inputs: vec![], - outputs: vec![], - }; - let hex_tx = "020000000001018ff27041f3d738f5f84fd5ee62f1c5b36afebfb15f6da0c9d1382ddd0eaaa23c0000000000feffffff02b3884703010000001600142ca3b4e53f17991582d47b15a053b3201891df5200e1f50500000000220020c0ebf552acd2a6f5dee4e067daaef17b3521e283aeaa44a475278617e3d2238a0247304402207b820860a9d425833f729775880b0ed59dd12b64b9a3d1ab677e27e4d6b370700220576003163f8420fe0b9dc8df726cff22cbc191104a2d4ae4f9dfedb087fcec72012103817e1da42a7701df4db94db8576f0e3605f3ab3701608b7e56f92321e4d8999100000000"; let depo_tx: Transaction = deserialize(&Vec::::from_hex(hex_tx).unwrap()).unwrap(); @@ -78,70 +90,97 @@ fn main() { let amount = 100000000; - let (outpoint, witness_utxo) = get_vout(&depo_tx, &bridge_descriptor.script_pubkey()); - - let mut txin = TxIn::default(); - txin.previous_output = outpoint; - - txin.sequence = Sequence::from_height(26); //Sequence::MAX; // - psbt.unsigned_tx.input.push(txin); - - psbt.unsigned_tx - .output - .push(TxOut { script_pubkey: receiver.script_pubkey(), value: amount / 5 - 500 }); - - psbt.unsigned_tx - .output - .push(TxOut { script_pubkey: bridge_descriptor.script_pubkey(), value: amount * 4 / 5 }); - - // Generating signatures & witness data - - let mut input = psbt::Input::default(); - input - .update_with_descriptor_unchecked(&bridge_descriptor) - .unwrap(); - - input.witness_utxo = Some(witness_utxo.clone()); - psbt.inputs.push(input); - psbt.outputs.push(psbt::Output::default()); + let (outpoint, witness_utxo) = get_vout(&depo_tx, &descriptor.script_pubkey()); - let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx); - - let msg = psbt - .sighash_msg(0, &mut sighash_cache, None) + let all_assets = Descriptor::::from_str(&s) .unwrap() - .to_secp_msg(); - - // Fixme: Take a parameter - let hash_ty = bitcoin::sighash::EcdsaSighashType::All; - - let sk1 = backup1_private.inner; - let sk2 = backup2_private.inner; - - // Finally construct the signature and add to psbt - let sig1 = secp256k1.sign_ecdsa(&msg, &sk1); - let pk1 = backup1_private.public_key(&secp256k1); - assert!(secp256k1.verify_ecdsa(&msg, &sig1, &pk1.inner).is_ok()); - - // Second key just in case - let sig2 = secp256k1.sign_ecdsa(&msg, &sk2); - let pk2 = backup2_private.public_key(&secp256k1); - assert!(secp256k1.verify_ecdsa(&msg, &sig2, &pk2.inner).is_ok()); - - psbt.inputs[0] - .partial_sigs - .insert(pk1, bitcoin::ecdsa::Signature { sig: sig1, hash_ty: hash_ty }); - - println!("{:#?}", psbt); - - let serialized = psbt.serialize(); - println!("{}", base64::encode(&serialized)); - - psbt.finalize_mut(&secp256k1).unwrap(); - println!("{:#?}", psbt); + .all_assets() + .unwrap(); - let tx = psbt.extract_tx(); - println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); + for asset in all_assets { + // Spend one input and spend one output for simplicity. + let mut psbt = Psbt { + unsigned_tx: spend_tx.clone(), + unknown: BTreeMap::new(), + proprietary: BTreeMap::new(), + xpub: BTreeMap::new(), + version: 0, + inputs: vec![], + outputs: vec![], + }; + + // Defining the Transaction Input + let mut txin = TxIn::default(); + txin.previous_output = outpoint; + txin.sequence = Sequence::from_height(26); //Sequence::MAX; // + psbt.unsigned_tx.input.push(txin); + + // Defining the Transaction Output + psbt.unsigned_tx + .output + .push(TxOut { script_pubkey: receiver.script_pubkey(), value: amount / 5 - 500 }); + + psbt.unsigned_tx + .output + .push(TxOut { script_pubkey: descriptor.script_pubkey(), value: amount * 4 / 5 }); + + // Consider that out of all the keys required to sign the descriptor, we only have some handful of assets. + // We can plan the PSBT with only few assets(keys or hashes) if that are enough for satisfying any policy. + // + // Here for example assume that we only have one key available i.e Key A(as seen from the descriptor above) + // Key A is enough to satisfy the given descriptor because it is OR. + // We have to add the key to `Asset` and then obtain plan with only available signature if the descriptor can be satisfied. + + // Check the possible asset which we can use + println!("{:#?}", asset); + + // Obtain the Plan based on available Assets + let plan = descriptor.clone().plan(&asset).unwrap(); + + // Creating a PSBT Input + let mut input = psbt::Input::default(); + + // Update the PSBT input from the result which we have obtained from the Plan. + plan.update_psbt_input(&mut input); + input.update_with_descriptor_unchecked(&descriptor).unwrap(); + input.witness_utxo = Some(witness_utxo.clone()); + + // Push the PSBT Input and declare an PSBT Output Structure + psbt.inputs.push(input); + psbt.outputs.push(psbt::Output::default()); + + let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx); + + let msg = psbt + .sighash_msg(0, &mut sighash_cache, None) + .unwrap() + .to_secp_msg(); + + // Fixme: Take a parameter + let hash_ty = bitcoin::sighash::EcdsaSighashType::All; + + let sk = backup1_private.inner; + + // Finally construct the signature and add to psbt + let sig = secp256k1.sign_ecdsa(&msg, &sk); + let key_a = backup1_private.public_key(&secp256k1); + assert!(secp256k1.verify_ecdsa(&msg, &sig, &key_a.inner).is_ok()); + + psbt.inputs[0] + .partial_sigs + .insert(key_a, bitcoin::ecdsa::Signature { sig, hash_ty }); + + println!("{:#?}", psbt); + + let serialized = psbt.serialize(); + println!("{}", base64::encode(&serialized)); + + psbt.finalize_mut(&secp256k1).unwrap(); + println!("{:#?}", psbt); + + let tx = psbt.extract_tx(); + println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); + } } // Find the Outpoint by spk