From 22224c3e078f9d5798fd52b67b928fc598d1d5ea Mon Sep 17 00:00:00 2001 From: Tobin Harding Date: Mon, 4 Apr 2022 10:18:31 +1000 Subject: [PATCH] WIP: Add improvements to psbt example This should be squashed with the previous commit if you are happy with it Dan. --- examples/psbt.rs | 98 +++++++++++++++--------------------------------- 1 file changed, 31 insertions(+), 67 deletions(-) diff --git a/examples/psbt.rs b/examples/psbt.rs index 0a7eafd554..354d8c3db1 100644 --- a/examples/psbt.rs +++ b/examples/psbt.rs @@ -7,6 +7,23 @@ //! The private keys in the tests below are derived from the following master private key: //! +// Attribution: `sign_psbt` logic is based on code in the `wallet` module of the `bdk` project. The +// code in the two functions `legacy_sighash()` and `segwit_v0_sighash` is copied directly from +// `bdk`, hence their licence applies to that code. +// +// ref: http://github.com/bitcoindevkit/bdk +// +// Bitcoin Dev Kit +// Written in 2020 by Alekos Filini +// +// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + extern crate bitcoin; use std::fmt; @@ -25,9 +42,6 @@ use bitcoin::util::bip32::{ExtendedPrivKey, ExtendedPubKey, Fingerprint, IntoDer use bitcoin::util::psbt::{PartiallySignedTransaction, PsbtSigHashType}; use bitcoin::util::sighash::SigHashCache; -// Enable this to print debugging output. -const DEBUG: bool = false; - const NETWORK: Network = Network::Testnet; type Psbt = PartiallySignedTransaction; // TODO: Consider adding this alias to `rust-bitcoin`. @@ -133,6 +147,7 @@ fn build_extended_private_key() -> ExtendedPrivKey { let ext_priv = ExtendedPrivKey::from_str(extended_private_key).unwrap(); + // TODO: Fix this so parsing seed works. let seed = base58::from(&seed).unwrap(); let seeded = ExtendedPrivKey::new_master(NETWORK, &seed).unwrap(); if seeded != ext_priv { @@ -181,7 +196,7 @@ fn create_transaction() -> Transaction { vout: input_0.index, }, script_sig: Script::new(), - sequence: 0xFFFFFFFF, // TODO: Consider adding a const that documents why this is u32_max. + sequence: 0xFFFFFFFF, // Disable nSequence. witness: Witness::default(), }, TxIn { @@ -243,9 +258,6 @@ fn update_psbt(mut psbt: Psbt, fingerprint: Fingerprint) -> Psbt { let expected_psbt_hex = include_str!("data/update_1_psbt_hex"); - print_transaction(previous_tx_0, "Previous transaction 0"); - print_transaction(previous_tx_1, "Previous transaction 1"); - let mut input_0 = psbt.inputs[0].clone(); let v = Vec::from_hex(&previous_tx_1).unwrap(); @@ -376,23 +388,11 @@ fn combine(mut this: Psbt, that: Psbt) -> Psbt { fn finalize(psbt: Psbt) -> Psbt { let expected_psbt_hex = include_str!("data/finalize_psbt_hex"); - // let msg = "Pre-finalize PSBT"; - // println!("\n\n{}\n", msg); - // println!("{:#?}", psbt); - let psbt = finalize_psbt(psbt); let want = hex_psbt!(expected_psbt_hex).unwrap(); assert_eq!(psbt, want); - // let msg = "Expected finalized PSBT"; - // println!("\n\n{}\n", msg); - // println!("{:#?}", want); - - // let msg = "Actual finalized PSBT"; - // println!("\n\n{}\n", msg); - // println!("{:#?}", psbt); - let got = serialize_hex(&psbt); assert_eq!(got, expected_psbt_hex); @@ -451,6 +451,7 @@ fn sign(mut psbt: Psbt, keys: &[PrivateKey]) -> Psbt { } /// Signs `psbt` input at `input_index` using `sk`. +// TODO: Consider adding this code to rust-bitcoin. fn sign_psbt( sk: &PrivateKey, psbt: &mut Psbt, @@ -516,17 +517,15 @@ fn sign_psbt( let mut final_signature = Vec::with_capacity(75); final_signature.extend_from_slice(&signature.serialize_der()); - // Why only a single byte, this won't work for non-standard sighash types? final_signature.push(sighash_type.to_u32() as u8); - // QUESTION: In bdk source code we insert into partial_sigs but according to - // the BIP test vector we only return the new signatures? psbt.inputs[input_index] .partial_sigs = BTreeMap::from([(pubkey, EcdsaSig::from_slice(&final_signature).map_err(|_| SignError::Ecdsa)?)]); Ok(()) } +// Copied directly from `impl ComputeSighash for Legacy` in `bdk`. fn legacy_sighash(psbt: &Psbt, input_index: usize) -> Result<(SigHash, EcdsaSigHashType), SignError> { if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() { return Err(SignError::InputIndexOutOfRange); @@ -559,6 +558,7 @@ fn legacy_sighash(psbt: &Psbt, input_index: usize) -> Result<(SigHash, EcdsaSigH )) } +// Copied directly from `impl ComputeSighash for Segwitv0` in `bdk`. fn segwit_v0_sighash(psbt: &Psbt, input_index: usize) -> Result<(SigHash, EcdsaSigHashType), SignError> { if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() { return Err(SignError::InputIndexOutOfRange); @@ -625,10 +625,11 @@ fn finalize_psbt(mut psbt: Psbt) -> Psbt { use bitcoin::util::psbt::serialize::Serialize; // Input 0: legacy UTXO + let sigs: Vec<_> = psbt.inputs[0].partial_sigs.values().collect(); let script_sig = script::Builder::new() - // OP_CHECKMULTISIG bug pops +1 value when evaluating. Need extra OP_0 byte in front - // Why is OP_0 called `OP_PUSHBYTES_0` in rust-bitcoin? + // OP_CHECKMULTISIG bug pops +1 value when evaluating so push OP_0. + // TODO: Consider adding OP_ZERO alias to rust-bitcoin (`pub static OP_ZERO: All = all::OP_PUSHBYTES_0;`). .push_opcode(opcodes::all::OP_PUSHBYTES_0) .push_slice(&sigs[0].serialize()) .push_slice(&sigs[1].serialize()) @@ -652,7 +653,7 @@ fn finalize_psbt(mut psbt: Psbt) -> Psbt { let sigs: Vec<_> = psbt.inputs[1].partial_sigs.values().collect(); let mut script_witness: Witness = Witness::new(); - script_witness.push([ ]); // What's the cleanest 0x00 push? + script_witness.push([]); // Pushes 0x00 to the stack. script_witness.push(&sigs[1].serialize()); script_witness.push(&sigs[0].serialize()); script_witness.push(&psbt.inputs[1].witness_script.clone().unwrap().serialize()); @@ -678,63 +679,26 @@ fn p2wpkh_script_code(script: &Script) -> Script { .into_script() } -/// Deserializes `hex` string and pretty prints the PSBT. -fn pretty_print_psbt(hex: &str, msg: &str) { - if DEBUG { - let v = Vec::from_hex(&hex).unwrap(); - let psbt: Psbt = deserialize(&v).unwrap(); - println!("\n\n{}\n", msg); - println!("{:#?}", psbt); - } -} - -/// Deserializes `hex` string and pretty prints the Transaction. -fn print_transaction(hex: &str, msg: &str) { - if DEBUG { - let v = Vec::from_hex(&hex).unwrap(); - let tx: Transaction = deserialize(&v).unwrap(); - println!("\n\n{}\n", msg); - println!("{:?}", tx); - } -} - -// TODO: Clean up this error. -/// Signing error +/// Signing error. #[derive(Debug, PartialEq, Eq, Clone)] pub enum SignError { - /// The private key is missing for the required public key - MissingKey, - /// The private key in use has the right fingerprint but derives differently than expected - InvalidKey, - /// The user canceled the operation - UserCanceled, /// Input index is out of range InputIndexOutOfRange, /// The `non_witness_utxo` field of the transaction is required to sign this input MissingNonWitnessUtxo, /// The `non_witness_utxo` specified is invalid InvalidNonWitnessUtxo, - /// The `witness_utxo` field of the transaction is required to sign this input - MissingWitnessUtxo, /// The `witness_script` field of the transaction is required to sign this input MissingWitnessScript, - /// The fingerprint and derivation path are missing from the psbt input - MissingHdKeypath, - /// The psbt contains a non-`SIGHASH_ALL` sighash in one of its input and the user hasn't - /// explicitly allowed them - /// - /// To enable signing transactions with non-standard sighashes set - /// [`SignOptions::allow_all_sighashes`] to `true`. - NonStandardSighash, /// bitcoin::ecdsa error. Ecdsa, - /// + /// Sighash encoding error. SigHash, - /// + /// BIP174: non-witness input txid must match txid of unsigned transaction. InvalidTxid, - /// + /// BIP174: witness script must match the hash in the redeem script. WitnessRedeemMismatch, - /// + /// BIP174: redeem script must match the hash in the UTXO. ScritpPubkeyMismatch, }