Skip to content

Commit 4ce074f

Browse files
committed
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 <[email protected]>
1 parent 50ffb8e commit 4ce074f

File tree

3 files changed

+245
-2
lines changed

3 files changed

+245
-2
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,7 @@ required-features = ["std", "base64"]
6464
[workspace]
6565
members = ["bitcoind-tests", "fuzz"]
6666
exclude = ["embedded"]
67+
68+
[[example]]
69+
name = "plan_spend"
70+
required-features = ["std", "base64"]

examples/plan_spend.rs

+241
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
use std::collections::BTreeMap;
2+
use std::str::FromStr;
3+
4+
use bitcoin::absolute::Height;
5+
use bitcoin::blockdata::locktime::absolute;
6+
use bitcoin::key::TapTweak;
7+
use bitcoin::psbt::{self, Psbt};
8+
use bitcoin::sighash::SighashCache;
9+
use bitcoin::{taproot, PrivateKey, ScriptBuf};
10+
use miniscript::bitcoin::consensus::encode::deserialize;
11+
use miniscript::bitcoin::hashes::hex::FromHex;
12+
use miniscript::bitcoin::{
13+
self, base64, secp256k1, Address, Network, OutPoint, Sequence, Transaction, TxIn, TxOut,
14+
};
15+
use miniscript::psbt::{PsbtExt, PsbtInputExt};
16+
use miniscript::{Descriptor, DescriptorPublicKey};
17+
use secp256k1::Secp256k1;
18+
19+
fn main() {
20+
// Defining the descriptor keys required.
21+
let secp256k1 = secp256k1::Secp256k1::new();
22+
let keys = vec![
23+
"036a7ae441409bd40af1b8efba7dbd34b822b9a72566eff10b889b8de13659e343",
24+
"03b6c8a1a901edf3c5f1cb0e3ffe1f20393435a5d467f435e2858c9ab43d3ca78c",
25+
"03500a2b48b0f66c8183cc0d6645ab21cc19c7fad8a33ff04d41c3ece54b0bc1c5",
26+
"033ad2d191da4f39512adbaac320cae1f12f298386a4e9d43fd98dec7cf5db2ac9",
27+
"023fc33527afab09fa97135f2180bcd22ce637b1d2fbcb2db748b1f2c33f45b2b4",
28+
];
29+
30+
// The taproot descriptor combines different spending paths and conditions, allowing the funds to be spent using
31+
// different methods depending on the desired conditions.
32+
33+
// tr({A},{{pkh({B}),{{multi_a(1,{C},{D}),and_v(v:pk({E}),after(10))}}}}) represents a taproot spending policy.
34+
// Let's break it down:
35+
//
36+
// * Key Spend Path
37+
// {A} represents the public key for the taproot key spending path.
38+
//
39+
// * Script Spend Paths
40+
// {B} represents the public key for the pay-to-pubkey-hash (P2PKH) spending path.
41+
// The multi_a(1,{C},{D}) construct represents a 1-of-2 multi-signature script condition.
42+
// It requires at least one signature from {C} and {D} to spend funds using the script spend path.
43+
// The and_v(v:pk({E}),after(10)) construct represents a combination of a P2PK script condition and a time lock.
44+
// It requires a valid signature from {E} and enforces a time lock of 10 blocks on spending funds.
45+
46+
// By constructing transactions using this taproot descriptor and signing them appropriately,
47+
// you can create flexible spending policies that enable different spending paths and conditions depending on the
48+
// transaction's inputs and outputs.
49+
let s = format!(
50+
"tr({},{{pkh({}),{{multi_a(1,{},{}),and_v(v:pk({}),after(10))}}}})",
51+
keys[0], // Key A
52+
keys[1], // Key B
53+
keys[2], // Key C
54+
keys[3], // Key D
55+
keys[4], // Key E
56+
);
57+
58+
let descriptor = Descriptor::from_str(&s).expect("parse descriptor string");
59+
assert!(descriptor.sanity_check().is_ok());
60+
61+
println!("Descriptor pubkey script: {}", descriptor.script_pubkey());
62+
println!("Descriptor address: {}", descriptor.address(Network::Regtest).unwrap());
63+
64+
let master_private_key_str = "KxQqtbUnMugSEbKHG3saknvVYux1cgFjFqWzMfwnFhLm8QrGq26v";
65+
let master_private_key =
66+
PrivateKey::from_str(master_private_key_str).expect("Can't create private key");
67+
println!("Master public key: {}", master_private_key.public_key(&secp256k1));
68+
69+
let backup1_private_key_str = "Kwb9oFfPNt6D3Fa9DCF5emRvLyJ3UUvCHnVxp4xf7bWDxWmeVdeH";
70+
let backup1_private =
71+
PrivateKey::from_str(backup1_private_key_str).expect("Can't create private key");
72+
73+
println!("Backup1 public key: {}", backup1_private.public_key(&secp256k1));
74+
75+
let backup2_private_key_str = "cPJFWUKk8sdL7pcDKrmNiWUyqgovimmhaaZ8WwsByDaJ45qLREkh";
76+
let backup2_private =
77+
PrivateKey::from_str(backup2_private_key_str).expect("Can't create private key");
78+
79+
println!("Backup2 public key: {}", backup2_private.public_key(&secp256k1));
80+
81+
let backup3_private_key_str = "cT5cH9UVm81W5QAf5KABXb23RKNSMbMzMx85y6R2mF42L94YwKX6";
82+
let _backup3_private =
83+
PrivateKey::from_str(backup3_private_key_str).expect("Can't create private key");
84+
85+
println!("Backup3 public key: {}", _backup3_private.public_key(&secp256k1));
86+
87+
// Create a spending transaction
88+
let spend_tx = Transaction {
89+
version: 2,
90+
lock_time: absolute::LockTime::Blocks(Height::ZERO),
91+
input: vec![],
92+
output: vec![],
93+
};
94+
95+
let hex_tx = "020000000001018ff27041f3d738f5f84fd5ee62f1c5b36afebfb15f6da0c9d1382ddd0eaaa23c0000000000feffffff02b3884703010000001600142ca3b4e53f17991582d47b15a053b3201891df5200e1f5050000000022512061763f4288d086c0347c4e3c387ce22ab9372cecada6c326e77efd57e9a5ea460247304402207b820860a9d425833f729775880b0ed59dd12b64b9a3d1ab677e27e4d6b370700220576003163f8420fe0b9dc8df726cff22cbc191104a2d4ae4f9dfedb087fcec72012103817e1da42a7701df4db94db8576f0e3605f3ab3701608b7e56f92321e4d8999100000000";
96+
let depo_tx: Transaction = deserialize(&Vec::<u8>::from_hex(hex_tx).unwrap()).unwrap();
97+
98+
let receiver = Address::from_str("bcrt1qsdks5za4t6sevaph6tz9ddfjzvhkdkxe9tfrcy").unwrap();
99+
100+
let amount = 100000000;
101+
102+
let (outpoint, witness_utxo) = get_vout(&depo_tx, descriptor.script_pubkey());
103+
104+
let all_assets = Descriptor::<DescriptorPublicKey>::from_str(&s)
105+
.unwrap()
106+
.all_assets()
107+
.unwrap();
108+
109+
for asset in all_assets {
110+
// Creating a PSBT Object
111+
let mut psbt = Psbt {
112+
unsigned_tx: spend_tx.clone(),
113+
unknown: BTreeMap::new(),
114+
proprietary: BTreeMap::new(),
115+
xpub: BTreeMap::new(),
116+
version: 0,
117+
inputs: vec![],
118+
outputs: vec![],
119+
};
120+
121+
// Defining the Transaction Input
122+
let mut txin = TxIn::default();
123+
txin.previous_output = outpoint;
124+
txin.sequence = Sequence::from_height(26); //Sequence::MAX; //
125+
psbt.unsigned_tx.input.push(txin);
126+
127+
// Defining the Transaction Output
128+
psbt.unsigned_tx.output.push(TxOut {
129+
script_pubkey: receiver.payload.script_pubkey(),
130+
value: amount / 5 - 500,
131+
});
132+
133+
psbt.unsigned_tx
134+
.output
135+
.push(TxOut { script_pubkey: descriptor.script_pubkey(), value: amount * 4 / 5 });
136+
137+
// Consider that out of all the keys required to sign the descriptor spend path we only have some handful of assets.
138+
// We can plan the PSBT with only few assets(keys or hashes) if that are enough for satisfying any policy.
139+
//
140+
// Here for example assume that we only have two keys available.
141+
// Key A and Key B (as seen from the descriptor above)
142+
// We have to add the keys to `Asset` and then obtain plan with only available signatures if the descriptor can be satisfied.
143+
144+
// Obtain the Plan based on available Assets
145+
let plan = descriptor.clone().plan(&asset).unwrap();
146+
147+
// Creating PSBT Input
148+
let mut input = psbt::Input::default();
149+
plan.update_psbt_input(&mut input);
150+
151+
// Update the PSBT input from the result which we have obtained from the Plan.
152+
input.update_with_descriptor_unchecked(&descriptor).unwrap();
153+
input.witness_utxo = Some(witness_utxo.clone());
154+
155+
// Push the PSBT Input and declare an PSBT Output Structure
156+
psbt.inputs.push(input);
157+
psbt.outputs.push(psbt::Output::default());
158+
159+
// Use private keys to sign
160+
let key_a = master_private_key.inner;
161+
let key_b = backup1_private.inner;
162+
163+
// Taproot script can be signed when we have either Key spend or Script spend or both.
164+
// Here you can try to verify by commenting one of the spend path or try signing with both.
165+
sign_taproot_psbt(&key_a, &mut psbt, &secp256k1); // Key Spend - With Key A
166+
sign_taproot_psbt(&key_b, &mut psbt, &secp256k1); // Script Spend - With Key B
167+
168+
// Serializing and finalizing the PSBT Transaction
169+
let serialized = psbt.serialize();
170+
println!("{}", base64::encode(&serialized));
171+
psbt.finalize_mut(&secp256k1).unwrap();
172+
173+
let tx = psbt.extract_tx();
174+
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
175+
}
176+
}
177+
178+
// Siging the Taproot PSBT Transaction
179+
fn sign_taproot_psbt(
180+
secret_key: &secp256k1::SecretKey,
181+
psbt: &mut psbt::Psbt,
182+
secp256k1: &Secp256k1<secp256k1::All>,
183+
) {
184+
// Creating signing entitites required
185+
let hash_ty = bitcoin::sighash::TapSighashType::Default;
186+
let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx);
187+
188+
// Defining Keypair for given private key
189+
let keypair = secp256k1::KeyPair::from_seckey_slice(&secp256k1, secret_key.as_ref()).unwrap();
190+
191+
// Checking if leaf hash exist or not.
192+
// For Key Spend -> Leaf Hash is None
193+
// For Script Spend -> Leaf Hash is Some(_)
194+
// Convert this leaf_hash tree to a full map.
195+
let (leaf_hashes, (_, _)) = &psbt.inputs[0].tap_key_origins[&keypair.x_only_public_key().0];
196+
let leaf_hash = if !leaf_hashes.is_empty() {
197+
Some(leaf_hashes[0])
198+
} else {
199+
None
200+
};
201+
202+
let keypair = match leaf_hash {
203+
None => keypair
204+
.tap_tweak(&secp256k1, psbt.inputs[0].tap_merkle_root)
205+
.to_inner(), // tweak for key spend
206+
Some(_) => keypair, // no tweak for script spend
207+
};
208+
209+
// Construct the message to input for schnorr signature
210+
let msg = psbt
211+
.sighash_msg(0, &mut sighash_cache, leaf_hash)
212+
.unwrap()
213+
.to_secp_msg();
214+
let sig = secp256k1.sign_schnorr(&msg, &keypair);
215+
let (pk, _parity) = keypair.x_only_public_key();
216+
assert!(secp256k1.verify_schnorr(&sig, &msg, &pk).is_ok());
217+
218+
// Create final signature with corresponding hash type
219+
let final_signature1 = taproot::Signature { hash_ty, sig };
220+
221+
if let Some(lh) = leaf_hash {
222+
// Script Spend
223+
psbt.inputs[0]
224+
.tap_script_sigs
225+
.insert((pk, lh), final_signature1);
226+
} else {
227+
// Key Spend
228+
psbt.inputs[0].tap_key_sig = Some(final_signature1);
229+
println!("{:#?}", psbt);
230+
}
231+
}
232+
233+
// Find the Outpoint by spk
234+
fn get_vout(tx: &Transaction, spk: ScriptBuf) -> (OutPoint, TxOut) {
235+
for (i, txout) in tx.clone().output.into_iter().enumerate() {
236+
if spk == txout.script_pubkey {
237+
return (OutPoint::new(tx.txid(), i as u32), txout);
238+
}
239+
}
240+
panic!("Only call get vout on functions which have the expected outpoint");
241+
}

src/descriptor/mod.rs

-2
Original file line numberDiff line numberDiff line change
@@ -639,9 +639,7 @@ impl Descriptor<DescriptorPublicKey> {
639639
Ok(asset_combination(k, &dpk_v))
640640
}
641641
WshInner::Ms(k) => {
642-
println!("{}", k);
643642
let a = k.all_assets();
644-
println!("{:#?}", a);
645643
Ok(a)
646644
}
647645
},

0 commit comments

Comments
 (0)