Skip to content

Commit 0ffd4a2

Browse files
committed
Using planning API for taproot example
Signed-off-by: Harshil Jani <[email protected]>
1 parent 09ee5b5 commit 0ffd4a2

File tree

2 files changed

+237
-0
lines changed

2 files changed

+237
-0
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ required-features = ["compiler","std"]
6868
name = "psbt_sign_finalize"
6969
required-features = ["std", "base64"]
7070

71+
[[example]]
72+
name = "plan_spend"
73+
required-features = ["std", "base64"]
74+
7175
[workspace]
7276
members = ["bitcoind-tests", "fuzz"]
7377
exclude = ["embedded"]

examples/plan_spend.rs

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

0 commit comments

Comments
 (0)