What changed, and why it matters
This commit is a routine internal code reorganization. It moves the ECDSA signature module from the main 'bitcoin' crate into a smaller 'crypto' sub-crate, then re-exports it so existing users see no change. There is no security fix or vulnerability here.
No security action required. Treat as normal refactoring/dependency reorganization.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change relocates bitcoin/src/crypto/ecdsa.rs to crypto/src/ecdsa.rs and updates bitcoin/src/crypto/mod.rs to re-export the moved types (Signature, SerializedSignature, DecodeError, ParseSignatureError). Cargo manifests and lockfiles are updated to add dependencies (secp256k1, bitcoin-io, hex-conservative) to the crypto crate. A single match arm _ => unreachable!("in crypto v0.1.0") is added in bitcoin/src/psbt/serialize.rs because DecodeError is now #[non_exhaustive] in its new crate. The logic of signature serialization, parsing, and validation is unchanged.
Changed components
bitcoin/src/crypto/mod.rsbitcoin/src/psbt/serialize.rscrypto/Cargo.tomlcrypto/src/ecdsa.rscrypto/src/lib.rsInspect captured patch +435 / −401
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index 6a1a0a67..e7326fd8 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -93,6 +93,10 @@ version = "0.0.0"
dependencies = [
"arbitrary",
"bitcoin-internals",
+ "bitcoin-io",
+ "hex-conservative 0.3.2",
+ "hex-conservative 1.0.0",
+ "secp256k1",
"serde",
]
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 05d05d4f..f7c7ade9 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -92,6 +92,10 @@ version = "0.0.0"
dependencies = [
"arbitrary",
"bitcoin-internals",
+ "bitcoin-io",
+ "hex-conservative 0.3.2",
+ "hex-conservative 1.0.0",
+ "secp256k1",
"serde",
]
diff --git a/bitcoin/src/crypto/ecdsa.rs b/bitcoin/src/crypto/ecdsa.rs
deleted file mode 100644
index 2fc1bf79..00000000
--- a/bitcoin/src/crypto/ecdsa.rs
+++ /dev/null
@@ -1,397 +0,0 @@
-// SPDX-License-Identifier: CC0-1.0
-
-//! ECDSA Bitcoin signatures.
-//!
-//! This module provides ECDSA signatures used by Bitcoin that can be roundtrip (de)serialized.
-
-use core::borrow::Borrow;
-use core::ops::Deref;
-use core::str::FromStr;
-use core::{fmt, iter};
-
-#[cfg(feature = "arbitrary")]
-use arbitrary::{Arbitrary, Unstructured};
-use internals::impl_to_hex_from_lower_hex;
-use io::Write;
-
-use crate::hex;
-use crate::prelude::{DisplayHex, Vec};
-#[cfg(doc)]
-use crate::script::ScriptPubKeyBufExt as _;
-use crate::sighash::EcdsaSighashType;
-
-#[rustfmt::skip] // Keep public re-exports separate.
-#[doc(no_inline)]
-pub use self::error::{DecodeError, ParseSignatureError};
-
-const MAX_SIG_LEN: usize = 73;
-
-/// An ECDSA signature with the corresponding hash type.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
-#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-pub struct Signature {
- /// The underlying ECDSA Signature.
- pub signature: secp256k1::ecdsa::Signature,
- /// The corresponding hash type.
- pub sighash_type: EcdsaSighashType,
-}
-
-impl Signature {
- /// Constructs a new ECDSA Bitcoin signature for [`EcdsaSighashType::All`].
- pub fn sighash_all(signature: secp256k1::ecdsa::Signature) -> Self {
- Self { signature, sighash_type: EcdsaSighashType::All }
- }
-
- /// Deserializes from slice following the standardness rules for [`EcdsaSighashType`].
- pub fn from_slice(sl: &[u8]) -> Result<Self, DecodeError> {
- let (sighash_type, sig) = sl.split_last().ok_or(DecodeError::EmptySignature)?;
- let sighash_type = EcdsaSighashType::from_standard(*sighash_type as u32)?;
- let signature =
- secp256k1::ecdsa::Signature::from_der(sig).map_err(DecodeError::Secp256k1)?;
- Ok(Self { signature, sighash_type })
- }
-
- /// Serializes an ECDSA signature (inner secp256k1 signature in DER format).
- ///
- /// This does **not** perform extra heap allocation.
- pub fn serialize(&self) -> SerializedSignature {
- let mut buf = [0u8; MAX_SIG_LEN];
- let signature = self.signature.serialize_der();
- buf[..signature.len()].copy_from_slice(&signature);
- buf[signature.len()] = self.sighash_type as u8;
- SerializedSignature { data: buf, len: signature.len() + 1 }
- }
-
- /// Serializes an ECDSA signature (inner secp256k1 signature in DER format) into `Vec`.
- ///
- /// Note: this performs an extra heap allocation, you might prefer the
- /// [`serialize`](Self::serialize) method instead.
- pub fn to_vec(self) -> Vec<u8> {
- self.signature
- .serialize_der()
- .iter()
- .copied()
- .chain(iter::once(self.sighash_type as u8))
- .collect()
- }
-
- /// Serializes an ECDSA signature (inner secp256k1 signature in DER format) to a `writer`.
- #[inline]
- pub fn serialize_to_writer<W: Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
- let sig = self.serialize();
- sig.write_to(writer)
- }
-}
-
-impl fmt::Display for Signature {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- fmt::LowerHex::fmt(&self.signature.serialize_der().as_hex(), f)?;
- fmt::LowerHex::fmt(&[self.sighash_type as u8].as_hex(), f)
- }
-}
-
-impl FromStr for Signature {
- type Err = ParseSignatureError;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let bytes = hex::decode_to_vec(s)?;
- Ok(Self::from_slice(&bytes)?)
- }
-}
-
-/// Holds signature serialized in-line (not in `Vec`).
-///
-/// This avoids allocation and allows proving maximum size of the signature (73 bytes).
-/// The type can be used largely as a byte slice. It implements all standard traits one would
-/// expect and has familiar methods.
-///
-/// However, the usual use case is to push it into a script. This can be done directly passing it
-/// into [`push_slice`](crate::script::ScriptBufExt::push_slice).
-#[derive(Copy, Clone)]
-pub struct SerializedSignature {
- data: [u8; MAX_SIG_LEN],
- len: usize,
-}
-
-impl SerializedSignature {
- /// Constructs a new SerializedSignature from a Signature.
- ///
- /// In other words this serializes a `Signature` into a `SerializedSignature`.
- #[inline]
- pub fn from_signature(sig: Signature) -> Self { sig.serialize() }
-
- /// Converts the serialized signature into the [`Signature`] struct.
- ///
- /// In other words this deserializes the `SerializedSignature`.
- #[inline]
- pub fn to_signature(self) -> Result<Signature, DecodeError> { Signature::from_slice(&self) }
-
- /// Returns the length of the serialized signature data.
- #[inline]
- // `len` is never 0, so `is_empty` would always return `false`.
- #[allow(clippy::len_without_is_empty)]
- pub fn len(&self) -> usize { self.len }
-
- /// Returns an iterator over bytes of the signature.
- #[inline]
- pub fn iter(&self) -> core::slice::Iter<'_, u8> { self.into_iter() }
-
- /// Writes this serialized signature to a `writer`.
- #[inline]
- pub fn write_to<W: Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
- writer.write_all(self)
- }
-}
-
-impl fmt::Debug for SerializedSignature {
- #[inline]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) }
-}
-
-impl fmt::Display for SerializedSignature {
- #[inline]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
-}
-
-impl fmt::LowerHex for SerializedSignature {
- #[inline]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- fmt::LowerHex::fmt(&(**self).as_hex(), f)
- }
-}
-impl_to_hex_from_lower_hex!(SerializedSignature, |signature: &SerializedSignature| signature.len
- * 2);
-
-impl fmt::UpperHex for SerializedSignature {
- #[inline]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- fmt::UpperHex::fmt(&(**self).as_hex(), f)
- }
-}
-
-impl PartialEq for SerializedSignature {
- #[inline]
- fn eq(&self, other: &Self) -> bool { **self == **other }
-}
-
-impl PartialEq<[u8]> for SerializedSignature {
- #[inline]
- fn eq(&self, other: &[u8]) -> bool { **self == *other }
-}
-
-impl PartialEq<SerializedSignature> for [u8] {
- #[inline]
- fn eq(&self, other: &SerializedSignature) -> bool { *self == **other }
-}
-
-impl PartialOrd for SerializedSignature {
- fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(other)) }
-}
-
-impl Ord for SerializedSignature {
- fn cmp(&self, other: &Self) -> core::cmp::Ordering { (**self).cmp(&**other) }
-}
-
-impl PartialOrd<[u8]> for SerializedSignature {
- fn partial_cmp(&self, other: &[u8]) -> Option<core::cmp::Ordering> {
- (**self).partial_cmp(other)
- }
-}
-
-impl PartialOrd<SerializedSignature> for [u8] {
- fn partial_cmp(&self, other: &SerializedSignature) -> Option<core::cmp::Ordering> {
- self.partial_cmp(&**other)
- }
-}
-
-impl Eq for SerializedSignature {}
-
-impl core::hash::Hash for SerializedSignature {
- fn hash<H: core::hash::Hasher>(&self, state: &mut H) { core::hash::Hash::hash(&**self, state) }
-}
-
-impl AsRef<[u8]> for SerializedSignature {
- #[inline]
- fn as_ref(&self) -> &[u8] { &self.data[..self.len] }
-}
-
-impl Borrow<[u8]> for SerializedSignature {
- #[inline]
- fn borrow(&self) -> &[u8] { &self.data[..self.len] }
-}
-
-impl Deref for SerializedSignature {
- type Target = [u8];
-
- #[inline]
- fn deref(&self) -> &Self::Target { &self.data[..self.len] }
-}
-
-impl<'a> IntoIterator for &'a SerializedSignature {
- type IntoIter = core::slice::Iter<'a, u8>;
- type Item = &'a u8;
-
- #[inline]
- fn into_iter(self) -> Self::IntoIter { (**self).iter() }
-}
-
-/// Error types for ECDSA
-pub mod error {
- use core::convert::Infallible;
- use core::fmt;
-
- use internals::write_err;
-
- use crate::sighash::NonStandardSighashTypeError;
-
- /// Error encountered while parsing an ECDSA signature from a byte slice.
- #[derive(Debug, Clone, PartialEq, Eq)]
- #[non_exhaustive]
- pub enum DecodeError {
- /// Non-standard sighash type.
- SighashType(NonStandardSighashTypeError),
- /// Signature was empty.
- EmptySignature,
- /// A secp256k1 error.
- Secp256k1(secp256k1::Error),
- }
-
- impl From<Infallible> for DecodeError {
- fn from(never: Infallible) -> Self { match never {} }
- }
-
- impl fmt::Display for DecodeError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::SighashType(ref e) => write_err!(f, "non-standard signature hash type"; e),
- Self::EmptySignature => write!(f, "empty ECDSA signature"),
- Self::Secp256k1(ref e) => write_err!(f, "secp256k1"; e),
- }
- }
- }
-
- #[cfg(feature = "std")]
- impl std::error::Error for DecodeError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- Self::Secp256k1(ref e) => Some(e),
- Self::SighashType(ref e) => Some(e),
- Self::EmptySignature => None,
- }
- }
- }
-
- impl From<secp256k1::Error> for DecodeError {
- fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) }
- }
-
- impl From<NonStandardSighashTypeError> for DecodeError {
- fn from(e: NonStandardSighashTypeError) -> Self { Self::SighashType(e) }
- }
-
- /// Error encountered while parsing an ECDSA signature from a string.
- #[derive(Debug, Clone, PartialEq, Eq)]
- #[non_exhaustive]
- pub enum ParseSignatureError {
- /// Hex string decoding error.
- Hex(hex::DecodeVariableLengthBytesError),
- /// Signature byte slice decoding error.
- Decode(DecodeError),
- }
-
- impl From<Infallible> for ParseSignatureError {
- fn from(never: Infallible) -> Self { match never {} }
- }
-
- impl fmt::Display for ParseSignatureError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::Hex(ref e) => write_err!(f, "signature hex decoding error"; e),
- Self::Decode(ref e) => write_err!(f, "signature byte slice decoding error"; e),
- }
- }
- }
-
- #[cfg(feature = "std")]
- impl std::error::Error for ParseSignatureError {
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match self {
- Self::Hex(ref e) => Some(e),
- Self::Decode(ref e) => Some(e),
- }
- }
- }
-
- impl From<hex::DecodeVariableLengthBytesError> for ParseSignatureError {
- fn from(e: hex::DecodeVariableLengthBytesError) -> Self { Self::Hex(e) }
- }
-
- impl From<DecodeError> for ParseSignatureError {
- fn from(e: DecodeError) -> Self { Self::Decode(e) }
- }
-}
-
-#[cfg(feature = "arbitrary")]
-impl<'a> Arbitrary<'a> for Signature {
- fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
- // The valid range of r and s should be between 0 and n-1 where
- // n = 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141
- let high_min = 0x0u128;
- let high_max = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEu128;
- let low_min = 0x0u128;
- let low_max = 0xBAAEDCE6AF48A03BBFD25E8CD0364140u128;
-
- // Equally weight the chances of getting a minimum value for a signature, maximum value for
- // a signature, and an arbitrary valid signature
- let choice = u.int_in_range(0..=2)?;
- let (high, low) = match choice {
- 0 => (high_min, low_min),
- 1 => (high_max, low_max),
- _ => (u.int_in_range(high_min..=high_max)?, u.int_in_range(low_min..=low_max)?),
- };
-
- // We can use the same bytes for r and s since they're just arbitrary values
- let mut bytes: [u8; 32] = [0; 32];
- bytes[..16].copy_from_slice(&high.to_be_bytes());
- bytes[16..].copy_from_slice(&low.to_be_bytes());
-
- let mut signature_bytes: [u8; 64] = [0; 64];
- signature_bytes[..32].copy_from_slice(&bytes);
- signature_bytes[32..].copy_from_slice(&bytes);
-
- Ok(Self {
- signature: secp256k1::ecdsa::Signature::from_compact(&signature_bytes).unwrap(),
- sighash_type: EcdsaSighashType::arbitrary(u)?,
- })
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- const TEST_SIGNATURE_HEX: &str = "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab45";
-
- #[test]
- fn write_serialized_signature() {
- let sig = Signature {
- signature: secp256k1::ecdsa::Signature::from_str(TEST_SIGNATURE_HEX).unwrap(),
- sighash_type: EcdsaSighashType::All,
- };
-
- let mut buf = vec![];
- sig.serialize_to_writer(&mut buf).expect("write failed");
-
- assert_eq!(sig.to_vec(), buf)
- }
-
- #[test]
- fn iterate_serialized_signature() {
- let sig = Signature {
- signature: secp256k1::ecdsa::Signature::from_str(TEST_SIGNATURE_HEX).unwrap(),
- sighash_type: EcdsaSighashType::All,
- };
-
- assert_eq!(sig.serialize().iter().copied().collect::<Vec<u8>>(), sig.to_vec());
- }
-}
diff --git a/bitcoin/src/crypto/mod.rs b/bitcoin/src/crypto/mod.rs
index d14e7304..1fa5dbf3 100644
--- a/bitcoin/src/crypto/mod.rs
+++ b/bitcoin/src/crypto/mod.rs
@@ -4,8 +4,15 @@
//!
//! Cryptography related functionality: keys and signatures.
-pub mod ecdsa;
pub mod key;
pub mod sighash;
// Contents re-exported in `bitcoin::taproot`.
pub(crate) mod taproot;
+
+/// ECDSA Bitcoin signatures.
+pub mod ecdsa {
+ #[doc(no_inline)]
+ pub use crypto::ecdsa::{DecodeError, ParseSignatureError};
+ #[doc(inline)]
+ pub use crypto::ecdsa::{SerializedSignature, Signature};
+}
diff --git a/bitcoin/src/psbt/serialize.rs b/bitcoin/src/psbt/serialize.rs
index c895b88c..cab042c5 100644
--- a/bitcoin/src/psbt/serialize.rs
+++ b/bitcoin/src/psbt/serialize.rs
@@ -236,6 +236,7 @@ impl Deserialize for ecdsa::Signature {
ecdsa::DecodeError::EmptySignature => Error::InvalidEcdsaSignature(e),
ecdsa::DecodeError::SighashType(err) => Error::NonStandardSighashType(err.0),
ecdsa::DecodeError::Secp256k1(..) => Error::InvalidEcdsaSignature(e),
+ _ => unreachable!("in crypto v0.1.0"),
})
}
}
diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml
index 37c9768a..88c8580d 100644
--- a/crypto/Cargo.toml
+++ b/crypto/Cargo.toml
@@ -15,12 +15,17 @@ exclude = ["tests", "contrib"]
[features]
default = ["std"]
-std = ["alloc", "internals/std", "serde?/std"]
-alloc = ["internals/alloc", "serde?/alloc"]
-serde = ["dep:serde", "internals/serde"]
+std = ["alloc", "hex-stable/std", "hex-unstable/std", "internals/std", "io/std", "secp256k1/std", "serde?/std"]
+alloc = ["hex-stable/alloc", "hex-unstable/alloc", "internals/alloc", "io/alloc", "secp256k1/alloc", "serde?/alloc"]
+serde = ["dep:serde", "internals/serde", "secp256k1/serde"]
+arbitrary = ["dep:arbitrary", "secp256k1/arbitrary"]
[dependencies]
+hex-unstable = { package = "hex-conservative", version = "0.3.2", default-features = false }
+hex-stable = { package = "hex-conservative", version = "1.0.0", default-features = false }
internals = { package = "bitcoin-internals", path = "../internals", version = "0.5.0", features = ["hex"] }
+io = { package = "bitcoin-io", path = "../io", version = "0.5.0", default-features = false, features = ["hashes"] }
+secp256k1 = { version = "0.32.0-beta.2", default-features = false }
arbitrary = { version = "1.4.1", optional = true }
serde = { version = "1.0.195", default-features = false, features = ["derive"], optional = true }
@@ -33,3 +38,8 @@ rustdoc-args = ["--cfg", "docsrs"]
[lints]
workspace = true
+
+[package.metadata.rbmt.lint]
+allowed_duplicates = [
+ "hex-conservative",
+]
diff --git a/crypto/src/ecdsa.rs b/crypto/src/ecdsa.rs
new file mode 100644
index 00000000..f7859a6b
--- /dev/null
+++ b/crypto/src/ecdsa.rs
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! ECDSA Bitcoin signatures.
+//!
+//! This module provides ECDSA signatures used by Bitcoin that can be roundtrip (de)serialized.
+
+use alloc::vec::Vec;
+use core::borrow::Borrow;
+use core::ops::Deref;
+use core::str::FromStr;
+use core::{fmt, iter};
+
+#[cfg(feature = "arbitrary")]
+use arbitrary::{Arbitrary, Unstructured};
+use hex_unstable::DisplayHex;
+use internals::impl_to_hex_from_lower_hex;
+use io::Write;
+#[cfg(feature = "serde")]
+use serde::{Deserialize, Serialize};
+
+use crate::hex;
+use crate::sighash::EcdsaSighashType;
+
+#[rustfmt::skip] // Keep public re-exports separate.
+#[doc(no_inline)]
+pub use self::error::{DecodeError, ParseSignatureError};
+
+const MAX_SIG_LEN: usize = 73;
+
+/// An ECDSA signature with the corresponding hash type.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+pub struct Signature {
+ /// The underlying ECDSA Signature.
+ pub signature: secp256k1::ecdsa::Signature,
+ /// The corresponding hash type.
+ pub sighash_type: EcdsaSighashType,
+}
+
+impl Signature {
+ /// Constructs a new ECDSA Bitcoin signature for [`EcdsaSighashType::All`].
+ pub fn sighash_all(signature: secp256k1::ecdsa::Signature) -> Self {
+ Self { signature, sighash_type: EcdsaSighashType::All }
+ }
+
+ /// Deserializes from slice following the standardness rules for [`EcdsaSighashType`].
+ pub fn from_slice(sl: &[u8]) -> Result<Self, DecodeError> {
+ let (sighash_type, sig) = sl.split_last().ok_or(DecodeError::EmptySignature)?;
+ let sighash_type = EcdsaSighashType::from_standard(*sighash_type as u32)?;
+ let signature =
+ secp256k1::ecdsa::Signature::from_der(sig).map_err(DecodeError::Secp256k1)?;
+ Ok(Self { signature, sighash_type })
+ }
+
+ /// Serializes an ECDSA signature (inner secp256k1 signature in DER format).
+ ///
+ /// This does **not** perform extra heap allocation.
+ pub fn serialize(&self) -> SerializedSignature {
+ let mut buf = [0u8; MAX_SIG_LEN];
+ let signature = self.signature.serialize_der();
+ buf[..signature.len()].copy_from_slice(&signature);
+ buf[signature.len()] = self.sighash_type as u8;
+ SerializedSignature { data: buf, len: signature.len() + 1 }
+ }
+
+ /// Serializes an ECDSA signature (inner secp256k1 signature in DER format) into `Vec`.
+ ///
+ /// Note: this performs an extra heap allocation, you might prefer the
+ /// [`serialize`](Self::serialize) method instead.
+ pub fn to_vec(self) -> Vec<u8> {
+ self.signature
+ .serialize_der()
+ .iter()
+ .copied()
+ .chain(iter::once(self.sighash_type as u8))
+ .collect()
+ }
+
+ /// Serializes an ECDSA signature (inner secp256k1 signature in DER format) to a `writer`.
+ #[inline]
+ pub fn serialize_to_writer<W: Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
+ let sig = self.serialize();
+ sig.write_to(writer)
+ }
+}
+
+impl fmt::Display for Signature {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ fmt::LowerHex::fmt(&self.signature.serialize_der().as_hex(), f)?;
+ fmt::LowerHex::fmt(&[self.sighash_type as u8].as_hex(), f)
+ }
+}
+
+impl FromStr for Signature {
+ type Err = ParseSignatureError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let bytes = hex::decode_to_vec(s)?;
+ Ok(Self::from_slice(&bytes)?)
+ }
+}
+
+/// Holds signature serialized in-line (not in `Vec`).
+///
+/// This avoids allocation and allows proving maximum size of the signature (73 bytes).
+/// The type can be used largely as a byte slice. It implements all standard traits one would
+/// expect and has familiar methods.
+///
+/// However, the usual use case is to push it into a script. This can be done directly passing it
+/// into a `ScriptBuf` with `push_slice`.
+#[derive(Copy, Clone)]
+pub struct SerializedSignature {
+ data: [u8; MAX_SIG_LEN],
+ len: usize,
+}
+
+impl SerializedSignature {
+ /// Constructs a new SerializedSignature from a Signature.
+ ///
+ /// In other words this serializes a `Signature` into a `SerializedSignature`.
+ #[inline]
+ pub fn from_signature(sig: Signature) -> Self { sig.serialize() }
+
+ /// Converts the serialized signature into the [`Signature`] struct.
+ ///
+ /// In other words this deserializes the `SerializedSignature`.
+ #[inline]
+ pub fn to_signature(self) -> Result<Signature, DecodeError> { Signature::from_slice(&self) }
+
+ /// Returns the length of the serialized signature data.
+ #[inline]
+ // `len` is never 0, so `is_empty` would always return `false`.
+ #[allow(clippy::len_without_is_empty)]
+ pub fn len(&self) -> usize { self.len }
+
+ /// Returns an iterator over bytes of the signature.
+ #[inline]
+ pub fn iter(&self) -> core::slice::Iter<'_, u8> { self.into_iter() }
+
+ /// Writes this serialized signature to a `writer`.
+ #[inline]
+ pub fn write_to<W: Write + ?Sized>(&self, writer: &mut W) -> Result<(), io::Error> {
+ writer.write_all(self)
+ }
+}
+
+impl fmt::Debug for SerializedSignature {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) }
+}
+
+impl fmt::Display for SerializedSignature {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
+}
+
+impl fmt::LowerHex for SerializedSignature {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::LowerHex::fmt(&(**self).as_hex(), f)
+ }
+}
+impl_to_hex_from_lower_hex!(SerializedSignature, |signature: &SerializedSignature| signature.len
+ * 2);
+
+impl fmt::UpperHex for SerializedSignature {
+ #[inline]
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::UpperHex::fmt(&(**self).as_hex(), f)
+ }
+}
+
+impl PartialEq for SerializedSignature {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool { **self == **other }
+}
+
+impl PartialEq<[u8]> for SerializedSignature {
+ #[inline]
+ fn eq(&self, other: &[u8]) -> bool { **self == *other }
+}
+
+impl PartialEq<SerializedSignature> for [u8] {
+ #[inline]
+ fn eq(&self, other: &SerializedSignature) -> bool { *self == **other }
+}
+
+impl PartialOrd for SerializedSignature {
+ fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(other)) }
+}
+
+impl Ord for SerializedSignature {
+ fn cmp(&self, other: &Self) -> core::cmp::Ordering { (**self).cmp(&**other) }
+}
+
+impl PartialOrd<[u8]> for SerializedSignature {
+ fn partial_cmp(&self, other: &[u8]) -> Option<core::cmp::Ordering> {
+ (**self).partial_cmp(other)
+ }
+}
+
+impl PartialOrd<SerializedSignature> for [u8] {
+ fn partial_cmp(&self, other: &SerializedSignature) -> Option<core::cmp::Ordering> {
+ self.partial_cmp(&**other)
+ }
+}
+
+impl Eq for SerializedSignature {}
+
+impl core::hash::Hash for SerializedSignature {
+ fn hash<H: core::hash::Hasher>(&self, state: &mut H) { core::hash::Hash::hash(&**self, state) }
+}
+
+impl AsRef<[u8]> for SerializedSignature {
+ #[inline]
+ fn as_ref(&self) -> &[u8] { &self.data[..self.len] }
+}
+
+impl Borrow<[u8]> for SerializedSignature {
+ #[inline]
+ fn borrow(&self) -> &[u8] { &self.data[..self.len] }
+}
+
+impl Deref for SerializedSignature {
+ type Target = [u8];
+
+ #[inline]
+ fn deref(&self) -> &Self::Target { &self.data[..self.len] }
+}
+
+impl<'a> IntoIterator for &'a SerializedSignature {
+ type IntoIter = core::slice::Iter<'a, u8>;
+ type Item = &'a u8;
+
+ #[inline]
+ fn into_iter(self) -> Self::IntoIter { (**self).iter() }
+}
+
+/// Error types for ECDSA
+pub mod error {
+ use core::convert::Infallible;
+ use core::fmt;
+
+ use internals::write_err;
+
+ use crate::sighash::NonStandardSighashTypeError;
+
+ /// Error encountered while parsing an ECDSA signature from a byte slice.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ #[non_exhaustive]
+ pub enum DecodeError {
+ /// Non-standard sighash type.
+ SighashType(NonStandardSighashTypeError),
+ /// Signature was empty.
+ EmptySignature,
+ /// A secp256k1 error.
+ Secp256k1(secp256k1::Error),
+ }
+
+ impl From<Infallible> for DecodeError {
+ fn from(never: Infallible) -> Self { match never {} }
+ }
+
+ impl fmt::Display for DecodeError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::SighashType(ref e) => write_err!(f, "non-standard signature hash type"; e),
+ Self::EmptySignature => write!(f, "empty ECDSA signature"),
+ Self::Secp256k1(ref e) => write_err!(f, "secp256k1"; e),
+ }
+ }
+ }
+
+ #[cfg(feature = "std")]
+ impl std::error::Error for DecodeError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::Secp256k1(ref e) => Some(e),
+ Self::SighashType(ref e) => Some(e),
+ Self::EmptySignature => None,
+ }
+ }
+ }
+
+ impl From<secp256k1::Error> for DecodeError {
+ fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) }
+ }
+
+ impl From<NonStandardSighashTypeError> for DecodeError {
+ fn from(e: NonStandardSighashTypeError) -> Self { Self::SighashType(e) }
+ }
+
+ /// Error encountered while parsing an ECDSA signature from a string.
+ #[derive(Debug, Clone, PartialEq, Eq)]
+ #[non_exhaustive]
+ pub enum ParseSignatureError {
+ /// Hex string decoding error.
+ Hex(hex::DecodeVariableLengthBytesError),
+ /// Signature byte slice decoding error.
+ Decode(DecodeError),
+ }
+
+ impl From<Infallible> for ParseSignatureError {
+ fn from(never: Infallible) -> Self { match never {} }
+ }
+
+ impl fmt::Display for ParseSignatureError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::Hex(ref e) => write_err!(f, "signature hex decoding error"; e),
+ Self::Decode(ref e) => write_err!(f, "signature byte slice decoding error"; e),
+ }
+ }
+ }
+
+ #[cfg(feature = "std")]
+ impl std::error::Error for ParseSignatureError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::Hex(ref e) => Some(e),
+ Self::Decode(ref e) => Some(e),
+ }
+ }
+ }
+
+ impl From<hex::DecodeVariableLengthBytesError> for ParseSignatureError {
+ fn from(e: hex::DecodeVariableLengthBytesError) -> Self { Self::Hex(e) }
+ }
+
+ impl From<DecodeError> for ParseSignatureError {
+ fn from(e: DecodeError) -> Self { Self::Decode(e) }
+ }
+}
+
+#[cfg(feature = "arbitrary")]
+impl<'a> Arbitrary<'a> for Signature {
+ fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
+ // The valid range of r and s should be between 0 and n-1 where
+ // n = 0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE BAAEDCE6 AF48A03B BFD25E8C D0364141
+ let high_min = 0x0u128;
+ let high_max = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEu128;
+ let low_min = 0x0u128;
+ let low_max = 0xBAAEDCE6AF48A03BBFD25E8CD0364140u128;
+
+ // Equally weight the chances of getting a minimum value for a signature, maximum value for
+ // a signature, and an arbitrary valid signature
+ let choice = u.int_in_range(0..=2)?;
+ let (high, low) = match choice {
+ 0 => (high_min, low_min),
+ 1 => (high_max, low_max),
+ _ => (u.int_in_range(high_min..=high_max)?, u.int_in_range(low_min..=low_max)?),
+ };
+
+ // We can use the same bytes for r and s since they're just arbitrary values
+ let mut bytes: [u8; 32] = [0; 32];
+ bytes[..16].copy_from_slice(&high.to_be_bytes());
+ bytes[16..].copy_from_slice(&low.to_be_bytes());
+
+ let mut signature_bytes: [u8; 64] = [0; 64];
+ signature_bytes[..32].copy_from_slice(&bytes);
+ signature_bytes[32..].copy_from_slice(&bytes);
+
+ Ok(Self {
+ signature: secp256k1::ecdsa::Signature::from_compact(&signature_bytes).unwrap(),
+ sighash_type: EcdsaSighashType::arbitrary(u)?,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use alloc::vec;
+
+ use super::*;
+
+ const TEST_SIGNATURE_HEX: &str = "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab45";
+
+ #[test]
+ fn write_serialized_signature() {
+ let sig = Signature {
+ signature: secp256k1::ecdsa::Signature::from_str(TEST_SIGNATURE_HEX).unwrap(),
+ sighash_type: EcdsaSighashType::All,
+ };
+
+ let mut buf = vec![];
+ sig.serialize_to_writer(&mut buf).expect("write failed");
+
+ assert_eq!(sig.to_vec(), buf)
+ }
+
+ #[test]
+ fn iterate_serialized_signature() {
+ let sig = Signature {
+ signature: secp256k1::ecdsa::Signature::from_str(TEST_SIGNATURE_HEX).unwrap(),
+ sighash_type: EcdsaSighashType::All,
+ };
+
+ assert_eq!(sig.serialize().iter().copied().collect::<Vec<u8>>(), sig.to_vec());
+ }
+}
diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs
index 351e7807..65ce5da7 100644
--- a/crypto/src/lib.rs
+++ b/crypto/src/lib.rs
@@ -15,5 +15,10 @@ extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
+/// Re-export the `hex-conservative` crate.
+pub extern crate hex_stable as hex;
+
+#[cfg(feature = "alloc")]
+pub mod ecdsa;
#[cfg(feature = "alloc")]
pub mod sighash;
Why this scored 15/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.