From 4efcb531a502e9cff0d3005d62177ea7178cd1ed Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 7 Aug 2024 12:12:10 +1000 Subject: [PATCH] Upgrade to latest bitcoin Includes upgrade to: - bitcoin v0.32.2 - miniscript v12.2.0 - secp256k1 v0.29.0 - bitcoin-internals v0.3.0 To upgrade we need to make the v0 module mirror the code in rust-bitcoin::psbt, by diffing the files that is reasonably easy to do and only slightly error prone - would be great if someone else would diff and view as well. It gets wild in the v2 module because we copied code from the v0 module then modified it for PSBT v2. All changes to the v2 module need going over very carefully. --- Cargo.toml | 20 +- contrib/test.sh | 11 +- examples/v0.rs | 9 +- examples/v2.rs | 10 +- justfile | 4 + rustfmt.toml | 2 +- src/lib.rs | 12 +- src/raw.rs | 33 ++-- src/serialize.rs | 8 +- src/v0/bitcoin/error.rs | 6 +- src/v0/bitcoin/macros.rs | 10 +- src/v0/bitcoin/map/global.rs | 4 +- src/v0/bitcoin/map/input.rs | 5 +- src/v0/bitcoin/mod.rs | 347 ++++++++++++++++++++++++++------- src/v0/bitcoin/raw.rs | 31 +-- src/v0/bitcoin/serialize.rs | 5 +- src/v0/miniscript/finalizer.rs | 34 +++- src/v0/miniscript/mod.rs | 83 ++++---- src/v0/mod.rs | 2 +- src/v2/error.rs | 26 ++- src/v2/map/global.rs | 4 +- src/v2/map/input.rs | 9 +- src/v2/map/output.rs | 9 +- src/v2/miniscript/finalize.rs | 35 ++-- src/v2/miniscript/satisfy.rs | 28 +-- src/v2/mod.rs | 35 ++-- 26 files changed, 500 insertions(+), 282 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 562c421..dac050a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,24 +18,22 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std"] -std = ["bitcoin/std"] -no-std = ["bitcoin/no-std", "core2"] +std = ["bitcoin/std", "bitcoin-internals/std"] +rand = ["bitcoin/rand"] rand-std = ["bitcoin/rand-std"] -serde = ["actual-serde", "bitcoin/serde"] +serde = ["actual-serde", "bitcoin/serde", "bitcoin-internals/serde"] base64 = ["bitcoin/base64"] - miniscript-std = ["std", "miniscript/std"] -miniscript-no-std = ["no-std", "miniscript/no-std"] +miniscript-no-std = ["miniscript/no-std"] [dependencies] -bitcoin = { version = "0.31.0", default-features = false } +bitcoin = { version = "0.32.2", default-features = false } +bitcoin-internals = { version = "0.3.0", features = ["alloc"] } -# Do not use this feature, use "miniscript-std" or "miniscript-no-std" instead. -miniscript = { version = "11.0.0", default-features = false, optional = true } +# Do NOT use this feature! Use one of the `miniscript-` features instead. +miniscript = { version = "12.2.0", default-features = false, optional = true } # Do NOT use this as a feature! Use the `serde` feature instead. actual-serde = { package = "serde", version = "1.0.103", default-features = false, features = [ "derive", "alloc" ], optional = true } -# There is no reason to use this dependency directly, it is activated by the "no-std" feature. -core2 = { version = "0.3.2", default-features = false, features = ["alloc"], optional = true } [dev-dependencies] anyhow = "1" @@ -43,7 +41,7 @@ serde_json = "1.0.0" serde_test = "1.0.19" serde_derive = "1.0.103" bincode = "1.3.1" -secp256k1 = { version = "0.28", features = ["rand-std", "global-context"] } +secp256k1 = { version = "0.29", features = ["rand-std", "global-context"] } [[example]] name = "v0" diff --git a/contrib/test.sh b/contrib/test.sh index 4beeb3b..4f314df 100755 --- a/contrib/test.sh +++ b/contrib/test.sh @@ -49,21 +49,18 @@ cargo run --example v2-separate-creator-constructor if [ "$DO_NO_STD" = true ] then # Build no_std, to make sure that cfg(test) doesn't hide any issues - cargo build --no-default-features --features="no-std" - - # Build std + no_std, to make sure they are not incompatible - cargo build --features="no-std" + cargo build --no-default-features # Test no_std - cargo test --no-default-features --features="no-std" + cargo test --no-default-features # Build all features - cargo build --no-default-features --features="no-std $FEATURES" + cargo build --no-default-features --features="$FEATURES" # Build specific features for feature in ${FEATURES} do - cargo build --no-default-features --features="no-std $feature" + cargo build --no-default-features --features="$feature" done fi diff --git a/examples/v0.rs b/examples/v0.rs index a0a602c..3aa3d58 100644 --- a/examples/v0.rs +++ b/examples/v0.rs @@ -13,8 +13,8 @@ use psbt_v2::bitcoin::locktime::absolute; use psbt_v2::bitcoin::opcodes::all::OP_CHECKMULTISIG; use psbt_v2::bitcoin::secp256k1::{self, rand, SECP256K1}; use psbt_v2::bitcoin::{ - script, transaction, Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Sequence, - Transaction, TxIn, TxOut, Txid, Witness, + script, transaction, Address, Amount, CompressedPublicKey, Network, OutPoint, PublicKey, + ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness, }; use psbt_v2::v0::{self, Psbt}; @@ -126,8 +126,9 @@ impl Alice { let out = OutPoint { txid: Txid::all_zeros(), vout: 0 }; // The usual caveat about reusing addresses applies here, this is just an example. - let address = - Address::p2wpkh(&self.public_key(), Network::Bitcoin).expect("uncompressed key"); + let compressed = + CompressedPublicKey::try_from(self.public_key()).expect("uncompressed key"); + let address = Address::p2wpkh(&compressed, Network::Bitcoin); // This is a made up value, it is supposed to represent the outpoints value minus the value // contributed to the multisig. diff --git a/examples/v2.rs b/examples/v2.rs index 7b7a8f8..9d657fd 100644 --- a/examples/v2.rs +++ b/examples/v2.rs @@ -16,7 +16,8 @@ use psbt_v2::bitcoin::locktime::absolute; use psbt_v2::bitcoin::opcodes::all::OP_CHECKMULTISIG; use psbt_v2::bitcoin::secp256k1::{self, SECP256K1}; use psbt_v2::bitcoin::{ - script, Address, Amount, Network, OutPoint, PublicKey, ScriptBuf, Sequence, TxOut, Txid, + script, Address, Amount, CompressedPublicKey, Network, OutPoint, PublicKey, ScriptBuf, + Sequence, TxOut, Txid, }; use psbt_v2::v2::{ self, Constructor, InputBuilder, Modifiable, Output, OutputBuilder, Psbt, Signer, Updater, @@ -147,8 +148,9 @@ impl Alice { let out = OutPoint { txid: Txid::all_zeros(), vout: 0 }; // The usual caveat about reusing addresses applies here, this is just an example. - let address = Address::p2wpkh(&self.multisig_public_key()?, Network::Bitcoin) - .expect("uncompressed key"); + let compressed = + CompressedPublicKey::try_from(self.multisig_public_key()?).expect("uncompressed key"); + let address = Address::p2wpkh(&compressed, Network::Bitcoin); // This is a made up value, it is supposed to represent the outpoints value minus the value // contributed to the multisig. @@ -250,7 +252,7 @@ impl Entity { let path = DerivationPath::from_str(derivation_path)?; let xpriv = self.master.derive_priv(SECP256K1, &path)?; let pk = Xpub::from_priv(SECP256K1, &xpriv); - Ok(pk.to_pub()) + Ok(pk.to_pub().into()) } /// Returns a dummy utxo that we can spend. diff --git a/justfile b/justfile index 2c42dc3..1d7022b 100644 --- a/justfile +++ b/justfile @@ -18,6 +18,10 @@ test: lint: cargo clippy --all --all-targets --all-features -- --deny warnings +# Run the formatter +fmt: + cargo +nightly fmt --all + # Check the formatting format: cargo +nightly fmt --all --check diff --git a/rustfmt.toml b/rustfmt.toml index 7fa503c..5df9d54 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -72,7 +72,7 @@ color = "Auto" unstable_features = false disable_all_formatting = false skip_children = false -hide_parse_errors = false +show_parse_errors = true error_on_line_overflow = false error_on_unformatted = false emit_mode = "Files" diff --git a/src/lib.rs b/src/lib.rs index 27425ba..5d904db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,15 +14,9 @@ // Coding conventions #![warn(missing_docs)] -#[cfg(not(any(feature = "std", feature = "no-std")))] -compile_error!("at least one of the `std` or `no-std` features must be enabled"); - #[macro_use] extern crate alloc; -#[cfg(not(feature = "std"))] -extern crate core2; - #[cfg(feature = "serde")] #[macro_use] extern crate actual_serde as serde; @@ -48,11 +42,7 @@ pub mod v0; pub mod v2; mod version; -#[cfg(feature = "std")] -use std::io; - -#[cfg(not(feature = "std"))] -use core2::io; +use bitcoin::io; #[rustfmt::skip] // Keep pubic re-exports separate #[doc(inline)] diff --git a/src/raw.rs b/src/raw.rs index 70d069d..a2c0b0a 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -18,9 +18,10 @@ use bitcoin::consensus::encode::{ }; use bitcoin::hex::DisplayHex; +use crate::io::{self, BufRead, Write}; use crate::prelude::*; +use crate::serialize; use crate::serialize::{Deserialize, Serialize}; -use crate::{io, serialize}; /// A PSBT key-value pair in its raw byte form. /// @@ -39,7 +40,7 @@ pub struct Pair { } impl Pair { - pub(crate) fn decode(r: &mut R) -> Result { + pub(crate) fn decode(r: &mut R) -> Result { Ok(Pair { key: Key::decode(r)?, value: Decodable::consensus_decode(r)? }) } } @@ -80,13 +81,12 @@ pub struct Key { /// The `keytype` of this PSBT map key (`keytype`). pub type_value: u8, /// The `keydata` itself in raw byte form. - // TODO: Consider renaming to `data`. #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::hex_bytes"))] pub key: Vec, } impl Key { - pub(crate) fn decode(r: &mut R) -> Result { + pub(crate) fn decode(r: &mut R) -> Result { let VarInt(byte_size): VarInt = Decodable::consensus_decode(r)?; if byte_size == 0 { @@ -186,7 +186,7 @@ impl Encodable for ProprietaryKey where Subtype: Copy + From + Into, { - fn consensus_encode(&self, w: &mut W) -> Result { + fn consensus_encode(&self, w: &mut W) -> Result { let mut len = self.prefix.consensus_encode(w)? + 1; w.emit_u8(self.subtype.into())?; w.write_all(&self.key)?; @@ -199,26 +199,15 @@ impl Decodable for ProprietaryKey where Subtype: Copy + From + Into, { - fn consensus_decode(r: &mut R) -> Result { + fn consensus_decode(r: &mut R) -> Result { let prefix = Vec::::consensus_decode(r)?; let subtype = Subtype::from(r.read_u8()?); - let key = read_to_end(r)?; - Ok(ProprietaryKey { prefix, subtype, key }) - } -} + // The limit is a DOS protection mechanism the exact value is not + // important, 1024 bytes is bigger than any key should be. + let mut key = vec![]; + let _ = r.read_to_limit(&mut key, 1024)?; -// core2 doesn't have read_to_end -pub(crate) fn read_to_end(mut d: D) -> Result, io::Error> { - let mut result = vec![]; - let mut buf = [0u8; 64]; - loop { - match d.read(&mut buf) { - Ok(0) => break, - Ok(n) => result.extend_from_slice(&buf[0..n]), - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} - Err(e) => return Err(e), - }; + Ok(ProprietaryKey { prefix, subtype, key }) } - Ok(result) } diff --git a/src/serialize.rs b/src/serialize.rs index e14bafe..8c4bfc2 100644 --- a/src/serialize.rs +++ b/src/serialize.rs @@ -389,7 +389,7 @@ pub enum Error { /// Serialization error in bitcoin consensus-encoded structures ConsensusEncoding(consensus::encode::Error), /// Parsing error indicating invalid public keys - InvalidPublicKey(bitcoin::key::Error), + InvalidPublicKey(bitcoin::key::FromSliceError), /// Parsing error indicating invalid secp256k1 public keys InvalidSecp256k1PublicKey(secp256k1::Error), /// Parsing error indicating invalid xonly public keys @@ -410,7 +410,7 @@ pub enum Error { /// PSBT data is not consumed entirely PartialDataConsumption, /// Couldn't converting parsed u32 to a lock time. - LockTime(absolute::Error), + LockTime(absolute::ConversionError), /// Unsupported PSBT version. UnsupportedVersion(version::UnsupportedVersionError), } @@ -480,8 +480,8 @@ impl From for Error { fn from(e: consensus::encode::Error) -> Self { Self::ConsensusEncoding(e) } } -impl From for Error { - fn from(e: absolute::Error) -> Self { Self::LockTime(e) } +impl From for Error { + fn from(e: absolute::ConversionError) -> Self { Self::LockTime(e) } } impl From for Error { diff --git a/src/v0/bitcoin/error.rs b/src/v0/bitcoin/error.rs index e17721d..0a16f27 100644 --- a/src/v0/bitcoin/error.rs +++ b/src/v0/bitcoin/error.rs @@ -78,7 +78,7 @@ pub enum Error { /// Integer overflow in fee calculation FeeOverflow, /// Parsing error indicating invalid public keys - InvalidPublicKey(bitcoin::key::Error), + InvalidPublicKey(bitcoin::key::FromSliceError), /// Parsing error indicating invalid secp256k1 public keys InvalidSecp256k1PublicKey(secp256k1::Error), /// Parsing error indicating invalid xonly public keys @@ -133,8 +133,8 @@ impl fmt::Display for Error { UnexpectedUnsignedTx { expected: ref e, actual: ref a } => write!( f, "different unsigned transaction: expected {}, actual {}", - e.txid(), - a.txid() + e.compute_txid(), + a.compute_txid() ), NonStandardSighashType(ref sht) => write!(f, "non-standard sighash type: {}", sht), InvalidHash(ref e) => write_err!(f, "invalid hash when parsing slice"; e), diff --git a/src/v0/bitcoin/macros.rs b/src/v0/bitcoin/macros.rs index a2adcaf..641b11e 100644 --- a/src/v0/bitcoin/macros.rs +++ b/src/v0/bitcoin/macros.rs @@ -19,7 +19,7 @@ macro_rules! impl_psbt_de_serialize { macro_rules! impl_psbt_deserialize { ($thing:ty) => { impl $crate::v0::bitcoin::serialize::Deserialize for $thing { - fn deserialize(bytes: &[u8]) -> Result { + fn deserialize(bytes: &[u8]) -> core::result::Result { $crate::bitcoin::consensus::deserialize(&bytes[..]) .map_err(|e| $crate::v0::bitcoin::Error::from(e)) } @@ -48,7 +48,7 @@ macro_rules! impl_psbtmap_serialize { macro_rules! impl_psbtmap_deserialize { ($thing:ty) => { impl $crate::v0::bitcoin::serialize::Deserialize for $thing { - fn deserialize(bytes: &[u8]) -> Result { + fn deserialize(bytes: &[u8]) -> core::result::Result { let mut decoder = bytes; Self::decode(&mut decoder) } @@ -59,9 +59,9 @@ macro_rules! impl_psbtmap_deserialize { macro_rules! impl_psbtmap_decoding { ($thing:ty) => { impl $thing { - pub(crate) fn decode( + pub(crate) fn decode( r: &mut R, - ) -> Result { + ) -> core::result::Result { let mut rv: Self = core::default::Default::default(); loop { @@ -151,7 +151,7 @@ macro_rules! impl_psbt_hash_de_serialize { macro_rules! impl_psbt_hash_deserialize { ($hash_type:ty) => { impl $crate::v0::bitcoin::serialize::Deserialize for $hash_type { - fn deserialize(bytes: &[u8]) -> Result { + fn deserialize(bytes: &[u8]) -> core::result::Result { <$hash_type>::from_slice(&bytes[..]) .map_err(|e| $crate::v0::bitcoin::Error::from(e)) } diff --git a/src/v0/bitcoin/map/global.rs b/src/v0/bitcoin/map/global.rs index 1c5c705..114c5df 100644 --- a/src/v0/bitcoin/map/global.rs +++ b/src/v0/bitcoin/map/global.rs @@ -7,7 +7,7 @@ use bitcoin::blockdata::transaction::Transaction; use bitcoin::consensus::encode::MAX_VEC_SIZE; use bitcoin::consensus::{encode, Decodable}; -use crate::io::{self, Cursor, Read}; +use crate::io::{BufRead, Cursor, Read}; use crate::prelude::*; use crate::v0::bitcoin::map::Map; use crate::v0::bitcoin::{raw, Error, Psbt}; @@ -83,7 +83,7 @@ impl Map for Psbt { } impl Psbt { - pub(crate) fn decode_global(r: &mut R) -> Result { + pub(crate) fn decode_global(r: &mut R) -> Result { let mut r = r.take(MAX_VEC_SIZE as u64); let mut tx: Option = None; let mut version: Option = None; diff --git a/src/v0/bitcoin/map/input.rs b/src/v0/bitcoin/map/input.rs index 426785e..9900264 100644 --- a/src/v0/bitcoin/map/input.rs +++ b/src/v0/bitcoin/map/input.rs @@ -104,7 +104,6 @@ pub struct Input { /// The finalized, fully-constructed scriptWitness with signatures and any /// other scripts necessary for this input to pass validation. pub final_script_witness: Option, - /// TODO: Proof of reserves commitment /// RIPEMD160 hash to preimage map. #[cfg_attr(feature = "serde", serde(with = "crate::serde_utils::btreemap_byte_values"))] pub ripemd160_preimages: BTreeMap>, @@ -159,9 +158,7 @@ impl Input { /// # Errors /// /// If the `sighash_type` field is set to a invalid Taproot sighash value. - pub fn taproot_hash_ty( - &self, - ) -> Result { + pub fn taproot_hash_ty(&self) -> Result { self.sighash_type .map(|sighash_type| sighash_type.taproot_hash_ty()) .unwrap_or(Ok(TapSighashType::Default)) diff --git a/src/v0/bitcoin/mod.rs b/src/v0/bitcoin/mod.rs index c6c0b81..70a83e0 100644 --- a/src/v0/bitcoin/mod.rs +++ b/src/v0/bitcoin/mod.rs @@ -7,34 +7,35 @@ //! except we define PSBTs containing non-standard sighash types as invalid. //! +#[macro_use] +mod macros; +mod error; +mod map; +pub mod raw; +pub mod serialize; + use core::{cmp, fmt}; #[cfg(feature = "std")] use std::collections::{HashMap, HashSet}; use bitcoin::bip32::{self, KeySource, Xpriv, Xpub}; -use bitcoin::blockdata::transaction::{Transaction, TxOut}; -use bitcoin::hashes::Hash; -use bitcoin::key::{PrivateKey, PublicKey}; -use bitcoin::secp256k1::{Message, Secp256k1, Signing}; -use bitcoin::sighash::{self, EcdsaSighashType, SighashCache}; -use bitcoin::{ecdsa, Amount, FeeRate}; +use bitcoin::blockdata::transaction::{self, Transaction, TxOut}; +use bitcoin::key::{Keypair, PrivateKey, PublicKey, TapTweak, XOnlyPublicKey}; +use bitcoin::secp256k1::{Message, Secp256k1, Signing, Verification}; +use bitcoin::sighash::{self, EcdsaSighashType, Prevouts, SighashCache, TapSighashType}; +use bitcoin::taproot::TapLeafHash; +use bitcoin::{ecdsa, taproot, Amount, FeeRate}; use crate::error::write_err; use crate::prelude::*; +use crate::PsbtSighashType; -#[macro_use] -mod macros; -pub mod raw; -pub mod serialize; - -mod error; -pub use self::error::Error; - -mod map; - -#[rustfmt::skip] // Keep public exports separate. +#[rustfmt::skip] // Keep public re-exports separate. #[doc(inline)] -pub use self::map::{Input, Output}; +pub use self::{ + map::{Input, Output}, + error::Error, +}; /// A Partially Signed Transaction. #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -282,17 +283,12 @@ impl Psbt { /// Attempts to create _all_ the required signatures for this PSBT using `k`. /// - /// **NOTE**: Taproot inputs are, as yet, not supported by this function. We currently only - /// attempt to sign ECDSA inputs. - /// - /// If you just want to sign an input with one specific key consider using `sighash_ecdsa`. This - /// function does not support scripts that contain `OP_CODESEPARATOR`. + /// If you just want to sign an input with one specific key consider using `sighash_ecdsa` or + /// `sighash_taproot`. This function does not support scripts that contain `OP_CODESEPARATOR`. /// /// # Returns /// - /// Either Ok(SigningKeys) or Err((SigningKeys, SigningErrors)), where - /// - SigningKeys: A map of input index -> pubkey associated with secret key used to sign. - /// - SigningKeys: A map of input index -> the error encountered while attempting to sign. + /// A map of input index -> keys used to sign, for Taproot specifics please see [`SigningKeys`]. /// /// If an error is returned some signatures may already have been added to the PSBT. Since /// `partial_sigs` is a [`BTreeMap`] it is safe to retry, previous sigs will be overwritten. @@ -300,9 +296,9 @@ impl Psbt { &mut self, k: &K, secp: &Secp256k1, - ) -> Result + ) -> Result where - C: Signing, + C: Signing + Verification, K: GetKey, { let tx = self.unsigned_tx.clone(); // clone because we need to mutably borrow when signing. @@ -312,16 +308,30 @@ impl Psbt { let mut errors = BTreeMap::new(); for i in 0..self.inputs.len() { - if let Ok(SigningAlgorithm::Ecdsa) = self.signing_algorithm(i) { - match self.bip32_sign_ecdsa(k, i, &mut cache, secp) { - Ok(v) => { - used.insert(i, v); - } - Err(e) => { - errors.insert(i, e); + match self.signing_algorithm(i) { + Ok(SigningAlgorithm::Ecdsa) => + match self.bip32_sign_ecdsa(k, i, &mut cache, secp) { + Ok(v) => { + used.insert(i, SigningKeys::Ecdsa(v)); + } + Err(e) => { + errors.insert(i, e); + } + }, + Ok(SigningAlgorithm::Schnorr) => { + match self.bip32_sign_schnorr(k, i, &mut cache, secp) { + Ok(v) => { + used.insert(i, SigningKeys::Schnorr(v)); + } + Err(e) => { + errors.insert(i, e); + } } } - }; + Err(e) => { + errors.insert(i, e); + } + } } if errors.is_empty() { Ok(used) @@ -370,8 +380,10 @@ impl Psbt { Ok((msg, sighash_ty)) => (msg, sighash_ty), }; - let sig = - ecdsa::Signature { sig: secp.sign_ecdsa(&msg, &sk.inner), hash_ty: sighash_ty }; + let sig = ecdsa::Signature { + signature: secp.sign_ecdsa(&msg, &sk.inner), + sighash_type: sighash_ty, + }; let pk = sk.public_key(secp); @@ -382,6 +394,102 @@ impl Psbt { Ok(used) } + /// Attempts to create all signatures required by this PSBT's `tap_key_origins` field, adding + /// them to `tap_key_sig` or `tap_script_sigs`. + /// + /// # Returns + /// + /// - Ok: A list of the xonly public keys used in signing. When signing a key path spend we + /// return the internal key. + /// - Err: Error encountered trying to calculate the sighash AND we had the signing key. + fn bip32_sign_schnorr( + &mut self, + k: &K, + input_index: usize, + cache: &mut SighashCache, + secp: &Secp256k1, + ) -> Result, SignError> + where + C: Signing + Verification, + T: Borrow, + K: GetKey, + { + let mut input = self.checked_input(input_index)?.clone(); + + let mut used = vec![]; // List of pubkeys used to sign the input. + + for (&xonly, (leaf_hashes, key_source)) in input.tap_key_origins.iter() { + let sk = if let Ok(Some(secret_key)) = + k.get_key(KeyRequest::Bip32(key_source.clone()), secp) + { + secret_key + } else { + continue; + }; + + // Considering the responsibility of the PSBT's finalizer to extract valid signatures, + // the goal of this algorithm is to provide signatures to the best of our ability: + // 1) If the conditions for key path spend are met, proceed to provide the signature for key path spend + // 2) If the conditions for script path spend are met, proceed to provide the signature for script path spend + + // key path spend + if let Some(internal_key) = input.tap_internal_key { + // BIP 371: The internal key does not have leaf hashes, so can be indicated with a hashes len of 0. + + // Based on input.tap_internal_key.is_some() alone, it is not sufficient to determine whether it is a key path spend. + // According to BIP 371, we also need to consider the condition leaf_hashes.is_empty() for a more accurate determination. + if internal_key == xonly && leaf_hashes.is_empty() && input.tap_key_sig.is_none() { + let (msg, sighash_type) = self.sighash_taproot(input_index, cache, None)?; + let key_pair = Keypair::from_secret_key(secp, &sk.inner) + .tap_tweak(secp, input.tap_merkle_root) + .to_inner(); + + #[cfg(feature = "rand")] + let signature = secp.sign_schnorr(&msg, &key_pair); + #[cfg(not(feature = "rand"))] + let signature = secp.sign_schnorr_no_aux_rand(&msg, &key_pair); + + let signature = taproot::Signature { signature, sighash_type }; + input.tap_key_sig = Some(signature); + + used.push(internal_key); + } + } + + // script path spend + if let Some((leaf_hashes, _)) = input.tap_key_origins.get(&xonly) { + let leaf_hashes = leaf_hashes + .iter() + .filter(|lh| !input.tap_script_sigs.contains_key(&(xonly, **lh))) + .cloned() + .collect::>(); + + if !leaf_hashes.is_empty() { + let key_pair = Keypair::from_secret_key(secp, &sk.inner); + + for lh in leaf_hashes { + let (msg, sighash_type) = + self.sighash_taproot(input_index, cache, Some(lh))?; + + #[cfg(feature = "rand")] + let signature = secp.sign_schnorr(&msg, &key_pair); + #[cfg(not(feature = "rand"))] + let signature = secp.sign_schnorr_no_aux_rand(&msg, &key_pair); + + let signature = taproot::Signature { signature, sighash_type }; + input.tap_script_sigs.insert((xonly, lh), signature); + } + + used.push(sk.public_key(secp).into()); + } + } + } + + self.inputs[input_index] = input; // input_index is checked above. + + Ok(used) + } + /// Returns the sighash message to sign an ECDSA input along with the sighash type. /// /// Uses the [`EcdsaSighashType`] from this input if one is specified. If no sighash type is @@ -406,32 +514,36 @@ impl Psbt { match self.output_type(input_index)? { Bare => { - let sighash = cache.legacy_signature_hash(input_index, spk, hash_ty.to_u32())?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + let sighash = cache + .legacy_signature_hash(input_index, spk, hash_ty.to_u32()) + .expect("input checked above"); + Ok((Message::from(sighash), hash_ty)) } Sh => { let script_code = input.redeem_script.as_ref().ok_or(SignError::MissingRedeemScript)?; - let sighash = - cache.legacy_signature_hash(input_index, script_code, hash_ty.to_u32())?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + let sighash = cache + .legacy_signature_hash(input_index, script_code, hash_ty.to_u32()) + .expect("input checked above"); + Ok((Message::from(sighash), hash_ty)) } Wpkh => { let sighash = cache.p2wpkh_signature_hash(input_index, spk, utxo.value, hash_ty)?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + Ok((Message::from(sighash), hash_ty)) } ShWpkh => { let redeem_script = input.redeem_script.as_ref().expect("checked above"); let sighash = cache.p2wpkh_signature_hash(input_index, redeem_script, utxo.value, hash_ty)?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + Ok((Message::from(sighash), hash_ty)) } Wsh | ShWsh => { let witness_script = input.witness_script.as_ref().ok_or(SignError::MissingWitnessScript)?; - let sighash = - cache.p2wsh_signature_hash(input_index, witness_script, utxo.value, hash_ty)?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + let sighash = cache + .p2wsh_signature_hash(input_index, witness_script, utxo.value, hash_ty) + .map_err(SignError::SegwitV0Sighash)?; + Ok((Message::from(sighash), hash_ty)) } Tr => { // This PSBT signing API is WIP, taproot to come shortly. @@ -440,6 +552,66 @@ impl Psbt { } } + /// Returns the sighash message to sign an SCHNORR input along with the sighash type. + /// + /// Uses the [`TapSighashType`] from this input if one is specified. If no sighash type is + /// specified uses [`TapSighashType::Default`]. + fn sighash_taproot>( + &self, + input_index: usize, + cache: &mut SighashCache, + leaf_hash: Option, + ) -> Result<(Message, TapSighashType), SignError> { + use OutputType::*; + + if self.signing_algorithm(input_index)? != SigningAlgorithm::Schnorr { + return Err(SignError::WrongSigningAlgorithm); + } + + let input = self.checked_input(input_index)?; + + match self.output_type(input_index)? { + Tr => { + let hash_ty = input + .sighash_type + .unwrap_or_else(|| TapSighashType::Default.into()) + .taproot_hash_ty() + .map_err(|_| SignError::InvalidSighashType)?; + + let spend_utxos = + (0..self.inputs.len()).map(|i| self.spend_utxo(i).ok()).collect::>(); + let all_spend_utxos; + + let is_anyone_can_pay = PsbtSighashType::from(hash_ty).to_u32() & 0x80 != 0; + + let prev_outs = if is_anyone_can_pay { + Prevouts::One( + input_index, + spend_utxos[input_index].ok_or(SignError::MissingSpendUtxo)?, + ) + } else if spend_utxos.iter().all(Option::is_some) { + all_spend_utxos = spend_utxos.iter().filter_map(|x| *x).collect::>(); + Prevouts::All(&all_spend_utxos) + } else { + return Err(SignError::MissingSpendUtxo); + }; + + let sighash = if let Some(leaf_hash) = leaf_hash { + cache.taproot_script_spend_signature_hash( + input_index, + &prev_outs, + leaf_hash, + hash_ty, + )? + } else { + cache.taproot_key_spend_signature_hash(input_index, &prev_outs, hash_ty)? + }; + Ok((Message::from(sighash), hash_ty)) + } + _ => Err(SignError::Unsupported), + } + } + /// Returns the spending utxo for this PSBT's input at `input_index`. pub fn spend_utxo(&self, input_index: usize) -> Result<&TxOut, SignError> { let input = self.checked_input(input_index)?; @@ -601,8 +773,20 @@ impl GetKey for Xpriv { } } -/// Map of input index -> pubkey associated with secret key used to create signature for that input. -pub type SigningKeys = BTreeMap>; +/// Map of input index -> signing key for that input (see [`SigningKeys`]). +pub type SigningKeysMap = BTreeMap; + +/// A list of keys used to sign an input. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum SigningKeys { + /// Keys used to sign an ECDSA input. + Ecdsa(Vec), + /// Keys used to sign a Taproot input. + /// + /// - Key path spend: This is the internal key. + /// - Script path spend: This is the pubkey associated with the secret key that signed. + Schnorr(Vec), +} /// Map of input index -> the error encountered while attempting to sign that input. pub type SigningErrors = BTreeMap; @@ -669,6 +853,8 @@ pub enum GetKeyError { NotSupported, } +bitcoin_internals::impl_from_infallible!(GetKeyError); + impl fmt::Display for GetKeyError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use GetKeyError::*; @@ -764,8 +950,12 @@ pub enum SignError { NotEcdsa, /// The `scriptPubkey` is not a P2WPKH script. NotWpkh, - /// Sighash computation error. - SighashComputation(sighash::Error), + /// Sighash computation error (segwit v0 input). + SegwitV0Sighash(transaction::InputsIndexError), + /// Sighash computation error (p2wpkh input). + P2wpkhSighash(sighash::P2wpkhError), + /// Sighash computation error (taproot input). + TaprootError(sighash::TaprootError), /// Unable to determine the output type. UnknownOutputType, /// Unable to find key. @@ -776,6 +966,8 @@ pub enum SignError { Unsupported, } +bitcoin_internals::impl_from_infallible!(SignError); + impl fmt::Display for SignError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use SignError::*; @@ -790,7 +982,9 @@ impl fmt::Display for SignError { MismatchedAlgoKey => write!(f, "signing algorithm and key type does not match"), NotEcdsa => write!(f, "attempted to ECDSA sign an non-ECDSA input"), NotWpkh => write!(f, "the scriptPubkey is not a P2WPKH script"), - SighashComputation(ref e) => write!(f, "sighash: {}", e), + SegwitV0Sighash(ref e) => write_err!(f, "segwit v0 sighash"; e), + P2wpkhSighash(ref e) => write_err!(f, "p2wpkh sighash"; e), + TaprootError(ref e) => write_err!(f, "taproot sighash"; e), UnknownOutputType => write!(f, "unable to determine the output type"), KeyNotFound => write!(f, "unable to find key"), WrongSigningAlgorithm => @@ -806,7 +1000,9 @@ impl std::error::Error for SignError { use SignError::*; match *self { - SighashComputation(ref e) => Some(e), + SegwitV0Sighash(ref e) => Some(e), + P2wpkhSighash(ref e) => Some(e), + TaprootError(ref e) => Some(e), IndexOutOfBounds(ref e) => Some(e), InvalidSighashType | MissingInputUtxo @@ -824,14 +1020,18 @@ impl std::error::Error for SignError { } } -impl From for SignError { - fn from(e: sighash::Error) -> Self { SignError::SighashComputation(e) } +impl From for SignError { + fn from(e: sighash::P2wpkhError) -> Self { Self::P2wpkhSighash(e) } } impl From for SignError { fn from(e: IndexOutOfBoundsError) -> Self { SignError::IndexOutOfBounds(e) } } +impl From for SignError { + fn from(e: sighash::TaprootError) -> Self { SignError::TaprootError(e) } +} + /// This error is returned when extracting a [`Transaction`] from a [`Psbt`]. #[derive(Debug, Clone, PartialEq, Eq)] #[non_exhaustive] @@ -855,6 +1055,8 @@ pub enum ExtractTxError { }, } +bitcoin_internals::impl_from_infallible!(ExtractTxError); + impl fmt::Display for ExtractTxError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use ExtractTxError::*; @@ -905,6 +1107,8 @@ pub enum IndexOutOfBoundsError { }, } +bitcoin_internals::impl_from_infallible!(IndexOutOfBoundsError); + impl fmt::Display for IndexOutOfBoundsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use IndexOutOfBoundsError::*; @@ -956,6 +1160,8 @@ mod display_from_str { Base64Encoding(bitcoin::base64::DecodeError), } + bitcoin_internals::impl_from_infallible!(PsbtParseError); + impl Display for PsbtParseError { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { use self::PsbtParseError::*; @@ -1008,13 +1214,12 @@ mod tests { use bitcoin::blockdata::witness::Witness; use bitcoin::hashes::{hash160, ripemd160, sha256, Hash}; use bitcoin::hex::{test_hex_unwrap as hex, FromHex}; - use bitcoin::network::Network::Bitcoin; use bitcoin::secp256k1::{self, Secp256k1}; - #[cfg(feature = "rand-std")] + #[cfg(feature = "rand")] use bitcoin::secp256k1::{All, SecretKey}; + use bitcoin::NetworkKind; use super::*; - use crate::sighash_type::PsbtSighashType; use crate::v0::bitcoin::map::{Input, Output}; use crate::v0::bitcoin::raw; use crate::v0::bitcoin::serialize::{Deserialize, Serialize}; @@ -1151,7 +1356,7 @@ mod tests { let mut hd_keypaths: BTreeMap = Default::default(); - let mut sk: Xpriv = Xpriv::new_master(Bitcoin, &seed).unwrap(); + let mut sk: Xpriv = Xpriv::new_master(NetworkKind::Main, &seed).unwrap(); let fprint = sk.fingerprint(secp); @@ -1291,7 +1496,7 @@ mod tests { vec![(raw::Key { type_value: 1, key: vec![0, 1] }, vec![3, 4, 5])] .into_iter() .collect(); - let key_source = ("deadbeef".parse().unwrap(), "m/0'/1".parse().unwrap()); + let key_source = ("deadbeef".parse().unwrap(), "0'/1".parse().unwrap()); let keypaths: BTreeMap = vec![( "0339880dc92394b7355e3d0439fa283c31de7590812ea011c4245c0674a685e883".parse().unwrap(), key_source.clone(), @@ -1585,7 +1790,7 @@ mod tests { let tx_input = &psbt.unsigned_tx.input[0]; let psbt_non_witness_utxo = psbt.inputs[0].non_witness_utxo.as_ref().unwrap(); - assert_eq!(tx_input.previous_output.txid, psbt_non_witness_utxo.txid()); + assert_eq!(tx_input.previous_output.txid, psbt_non_witness_utxo.compute_txid()); assert!(psbt_non_witness_utxo.output[tx_input.previous_output.vout as usize] .script_pubkey .is_p2pkh()); @@ -1655,7 +1860,7 @@ mod tests { // let tx = &psbt.unsigned_tx; // assert_eq!( - // tx.txid(), + // tx.compute_txid(), // "75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115".parse().unwrap(), // ); @@ -1923,21 +2128,21 @@ mod tests { assert_eq!(psbt1, psbt2); } - #[cfg(feature = "rand-std")] + #[cfg(feature = "rand")] fn gen_keys() -> (PrivateKey, PublicKey, Secp256k1) { use bitcoin::secp256k1::rand::thread_rng; let secp = Secp256k1::new(); let sk = SecretKey::new(&mut thread_rng()); - let priv_key = PrivateKey::new(sk, bitcoin::Network::Regtest); + let priv_key = PrivateKey::new(sk, NetworkKind::Test); let pk = PublicKey::from_private_key(&secp, &priv_key); (priv_key, pk, secp) } #[test] - #[cfg(feature = "rand-std")] + #[cfg(feature = "rand")] fn get_key_btree_map() { let (priv_key, pk, secp) = gen_keys(); @@ -2058,7 +2263,7 @@ mod tests { } #[test] - #[cfg(feature = "rand-std")] + #[cfg(feature = "rand")] fn sign_psbt() { use bitcoin::bip32::{DerivationPath, Fingerprint}; use bitcoin::witness_version::WitnessVersion; @@ -2091,16 +2296,16 @@ mod tests { psbt.inputs[0].bip32_derivation = map; // Second input is unspendable by us e.g., from another wallet that supports future upgrades. - let unknown_prog = WitnessProgram::new(WitnessVersion::V4, vec![0xaa; 34]).unwrap(); + let unknown_prog = WitnessProgram::new(WitnessVersion::V4, &[0xaa; 34]).unwrap(); let txout_unknown_future = TxOut { value: Amount::from_sat(10), script_pubkey: ScriptBuf::new_witness_program(&unknown_prog), }; psbt.inputs[1].witness_utxo = Some(txout_unknown_future); - let sigs = psbt.sign(&key_map, &secp).unwrap(); + let (signing_keys, _) = psbt.sign(&key_map, &secp).unwrap_err(); - assert!(sigs.len() == 1); - assert!(sigs[&0] == vec![pk]); + assert_eq!(signing_keys.len(), 1); + assert_eq!(signing_keys[&0], SigningKeys::Ecdsa(vec![pk])); } } diff --git a/src/v0/bitcoin/raw.rs b/src/v0/bitcoin/raw.rs index b721450..8dcb27d 100644 --- a/src/v0/bitcoin/raw.rs +++ b/src/v0/bitcoin/raw.rs @@ -14,7 +14,7 @@ use bitcoin::consensus::encode::{ }; use super::serialize::{Deserialize, Serialize}; -use crate::io; +use crate::io::{self, BufRead, Write}; use crate::prelude::*; use crate::v0::bitcoin::Error; @@ -75,7 +75,7 @@ impl fmt::Display for Key { } impl Key { - pub(crate) fn decode(r: &mut R) -> Result { + pub(crate) fn decode(r: &mut R) -> Result { let VarInt(byte_size): VarInt = Decodable::consensus_decode(r)?; if byte_size == 0 { @@ -138,7 +138,7 @@ impl Deserialize for Pair { } impl Pair { - pub(crate) fn decode(r: &mut R) -> Result { + pub(crate) fn decode(r: &mut R) -> Result { Ok(Pair { key: Key::decode(r)?, value: Decodable::consensus_decode(r)? }) } } @@ -147,7 +147,7 @@ impl Encodable for ProprietaryKey where Subtype: Copy + From + Into, { - fn consensus_encode(&self, w: &mut W) -> Result { + fn consensus_encode(&self, w: &mut W) -> Result { let mut len = self.prefix.consensus_encode(w)? + 1; w.emit_u8(self.subtype.into())?; w.write_all(&self.key)?; @@ -160,10 +160,14 @@ impl Decodable for ProprietaryKey where Subtype: Copy + From + Into, { - fn consensus_decode(r: &mut R) -> Result { + fn consensus_decode(r: &mut R) -> Result { let prefix = Vec::::consensus_decode(r)?; let subtype = Subtype::from(r.read_u8()?); - let key = read_to_end(r)?; + + // The limit is a DOS protection mechanism the exact value is not + // important, 1024 bytes is bigger than any key should be. + let mut key = vec![]; + let _ = r.read_to_limit(&mut key, 1024)?; Ok(ProprietaryKey { prefix, subtype, key }) } @@ -195,18 +199,3 @@ where Ok(deserialize(&key.key)?) } } - -// core2 doesn't have read_to_end -pub(crate) fn read_to_end(mut d: D) -> Result, io::Error> { - let mut result = vec![]; - let mut buf = [0u8; 64]; - loop { - match d.read(&mut buf) { - Ok(0) => break, - Ok(n) => result.extend_from_slice(&buf[0..n]), - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {} - Err(e) => return Err(e), - }; - } - Ok(result) -} diff --git a/src/v0/bitcoin/serialize.rs b/src/v0/bitcoin/serialize.rs index 6f4793a..def826d 100644 --- a/src/v0/bitcoin/serialize.rs +++ b/src/v0/bitcoin/serialize.rs @@ -181,7 +181,7 @@ impl Deserialize for ecdsa::Signature { ecdsa::Error::SighashType(err) => Error::NonStandardSighashType(err.0), ecdsa::Error::Secp256k1(..) => Error::InvalidEcdsaSignature(e), ecdsa::Error::Hex(..) => unreachable!("Decoding from slice, not hex"), - _ => unreachable!("in rust-bitcoin v0.31.1"), + _ => unreachable!("in rust-bitcoin v0.32.2"), }) } } @@ -264,7 +264,7 @@ impl Deserialize for taproot::Signature { SighashType(err) => Error::NonStandardSighashType(err.0), InvalidSignatureSize(_) => Error::InvalidTaprootSignature(e), Secp256k1(..) => Error::InvalidTaprootSignature(e), - _ => unreachable!("in rust-bitcoin v0.31.1"), + _ => unreachable!("in rust-bitcoin v0.32.2"), }) } } @@ -327,7 +327,6 @@ impl Serialize for (Vec, KeySource) { fn serialize(&self) -> Vec { let mut buf = Vec::with_capacity(32 * self.0.len() + key_source_len(&self.1)); self.0.consensus_encode(&mut buf).expect("Vecs don't error allocation"); - // TODO: Add support for writing into a writer for key-source buf.extend(self.1.serialize()); buf } diff --git a/src/v0/miniscript/finalizer.rs b/src/v0/miniscript/finalizer.rs index a9f1943..b2130bb 100644 --- a/src/v0/miniscript/finalizer.rs +++ b/src/v0/miniscript/finalizer.rs @@ -8,11 +8,14 @@ //! `https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki` //! +use core::convert::TryFrom; use core::mem; use bitcoin::hashes::hash160; use bitcoin::key::XOnlyPublicKey; -use bitcoin::secp256k1::{self, Secp256k1}; +#[cfg(not(test))] // https://github.com/rust-lang/rust/issues/121684 +use bitcoin::secp256k1; +use bitcoin::secp256k1::{Secp256k1}; use bitcoin::sighash::Prevouts; use bitcoin::taproot::LeafVersion; use bitcoin::{PublicKey, Script, ScriptBuf, TxOut, Witness}; @@ -173,7 +176,7 @@ fn get_descriptor(psbt: &Psbt, index: usize) -> Result, In // Partial sigs loses the compressed flag that is necessary // TODO: See https://github.com/rust-bitcoin/rust-bitcoin/pull/836 // The type checker will fail again after we update to 0.28 and this can be removed - let addr = bitcoin::Address::p2pkh(&pk, bitcoin::Network::Bitcoin); + let addr = bitcoin::Address::p2pkh(pk, bitcoin::Network::Bitcoin); *script_pubkey == addr.script_pubkey() }); match partial_sig_contains_pk { @@ -183,11 +186,15 @@ fn get_descriptor(psbt: &Psbt, index: usize) -> Result, In } else if script_pubkey.is_p2wpkh() { // 3. `Wpkh`: creates a `wpkh` descriptor if the partial sig has corresponding pk. let partial_sig_contains_pk = inp.partial_sigs.iter().find(|&(&pk, _sig)| { - // Indirect way to check the equivalence of pubkey-hashes. - // Create a pubkey hash and check if they are the same. - let addr = bitcoin::Address::p2wpkh(&pk, bitcoin::Network::Bitcoin) - .expect("Address corresponding to valid pubkey"); - *script_pubkey == addr.script_pubkey() + match bitcoin::key::CompressedPublicKey::try_from(pk) { + Ok(compressed) => { + // Indirect way to check the equivalence of pubkey-hashes. + // Create a pubkey hash and check if they are the same. + let addr = bitcoin::Address::p2wpkh(&compressed, bitcoin::Network::Bitcoin); + *script_pubkey == addr.script_pubkey() + } + Err(_) => false, + } }); match partial_sig_contains_pk { Some((pk, _sig)) => Ok(Descriptor::new_wpkh(*pk)?), @@ -243,9 +250,16 @@ fn get_descriptor(psbt: &Psbt, index: usize) -> Result, In } else if redeem_script.is_p2wpkh() { // 6. `ShWpkh` case let partial_sig_contains_pk = inp.partial_sigs.iter().find(|&(&pk, _sig)| { - let addr = bitcoin::Address::p2wpkh(&pk, bitcoin::Network::Bitcoin) - .expect("Address corresponding to valid pubkey"); - *redeem_script == addr.script_pubkey() + match bitcoin::key::CompressedPublicKey::try_from(pk) { + Ok(compressed) => { + let addr = bitcoin::Address::p2wpkh( + &compressed, + bitcoin::Network::Bitcoin, + ); + *redeem_script == addr.script_pubkey() + } + Err(_) => false, + } }); match partial_sig_contains_pk { Some((pk, _sig)) => Ok(Descriptor::new_sh_wpkh(*pk)?), diff --git a/src/v0/miniscript/mod.rs b/src/v0/miniscript/mod.rs index aa5e48e..bc917f3 100644 --- a/src/v0/miniscript/mod.rs +++ b/src/v0/miniscript/mod.rs @@ -15,10 +15,12 @@ use core::fmt; use std::error; use bitcoin::hashes::{hash160, sha256d, Hash}; -use bitcoin::secp256k1::{self, Secp256k1, VerifyOnly}; +#[cfg(not(test))] // https://github.com/rust-lang/rust/issues/121684 +use bitcoin::secp256k1; +use bitcoin::secp256k1::{Secp256k1, VerifyOnly}; use bitcoin::sighash::{self, SighashCache}; use bitcoin::taproot::{self, ControlBlock, LeafVersion, TapLeafHash}; -use bitcoin::{absolute, bip32, transaction, Script, ScriptBuf, Sequence}; +use bitcoin::{absolute, bip32, relative, transaction, Script, ScriptBuf}; use miniscript::{ descriptor, interpreter, DefiniteDescriptorKey, Descriptor, DescriptorPublicKey, MiniscriptKey, @@ -89,7 +91,7 @@ pub enum InputError { /// Get the secp Errors directly SecpErr(bitcoin::secp256k1::Error), /// Key errors - KeyErr(bitcoin::key::Error), + KeyErr(bitcoin::key::FromSliceError), /// Could not satisfy taproot descriptor /// This error is returned when both script path and key paths could not be /// satisfied. We cannot return a detailed error because we try all miniscripts @@ -232,8 +234,8 @@ impl From for InputError { } #[doc(hidden)] -impl From for InputError { - fn from(e: bitcoin::key::Error) -> InputError { InputError::KeyErr(e) } +impl From for InputError { + fn from(e: bitcoin::key::FromSliceError) -> InputError { InputError::KeyErr(e) } } /// Psbt satisfier for at inputs at a particular index @@ -325,15 +327,9 @@ impl<'psbt, Pk: MiniscriptKey + ToPublicKey> Satisfier for PsbtInputSatisfie >::check_after(&lock_time, n) } - fn check_older(&self, n: Sequence) -> bool { + fn check_older(&self, n: relative::LockTime) -> bool { let seq = self.psbt.unsigned_tx.input[self.index].sequence; - // https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki - // Disable flag set => return true. - if !n.is_relative_lock_time() { - return true; - } - if self.psbt.unsigned_tx.version < transaction::Version::TWO || !seq.is_relative_lock_time() { return false; @@ -346,32 +342,32 @@ impl<'psbt, Pk: MiniscriptKey + ToPublicKey> Satisfier for PsbtInputSatisfie self.psbt.inputs[self.index] .hash160_preimages .get(&Pk::to_hash160(h)) - .and_then(try_vec_as_preimage32) + .and_then(|x: &Vec| try_vec_as_preimage32(x)) } fn lookup_sha256(&self, h: &Pk::Sha256) -> Option { self.psbt.inputs[self.index] .sha256_preimages .get(&Pk::to_sha256(h)) - .and_then(try_vec_as_preimage32) + .and_then(|x: &Vec| try_vec_as_preimage32(x)) } fn lookup_hash256(&self, h: &Pk::Hash256) -> Option { self.psbt.inputs[self.index] .hash256_preimages .get(&sha256d::Hash::from_byte_array(Pk::to_hash256(h).to_byte_array())) // upstream psbt operates on hash256 - .and_then(try_vec_as_preimage32) + .and_then(|x: &Vec| try_vec_as_preimage32(x)) } fn lookup_ripemd160(&self, h: &Pk::Ripemd160) -> Option { self.psbt.inputs[self.index] .ripemd160_preimages .get(&Pk::to_ripemd160(h)) - .and_then(try_vec_as_preimage32) + .and_then(|x: &Vec| try_vec_as_preimage32(x)) } } -fn try_vec_as_preimage32(vec: &Vec) -> Option { +fn try_vec_as_preimage32(vec: &[u8]) -> Option { if vec.len() == 32 { let mut arr = [0u8; 32]; arr.copy_from_slice(vec); @@ -402,16 +398,15 @@ pub(crate) fn sanity_check(psbt: &Psbt) -> Result<(), Error> { None => sighash::EcdsaSighashType::All, }; for (key, ecdsa_sig) in &input.partial_sigs { - let flag = sighash::EcdsaSighashType::from_standard(ecdsa_sig.hash_ty as u32).map_err( - |_| { + let flag = sighash::EcdsaSighashType::from_standard(ecdsa_sig.sighash_type as u32) + .map_err(|_| { Error::InputError( InputError::Interpreter(interpreter::Error::NonStandardSighash( ecdsa_sig.to_vec(), )), index, ) - }, - )?; + })?; if target_ecdsa_sighash_ty != flag { return Err(Error::InputError( InputError::WrongSighashFlag { @@ -743,7 +738,7 @@ impl PsbtExt for Psbt { let desc_type = desc.desc_type(); if let Some(non_witness_utxo) = &input.non_witness_utxo { - if txin.previous_output.txid != non_witness_utxo.txid() { + if txin.previous_output.txid != non_witness_utxo.compute_txid() { return Err(UtxoUpdateError::UtxoCheck); } } @@ -881,7 +876,7 @@ impl PsbtExt for Psbt { .redeem_script .as_ref() .expect("redeem script non-empty checked earlier"); - cache.p2wpkh_signature_hash(idx, &script_code, amt, hash_ty)? + cache.p2wpkh_signature_hash(idx, script_code, amt, hash_ty)? } else { let witness_script = inp .witness_script @@ -1322,10 +1317,12 @@ pub enum SighashError { MissingSpendUtxos, /// Invalid Sighash type InvalidSighashType, - /// Sighash computation error - /// Only happens when single does not have corresponding output as psbts - /// already have information to compute the sighash - SighashComputationError(sighash::Error), + /// Computation error for taproot sighash. + SighashTaproot(sighash::TaprootError), + /// Computation error for P2WPKH sighash. + SighashP2wpkh(sighash::P2wpkhError), + /// Computation error for P2WSH sighash. + TransactionInputsIndex(transaction::InputsIndexError), /// Missing Witness script MissingWitnessScript, /// Missing Redeem script, @@ -1341,11 +1338,11 @@ impl fmt::Display for SighashError { SighashError::MissingInputUtxo => write!(f, "Missing input utxo in pbst"), SighashError::MissingSpendUtxos => write!(f, "Missing Psbt spend utxos"), SighashError::InvalidSighashType => write!(f, "Invalid Sighash type"), - SighashError::SighashComputationError(e) => { - write!(f, "Sighash computation error : {}", e) - } SighashError::MissingWitnessScript => write!(f, "Missing Witness Script"), SighashError::MissingRedeemScript => write!(f, "Missing Redeem Script"), + SighashError::SighashTaproot(ref e) => write!(f, "sighash taproot: {}", e), + SighashError::SighashP2wpkh(ref e) => write!(f, "sighash p2wpkh: {}", e), + SighashError::TransactionInputsIndex(ref e) => write!(f, "tx inputs index: {}", e), } } } @@ -1362,13 +1359,23 @@ impl error::Error for SighashError { | InvalidSighashType | MissingWitnessScript | MissingRedeemScript => None, - SighashComputationError(e) => Some(e), + SighashTaproot(ref e) => Some(e), + SighashP2wpkh(ref e) => Some(e), + TransactionInputsIndex(ref e) => Some(e), } } } -impl From for SighashError { - fn from(e: sighash::Error) -> Self { SighashError::SighashComputationError(e) } +impl From for SighashError { + fn from(e: sighash::TaprootError) -> Self { SighashError::SighashTaproot(e) } +} + +impl From for SighashError { + fn from(e: sighash::P2wpkhError) -> Self { SighashError::SighashP2wpkh(e) } +} + +impl From for SighashError { + fn from(e: transaction::InputsIndexError) -> Self { SighashError::TransactionInputsIndex(e) } } /// Sighash message(signing data) for a given psbt transaction input. @@ -1502,7 +1509,7 @@ mod tests { let (leaf_hashes, (key_fingerprint, deriv_path)) = psbt_input.tap_key_origins.get(&key_0_1).unwrap(); assert_eq!(key_fingerprint, &fingerprint); - assert_eq!(&deriv_path.to_string(), "m/86'/0'/0'/0/1"); + assert_eq!(&deriv_path.to_string(), "86'/0'/0'/0/1"); assert_eq!(leaf_hashes.len(), 2); assert!(leaf_hashes.contains(&first_leaf_hash)); } @@ -1516,7 +1523,7 @@ mod tests { let (leaf_hashes, (key_fingerprint, deriv_path)) = psbt_input.tap_key_origins.get(&key_1_0).unwrap(); assert_eq!(key_fingerprint, &fingerprint); - assert_eq!(&deriv_path.to_string(), "m/86'/0'/0'/1/0"); + assert_eq!(&deriv_path.to_string(), "86'/0'/0'/1/0"); assert_eq!(leaf_hashes.len(), 1); assert!(!leaf_hashes.contains(&first_leaf_hash)); } @@ -1594,7 +1601,7 @@ mod tests { #[test] fn test_update_input_checks() { let desc = "tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/0)"; - let desc = Descriptor::::from_str(&desc).unwrap(); + let desc = Descriptor::::from_str(desc).unwrap(); let mut non_witness_utxo = bitcoin::Transaction { version: transaction::Version::ONE, @@ -1613,7 +1620,7 @@ mod tests { version: transaction::Version::ONE, lock_time: absolute::LockTime::ZERO, input: vec![TxIn { - previous_output: OutPoint { txid: non_witness_utxo.txid(), vout: 0 }, + previous_output: OutPoint { txid: non_witness_utxo.compute_txid(), vout: 0 }, ..Default::default() }], output: vec![], @@ -1656,7 +1663,7 @@ mod tests { #[test] fn test_update_output_checks() { let desc = "tr([73c5da0a/86'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/0/0)"; - let desc = Descriptor::::from_str(&desc).unwrap(); + let desc = Descriptor::::from_str(desc).unwrap(); let tx = bitcoin::Transaction { version: transaction::Version::ONE, diff --git a/src/v0/mod.rs b/src/v0/mod.rs index a7baf98..4dbd8b4 100644 --- a/src/v0/mod.rs +++ b/src/v0/mod.rs @@ -66,7 +66,7 @@ impl Psbt { } if let Some(ref tx) = input.non_witness_utxo { - if tx.txid() != unsigned_tx.input[i].previous_output.txid { + if tx.compute_txid() != unsigned_tx.input[i].previous_output.txid { return Err(SignerChecksError::NonWitnessUtxoTxidMismatch); } } diff --git a/src/v2/error.rs b/src/v2/error.rs index 93a43e3..60d6533 100644 --- a/src/v2/error.rs +++ b/src/v2/error.rs @@ -5,7 +5,7 @@ use core::fmt; use bitcoin::sighash::{self, EcdsaSighashType, NonStandardSighashTypeError}; -use bitcoin::PublicKey; +use bitcoin::{transaction, PublicKey}; use crate::error::{write_err, FundingUtxoError}; use crate::v2::map::{global, input, output}; @@ -123,8 +123,12 @@ pub enum SignError { NotEcdsa, /// The `scriptPubkey` is not a P2WPKH script. NotWpkh, - /// Sighash computation error. - SighashComputation(sighash::Error), + /// Sighash computation error (segwit v0 input). + SegwitV0Sighash(transaction::InputsIndexError), + /// Sighash computation error (p2wpkh input). + P2wpkhSighash(sighash::P2wpkhError), + /// Sighash computation error (taproot input). + TaprootError(sighash::TaprootError), /// Unable to determine the output type. UnknownOutputType, /// Unable to find key. @@ -149,7 +153,9 @@ impl fmt::Display for SignError { MismatchedAlgoKey => write!(f, "signing algorithm and key type does not match"), NotEcdsa => write!(f, "attempted to ECDSA sign an non-ECDSA input"), NotWpkh => write!(f, "the scriptPubkey is not a P2WPKH script"), - SighashComputation(ref e) => write!(f, "sighash: {}", e), + SegwitV0Sighash(ref e) => write_err!(f, "segwit v0 sighash"; e), + P2wpkhSighash(ref e) => write_err!(f, "p2wpkh sighash"; e), + TaprootError(ref e) => write_err!(f, "taproot sighash"; e), UnknownOutputType => write!(f, "unable to determine the output type"), KeyNotFound => write!(f, "unable to find key"), WrongSigningAlgorithm => @@ -165,7 +171,9 @@ impl std::error::Error for SignError { use SignError::*; match *self { - SighashComputation(ref e) => Some(e), + SegwitV0Sighash(ref e) => Some(e), + P2wpkhSighash(ref e) => Some(e), + TaprootError(ref e) => Some(e), IndexOutOfBounds(ref e) => Some(e), FundingUtxo(ref e) => Some(e), InvalidSighashType @@ -183,14 +191,18 @@ impl std::error::Error for SignError { } } -impl From for SignError { - fn from(e: sighash::Error) -> Self { Self::SighashComputation(e) } +impl From for SignError { + fn from(e: sighash::P2wpkhError) -> Self { Self::P2wpkhSighash(e) } } impl From for SignError { fn from(e: IndexOutOfBoundsError) -> Self { Self::IndexOutOfBounds(e) } } +impl From for SignError { + fn from(e: sighash::TaprootError) -> Self { SignError::TaprootError(e) } +} + impl From for SignError { fn from(e: FundingUtxoError) -> Self { Self::FundingUtxo(e) } } diff --git a/src/v2/map/global.rs b/src/v2/map/global.rs index 43e81b0..078e079 100644 --- a/src/v2/map/global.rs +++ b/src/v2/map/global.rs @@ -14,7 +14,7 @@ use crate::consts::{ PSBT_GLOBAL_UNSIGNED_TX, PSBT_GLOBAL_VERSION, PSBT_GLOBAL_XPUB, }; use crate::error::{write_err, InconsistentKeySourcesError}; -use crate::io::{self, Cursor, Read}; +use crate::io::{BufRead, Cursor, Read}; use crate::prelude::*; use crate::serialize::Serialize; use crate::v2::map::Map; @@ -121,7 +121,7 @@ impl Global { self.tx_modifiable_flags & SIGHASH_SINGLE > 0 } - pub(crate) fn decode(r: &mut R) -> Result { + pub(crate) fn decode(r: &mut R) -> Result { // TODO: Consider adding protection against memory exhaustion here by defining a maximum // PBST size and using `take` as we do in rust-bitcoin consensus decoding. let mut version: Option = None; diff --git a/src/v2/map/input.rs b/src/v2/map/input.rs index 5ba0caf..1689d30 100644 --- a/src/v2/map/input.rs +++ b/src/v2/map/input.rs @@ -6,6 +6,7 @@ use core::fmt; use bitcoin::bip32::KeySource; use bitcoin::hashes::{hash160, ripemd160, sha256, sha256d, Hash as _}; use bitcoin::hex::DisplayHex; +use bitcoin::io::BufRead; use bitcoin::key::{PublicKey, XOnlyPublicKey}; use bitcoin::locktime::absolute; use bitcoin::sighash::{EcdsaSighashType, NonStandardSighashTypeError, TapSighashType}; @@ -29,7 +30,7 @@ use crate::prelude::*; use crate::serialize::{Deserialize, Serialize}; use crate::sighash_type::{InvalidSighashTypeError, PsbtSighashType}; use crate::v2::map::Map; -use crate::{io, raw, serialize}; +use crate::{raw, serialize}; /// A key-value map for an input of the corresponding index in the unsigned /// transaction. @@ -367,7 +368,7 @@ impl Input { .unwrap_or(Ok(TapSighashType::Default)) } - pub(in crate::v2) fn decode(r: &mut R) -> Result { + pub(in crate::v2) fn decode(r: &mut R) -> Result { // These are placeholder values that never exist in a encode `Input`. let invalid = OutPoint { txid: Txid::all_zeros(), vout: u32::MAX }; let mut rv = Self::new(&invalid); @@ -998,6 +999,8 @@ impl std::error::Error for CombineError { #[cfg(test)] mod test { + use bitcoin::io::Cursor; + use super::*; #[cfg(feature = "std")] @@ -1013,7 +1016,7 @@ mod test { let input = Input::new(&out_point()); let ser = input.serialize_map(); - let mut d = std::io::Cursor::new(ser); + let mut d = Cursor::new(ser); let decoded = Input::decode(&mut d).expect("failed to decode"); diff --git a/src/v2/map/output.rs b/src/v2/map/output.rs index f24413a..60745cf 100644 --- a/src/v2/map/output.rs +++ b/src/v2/map/output.rs @@ -4,6 +4,7 @@ use core::convert::TryFrom; use core::fmt; use bitcoin::bip32::KeySource; +use bitcoin::io::BufRead; use bitcoin::key::XOnlyPublicKey; use bitcoin::taproot::{TapLeafHash, TapTree}; use bitcoin::{secp256k1, Amount, ScriptBuf, TxOut}; @@ -17,7 +18,7 @@ use crate::error::write_err; use crate::prelude::*; use crate::serialize::{Deserialize, Serialize}; use crate::v2::map::Map; -use crate::{io, raw, serialize}; +use crate::{raw, serialize}; /// A key-value map for an output of the corresponding index in the unsigned /// transaction. @@ -90,7 +91,7 @@ impl Output { TxOut { value: self.amount, script_pubkey: self.script_pubkey.clone() } } - pub(in crate::v2) fn decode(r: &mut R) -> Result { + pub(in crate::v2) fn decode(r: &mut R) -> Result { // These are placeholder values that never exist in a encode `Output`. let invalid = TxOut { value: Amount::ZERO, script_pubkey: ScriptBuf::default() }; let mut rv = Self::new(invalid); @@ -405,6 +406,8 @@ impl std::error::Error for CombineError { #[cfg(test)] #[cfg(feature = "std")] mod tests { + use bitcoin::io::Cursor; + use super::*; fn tx_out() -> TxOut { @@ -420,7 +423,7 @@ mod tests { let output = Output::new(tx_out()); let ser = output.serialize_map(); - let mut d = std::io::Cursor::new(ser); + let mut d = Cursor::new(ser); let decoded = Output::decode(&mut d).expect("failed to decode"); diff --git a/src/v2/miniscript/finalize.rs b/src/v2/miniscript/finalize.rs index 53197b3..7232c5a 100644 --- a/src/v2/miniscript/finalize.rs +++ b/src/v2/miniscript/finalize.rs @@ -172,7 +172,7 @@ impl Finalizer { // Partial sigs loses the compressed flag that is necessary // TODO: See https://github.com/rust-bitcoin/rust-bitcoin/pull/836 // The type checker will fail again after we update to 0.28 and this can be removed - let addr = Address::p2pkh(&pk, Network::Bitcoin); + let addr = Address::p2pkh(pk, Network::Bitcoin); *script_pubkey == addr.script_pubkey() }); match partial_sig_contains_pk { @@ -182,11 +182,15 @@ impl Finalizer { } else if script_pubkey.is_p2wpkh() { // 3. `Wpkh`: creates a `wpkh` descriptor if the partial sig has corresponding pk. let partial_sig_contains_pk = input.partial_sigs.iter().find(|&(&pk, _sig)| { - // Indirect way to check the equivalence of pubkey-hashes. - // Create a pubkey hash and check if they are the same. - let addr = Address::p2wpkh(&pk, Network::Bitcoin) - .expect("Address corresponding to valid pubkey"); - *script_pubkey == addr.script_pubkey() + match bitcoin::key::CompressedPublicKey::try_from(pk) { + Ok(compressed) => { + // Indirect way to check the equivalence of pubkey-hashes. + // Create a pubkey hash and check if they are the same. + let addr = bitcoin::Address::p2wpkh(&compressed, bitcoin::Network::Bitcoin); + *script_pubkey == addr.script_pubkey() + } + Err(_) => false, + } }); match partial_sig_contains_pk { Some((pk, _sig)) => Ok(Descriptor::new_wpkh(*pk)?), @@ -243,9 +247,16 @@ impl Finalizer { // 6. `ShWpkh` case let partial_sig_contains_pk = input.partial_sigs.iter().find(|&(&pk, _sig)| { - let addr = Address::p2wpkh(&pk, Network::Bitcoin) - .expect("Address corresponding to valid pubkey"); - *redeem_script == addr.script_pubkey() + match bitcoin::key::CompressedPublicKey::try_from(pk) { + Ok(compressed) => { + let addr = bitcoin::Address::p2wpkh( + &compressed, + bitcoin::Network::Bitcoin, + ); + *redeem_script == addr.script_pubkey() + } + Err(_) => false, + } }); match partial_sig_contains_pk { Some((pk, _sig)) => Ok(Descriptor::new_sh_wpkh(*pk)?), @@ -500,7 +511,7 @@ pub enum InputError { /// Get the secp Errors directly SecpErr(bitcoin::secp256k1::Error), /// Key errors - KeyErr(bitcoin::key::Error), + KeyErr(bitcoin::key::FromSliceError), /// Could not satisfy taproot descriptor /// This error is returned when both script path and key paths could not be /// satisfied. We cannot return a detailed error because we try all miniscripts @@ -643,6 +654,6 @@ impl From for InputError { fn from(e: bitcoin::secp256k1::Error) -> Self { Self::SecpErr(e) } } -impl From for InputError { - fn from(e: bitcoin::key::Error) -> Self { Self::KeyErr(e) } +impl From for InputError { + fn from(e: bitcoin::key::FromSliceError) -> Self { Self::KeyErr(e) } } diff --git a/src/v2/miniscript/satisfy.rs b/src/v2/miniscript/satisfy.rs index b1029d5..c203830 100644 --- a/src/v2/miniscript/satisfy.rs +++ b/src/v2/miniscript/satisfy.rs @@ -3,7 +3,7 @@ use crate::bitcoin::hashes::{hash160, sha256d, Hash}; use crate::bitcoin::key::XOnlyPublicKey; use crate::bitcoin::taproot::{self, ControlBlock, LeafVersion, TapLeafHash}; -use crate::bitcoin::{absolute, ecdsa, ScriptBuf, Sequence}; +use crate::bitcoin::{absolute, ecdsa, relative, ScriptBuf}; use crate::miniscript::{MiniscriptKey, Preimage32, Satisfier, SigType, ToPublicKey}; use crate::prelude::*; use crate::v2::map::input::Input; @@ -81,27 +81,19 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey> Satisfier for InputSatisfier<'a> { return time <= lock_time; }, } - true + false } // TODO: Verify this is correct. - fn check_older(&self, n: Sequence) -> bool { - // https://github.com/bitcoin/bips/blob/master/bip-0112.mediawiki - // Disable flag set => return true. - if !n.is_relative_lock_time() { - return true; - } - + fn check_older(&self, n: relative::LockTime) -> bool { match self.input.sequence { - Some(sequence) => { - // TODO: Do we need to check the tx version? - if !sequence.is_relative_lock_time() { - return false; + Some(seq) => { + match relative::LockTime::from_sequence(seq) { + Err(_) => false, + Ok(lock_time) => n.is_implied_by(lock_time), } - >::check_older(&sequence, n) - } - // TODO: What to check here? - None => true, + }, + None => false } } @@ -125,7 +117,7 @@ impl<'a, Pk: MiniscriptKey + ToPublicKey> Satisfier for InputSatisfier<'a> { } } -#[allow(clippy::ptr_arg)] // We don't control the function signature this is used in. +#[allow(clippy::ptr_arg)] // We don't control the function signature this is used in. fn try_vec_as_preimage32(vec: &Vec) -> Option { if vec.len() == 32 { let mut arr = [0u8; 32]; diff --git a/src/v2/mod.rs b/src/v2/mod.rs index 6816add..73a7208 100644 --- a/src/v2/mod.rs +++ b/src/v2/mod.rs @@ -35,7 +35,6 @@ use core::marker::PhantomData; use std::collections::{HashMap, HashSet}; use bitcoin::bip32::{self, KeySource, Xpriv}; -use bitcoin::hashes::Hash; use bitcoin::key::{PrivateKey, PublicKey}; use bitcoin::locktime::absolute; use bitcoin::secp256k1::{Message, Secp256k1, Signing}; @@ -516,7 +515,7 @@ impl Psbt { // Updaters may change the sequence so to calculate ID we set it to zero. tx.input.iter_mut().for_each(|input| input.sequence = Sequence::ZERO); - Ok(tx.txid()) + Ok(tx.compute_txid()) } /// Creates an unsigned transaction from the inner [`Psbt`]. @@ -801,8 +800,10 @@ impl Psbt { Ok((msg, sighash_ty)) => (msg, sighash_ty), }; - let sig = - ecdsa::Signature { sig: secp.sign_ecdsa(&msg, &sk.inner), hash_ty: sighash_ty }; + let sig = ecdsa::Signature { + signature: secp.sign_ecdsa(&msg, &sk.inner), + sighash_type: sighash_ty, + }; let pk = sk.public_key(secp); @@ -840,32 +841,36 @@ impl Psbt { match self.output_type(input_index)? { Bare => { - let sighash = cache.legacy_signature_hash(input_index, spk, hash_ty.to_u32())?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + let sighash = cache + .legacy_signature_hash(input_index, spk, hash_ty.to_u32()) + .expect("input checked above"); + Ok((Message::from(sighash), hash_ty)) } Sh => { let script_code = input.redeem_script.as_ref().ok_or(SignError::MissingRedeemScript)?; - let sighash = - cache.legacy_signature_hash(input_index, script_code, hash_ty.to_u32())?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + let sighash = cache + .legacy_signature_hash(input_index, script_code, hash_ty.to_u32()) + .expect("input checked above"); + Ok((Message::from(sighash), hash_ty)) } Wpkh => { let sighash = cache.p2wpkh_signature_hash(input_index, spk, utxo.value, hash_ty)?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + Ok((Message::from(sighash), hash_ty)) } ShWpkh => { let redeem_script = input.redeem_script.as_ref().expect("checked above"); let sighash = cache.p2wpkh_signature_hash(input_index, redeem_script, utxo.value, hash_ty)?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + Ok((Message::from(sighash), hash_ty)) } Wsh | ShWsh => { let witness_script = input.witness_script.as_ref().ok_or(SignError::MissingWitnessScript)?; - let sighash = - cache.p2wsh_signature_hash(input_index, witness_script, utxo.value, hash_ty)?; - Ok((Message::from_digest(sighash.to_byte_array()), hash_ty)) + let sighash = cache + .p2wsh_signature_hash(input_index, witness_script, utxo.value, hash_ty) + .map_err(SignError::SegwitV0Sighash)?; + Ok((Message::from(sighash), hash_ty)) } Tr => { // This PSBT signing API is WIP, taproot to come shortly. @@ -980,7 +985,7 @@ impl Psbt { }; for (key, ecdsa_sig) in &input.partial_sigs { - let flag = EcdsaSighashType::from_standard(ecdsa_sig.hash_ty as u32) + let flag = EcdsaSighashType::from_standard(ecdsa_sig.sighash_type as u32) .map_err(|error| NonStandardPartialSigsSighashType { input_index, error })?; if target_ecdsa_sighash_ty != flag { return Err(WrongSighashFlag {