diff --git a/Cargo.toml b/Cargo.toml index 8e42746..1b8b2e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,23 +18,21 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std"] -std = ["bitcoin/std"] -no-std = ["bitcoin/no-std", "core2"] -serde = ["actual-serde", "bitcoin/serde"] +std = ["bitcoin/std", "bitcoin-internals/std"] +rand-std = ["bitcoin/rand-std", "std"] +rand = ["bitcoin/rand"] +serde = ["actual-serde", "bitcoin/serde", "bitcoin-internals/serde"] base64 = ["bitcoin/base64"] - miniscript-std = ["std", "miniscript/std"] -miniscript-no-std = ["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 } +# Only use this feature if you are doing a no-std build, othewise use "miniscript-std". +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" @@ -42,7 +40,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" @@ -55,3 +53,6 @@ required-features = ["std"] [[example]] name = "v2-separate-creator-constructor" required-features = ["std"] + +[lints.rust] +unexpected_cfgs = { level = "deny", check-cfg = ['cfg(rust_v_1_60)'] } 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 c28482d..318ef4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,9 +14,6 @@ // 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; @@ -48,11 +45,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 be2f9b3..4ef11bf 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-std")] + let signature = secp.sign_schnorr(&msg, &key_pair); + #[cfg(not(feature = "rand-std"))] + 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-std")] + let signature = secp.sign_schnorr(&msg, &key_pair); + #[cfg(not(feature = "rand-std"))] + 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,10 +1214,10 @@ 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")] use bitcoin::secp256k1::{All, SecretKey}; + use bitcoin::NetworkKind; use super::*; use crate::sighash_type::PsbtSighashType; @@ -1151,7 +1357,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 +1497,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 +1791,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 +1861,7 @@ mod tests { // let tx = &psbt.unsigned_tx; // assert_eq!( - // tx.txid(), + // tx.compute_txid(), // "75c5c9665a570569ad77dd1279e6fd4628a093c4dcbf8d41532614044c14c115".parse().unwrap(), // ); @@ -1930,7 +2136,7 @@ mod tests { let secp = Secp256k1::new(); let sk = SecretKey::new(&mut thread_rng()); - let priv_key = PrivateKey::new(sk, crate::Network::Regtest); + let priv_key = PrivateKey::new(sk, NetworkKind::Test); let pk = PublicKey::from_private_key(&secp, &priv_key); (priv_key, pk, secp) @@ -2091,16 +2297,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 449e04b..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 @@ -1013,7 +1008,9 @@ trait PsbtFields { fn tap_key_origins( &mut self, ) -> &mut BTreeMap, bip32::KeySource)>; + #[allow(dead_code)] fn proprietary(&mut self) -> &mut BTreeMap>; + #[allow(dead_code)] fn unknown(&mut self) -> &mut BTreeMap>; // `tap_tree` only appears in Output, so it's returned as an option of a mutable ref @@ -1040,9 +1037,11 @@ impl PsbtFields for Input { ) -> &mut BTreeMap, bip32::KeySource)> { &mut self.tap_key_origins } + #[allow(dead_code)] fn proprietary(&mut self) -> &mut BTreeMap> { &mut self.proprietary } + #[allow(dead_code)] fn unknown(&mut self) -> &mut BTreeMap> { &mut self.unknown } fn tap_scripts(&mut self) -> Option<&mut BTreeMap> { @@ -1067,9 +1066,11 @@ impl PsbtFields for Output { ) -> &mut BTreeMap, bip32::KeySource)> { &mut self.tap_key_origins } + #[allow(dead_code)] fn proprietary(&mut self) -> &mut BTreeMap> { &mut self.proprietary } + #[allow(dead_code)] fn unknown(&mut self) -> &mut BTreeMap> { &mut self.unknown } fn tap_tree(&mut self) -> Option<&mut Option> { Some(&mut self.tap_tree) } @@ -1316,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, @@ -1335,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), } } } @@ -1356,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. @@ -1496,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)); } @@ -1510,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)); } @@ -1588,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, @@ -1607,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![], @@ -1650,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 50623c2..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. @@ -323,7 +324,7 @@ impl Input { /// Returns true if this input has been finalized. /// /// > It checks whether all inputs have complete scriptSigs and scriptWitnesses by checking for - /// the presence of 0x07 Finalized scriptSig and 0x08 Finalized scriptWitness typed records. + /// > the presence of 0x07 Finalized scriptSig and 0x08 Finalized scriptWitness typed records. /// /// Therefore a finalized input must have both `final_script_sig` and `final_script_witness` /// fields set. For legacy transactions the `final_script_witness` will be an empty [`Witness`]. @@ -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 {