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/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 diff --git a/src/descriptor/mod.rs b/src/descriptor/mod.rs index 616bb37cc..e9c48e1c5 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::{asset_combination, k_of_n}; use crate::{ expression, hash256, BareCtx, Error, ForEachKey, MiniscriptKey, Satisfier, ToPublicKey, TranslateErr, TranslatePk, Translator, @@ -546,6 +547,118 @@ impl Descriptor { } } +impl Descriptor { + /// Count total possible assets for a given descriptor. + pub fn count_assets(&self) -> u32 { + 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 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 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 u32; + let k = k.clone().k as u32; + k_of_n(k, n).unwrap() + } + 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> { + 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) => { + 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(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(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(asset_combination(k, &dpk_v)) + } + WshInner::Ms(k) => { + let a = k.all_assets(); + 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()), + } + } + } + } +} + impl TranslatePk for Descriptor

where P: MiniscriptKey, @@ -2041,4 +2154,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..20be1bb92 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) -> u32 { + 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..14edbf90e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -501,12 +501,17 @@ 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 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 { @@ -577,6 +582,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 +625,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 b272830e1..566f2d651 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::util::{asset_combination, get_combinations_product, k_of_n, 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,238 @@ impl Terminal { } } } + +impl Terminal { + /// Count total possible assets + pub fn count_assets(&self) -> u32 { + match self { + Terminal::True => 0, + Terminal::False => 0, + Terminal::PkK(_) => 1, + Terminal::PkH(_) => 1, + Terminal::RawPkH(_) => 1, + 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) | 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) + | 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) => { + let mut count_array = Vec::new(); + for ms in ms_v { + count_array.push(ms.count_assets()); + } + 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: u32 = *k as u32; + let n: u32 = dpk.len() as u32; + k_of_n(k, n).unwrap() + } + } + } + + /// 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) => asset_combination(*k, dpk_v), + } + } + + // 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..d6bbb07ae 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,14 @@ 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() } + + /// Get the total number of assets possible + pub fn count_assets(&self) -> u32 { self.node.count_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 +619,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 +1361,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`, diff --git a/src/util.rs b/src/util.rs index 11643b643..d963c42e2 100644 --- a/src/util.rs +++ b/src/util.rs @@ -8,8 +8,11 @@ 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, 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 { @@ -55,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() @@ -101,3 +104,77 @@ impl MsKeyBuilder for script::Builder { } } } + +// Helper to get all possible pairs of K of N assets +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); + 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()); + combine_assets(k - 1, dpk_v, index + 1, new_asset, all_assets) +} + +// Do product of K combinations +pub fn get_combinations_product(values: &[u32], k: u32) -> 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: u32, n: u32) -> Result { + let mut k = k; + if k > n - k { + k = n - k; + } + + 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) +}