Skip to content

Commit ed61ee1

Browse files
committed
Adding Plan Example for taproot based on asset planning
Signed-off-by: Harshil Jani <[email protected]>
1 parent b4e31f8 commit ed61ee1

File tree

3 files changed

+245
-2
lines changed

3 files changed

+245
-2
lines changed

Diff for: 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"]

Diff for: 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+
}

Diff for: 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)