Skip to content

Improving Plan API and adding examples. #559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -64,3 +64,7 @@ required-features = ["std", "base64"]
[workspace]
members = ["bitcoind-tests", "fuzz"]
exclude = ["embedded"]

[[example]]
name = "plan_spend"
required-features = ["std", "base64"]
241 changes: 241 additions & 0 deletions examples/plan_spend.rs
Original file line number Diff line number Diff line change
@@ -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::<u8>::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::<DescriptorPublicKey>::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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we update the input with the descriptor after updating it with the plan. The plan should do everything you need?

Copy link
Contributor Author

@Harshil-Jani Harshil-Jani Nov 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While finalizing we need to inform the signer which keys we want to sign. And to do this we need pkh in place of their hashes.

The way current planner module is designed it doesn't support raw pkh and converts things into hashes. So, there is this workaround #589 and #557 which substitutes pkh from expr_raw_pkh.

Here what we did is that we took all the possible keys and mapped them with their hashes only to convert them back to their pkh form. Assuming that we only depend on plan then it is going to return us with the hash of keys that are required to be signed. But we would also need other pkh's that we need to dissatisfy.

Reference to more context on this :
#481 (comment)
#481 (comment)
#589
#557

Example of above discussion

Assume five keys A,B,C,D,E
User wants to plan txn using with A,C,E.

Case 1 : We don't update the input with descriptor

  • Plan returns Hash(A), Hash(C), Hash(E)
  • Input only contains hashes for A,C,E
  • Map contains : | A, Hash(A) | C, Hash(C) | E, Hash(E) |
  • We don't get pkhs for B and D since they are not present in the map.

Case 2 : We update the input with descriptor

  • Plan returns Hash(A), Hash(C), Hash(E)
  • We update input with descriptor so it contains A,B,C,D,E
  • Map contains : | A, Hash(A) | B, Hash(B) | C, Hash(C) | D, Hash(D) | E, Hash(E) |
  • We have all pkh full keys.

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<secp256k1::All>,
) {
// 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");
}
211 changes: 125 additions & 86 deletions examples/psbt_sign_finalize.rs
Original file line number Diff line number Diff line change
@@ -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::<bitcoin::PublicKey>::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,24 +73,14 @@ 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),
input: vec![],
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::<u8>::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::<DescriptorPublicKey>::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
186 changes: 185 additions & 1 deletion src/descriptor/mod.rs
Original file line number Diff line number Diff line change
@@ -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<DefiniteDescriptorKey> {
}
}

impl Descriptor<DescriptorPublicKey> {
/// 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<Vec<Assets>, 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<P, Q> TranslatePk<P, Q> for Descriptor<P>
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::<DescriptorPublicKey>::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::<DescriptorPublicKey>::from_str(&format!(
"sh(sortedmulti(1,{},{}))",
keys[0], keys[1]
))
.unwrap();

let assets = descriptor.all_assets().unwrap();

let mut expected_assets: Vec<Assets> = 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<Assets> = 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);
}
}
33 changes: 30 additions & 3 deletions src/descriptor/tr.rs
Original file line number Diff line number Diff line change
@@ -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<Pk: MiniscriptKey> TapTree<Pk> {
}
}

impl TapTree<DescriptorPublicKey> {
/// Get all possible assets for Taptree
pub fn all_assets(&self) -> Vec<Assets> {
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<Pk: MiniscriptKey> fmt::Display for TapTree<Pk> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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),
241 changes: 239 additions & 2 deletions src/miniscript/astelem.rs
Original file line number Diff line number Diff line change
@@ -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<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
@@ -593,3 +595,238 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> Terminal<Pk, Ctx> {
}
}
}

impl<Ctx: ScriptContext> Terminal<DescriptorPublicKey, Ctx> {
/// 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<Assets> {
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<Assets> = 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<Assets> = 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<Assets> = 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<Arc<Miniscript<DescriptorPublicKey, Ctx>>>,
) -> Vec<Vec<Arc<Miniscript<DescriptorPublicKey, Ctx>>>> {
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<Arc<Miniscript<DescriptorPublicKey, Ctx>>>,
result: &mut Vec<Vec<Arc<Miniscript<DescriptorPublicKey, Ctx>>>>,
ms: &Vec<Arc<Miniscript<DescriptorPublicKey, Ctx>>>,
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);
}
}
}
149 changes: 147 additions & 2 deletions src/miniscript/mod.rs
Original file line number Diff line number Diff line change
@@ -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<Pk: MiniscriptKey, Ctx: ScriptContext> fmt::Display for Miniscript<Pk, Ctx>
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.node) }
}

impl<Ctx: ScriptContext> Miniscript<DescriptorPublicKey, Ctx> {
/// Get all possible asset for a given node of Miniscript AST
pub fn all_assets(&self) -> Vec<Assets> { self.node.all_assets() }

/// Get the total number of assets possible
pub fn count_assets(&self) -> u32 { self.node.count_assets() }
}

impl<Pk: MiniscriptKey, Ctx: ScriptContext> ForEachKey<Pk> for Miniscript<Pk, Ctx> {
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<bitcoin::PublicKey, Segwitv0>;
type Tapscript = Miniscript<bitcoin::secp256k1::XOnlyPublicKey, Tap>;
@@ -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::<DescriptorPublicKey, Segwitv0>::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::<DescriptorPublicKey, Segwitv0>::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<Assets> = 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::<DescriptorPublicKey, Segwitv0>::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<Assets> = 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::<DescriptorPublicKey, Segwitv0>::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<Assets> = 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));
}
}
}
2 changes: 1 addition & 1 deletion src/plan.rs
Original file line number Diff line number Diff line change
@@ -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`,
81 changes: 79 additions & 2 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -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<u8>]) -> 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<DescriptorPublicKey>) -> Vec<Assets> {
let mut all_assets: Vec<Assets> = 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<Assets>,
) {
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<u32> {
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<u32, Error> {
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)
}