Move minimal sighash types to crypto crate
What changed, and why it matters
This commit is a routine code reorganization: it moves some Bitcoin signature-hash type definitions (EcdsaSighashType, TapSighashType, and related error types) from the main bitcoin crate into a new bitcoin-crypto sub-crate, then re-exports them so existing users see no change. There is no functional change to how signatures or transactions are validated.
No security action required. Treat as normal maintenance; verify downstream builds compile and public API tests pass.
Security signals we found
No security-relevant behavioral change in moved code
Public API preserved via re-exports
New dependency introduced (bitcoin-crypto) within the same workspace
Evidence from the diff
The patch relocates minimal sighash types and their error types to a new crypto crate (bitcoin-crypto) and re-exports them in bitcoin/src/crypto/sighash.rs to preserve the public API. Cargo manifests and lockfiles are updated to add the new dependency and propagate feature flags (std, serde, arbitrary). The actual enum definitions, parsing logic, From/Display/Arbitrary implementations, and error structs are copied verbatim with no behavioral modifications. This is a pure refactor intended to reduce coupling between crates.
Changed components
bitcoin/src/crypto/sighash.rscrypto/src/sighash.rsbitcoin/Cargo.tomlcrypto/Cargo.tomlInspect captured patch +364 / −310
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index 93c71bf6..6a1a0a67 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -55,6 +55,7 @@ dependencies = [
"bech32",
"bincode",
"bitcoin-consensus-encoding",
+ "bitcoin-crypto",
"bitcoin-internals",
"bitcoin-io",
"bitcoin-network-kind",
@@ -89,6 +90,11 @@ dependencies = [
[[package]]
name = "bitcoin-crypto"
version = "0.0.0"
+dependencies = [
+ "arbitrary",
+ "bitcoin-internals",
+ "serde",
+]
[[package]]
name = "bitcoin-fuzz"
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 0fce21ab..05d05d4f 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -54,6 +54,7 @@ dependencies = [
"bech32",
"bincode",
"bitcoin-consensus-encoding",
+ "bitcoin-crypto",
"bitcoin-internals",
"bitcoin-io",
"bitcoin-network-kind",
@@ -88,6 +89,11 @@ dependencies = [
[[package]]
name = "bitcoin-crypto"
version = "0.0.0"
+dependencies = [
+ "arbitrary",
+ "bitcoin-internals",
+ "serde",
+]
[[package]]
name = "bitcoin-fuzz"
diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml
index 479ec45d..3b2522fb 100644
--- a/bitcoin/Cargo.toml
+++ b/bitcoin/Cargo.toml
@@ -17,17 +17,18 @@ exclude = ["tests", "contrib"]
# If you change features or optional dependencies in any way please update the "# Cargo features" section in lib.rs as well.
[features]
default = [ "std", "secp-recovery" ]
-std = ["base58/std", "bech32/std", "encoding/std", "hashes/std", "hex-stable/std", "hex-unstable/std", "internals/std", "io/std", "network/std", "primitives/std", "secp256k1/std", "units/std", "base64?/std", "bitcoinconsensus?/std"]
+std = ["base58/std", "bech32/std", "crypto/std", "encoding/std", "hashes/std", "hex-stable/std", "hex-unstable/std", "internals/std", "io/std", "network/std", "primitives/std", "secp256k1/std", "units/std", "base64?/std", "bitcoinconsensus?/std"]
rand = ["secp256k1/rand"]
-serde = ["base64", "dep:serde", "hashes/serde", "internals/serde", "network/serde", "primitives/serde", "secp256k1/serde", "units/serde"]
+serde = ["base64", "crypto/serde", "dep:serde", "hashes/serde", "internals/serde", "network/serde", "primitives/serde", "secp256k1/serde", "units/serde"]
secp-global-context = ["secp256k1/global-context"]
secp-lowmemory = ["secp256k1/lowmemory"]
secp-recovery = ["secp256k1/recovery"]
-arbitrary = ["dep:arbitrary", "units/arbitrary", "primitives/arbitrary", "hashes/arbitrary", "secp256k1/arbitrary", "network/arbitrary"]
+arbitrary = ["crypto/arbitrary", "dep:arbitrary", "units/arbitrary", "primitives/arbitrary", "hashes/arbitrary", "secp256k1/arbitrary", "network/arbitrary"]
[dependencies]
base58 = { package = "base58ck", path = "../base58", version = "0.4.0", default-features = false, features = ["alloc"] }
bech32 = { version = "0.11.0", default-features = false, features = ["alloc"] }
+crypto = { package = "bitcoin-crypto", path = "../crypto", default-features = false, features = ["alloc"] }
hashes = { package = "bitcoin_hashes", path = "../hashes", version = "0.20.0", default-features = false, features = ["alloc", "hex"] }
encoding = { package = "bitcoin-consensus-encoding", path = "../consensus_encoding", version = "0.2.0", default-features = false, features = ["alloc"] }
hex-stable = { package = "hex-conservative", version = "1.0.0", default-features = false, features = ["alloc"] }
diff --git a/bitcoin/src/crypto/sighash.rs b/bitcoin/src/crypto/sighash.rs
index 71e7fe0c..0d7a75f9 100644
--- a/bitcoin/src/crypto/sighash.rs
+++ b/bitcoin/src/crypto/sighash.rs
@@ -12,7 +12,7 @@
//! [BIP-0341]: <https://github.com/bitcoin/bips/blob/150ab6f5c3aca9da05fccc5b435e9667853407f4/bip-0341.mediawiki>
//! [BIP-0143]: <https://github.com/bitcoin/bips/blob/99701f68a88ce33b2d0838eb84e115cef505b4c2/bip-0143.mediawiki>
-use core::{fmt, str};
+use core::str;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
@@ -20,7 +20,7 @@ use hashes::{hash_newtype, sha256, sha256d, sha256t, sha256t_tag};
use io::Write;
use crate::consensus::{encode, Encodable};
-use crate::prelude::{Borrow, BorrowMut, ToOwned};
+use crate::prelude::{Borrow, BorrowMut};
use crate::script::{ScriptExt as _, ScriptHashableTag};
use crate::taproot::{LeafVersion, TapLeafHash, TapLeafTag, TAPROOT_ANNEX_PREFIX};
use crate::transaction::TransactionExt as _;
@@ -36,6 +36,8 @@ pub use self::error::{
SigningDataError, SingleMissingOutputError, P2wpkhError, PrevoutsIndexError, PrevoutsKindError,
PrevoutsSizeError, TaprootError,
};
+#[doc(inline)]
+pub use crypto::sighash::{EcdsaSighashType, TapSighashType};
/// Used for signature hash for invalid use of SIGHASH_SINGLE.
#[rustfmt::skip]
@@ -171,67 +173,6 @@ pub struct ScriptPath<'s> {
leaf_version: LeafVersion,
}
-/// 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.
-#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
-pub enum TapSighashType {
- /// 0x0: Used when not explicitly specified, defaults to [`TapSighashType::All`]
- Default = 0x00,
- /// 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,
-}
-#[cfg(feature = "serde")]
-internals::serde_string_impl!(TapSighashType, "a TapSighashType data");
-
-impl fmt::Display for TapSighashType {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- use TapSighashType::*;
-
- let s = match self {
- Default => "SIGHASH_DEFAULT",
- All => "SIGHASH_ALL",
- None => "SIGHASH_NONE",
- Single => "SIGHASH_SINGLE",
- AllPlusAnyoneCanPay => "SIGHASH_ALL|SIGHASH_ANYONECANPAY",
- NonePlusAnyoneCanPay => "SIGHASH_NONE|SIGHASH_ANYONECANPAY",
- SinglePlusAnyoneCanPay => "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY",
- };
- f.write_str(s)
- }
-}
-
-impl str::FromStr for TapSighashType {
- type Err = SighashTypeParseError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- use TapSighashType::*;
-
- match s {
- "SIGHASH_DEFAULT" => Ok(Default),
- "SIGHASH_ALL" => Ok(All),
- "SIGHASH_NONE" => Ok(None),
- "SIGHASH_SINGLE" => Ok(Single),
- "SIGHASH_ALL|SIGHASH_ANYONECANPAY" => Ok(AllPlusAnyoneCanPay),
- "SIGHASH_NONE|SIGHASH_ANYONECANPAY" => Ok(NonePlusAnyoneCanPay),
- "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY" => Ok(SinglePlusAnyoneCanPay),
- _ => Err(SighashTypeParseError { unrecognized: s.to_owned() }),
- }
- }
-}
-
impl<T> Prevouts<'_, T>
where
T: Borrow<TxOut>,
@@ -294,164 +235,6 @@ impl<'s> From<ScriptPath<'s>> for TapLeafHash {
fn from(script_path: ScriptPath<'s>) -> Self { script_path.leaf_hash() }
}
-/// 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
-/// [`TapSighashType`]).
-#[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,
-}
-#[cfg(feature = "serde")]
-internals::serde_string_impl!(EcdsaSighashType, "a EcdsaSighashType data");
-
-impl fmt::Display for EcdsaSighashType {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- use EcdsaSighashType::*;
-
- let s = match self {
- All => "SIGHASH_ALL",
- None => "SIGHASH_NONE",
- Single => "SIGHASH_SINGLE",
- AllPlusAnyoneCanPay => "SIGHASH_ALL|SIGHASH_ANYONECANPAY",
- NonePlusAnyoneCanPay => "SIGHASH_NONE|SIGHASH_ANYONECANPAY",
- SinglePlusAnyoneCanPay => "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY",
- };
- f.write_str(s)
- }
-}
-
-impl str::FromStr for EcdsaSighashType {
- type Err = SighashTypeParseError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- use EcdsaSighashType::*;
-
- match s {
- "SIGHASH_ALL" => Ok(All),
- "SIGHASH_NONE" => Ok(None),
- "SIGHASH_SINGLE" => Ok(Single),
- "SIGHASH_ALL|SIGHASH_ANYONECANPAY" => Ok(AllPlusAnyoneCanPay),
- "SIGHASH_NONE|SIGHASH_ANYONECANPAY" => Ok(NonePlusAnyoneCanPay),
- "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY" => Ok(SinglePlusAnyoneCanPay),
- _ => Err(SighashTypeParseError { unrecognized: s.to_owned() }),
- }
- }
-}
-
-impl EcdsaSighashType {
- /// Checks if the sighash type is [`Self::Single`] or [`Self::SinglePlusAnyoneCanPay`].
- ///
- /// This matches Bitcoin Core's behavior where SIGHASH_SINGLE bug check is based on the base
- /// type (after masking with 0x1f), regardless of the ANYONECANPAY flag.
- ///
- /// See: <https://github.com/bitcoin/bitcoin/blob/e486597/src/script/interpreter.cpp#L1618-L1619>
- pub fn is_single(&self) -> bool { matches!(self, Self::Single | Self::SinglePlusAnyoneCanPay) }
-
- /// Constructs a new [`EcdsaSighashType`] from a raw `u32`.
- ///
- /// **Note**: this replicates consensus behavior, 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 to compute the signature hash
- /// message.
- pub fn from_consensus(n: u32) -> Self {
- use 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 => All,
- 0x02 => None,
- 0x03 => Single,
- 0x81 => AllPlusAnyoneCanPay,
- 0x82 => NonePlusAnyoneCanPay,
- 0x83 => SinglePlusAnyoneCanPay,
- // catchalls
- x if x & 0x80 == 0x80 => AllPlusAnyoneCanPay,
- _ => All,
- }
- }
-
- /// Constructs a new [`EcdsaSighashType`] from a raw `u32`.
- ///
- /// # Errors
- ///
- /// If `n` is a non-standard sighash value.
- pub fn from_standard(n: u32) -> Result<Self, NonStandardSighashTypeError> {
- use EcdsaSighashType::*;
-
- match n {
- // Standard sighashes, see https://github.com/bitcoin/bitcoin/blob/b805dbb0b9c90dadef0424e5b3bf86ac308e103e/src/script/interpreter.cpp#L189-L198
- 0x01 => Ok(All),
- 0x02 => Ok(None),
- 0x03 => Ok(Single),
- 0x81 => Ok(AllPlusAnyoneCanPay),
- 0x82 => Ok(NonePlusAnyoneCanPay),
- 0x83 => Ok(SinglePlusAnyoneCanPay),
- non_standard => Err(NonStandardSighashTypeError(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<EcdsaSighashType> for TapSighashType {
- fn from(s: EcdsaSighashType) -> Self {
- use TapSighashType::*;
-
- match s {
- EcdsaSighashType::All => All,
- EcdsaSighashType::None => None,
- EcdsaSighashType::Single => Single,
- EcdsaSighashType::AllPlusAnyoneCanPay => AllPlusAnyoneCanPay,
- EcdsaSighashType::NonePlusAnyoneCanPay => NonePlusAnyoneCanPay,
- EcdsaSighashType::SinglePlusAnyoneCanPay => SinglePlusAnyoneCanPay,
- }
- }
-}
-
-impl TapSighashType {
- /// Constructs a new [`TapSighashType`] from a raw `u8`.
- pub fn from_consensus_u8(sighash_type: u8) -> Result<Self, InvalidSighashTypeError> {
- use TapSighashType::*;
-
- Ok(match sighash_type {
- 0x00 => Default,
- 0x01 => All,
- 0x02 => None,
- 0x03 => Single,
- 0x81 => AllPlusAnyoneCanPay,
- 0x82 => NonePlusAnyoneCanPay,
- 0x83 => SinglePlusAnyoneCanPay,
- x => return Err(InvalidSighashTypeError(x.into())),
- })
- }
-}
-
/// A trait for representing sighash types which can be split into a flag and an
/// 'SIGHASH_ANYONECANPAY' boolean.
pub(crate) trait SplitAnyoneCanPay
@@ -1197,9 +980,14 @@ pub mod error {
use internals::write_err;
- use crate::prelude::String;
use crate::transaction;
+ #[rustfmt::skip] // Keep public re-exports separate.
+ #[doc(no_inline)]
+ pub use crypto::sighash::{
+ InvalidSighashTypeError, NonStandardSighashTypeError, SighashTypeParseError,
+ };
+
/// The number of supplied prevouts differs from the number of inputs in the transaction.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
@@ -1275,58 +1063,6 @@ pub mod error {
}
}
- /// Integer is not a consensus valid sighash type.
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct InvalidSighashTypeError(pub u32);
-
- impl fmt::Display for InvalidSighashTypeError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "invalid sighash type {}", self.0)
- }
- }
-
- #[cfg(feature = "std")]
- impl std::error::Error for InvalidSighashTypeError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
- }
-
- /// This type is consensus valid but an input including it would prevent the transaction from
- /// being relayed on today's Bitcoin network.
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct NonStandardSighashTypeError(pub u32);
-
- impl fmt::Display for NonStandardSighashTypeError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "non-standard sighash type {}", self.0)
- }
- }
-
- #[cfg(feature = "std")]
- impl std::error::Error for NonStandardSighashTypeError {
- 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, PartialEq, Eq)]
- #[non_exhaustive]
- 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")]
- impl std::error::Error for SighashTypeParseError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
- }
-
/// Error computing a Taproot sighash.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
@@ -1544,37 +1280,6 @@ pub mod error {
}
}
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for EcdsaSighashType {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- let choice = u.int_in_range(0..=5)?;
- match choice {
- 0 => Ok(Self::All),
- 1 => Ok(Self::None),
- 2 => Ok(Self::Single),
- 3 => Ok(Self::AllPlusAnyoneCanPay),
- 4 => Ok(Self::NonePlusAnyoneCanPay),
- _ => Ok(Self::SinglePlusAnyoneCanPay),
- }
- }
-}
-
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for TapSighashType {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- let choice = u.int_in_range(0..=6)?;
- match choice {
- 0 => Ok(Self::Default),
- 1 => Ok(Self::All),
- 2 => Ok(Self::None),
- 3 => Ok(Self::Single),
- 4 => Ok(Self::AllPlusAnyoneCanPay),
- 5 => Ok(Self::NonePlusAnyoneCanPay),
- _ => Ok(Self::SinglePlusAnyoneCanPay),
- }
- }
-}
-
#[cfg(feature = "arbitrary")]
impl<'a, T> Arbitrary<'a> for SighashCache<T>
where
diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml
index 7f0bc1b6..37c9768a 100644
--- a/crypto/Cargo.toml
+++ b/crypto/Cargo.toml
@@ -15,10 +15,15 @@ exclude = ["tests", "contrib"]
[features]
default = ["std"]
-std = ["alloc"]
-alloc = []
+std = ["alloc", "internals/std", "serde?/std"]
+alloc = ["internals/alloc", "serde?/alloc"]
+serde = ["dep:serde", "internals/serde"]
[dependencies]
+internals = { package = "bitcoin-internals", path = "../internals", version = "0.5.0", features = ["hex"] }
+
+arbitrary = { version = "1.4.1", optional = true }
+serde = { version = "1.0.195", default-features = false, features = ["derive"], optional = true }
[dev-dependencies]
diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs
index 4374cadc..351e7807 100644
--- a/crypto/src/lib.rs
+++ b/crypto/src/lib.rs
@@ -14,3 +14,6 @@ extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
+
+#[cfg(feature = "alloc")]
+pub mod sighash;
diff --git a/crypto/src/sighash.rs b/crypto/src/sighash.rs
new file mode 100644
index 00000000..791c94ac
--- /dev/null
+++ b/crypto/src/sighash.rs
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Signature hash implementation (used in transaction signing).
+//!
+//! Efficient implementation of the algorithm to compute the message to be signed according to
+//! [BIP-0341], [BIP-0143] and legacy (before BIP-0143).
+//!
+//! [BIP-0341]: <https://github.com/bitcoin/bips/blob/150ab6f5c3aca9da05fccc5b435e9667853407f4/bip-0341.mediawiki>
+//! [BIP-0143]: <https://github.com/bitcoin/bips/blob/99701f68a88ce33b2d0838eb84e115cef505b4c2/bip-0143.mediawiki>
+
+use alloc::borrow::ToOwned;
+use core::{fmt, str};
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+
+#[doc(no_inline)]
+pub use self::error::{
+ InvalidSighashTypeError, NonStandardSighashTypeError, SighashTypeParseError,
+};
+
+/// 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.
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+pub enum TapSighashType {
+ /// 0x0: Used when not explicitly specified, defaults to [`TapSighashType::All`]
+ Default = 0x00,
+ /// 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,
+}
+#[cfg(feature = "serde")]
+internals::serde_string_impl!(TapSighashType, "a TapSighashType data");
+
+impl fmt::Display for TapSighashType {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use TapSighashType::*;
+
+ let s = match self {
+ Default => "SIGHASH_DEFAULT",
+ All => "SIGHASH_ALL",
+ None => "SIGHASH_NONE",
+ Single => "SIGHASH_SINGLE",
+ AllPlusAnyoneCanPay => "SIGHASH_ALL|SIGHASH_ANYONECANPAY",
+ NonePlusAnyoneCanPay => "SIGHASH_NONE|SIGHASH_ANYONECANPAY",
+ SinglePlusAnyoneCanPay => "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY",
+ };
+ f.write_str(s)
+ }
+}
+
+impl str::FromStr for TapSighashType {
+ type Err = SighashTypeParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ use TapSighashType::*;
+
+ match s {
+ "SIGHASH_DEFAULT" => Ok(Default),
+ "SIGHASH_ALL" => Ok(All),
+ "SIGHASH_NONE" => Ok(None),
+ "SIGHASH_SINGLE" => Ok(Single),
+ "SIGHASH_ALL|SIGHASH_ANYONECANPAY" => Ok(AllPlusAnyoneCanPay),
+ "SIGHASH_NONE|SIGHASH_ANYONECANPAY" => Ok(NonePlusAnyoneCanPay),
+ "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY" => Ok(SinglePlusAnyoneCanPay),
+ _ => Err(SighashTypeParseError { unrecognized: s.to_owned() }),
+ }
+ }
+}
+
+impl TapSighashType {
+ /// Constructs a new [`TapSighashType`] from a raw `u8`.
+ pub fn from_consensus_u8(sighash_type: u8) -> Result<Self, InvalidSighashTypeError> {
+ use TapSighashType::*;
+
+ Ok(match sighash_type {
+ 0x00 => Default,
+ 0x01 => All,
+ 0x02 => None,
+ 0x03 => Single,
+ 0x81 => AllPlusAnyoneCanPay,
+ 0x82 => NonePlusAnyoneCanPay,
+ 0x83 => SinglePlusAnyoneCanPay,
+ x => return Err(InvalidSighashTypeError(x.into())),
+ })
+ }
+}
+
+/// 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
+/// [`TapSighashType`]).
+#[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,
+}
+#[cfg(feature = "serde")]
+internals::serde_string_impl!(EcdsaSighashType, "a EcdsaSighashType data");
+
+impl fmt::Display for EcdsaSighashType {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use EcdsaSighashType::*;
+
+ let s = match self {
+ All => "SIGHASH_ALL",
+ None => "SIGHASH_NONE",
+ Single => "SIGHASH_SINGLE",
+ AllPlusAnyoneCanPay => "SIGHASH_ALL|SIGHASH_ANYONECANPAY",
+ NonePlusAnyoneCanPay => "SIGHASH_NONE|SIGHASH_ANYONECANPAY",
+ SinglePlusAnyoneCanPay => "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY",
+ };
+ f.write_str(s)
+ }
+}
+
+impl str::FromStr for EcdsaSighashType {
+ type Err = SighashTypeParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ use EcdsaSighashType::*;
+
+ match s {
+ "SIGHASH_ALL" => Ok(All),
+ "SIGHASH_NONE" => Ok(None),
+ "SIGHASH_SINGLE" => Ok(Single),
+ "SIGHASH_ALL|SIGHASH_ANYONECANPAY" => Ok(AllPlusAnyoneCanPay),
+ "SIGHASH_NONE|SIGHASH_ANYONECANPAY" => Ok(NonePlusAnyoneCanPay),
+ "SIGHASH_SINGLE|SIGHASH_ANYONECANPAY" => Ok(SinglePlusAnyoneCanPay),
+ _ => Err(SighashTypeParseError { unrecognized: s.to_owned() }),
+ }
+ }
+}
+
+impl EcdsaSighashType {
+ /// Checks if the sighash type is [`Self::Single`] or [`Self::SinglePlusAnyoneCanPay`].
+ ///
+ /// This matches Bitcoin Core's behavior where SIGHASH_SINGLE bug check is based on the base
+ /// type (after masking with 0x1f), regardless of the ANYONECANPAY flag.
+ ///
+ /// See: <https://github.com/bitcoin/bitcoin/blob/e486597/src/script/interpreter.cpp#L1618-L1619>
+ pub fn is_single(&self) -> bool { matches!(self, Self::Single | Self::SinglePlusAnyoneCanPay) }
+
+ /// Constructs a new [`EcdsaSighashType`] from a raw `u32`.
+ ///
+ /// **Note**: this replicates consensus behavior, 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 to compute the signature hash
+ /// message.
+ pub fn from_consensus(n: u32) -> Self {
+ use 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 => All,
+ 0x02 => None,
+ 0x03 => Single,
+ 0x81 => AllPlusAnyoneCanPay,
+ 0x82 => NonePlusAnyoneCanPay,
+ 0x83 => SinglePlusAnyoneCanPay,
+ // catchalls
+ x if x & 0x80 == 0x80 => AllPlusAnyoneCanPay,
+ _ => All,
+ }
+ }
+
+ /// Constructs a new [`EcdsaSighashType`] from a raw `u32`.
+ ///
+ /// # Errors
+ ///
+ /// If `n` is a non-standard sighash value.
+ pub fn from_standard(n: u32) -> Result<Self, NonStandardSighashTypeError> {
+ use EcdsaSighashType::*;
+
+ match n {
+ // Standard sighashes, see https://github.com/bitcoin/bitcoin/blob/b805dbb0b9c90dadef0424e5b3bf86ac308e103e/src/script/interpreter.cpp#L189-L198
+ 0x01 => Ok(All),
+ 0x02 => Ok(None),
+ 0x03 => Ok(Single),
+ 0x81 => Ok(AllPlusAnyoneCanPay),
+ 0x82 => Ok(NonePlusAnyoneCanPay),
+ 0x83 => Ok(SinglePlusAnyoneCanPay),
+ non_standard => Err(NonStandardSighashTypeError(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<EcdsaSighashType> for TapSighashType {
+ fn from(s: EcdsaSighashType) -> Self {
+ use TapSighashType::*;
+
+ match s {
+ EcdsaSighashType::All => All,
+ EcdsaSighashType::None => None,
+ EcdsaSighashType::Single => Single,
+ EcdsaSighashType::AllPlusAnyoneCanPay => AllPlusAnyoneCanPay,
+ EcdsaSighashType::NonePlusAnyoneCanPay => NonePlusAnyoneCanPay,
+ EcdsaSighashType::SinglePlusAnyoneCanPay => SinglePlusAnyoneCanPay,
+ }
+ }
+}
+
+/// Error types for signature hashing.
+pub mod error {
+ use alloc::string::String;
+ use core::fmt;
+
+ /// Integer is not a consensus valid sighash type.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct InvalidSighashTypeError(pub u32);
+
+ impl fmt::Display for InvalidSighashTypeError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "invalid sighash type {}", self.0)
+ }
+ }
+
+ #[cfg(feature = "std")]
+ impl std::error::Error for InvalidSighashTypeError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
+ }
+
+ /// This type is consensus valid but an input including it would prevent the transaction from
+ /// being relayed on today's Bitcoin network.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ pub struct NonStandardSighashTypeError(pub u32);
+
+ impl fmt::Display for NonStandardSighashTypeError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "non-standard sighash type {}", self.0)
+ }
+ }
+
+ #[cfg(feature = "std")]
+ impl std::error::Error for NonStandardSighashTypeError {
+ 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, PartialEq, Eq)]
+ #[non_exhaustive]
+ 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")]
+ impl std::error::Error for SighashTypeParseError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
+ }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for EcdsaSighashType {
+ fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+ let choice = u.int_in_range(0..=5)?;
+ match choice {
+ 0 => Ok(Self::All),
+ 1 => Ok(Self::None),
+ 2 => Ok(Self::Single),
+ 3 => Ok(Self::AllPlusAnyoneCanPay),
+ 4 => Ok(Self::NonePlusAnyoneCanPay),
+ _ => Ok(Self::SinglePlusAnyoneCanPay),
+ }
+ }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for TapSighashType {
+ fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+ let choice = u.int_in_range(0..=6)?;
+ match choice {
+ 0 => Ok(Self::Default),
+ 1 => Ok(Self::All),
+ 2 => Ok(Self::None),
+ 3 => Ok(Self::Single),
+ 4 => Ok(Self::AllPlusAnyoneCanPay),
+ 5 => Ok(Self::NonePlusAnyoneCanPay),
+ _ => Ok(Self::SinglePlusAnyoneCanPay),
+ }
+ }
+}
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.