Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit cfbd65a

Browse files
committedAug 16, 2023
Updating psbt_sign_finalize example with planning module
Signed-off-by: Harshil Jani <harshiljani2002@gmail.com>
1 parent 3c56c5e commit cfbd65a

File tree

1 file changed

+128
-96
lines changed

1 file changed

+128
-96
lines changed
 

‎examples/psbt_sign_finalize.rs

+128-96
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,56 @@
1-
// SPDX-License-Identifier: CC0-1.0
2-
31
use std::collections::BTreeMap;
42
use std::str::FromStr;
53

4+
use bitcoin::sighash::SighashCache;
5+
use bitcoin::PrivateKey;
66
use miniscript::bitcoin::consensus::encode::deserialize;
77
use miniscript::bitcoin::hashes::hex::FromHex;
8-
use miniscript::bitcoin::psbt::{self, Psbt};
9-
use miniscript::bitcoin::sighash::SighashCache;
8+
use miniscript::bitcoin::psbt::PartiallySignedTransaction as Psbt;
109
use miniscript::bitcoin::{
11-
self, base64, secp256k1, Address, Network, OutPoint, PrivateKey, Script, Sequence, Transaction,
12-
TxIn, TxOut,
10+
self, base64, psbt, secp256k1, Address, Network, OutPoint, Script, Sequence, Transaction, TxIn,
11+
TxOut,
1312
};
1413
use miniscript::psbt::{PsbtExt, PsbtInputExt};
15-
use miniscript::Descriptor;
14+
use miniscript::{Descriptor, DescriptorPublicKey};
1615

1716
fn main() {
17+
// Defining the descriptor keys
1818
let secp256k1 = secp256k1::Secp256k1::new();
19-
20-
let s = "wsh(t:or_c(pk(027a3565454fe1b749bccaef22aff72843a9c3efefd7b16ac54537a0c23f0ec0de),v:thresh(1,pkh(032d672a1a91cc39d154d366cd231983661b0785c7f27bc338447565844f4a6813),a:pkh(03417129311ed34c242c012cd0a3e0b9bca0065f742d0dfb63c78083ea6a02d4d9),a:pkh(025a687659658baeabdfc415164528065be7bcaade19342241941e556557f01e28))))#7hut9ukn";
21-
let bridge_descriptor = Descriptor::from_str(&s).unwrap();
22-
//let bridge_descriptor = Descriptor::<bitcoin::PublicKey>::from_str(&s).expect("parse descriptor string");
23-
assert!(bridge_descriptor.sanity_check().is_ok());
24-
println!(
25-
"Bridge pubkey script: {}",
26-
bridge_descriptor.script_pubkey()
19+
let keys = vec![
20+
"027a3565454fe1b749bccaef22aff72843a9c3efefd7b16ac54537a0c23f0ec0de",
21+
"032d672a1a91cc39d154d366cd231983661b0785c7f27bc338447565844f4a6813",
22+
"03417129311ed34c242c012cd0a3e0b9bca0065f742d0dfb63c78083ea6a02d4d9",
23+
"025a687659658baeabdfc415164528065be7bcaade19342241941e556557f01e28",
24+
];
25+
// The wsh descriptor indicates a Witness Script Hash, meaning the descriptor is for a SegWit script.
26+
// wsh(or(pk(A),thresh(1,pkh(B),pkh(C),pkh(D))))
27+
28+
// Let's break it down:
29+
// t:or_c specifies an "or" construct, which means the script can be satisfied by one of the given conditions:
30+
// pk(A) OR thresh(1,pkh(B),pkh(C),pkh(D))
31+
// Inside threshold condition atleast 1 out of all given conditions should satisfy.
32+
33+
// By constructing transactions using this wsh descriptor and signing them appropriately,
34+
// you can create flexible spending policies that enable different spending paths and conditions depending on the
35+
// transaction's inputs and outputs.
36+
let s = format!(
37+
"wsh(t:or_c(pk({}),v:thresh(1,pkh({}),a:pkh({}),a:pkh({}))))",
38+
keys[0], // key A
39+
keys[1], // key B
40+
keys[2], // key C
41+
keys[3], // key D
2742
);
43+
let descriptor = Descriptor::from_str(&s).expect("parse descriptor string");
44+
45+
assert!(descriptor.sanity_check().is_ok());
46+
println!("descriptor pubkey script: {}", descriptor.script_pubkey());
2847
println!(
29-
"Bridge address: {}",
30-
bridge_descriptor.address(Network::Regtest).unwrap()
48+
"descriptor address: {}",
49+
descriptor.address(Network::Regtest).unwrap()
3150
);
3251
println!(
3352
"Weight for witness satisfaction cost {}",
34-
bridge_descriptor.max_weight_to_satisfy().unwrap()
53+
descriptor.max_weight_to_satisfy().unwrap()
3554
);
3655

3756
let master_private_key_str = "cQhdvB3McbBJdx78VSSumqoHQiSXs75qwLptqwxSQBNBMDxafvaw";
@@ -69,24 +88,14 @@ fn main() {
6988
_backup3_private.public_key(&secp256k1)
7089
);
7190

91+
// Create a spending transaction
7292
let spend_tx = Transaction {
7393
version: 2,
7494
lock_time: bitcoin::absolute::LockTime::from_consensus(5000),
7595
input: vec![],
7696
output: vec![],
7797
};
7898

79-
// Spend one input and spend one output for simplicity.
80-
let mut psbt = Psbt {
81-
unsigned_tx: spend_tx,
82-
unknown: BTreeMap::new(),
83-
proprietary: BTreeMap::new(),
84-
xpub: BTreeMap::new(),
85-
version: 0,
86-
inputs: vec![],
87-
outputs: vec![],
88-
};
89-
9099
let hex_tx = "020000000001018ff27041f3d738f5f84fd5ee62f1c5b36afebfb15f6da0c9d1382ddd0eaaa23c0000000000feffffff02b3884703010000001600142ca3b4e53f17991582d47b15a053b3201891df5200e1f50500000000220020c0ebf552acd2a6f5dee4e067daaef17b3521e283aeaa44a475278617e3d2238a0247304402207b820860a9d425833f729775880b0ed59dd12b64b9a3d1ab677e27e4d6b370700220576003163f8420fe0b9dc8df726cff22cbc191104a2d4ae4f9dfedb087fcec72012103817e1da42a7701df4db94db8576f0e3605f3ab3701608b7e56f92321e4d8999100000000";
91100
let depo_tx: Transaction = deserialize(&Vec::<u8>::from_hex(hex_tx).unwrap()).unwrap();
92101

@@ -96,76 +105,99 @@ fn main() {
96105

97106
let amount = 100000000;
98107

99-
let (outpoint, witness_utxo) = get_vout(&depo_tx, &bridge_descriptor.script_pubkey());
100-
101-
let mut txin = TxIn::default();
102-
txin.previous_output = outpoint;
103-
104-
txin.sequence = Sequence::from_height(26); //Sequence::MAX; //
105-
psbt.unsigned_tx.input.push(txin);
106-
107-
psbt.unsigned_tx.output.push(TxOut {
108-
script_pubkey: receiver.script_pubkey(),
109-
value: amount / 5 - 500,
110-
});
111-
112-
psbt.unsigned_tx.output.push(TxOut {
113-
script_pubkey: bridge_descriptor.script_pubkey(),
114-
value: amount * 4 / 5,
115-
});
116-
117-
// Generating signatures & witness data
108+
let (outpoint, witness_utxo) = get_vout(&depo_tx, &descriptor.script_pubkey());
118109

119-
let mut input = psbt::Input::default();
120-
input
121-
.update_with_descriptor_unchecked(&bridge_descriptor)
122-
.unwrap();
123-
124-
input.witness_utxo = Some(witness_utxo.clone());
125-
psbt.inputs.push(input);
126-
psbt.outputs.push(psbt::Output::default());
127-
128-
let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx);
129-
130-
let msg = psbt
131-
.sighash_msg(0, &mut sighash_cache, None)
110+
let all_assets = Descriptor::<DescriptorPublicKey>::from_str(&s)
132111
.unwrap()
133-
.to_secp_msg();
134-
135-
// Fixme: Take a parameter
136-
let hash_ty = bitcoin::sighash::EcdsaSighashType::All;
137-
138-
let sk1 = backup1_private.inner;
139-
let sk2 = backup2_private.inner;
140-
141-
// Finally construct the signature and add to psbt
142-
let sig1 = secp256k1.sign_ecdsa(&msg, &sk1);
143-
let pk1 = backup1_private.public_key(&secp256k1);
144-
assert!(secp256k1.verify_ecdsa(&msg, &sig1, &pk1.inner).is_ok());
145-
146-
// Second key just in case
147-
let sig2 = secp256k1.sign_ecdsa(&msg, &sk2);
148-
let pk2 = backup2_private.public_key(&secp256k1);
149-
assert!(secp256k1.verify_ecdsa(&msg, &sig2, &pk2.inner).is_ok());
150-
151-
psbt.inputs[0].partial_sigs.insert(
152-
pk1,
153-
bitcoin::ecdsa::Signature {
154-
sig: sig1,
155-
hash_ty: hash_ty,
156-
},
157-
);
158-
159-
println!("{:#?}", psbt);
160-
161-
let serialized = psbt.serialize();
162-
println!("{}", base64::encode(&serialized));
163-
164-
psbt.finalize_mut(&secp256k1).unwrap();
165-
println!("{:#?}", psbt);
112+
.all_assets()
113+
.unwrap();
166114

167-
let tx = psbt.extract_tx();
168-
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
115+
for asset in all_assets {
116+
// Spend one input and spend one output for simplicity.
117+
let mut psbt = Psbt {
118+
unsigned_tx: spend_tx.clone(),
119+
unknown: BTreeMap::new(),
120+
proprietary: BTreeMap::new(),
121+
xpub: BTreeMap::new(),
122+
version: 0,
123+
inputs: vec![],
124+
outputs: vec![],
125+
};
126+
127+
// Defining the Transaction Input
128+
let mut txin = TxIn::default();
129+
txin.previous_output = outpoint;
130+
txin.sequence = Sequence::from_height(26); //Sequence::MAX; //
131+
psbt.unsigned_tx.input.push(txin);
132+
133+
// Defining the Transaction Output
134+
psbt.unsigned_tx.output.push(TxOut {
135+
script_pubkey: receiver.script_pubkey(),
136+
value: amount / 5 - 500,
137+
});
138+
139+
psbt.unsigned_tx.output.push(TxOut {
140+
script_pubkey: descriptor.script_pubkey(),
141+
value: amount * 4 / 5,
142+
});
143+
144+
// Consider that out of all the keys required to sign the descriptor, we only have some handful of assets.
145+
// We can plan the PSBT with only few assets(keys or hashes) if that are enough for satisfying any policy.
146+
//
147+
// Here for example assume that we only have one key available i.e Key A(as seen from the descriptor above)
148+
// Key A is enough to satisfy the given descriptor because it is OR.
149+
// We have to add the key to `Asset` and then obtain plan with only available signature if the descriptor can be satisfied.
150+
151+
// Check the possible asset which we can use
152+
println!("{:#?}", asset);
153+
154+
// Obtain the Plan based on available Assets
155+
let plan = descriptor.clone().plan(&asset).unwrap();
156+
157+
// Creating a PSBT Input
158+
let mut input = psbt::Input::default();
159+
160+
// Update the PSBT input from the result which we have obtained from the Plan.
161+
plan.update_psbt_input(&mut input);
162+
input.update_with_descriptor_unchecked(&descriptor).unwrap();
163+
input.witness_utxo = Some(witness_utxo.clone());
164+
165+
// Push the PSBT Input and declare an PSBT Output Structure
166+
psbt.inputs.push(input);
167+
psbt.outputs.push(psbt::Output::default());
168+
169+
let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx);
170+
171+
let msg = psbt
172+
.sighash_msg(0, &mut sighash_cache, None)
173+
.unwrap()
174+
.to_secp_msg();
175+
176+
// Fixme: Take a parameter
177+
let hash_ty = bitcoin::sighash::EcdsaSighashType::All;
178+
179+
let sk = backup1_private.inner;
180+
181+
// Finally construct the signature and add to psbt
182+
let sig = secp256k1.sign_ecdsa(&msg, &sk);
183+
let key_a = backup1_private.public_key(&secp256k1);
184+
assert!(secp256k1.verify_ecdsa(&msg, &sig, &key_a.inner).is_ok());
185+
186+
psbt.inputs[0]
187+
.partial_sigs
188+
.insert(key_a, bitcoin::ecdsa::Signature { sig, hash_ty });
189+
190+
println!("{:#?}", psbt);
191+
192+
let serialized = psbt.serialize();
193+
println!("{}", base64::encode(&serialized));
194+
195+
psbt.finalize_mut(&secp256k1).unwrap();
196+
println!("{:#?}", psbt);
197+
198+
let tx = psbt.extract_tx();
199+
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
200+
}
169201
}
170202

171203
// Find the Outpoint by spk

0 commit comments

Comments
 (0)
Please sign in to comment.