From 29f21d6ff51c3c0e3a3cc6c5f9bd1f55cfd7a20d Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 29 Jun 2022 14:48:19 +1000 Subject: [PATCH 1/3] Add additional sighash related pubic exports We already export some of the sighash related types, there are others that are uniquely named that can also be exported. Doing so makes use of the library more ergonomic because devs do not have to know where types are defined. --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a92c40cd..c962da7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,7 +101,8 @@ pub use crate::blockdata::script::Script; #[allow(deprecated)] pub use crate::blockdata::transaction::SigHashType; pub use crate::blockdata::transaction::{ - EcdsaSighashType, OutPoint, Sequence, Transaction, TxIn, TxOut, + EcdsaSighashType, NonStandardSighashType, OutPoint, Sequence, SighashTypeParseError, + Transaction, TxIn, TxOut, }; pub use crate::blockdata::witness::Witness; pub use crate::consensus::encode::VarInt; @@ -113,7 +114,7 @@ pub use crate::util::ecdsa::{self, EcdsaSig, EcdsaSigError}; pub use crate::util::key::{KeyPair, PrivateKey, PublicKey, XOnlyPublicKey}; pub use crate::util::merkleblock::MerkleBlock; pub use crate::util::schnorr::{self, SchnorrSig, SchnorrSigError}; -pub use crate::util::sighash::SchnorrSighashType; +pub use crate::util::sighash::{SchnorrSighashType, SighashCache}; pub use crate::util::{psbt, Error}; #[cfg(not(feature = "std"))] From 7c040f4437174692b5a85fdba0de148292566c1a Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Wed, 29 Jun 2022 14:54:54 +1000 Subject: [PATCH 2/3] Refactor imports Clean up some imports, only do the ones that we are about to touch. - Remove/add whitespace to group by crate - move modules/types to their module instead of using lib wide re-export (this helps minimise future merge conflicts) Re-order imports, refactor only. No logic changes. --- src/util/bip143.rs | 9 +++++---- src/util/ecdsa.rs | 6 ++++-- src/util/psbt/map/input.rs | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/util/bip143.rs b/src/util/bip143.rs index 73f40575..c4b35e34 100644 --- a/src/util/bip143.rs +++ b/src/util/bip143.rs @@ -9,15 +9,16 @@ //! signatures, which are placed in the scriptSig. //! +use core::ops::{Deref, DerefMut}; + +use crate::io; + use crate::hashes::Hash; use crate::hash_types::Sighash; use crate::blockdata::script::Script; -use crate::blockdata::witness::Witness; use crate::blockdata::transaction::{Transaction, EcdsaSighashType}; +use crate::blockdata::witness::Witness; use crate::consensus::encode; - -use crate::io; -use core::ops::{Deref, DerefMut}; use crate::util::sighash; /// A replacement for SigHashComponents which supports all sighash modes diff --git a/src/util/ecdsa.rs b/src/util/ecdsa.rs index 9ad215c7..7ae9cff3 100644 --- a/src/util/ecdsa.rs +++ b/src/util/ecdsa.rs @@ -5,12 +5,14 @@ //! //! This module provides ECDSA signatures used Bitcoin that can be roundtrip (de)serialized. -use crate::prelude::*; use core::str::FromStr; use core::{fmt, iter}; + +use secp256k1; + +use crate::prelude::*; use crate::hashes::hex::{self, FromHex}; use crate::blockdata::transaction::NonStandardSighashType; -use secp256k1; use crate::EcdsaSighashType; use crate::internal_macros::write_err; diff --git a/src/util/psbt/map/input.rs b/src/util/psbt/map/input.rs index 754df14b..e6f0d30b 100644 --- a/src/util/psbt/map/input.rs +++ b/src/util/psbt/map/input.rs @@ -2,17 +2,18 @@ use crate::prelude::*; use crate::io; + use core::fmt; use core::str::FromStr; use core::convert::TryFrom; -use secp256k1; +use secp256k1::XOnlyPublicKey; + use crate::blockdata::script::Script; use crate::blockdata::witness::Witness; use crate::blockdata::transaction::{Transaction, TxOut, NonStandardSighashType, SighashTypeParseError}; use crate::consensus::encode; use crate::hashes::{self, hash160, ripemd160, sha256, sha256d}; -use secp256k1::XOnlyPublicKey; use crate::util::bip32::KeySource; use crate::util::psbt; use crate::util::psbt::map::Map; @@ -20,7 +21,6 @@ use crate::util::psbt::raw; use crate::util::psbt::serialize::Deserialize; use crate::util::psbt::{Error, error}; use crate::util::key::PublicKey; - use crate::util::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapBranchHash}; use crate::util::sighash; use crate::{EcdsaSighashType, SchnorrSighashType, EcdsaSig, SchnorrSig}; From cc2c67fc2dab683f324546feb53fef9f5dd9652f Mon Sep 17 00:00:00 2001 From: "Tobin C. Harding" Date: Tue, 19 Jul 2022 11:41:35 +1000 Subject: [PATCH 3/3] Move sighash types to the sighash module Currently we have a bunch of sighash types defined in the `transaction` module and we have a newer `sighash` module that defines a bunch of related types. This is kruft from the development process as we added the new types. If we put things that are related together it makes it easier for devs to find their way around the library and grok the code. Move all the sighash types from the `blockdata::transaction` module to the `sighash` module. Refactor only, no logic changes. --- src/blockdata/transaction.rs | 183 ++--------------------------------- src/blockdata/witness.rs | 2 +- src/lib.rs | 10 +- src/util/bip143.rs | 7 +- src/util/ecdsa.rs | 3 +- src/util/psbt/map/input.rs | 5 +- src/util/psbt/mod.rs | 3 +- src/util/sighash.rs | 180 +++++++++++++++++++++++++++++++++- 8 files changed, 199 insertions(+), 194 deletions(-) diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index ea2cd2b7..f837ad63 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -29,7 +29,7 @@ use crate::blockdata::locktime::{LockTime, PackedLockTime, Height, Time}; use crate::consensus::{encode, Decodable, Encodable}; use crate::hash_types::{Sighash, Txid, Wtxid}; use crate::VarInt; -use crate::internal_macros::{impl_consensus_encoding, serde_string_impl, serde_struct_human_string_impl, write_err}; +use crate::internal_macros::{impl_consensus_encoding, serde_struct_human_string_impl, write_err}; use crate::parse::impl_parse_str_through_int; #[cfg(doc)] @@ -1014,183 +1014,16 @@ impl Decodable for Transaction { } } -/// This type is consensus valid but an input including it would prevent the transaction from -/// being relayed on today's Bitcoin network. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct NonStandardSighashType(pub u32); - -impl fmt::Display for NonStandardSighashType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Non standard sighash type {}", self.0) - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl std::error::Error for NonStandardSighashType { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } -} - /// Legacy Hashtype of an input's signature #[deprecated(since = "0.28.0", note = "Please use [`EcdsaSighashType`] instead")] pub type SigHashType = EcdsaSighashType; -/// Hashtype of an input's signature, encoded in the last byte of the signature. -/// -/// Fixed values so they can be cast as integer types for encoding (see also -/// [`SchnorrSighashType`]). -#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] -pub enum EcdsaSighashType { - /// 0x1: Sign all outputs. - All = 0x01, - /// 0x2: Sign no outputs --- anyone can choose the destination. - None = 0x02, - /// 0x3: Sign the output whose index matches this input's index. If none exists, - /// sign the hash `0000000000000000000000000000000000000000000000000000000000000001`. - /// (This rule is probably an unintentional C++ism, but it's consensus so we have - /// to follow it.) - Single = 0x03, - /// 0x81: Sign all outputs but only this input. - AllPlusAnyoneCanPay = 0x81, - /// 0x82: Sign no outputs and only this input. - NonePlusAnyoneCanPay = 0x82, - /// 0x83: Sign one output and only this input (see `Single` for what "one output" means). - SinglePlusAnyoneCanPay = 0x83 -} -serde_string_impl!(EcdsaSighashType, "a EcdsaSighashType data"); - -impl fmt::Display for EcdsaSighashType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let s = match self { - EcdsaSighashType::All => "SIGHASH_ALL", - EcdsaSighashType::None => "SIGHASH_NONE", - EcdsaSighashType::Single => "SIGHASH_SINGLE", - EcdsaSighashType::AllPlusAnyoneCanPay => "SIGHASH_ALL|SIGHASH_ANYONECANPAY", - EcdsaSighashType::NonePlusAnyoneCanPay => "SIGHASH_NONE|SIGHASH_ANYONECANPAY", - EcdsaSighashType::SinglePlusAnyoneCanPay => "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY", - }; - f.write_str(s) - } -} - -impl str::FromStr for EcdsaSighashType { - type Err = SighashTypeParseError; - - fn from_str(s: &str) -> Result { - match s { - "SIGHASH_ALL" => Ok(EcdsaSighashType::All), - "SIGHASH_NONE" => Ok(EcdsaSighashType::None), - "SIGHASH_SINGLE" => Ok(EcdsaSighashType::Single), - "SIGHASH_ALL|SIGHASH_ANYONECANPAY" => Ok(EcdsaSighashType::AllPlusAnyoneCanPay), - "SIGHASH_NONE|SIGHASH_ANYONECANPAY" => Ok(EcdsaSighashType::NonePlusAnyoneCanPay), - "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY" => Ok(EcdsaSighashType::SinglePlusAnyoneCanPay), - _ => Err(SighashTypeParseError { unrecognized: s.to_owned() }), - } - } -} - -impl EcdsaSighashType { - /// Splits the sighash flag into the "real" sighash flag and the ANYONECANPAY boolean. - pub(crate) fn split_anyonecanpay_flag(self) -> (EcdsaSighashType, bool) { - match self { - EcdsaSighashType::All => (EcdsaSighashType::All, false), - EcdsaSighashType::None => (EcdsaSighashType::None, false), - EcdsaSighashType::Single => (EcdsaSighashType::Single, false), - EcdsaSighashType::AllPlusAnyoneCanPay => (EcdsaSighashType::All, true), - EcdsaSighashType::NonePlusAnyoneCanPay => (EcdsaSighashType::None, true), - EcdsaSighashType::SinglePlusAnyoneCanPay => (EcdsaSighashType::Single, true) - } - } - - /// Creates a [`EcdsaSighashType`] from a raw `u32`. - #[deprecated(since="0.28.0", note="please use `from_consensus`")] - pub fn from_u32_consensus(n: u32) -> EcdsaSighashType { - EcdsaSighashType::from_consensus(n) - } - - /// Creates a [`EcdsaSighashType`] from a raw `u32`. - /// - /// **Note**: this replicates consensus behaviour, for current standardness rules correctness - /// you probably want [`Self::from_standard`]. - /// - /// This might cause unexpected behavior because it does not roundtrip. That is, - /// `EcdsaSighashType::from_consensus(n) as u32 != n` for non-standard values of `n`. While - /// verifying signatures, the user should retain the `n` and use it compute the signature hash - /// message. - pub fn from_consensus(n: u32) -> EcdsaSighashType { - // In Bitcoin Core, the SignatureHash function will mask the (int32) value with - // 0x1f to (apparently) deactivate ACP when checking for SINGLE and NONE bits. - // We however want to be matching also against on ACP-masked ALL, SINGLE, and NONE. - // So here we re-activate ACP. - let mask = 0x1f | 0x80; - match n & mask { - // "real" sighashes - 0x01 => EcdsaSighashType::All, - 0x02 => EcdsaSighashType::None, - 0x03 => EcdsaSighashType::Single, - 0x81 => EcdsaSighashType::AllPlusAnyoneCanPay, - 0x82 => EcdsaSighashType::NonePlusAnyoneCanPay, - 0x83 => EcdsaSighashType::SinglePlusAnyoneCanPay, - // catchalls - x if x & 0x80 == 0x80 => EcdsaSighashType::AllPlusAnyoneCanPay, - _ => EcdsaSighashType::All - } - } - - /// Creates a [`EcdsaSighashType`] from a raw `u32`. - #[deprecated(since="0.28.0", note="please use `from_standard`")] - pub fn from_u32_standard(n: u32) -> Result { - EcdsaSighashType::from_standard(n) - } - - /// Creates a [`EcdsaSighashType`] from a raw `u32`. - /// - /// # Errors - /// - /// If `n` is a non-standard sighash value. - pub fn from_standard(n: u32) -> Result { - match n { - // Standard sighashes, see https://github.com/bitcoin/bitcoin/blob/b805dbb0b9c90dadef0424e5b3bf86ac308e103e/src/script/interpreter.cpp#L189-L198 - 0x01 => Ok(EcdsaSighashType::All), - 0x02 => Ok(EcdsaSighashType::None), - 0x03 => Ok(EcdsaSighashType::Single), - 0x81 => Ok(EcdsaSighashType::AllPlusAnyoneCanPay), - 0x82 => Ok(EcdsaSighashType::NonePlusAnyoneCanPay), - 0x83 => Ok(EcdsaSighashType::SinglePlusAnyoneCanPay), - non_standard => Err(NonStandardSighashType(non_standard)) - } - } - - /// Converts [`EcdsaSighashType`] to a `u32` sighash flag. - /// - /// The returned value is guaranteed to be a valid according to standardness rules. - pub fn to_u32(self) -> u32 { self as u32 } -} - -/// Error returned for failure during parsing one of the sighash types. -/// -/// This is currently returned for unrecognized sighash strings. -#[derive(Debug, Clone)] -pub struct SighashTypeParseError { - /// The unrecognized string we attempted to parse. - pub unrecognized: String, -} - -impl fmt::Display for SighashTypeParseError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Unrecognized SIGHASH string '{}'", self.unrecognized) - } -} - -#[cfg(feature = "std")] -#[cfg_attr(docsrs, doc(cfg(feature = "std")))] -impl std::error::Error for SighashTypeParseError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } -} +#[deprecated(since = "0.30.0", note = "use crate::NonStandardSighashType instead")] +pub use crate::util::sighash::NonStandardSighashType; +#[deprecated(since = "0.30.0", note = "use crate::EcdsaSighashType instead")] +pub use crate::util::sighash::EcdsaSighashType; +#[deprecated(since = "0.30.0", note = "use crate::SighashTypeParseError instead")] +pub use crate::util::sighash::SighashTypeParseError; #[cfg(test)] mod tests { @@ -1207,7 +1040,7 @@ mod tests { use crate::hashes::hex::FromHex; use crate::hash_types::*; - use super::EcdsaSighashType; + use crate::util::sighash::NonStandardSighashType; const SOME_TX: &str = "0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000"; diff --git a/src/blockdata/witness.rs b/src/blockdata/witness.rs index 9b16e647..6f2ff7f5 100644 --- a/src/blockdata/witness.rs +++ b/src/blockdata/witness.rs @@ -7,9 +7,9 @@ use secp256k1::ecdsa; -use crate::blockdata::transaction::EcdsaSighashType; use crate::consensus::encode::{Error, MAX_VEC_SIZE}; use crate::consensus::{Decodable, Encodable, WriteExt}; +use crate::util::sighash::EcdsaSighashType; use crate::io::{self, Read, Write}; use crate::prelude::*; use crate::VarInt; diff --git a/src/lib.rs b/src/lib.rs index c962da7a..1d3280f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,10 +100,7 @@ pub use crate::blockdata::locktime::{self, LockTime, PackedLockTime}; pub use crate::blockdata::script::Script; #[allow(deprecated)] pub use crate::blockdata::transaction::SigHashType; -pub use crate::blockdata::transaction::{ - EcdsaSighashType, NonStandardSighashType, OutPoint, Sequence, SighashTypeParseError, - Transaction, TxIn, TxOut, -}; +pub use crate::blockdata::transaction::{OutPoint, Sequence, Transaction, TxIn, TxOut}; pub use crate::blockdata::witness::Witness; pub use crate::consensus::encode::VarInt; pub use crate::hash_types::*; @@ -114,7 +111,10 @@ pub use crate::util::ecdsa::{self, EcdsaSig, EcdsaSigError}; pub use crate::util::key::{KeyPair, PrivateKey, PublicKey, XOnlyPublicKey}; pub use crate::util::merkleblock::MerkleBlock; pub use crate::util::schnorr::{self, SchnorrSig, SchnorrSigError}; -pub use crate::util::sighash::{SchnorrSighashType, SighashCache}; +pub use crate::util::sighash::{ + EcdsaSighashType, NonStandardSighashType, SchnorrSighashType, SighashCache, + SighashTypeParseError, +}; pub use crate::util::{psbt, Error}; #[cfg(not(feature = "std"))] diff --git a/src/util/bip143.rs b/src/util/bip143.rs index c4b35e34..34c739d2 100644 --- a/src/util/bip143.rs +++ b/src/util/bip143.rs @@ -16,10 +16,10 @@ use crate::io; use crate::hashes::Hash; use crate::hash_types::Sighash; use crate::blockdata::script::Script; -use crate::blockdata::transaction::{Transaction, EcdsaSighashType}; +use crate::blockdata::transaction::Transaction; use crate::blockdata::witness::Witness; use crate::consensus::encode; -use crate::util::sighash; +use crate::util::sighash::{self, EcdsaSighashType}; /// A replacement for SigHashComponents which supports all sighash modes #[deprecated(since = "0.28.0", note = "please use [sighash::SighashCache] instead")] @@ -78,9 +78,8 @@ impl> SigHashCache { /// panics if `input_index` is out of bounds with respect of the number of inputs /// /// ``` - /// use bitcoin::blockdata::transaction::{Transaction, EcdsaSighashType}; /// use bitcoin::util::bip143::SigHashCache; - /// use bitcoin::{PackedLockTime, Script}; + /// use bitcoin::{EcdsaSighashType, Script, Transaction, PackedLockTime}; /// /// let mut tx_to_sign = Transaction { version: 2, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() }; /// let input_count = tx_to_sign.input.len(); diff --git a/src/util/ecdsa.rs b/src/util/ecdsa.rs index 7ae9cff3..50116723 100644 --- a/src/util/ecdsa.rs +++ b/src/util/ecdsa.rs @@ -12,9 +12,8 @@ use secp256k1; use crate::prelude::*; use crate::hashes::hex::{self, FromHex}; -use crate::blockdata::transaction::NonStandardSighashType; -use crate::EcdsaSighashType; use crate::internal_macros::write_err; +use crate::util::sighash::{EcdsaSighashType, NonStandardSighashType}; /// An ECDSA signature with the corresponding hash type. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] diff --git a/src/util/psbt/map/input.rs b/src/util/psbt/map/input.rs index e6f0d30b..1e9186f6 100644 --- a/src/util/psbt/map/input.rs +++ b/src/util/psbt/map/input.rs @@ -11,7 +11,7 @@ use secp256k1::XOnlyPublicKey; use crate::blockdata::script::Script; use crate::blockdata::witness::Witness; -use crate::blockdata::transaction::{Transaction, TxOut, NonStandardSighashType, SighashTypeParseError}; +use crate::blockdata::transaction::{Transaction, TxOut}; use crate::consensus::encode; use crate::hashes::{self, hash160, ripemd160, sha256, sha256d}; use crate::util::bip32::KeySource; @@ -21,9 +21,10 @@ use crate::util::psbt::raw; use crate::util::psbt::serialize::Deserialize; use crate::util::psbt::{Error, error}; use crate::util::key::PublicKey; +use crate::util::sighash::{NonStandardSighashType, SighashTypeParseError, EcdsaSighashType, SchnorrSighashType}; use crate::util::taproot::{ControlBlock, LeafVersion, TapLeafHash, TapBranchHash}; use crate::util::sighash; -use crate::{EcdsaSighashType, SchnorrSighashType, EcdsaSig, SchnorrSig}; +use crate::{EcdsaSig, SchnorrSig}; /// Type: Non-Witness UTXO PSBT_IN_NON_WITNESS_UTXO = 0x00 const PSBT_IN_NON_WITNESS_UTXO: u8 = 0x00; diff --git a/src/util/psbt/mod.rs b/src/util/psbt/mod.rs index 2cb7e6e7..6ee66e82 100644 --- a/src/util/psbt/mod.rs +++ b/src/util/psbt/mod.rs @@ -598,12 +598,13 @@ mod tests { use crate::hash_types::Txid; use crate::blockdata::script::Script; - use crate::blockdata::transaction::{EcdsaSighashType, Transaction, TxIn, TxOut, OutPoint, Sequence}; + use crate::blockdata::transaction::{Transaction, TxIn, TxOut, OutPoint, Sequence}; use crate::consensus::encode::serialize_hex; use crate::blockdata::locktime::PackedLockTime; use crate::util::psbt::map::{Map, Input, Output}; use crate::util::psbt::raw; use crate::util::psbt::{PartiallySignedTransaction, Error}; + use crate::util::sighash::EcdsaSighashType; use std::collections::BTreeMap; use crate::blockdata::witness::Witness; use crate::internal_macros::hex_script; diff --git a/src/util/sighash.rs b/src/util/sighash.rs index 62e9c9ef..90287d57 100644 --- a/src/util/sighash.rs +++ b/src/util/sighash.rs @@ -22,8 +22,6 @@ use crate::internal_macros::serde_string_impl; use crate::prelude::*; use crate::util::taproot::{TapLeafHash, TAPROOT_ANNEX_PREFIX, TapSighashHash, LeafVersion}; -pub use crate::blockdata::transaction::{EcdsaSighashType, SighashTypeParseError}; - /// Used for signature hash for invalid use of SIGHASH_SINGLE. pub(crate) const UINT256_ONE: [u8; 32] = [ 1, 0, 0, 0, 0, 0, 0, 0, @@ -297,6 +295,138 @@ impl<'s> From> for TapLeafHash { } } +/// Hashtype of an input's signature, encoded in the last byte of the signature. +/// +/// Fixed values so they can be cast as integer types for encoding (see also +/// [`SchnorrSighashType`]). +#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] +pub enum EcdsaSighashType { + /// 0x1: Sign all outputs. + All = 0x01, + /// 0x2: Sign no outputs --- anyone can choose the destination. + None = 0x02, + /// 0x3: Sign the output whose index matches this input's index. If none exists, + /// sign the hash `0000000000000000000000000000000000000000000000000000000000000001`. + /// (This rule is probably an unintentional C++ism, but it's consensus so we have + /// to follow it.) + Single = 0x03, + /// 0x81: Sign all outputs but only this input. + AllPlusAnyoneCanPay = 0x81, + /// 0x82: Sign no outputs and only this input. + NonePlusAnyoneCanPay = 0x82, + /// 0x83: Sign one output and only this input (see `Single` for what "one output" means). + SinglePlusAnyoneCanPay = 0x83 +} +serde_string_impl!(EcdsaSighashType, "a EcdsaSighashType data"); + +impl fmt::Display for EcdsaSighashType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + EcdsaSighashType::All => "SIGHASH_ALL", + EcdsaSighashType::None => "SIGHASH_NONE", + EcdsaSighashType::Single => "SIGHASH_SINGLE", + EcdsaSighashType::AllPlusAnyoneCanPay => "SIGHASH_ALL|SIGHASH_ANYONECANPAY", + EcdsaSighashType::NonePlusAnyoneCanPay => "SIGHASH_NONE|SIGHASH_ANYONECANPAY", + EcdsaSighashType::SinglePlusAnyoneCanPay => "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY", + }; + f.write_str(s) + } +} + +impl str::FromStr for EcdsaSighashType { + type Err = SighashTypeParseError; + + fn from_str(s: &str) -> Result { + match s { + "SIGHASH_ALL" => Ok(EcdsaSighashType::All), + "SIGHASH_NONE" => Ok(EcdsaSighashType::None), + "SIGHASH_SINGLE" => Ok(EcdsaSighashType::Single), + "SIGHASH_ALL|SIGHASH_ANYONECANPAY" => Ok(EcdsaSighashType::AllPlusAnyoneCanPay), + "SIGHASH_NONE|SIGHASH_ANYONECANPAY" => Ok(EcdsaSighashType::NonePlusAnyoneCanPay), + "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY" => Ok(EcdsaSighashType::SinglePlusAnyoneCanPay), + _ => Err(SighashTypeParseError { unrecognized: s.to_owned() }), + } + } +} + +impl EcdsaSighashType { + /// Splits the sighash flag into the "real" sighash flag and the ANYONECANPAY boolean. + pub(crate) fn split_anyonecanpay_flag(self) -> (EcdsaSighashType, bool) { + match self { + EcdsaSighashType::All => (EcdsaSighashType::All, false), + EcdsaSighashType::None => (EcdsaSighashType::None, false), + EcdsaSighashType::Single => (EcdsaSighashType::Single, false), + EcdsaSighashType::AllPlusAnyoneCanPay => (EcdsaSighashType::All, true), + EcdsaSighashType::NonePlusAnyoneCanPay => (EcdsaSighashType::None, true), + EcdsaSighashType::SinglePlusAnyoneCanPay => (EcdsaSighashType::Single, true) + } + } + + /// Creates a [`EcdsaSighashType`] from a raw `u32`. + #[deprecated(since="0.28.0", note="please use `from_consensus`")] + pub fn from_u32_consensus(n: u32) -> EcdsaSighashType { + EcdsaSighashType::from_consensus(n) + } + + /// Creates a [`EcdsaSighashType`] from a raw `u32`. + /// + /// **Note**: this replicates consensus behaviour, for current standardness rules correctness + /// you probably want [`Self::from_standard`]. + /// + /// This might cause unexpected behavior because it does not roundtrip. That is, + /// `EcdsaSighashType::from_consensus(n) as u32 != n` for non-standard values of `n`. While + /// verifying signatures, the user should retain the `n` and use it compute the signature hash + /// message. + pub fn from_consensus(n: u32) -> EcdsaSighashType { + // In Bitcoin Core, the SignatureHash function will mask the (int32) value with + // 0x1f to (apparently) deactivate ACP when checking for SINGLE and NONE bits. + // We however want to be matching also against on ACP-masked ALL, SINGLE, and NONE. + // So here we re-activate ACP. + let mask = 0x1f | 0x80; + match n & mask { + // "real" sighashes + 0x01 => EcdsaSighashType::All, + 0x02 => EcdsaSighashType::None, + 0x03 => EcdsaSighashType::Single, + 0x81 => EcdsaSighashType::AllPlusAnyoneCanPay, + 0x82 => EcdsaSighashType::NonePlusAnyoneCanPay, + 0x83 => EcdsaSighashType::SinglePlusAnyoneCanPay, + // catchalls + x if x & 0x80 == 0x80 => EcdsaSighashType::AllPlusAnyoneCanPay, + _ => EcdsaSighashType::All + } + } + + /// Creates a [`EcdsaSighashType`] from a raw `u32`. + #[deprecated(since="0.28.0", note="please use `from_standard`")] + pub fn from_u32_standard(n: u32) -> Result { + EcdsaSighashType::from_standard(n) + } + + /// Creates a [`EcdsaSighashType`] from a raw `u32`. + /// + /// # Errors + /// + /// If `n` is a non-standard sighash value. + pub fn from_standard(n: u32) -> Result { + match n { + // Standard sighashes, see https://github.com/bitcoin/bitcoin/blob/b805dbb0b9c90dadef0424e5b3bf86ac308e103e/src/script/interpreter.cpp#L189-L198 + 0x01 => Ok(EcdsaSighashType::All), + 0x02 => Ok(EcdsaSighashType::None), + 0x03 => Ok(EcdsaSighashType::Single), + 0x81 => Ok(EcdsaSighashType::AllPlusAnyoneCanPay), + 0x82 => Ok(EcdsaSighashType::NonePlusAnyoneCanPay), + 0x83 => Ok(EcdsaSighashType::SinglePlusAnyoneCanPay), + non_standard => Err(NonStandardSighashType(non_standard)) + } + } + + /// Converts [`EcdsaSighashType`] to a `u32` sighash flag. + /// + /// The returned value is guaranteed to be a valid according to standardness rules. + pub fn to_u32(self) -> u32 { self as u32 } +} + impl From for SchnorrSighashType { fn from(s: EcdsaSighashType) -> Self { match s { @@ -347,6 +477,48 @@ impl SchnorrSighashType { } } +/// This type is consensus valid but an input including it would prevent the transaction from +/// being relayed on today's Bitcoin network. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct NonStandardSighashType(pub u32); + +impl fmt::Display for NonStandardSighashType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Non standard sighash type {}", self.0) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for NonStandardSighashType { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +/// Error returned for failure during parsing one of the sighash types. +/// +/// This is currently returned for unrecognized sighash strings. +#[derive(Debug, Clone)] +pub struct SighashTypeParseError { + /// The unrecognized string we attempted to parse. + pub unrecognized: String, +} + +impl fmt::Display for SighashTypeParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Unrecognized SIGHASH string '{}'", self.unrecognized) + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl std::error::Error for SighashTypeParseError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + impl> SighashCache { /// Constructs a new `SighashCache` from an unsigned transaction. /// @@ -863,8 +1035,8 @@ impl> SighashCache { /// /// This allows in-line signing such as /// ``` - /// use bitcoin::blockdata::transaction::{Transaction, EcdsaSighashType}; - /// use bitcoin::util::sighash::SighashCache; + /// use bitcoin::blockdata::transaction::Transaction; + /// use bitcoin::util::sighash::{EcdsaSighashType, SighashCache}; /// use bitcoin::{PackedLockTime, Script}; /// /// let mut tx_to_sign = Transaction { version: 2, lock_time: PackedLockTime::ZERO, input: Vec::new(), output: Vec::new() };