From bee48ff264795ba0ae1b68fec0385010a7ddfec9 Mon Sep 17 00:00:00 2001 From: Harshil Jani Date: Mon, 25 Sep 2023 18:32:50 +0530 Subject: [PATCH] Updating psbt_sign_finalize example with planning module Signed-off-by: Harshil Jani --- examples/psbt_sign_finalize.rs | 211 +++++++++++++++++++-------------- 1 file changed, 125 insertions(+), 86 deletions(-) diff --git a/examples/psbt_sign_finalize.rs b/examples/psbt_sign_finalize.rs index 08abd5abe..b90016802 100644 --- a/examples/psbt_sign_finalize.rs +++ b/examples/psbt_sign_finalize.rs @@ -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::::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"; @@ -51,6 +73,7 @@ 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), @@ -58,17 +81,6 @@ fn main() { 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::::from_hex(hex_tx).unwrap()).unwrap(); @@ -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::::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