Skip to content

Commit

Permalink
Updating psbt_sign_finalize.rs with Plan API
Browse files Browse the repository at this point in the history
Recently we have had plan API into the rust-miniscript so this example
is being updated for better use of plan API with more clear explanation

Signed-off-by: Harshil Jani <harshiljani2002@gmail.com>
  • Loading branch information
Harshil-Jani committed Nov 11, 2023
1 parent 599ac6f commit d7c28c2
Showing 1 changed file with 125 additions and 86 deletions.
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";
Expand All @@ -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();

Expand All @@ -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
Expand Down

0 comments on commit d7c28c2

Please sign in to comment.