crypto: Gate all usage of hex-conservative behind hex feature
What changed, and why it matters
This commit reorganizes how an optional hex-encoding helper library is enabled in the bitcoin-crypto crate. It makes the hex-conservative dependency optional and gated behind a new 'hex' feature, instead of being included by default. The main bitcoin crate explicitly enables this feature, so end-user behavior is unchanged. This is a build/configuration cleanup, not a fix for an exploitable vulnerability.
No security action required. Treat as a normal dependency/feature refactor. Verify downstream crates that depend on bitcoin-crypto directly (not via bitcoin) enable the 'hex' feature if they require hex Display/FromStr functionality.
Security signals we found
No security-relevant code change: only feature-gating and dependency optionality
No input validation, buffer handling, or cryptographic operations modified
No advisory, CVE, or vendor security disclosure referenced in commit
Build-time feature reorganization with backward-compatible default behavior via parent crate enabling feature
Evidence from the diff
The patch introduces a new ‘hex’ Cargo feature in bitcoin-crypto that gates all imports, trait implementations, and public re-exports related to hex-conservative. It changes hex from a mandatory dependency to an optional one, updates feature propagation (std/alloc now use ‘hex?/std’ and ‘hex?/alloc’), and removes the baked-in ‘hex’ feature from bitcoin-primitives in crypto’s dependencies. The parent bitcoin crate adds ‘hex’ to its crypto dependency features. Code paths for Debug formatting are adjusted to work without hex by manually printing bytes. No cryptographic logic, parsing validation, or memory handling is altered.
Changed components
bitcoin-crypto crate feature flagsbitcoin crate Cargo.toml dependency featurescrypto/src/ecdsa.rs hex formatting and parsingcrypto/src/key.rs hex formatting and parsingcrypto/src/taproot.rs hex formatting and parsingcrypto/src/lib.rs public hex re-exportInspect captured patch +140 / −15
diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml
index 756838e3..27647d72 100644
--- a/bitcoin/Cargo.toml
+++ b/bitcoin/Cargo.toml
@@ -28,7 +28,7 @@ arbitrary = ["crypto/arbitrary", "dep:arbitrary", "units/arbitrary", "primitives
[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", version = "0.2.0", default-features = false, features = ["alloc"] }
+crypto = { package = "bitcoin-crypto", path = "../crypto", version = "0.2.0", default-features = false, features = ["alloc", "hex"] }
hashes = { package = "bitcoin_hashes", path = "../hashes", version = "1.0.0", default-features = false, features = ["alloc", "hex"] }
key-expression = { package = "bitcoin-key-expression", path = "../key_expression", version = "0.0.0", default-features = false, features = ["alloc"] }
encoding = { package = "bitcoin-consensus-encoding", path = "../consensus_encoding", version = "1.0.0", default-features = false, features = ["alloc"] }
diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml
index 1f16fc7d..9f6cd8fe 100644
--- a/crypto/Cargo.toml
+++ b/crypto/Cargo.toml
@@ -14,24 +14,25 @@ rust-version = "1.74.0"
exclude = ["tests", "contrib"]
[features]
-default = ["std"]
-std = ["alloc", "base58/std", "hashes/std", "hex/std", "internals/std", "network/std", "primitives/std", "secp256k1/std", "serde?/std"]
+default = ["std", "hex"]
+std = ["alloc", "base58/std", "hashes/std", "hex?/std", "internals/std", "network/std", "primitives/std", "secp256k1/std", "serde?/std"]
rand = ["secp256k1/rand"]
-alloc = ["base58/alloc", "hashes/alloc", "hex/alloc", "internals/alloc", "network/alloc", "primitives/alloc", "secp256k1/alloc", "serde?/alloc"]
+alloc = ["base58/alloc", "hashes/alloc", "hex?/alloc", "internals/alloc", "network/alloc", "primitives/alloc", "secp256k1/alloc", "serde?/alloc"]
serde = ["dep:serde", "hashes/serde", "internals/serde", "secp256k1/serde"]
arbitrary = ["dep:arbitrary", "secp256k1/arbitrary"]
+hex = ["dep:hex", "hashes/hex", "internals/hex", "primitives/hex"]
[dependencies]
base58 = { package = "base58ck", path = "../base58", version = "0.4.0", default-features = false }
hashes = { package = "bitcoin_hashes", path = "../hashes", version = "1.0.0", default-features = false, features = ["hex"] }
-hex = { package = "hex-conservative", version = "1.1.0", default-features = false }
internals = { package = "bitcoin-internals", path = "../internals", version = "0.5.0", features = ["hex"] }
network = { package = "bitcoin-network-kind", path = "../network", version = "0.1.0", default-features = false }
# This is a temporary dep for the sake of PushBytes impls. This should not be retained for crypto 1.0.
-primitives = { package = "bitcoin-primitives", path = "../primitives", version = "0.102.0", default-features = false, features = ["hex"] }
+primitives = { package = "bitcoin-primitives", path = "../primitives", version = "0.102.0", default-features = false, features = [] }
secp256k1 = { version = "0.32.0-beta.2", default-features = false }
arbitrary = { version = "1.4.1", optional = true }
+hex = { package = "hex-conservative", version = "1.1.0", default-features = false, optional = true }
serde = { version = "1.0.195", default-features = false, features = ["derive"], optional = true }
[dev-dependencies]
diff --git a/crypto/src/ecdsa.rs b/crypto/src/ecdsa.rs
index 5e7e0aaf..7668859f 100644
--- a/crypto/src/ecdsa.rs
+++ b/crypto/src/ecdsa.rs
@@ -11,24 +11,31 @@ use core::fmt;
#[cfg(feature = "alloc")]
use core::iter;
use core::ops::Deref;
+#[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
use core::str::FromStr;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
+#[cfg(feature = "hex")]
use hex::DisplayHex;
+#[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
use internals::impl_to_hex_from_lower_hex;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
+#[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
use crate::hex;
use crate::sighash::EcdsaSighashType;
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(no_inline)]
-pub use self::error::{DecodeError, ParseSignatureError};
+pub use self::error::DecodeError;
+#[cfg(feature = "hex")]
+#[doc(no_inline)]
+pub use self::error::ParseSignatureError;
const MAX_SIG_LEN: usize = 73;
@@ -89,6 +96,7 @@ impl Signature {
}
}
+#[cfg(feature = "hex")]
impl fmt::Display for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::LowerHex::fmt(&self.signature.serialize_der().as_hex(), f)?;
@@ -96,6 +104,7 @@ impl fmt::Display for Signature {
}
}
+#[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
impl FromStr for Signature {
type Err = ParseSignatureError;
@@ -152,24 +161,40 @@ impl SerializedSignature {
impl fmt::Debug for SerializedSignature {
#[inline]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) }
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ #[cfg(feature = "hex")]
+ {
+ fmt::Display::fmt(self, f)
+ }
+ #[cfg(not(feature = "hex"))]
+ {
+ for b in self {
+ write!(f, "{:02x}", b)?;
+ }
+ Ok(())
+ }
+ }
}
+#[cfg(feature = "hex")]
impl fmt::Display for SerializedSignature {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
}
+#[cfg(feature = "hex")]
impl fmt::LowerHex for SerializedSignature {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::LowerHex::fmt(&(**self).as_hex(), f)
}
}
+#[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
impl_to_hex_from_lower_hex!(SerializedSignature, |signature: &SerializedSignature| signature.len
* 2);
+#[cfg(feature = "hex")]
impl fmt::UpperHex for SerializedSignature {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -292,6 +317,7 @@ pub mod error {
/// Error encountered while parsing an ECDSA signature from a string.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
+ #[cfg(feature = "hex")]
pub enum ParseSignatureError {
/// Hex string decoding error.
Hex(hex::DecodeVariableLengthBytesError),
@@ -299,10 +325,12 @@ pub mod error {
Decode(DecodeError),
}
+ #[cfg(feature = "hex")]
impl From<Infallible> for ParseSignatureError {
fn from(never: Infallible) -> Self { match never {} }
}
+ #[cfg(feature = "hex")]
impl fmt::Display for ParseSignatureError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
@@ -312,6 +340,7 @@ pub mod error {
}
}
+ #[cfg(feature = "hex")]
#[cfg(feature = "std")]
impl std::error::Error for ParseSignatureError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
@@ -360,13 +389,16 @@ impl<'a> Arbitrary<'a> for Signature {
#[cfg(test)]
mod tests {
+ #[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
use super::*;
+ #[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
const TEST_SIGNATURE_HEX: &str = "3046022100839c1fbc5304de944f697c9f4b1d01d1faeba32d751c0f7acb21ac8a0f436a72022100e89bd46bb3a5a62adc679f659b7ce876d83ee297c7a5587b2011c4fcc72eab45";
#[test]
+ #[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
fn iterate_serialized_signature() {
let sig = Signature {
diff --git a/crypto/src/key.rs b/crypto/src/key.rs
index 2d16d2ce..e0a6ff36 100644
--- a/crypto/src/key.rs
+++ b/crypto/src/key.rs
@@ -16,6 +16,7 @@ use core::str::FromStr;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::hash160;
+#[cfg(feature = "hex")]
use hex::DisplayHex;
#[cfg(feature = "alloc")]
use internals::array::ArrayExt;
@@ -28,19 +29,22 @@ use network::NetworkKind;
pub use secp256k1::rand;
use crate::ecdsa;
+#[cfg(feature = "hex")]
use crate::hex::{self, DecodeFixedLengthBytesError};
#[rustfmt::skip] // Keep public re-exports separate.
pub use secp256k1::{constants, Parity, Verification};
#[doc(no_inline)]
pub use self::error::{
- FromSliceError, InvalidAddressVersionError, InvalidBase58PayloadLengthError,
- ParseFullPublicKeyError, ParseKeypairError, ParsePublicKeyError, ParseXOnlyPublicKeyError,
- TweakXOnlyPublicKeyError, UncompressedPublicKeyError,
+ FromSliceError, InvalidAddressVersionError, InvalidBase58PayloadLengthError, ParseKeypairError,
+ ParseXOnlyPublicKeyError, TweakXOnlyPublicKeyError, UncompressedPublicKeyError,
};
#[cfg(feature = "alloc")]
#[doc(no_inline)]
pub use self::error::{FromWifError, InvalidWifCompressionFlagError};
+#[cfg(feature = "hex")]
+#[doc(no_inline)]
+pub use self::error::{ParseFullPublicKeyError, ParsePublicKeyError};
pub use self::full_public_key::FullPublicKey;
pub use self::keypair::Keypair;
pub use self::legacy_public_key::LegacyPublicKey;
@@ -715,6 +719,7 @@ impl LegacyPublicKey {
/// # Example: Using with `sort_unstable_by_key`
///
/// ```rust
+ /// # #[cfg(feature = "hex")] {
/// use bitcoin_crypto::key::LegacyPublicKey;
///
/// let pk = |s: &str| s.parse::<LegacyPublicKey>().unwrap();
@@ -745,6 +750,7 @@ impl LegacyPublicKey {
/// unsorted.sort_unstable_by_key(|k| LegacyPublicKey::to_sort_key(*k));
///
/// assert_eq!(unsorted, sorted);
+ /// # }
/// ```
#[inline]
pub fn to_sort_key(self) -> SortKey {
@@ -810,6 +816,7 @@ impl LegacyPublicKey {
}
}
+#[cfg(feature = "hex")]
impl FromStr for LegacyPublicKey {
type Err = ParsePublicKeyError;
#[inline]
@@ -848,6 +855,7 @@ impl From<FullPublicKey> for LegacyPublicKey {
fn from(value: FullPublicKey) -> Self { Self::from_secp(value.to_inner()) }
}
+#[cfg(feature = "hex")]
impl fmt::Display for LegacyPublicKey {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.serialize().as_hex().fmt(f) }
@@ -864,7 +872,9 @@ hashes::hash_newtype! {
pub struct WPubkeyHash(hash160::Hash);
}
+#[cfg(feature = "hex")]
hashes::impl_hex_for_newtype!(PubkeyHash, WPubkeyHash);
+#[cfg(feature = "hex")]
#[cfg(feature = "serde")]
hashes::impl_serde_for_newtype!(PubkeyHash, WPubkeyHash);
@@ -968,6 +978,7 @@ impl FullPublicKey {
}
}
+#[cfg(feature = "hex")]
impl FromStr for FullPublicKey {
type Err = ParseFullPublicKeyError;
@@ -996,11 +1007,13 @@ impl From<secp256k1::PublicKey> for FullPublicKey {
fn from(pk: secp256k1::PublicKey) -> Self { Self::from_secp(pk) }
}
+#[cfg(feature = "hex")]
impl fmt::Display for FullPublicKey {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.to_bytes().as_hex().fmt(f) }
}
+#[cfg(feature = "hex")]
impl fmt::Debug for FullPublicKey {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -1307,6 +1320,7 @@ impl<'de> serde::Deserialize<'de> for XOnlyPublicKey {
}
}
+#[cfg(feature = "hex")]
#[cfg(feature = "serde")]
#[allow(clippy::collapsible_else_if)] // Aids readability.
impl serde::Serialize for LegacyPublicKey {
@@ -1320,6 +1334,7 @@ impl serde::Serialize for LegacyPublicKey {
}
}
+#[cfg(feature = "hex")]
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for LegacyPublicKey {
#[inline]
@@ -1381,6 +1396,7 @@ impl<'de> serde::Deserialize<'de> for LegacyPublicKey {
}
}
+#[cfg(feature = "hex")]
#[cfg(feature = "serde")]
impl serde::Serialize for FullPublicKey {
#[inline]
@@ -1393,6 +1409,7 @@ impl serde::Serialize for FullPublicKey {
}
}
+#[cfg(feature = "hex")]
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for FullPublicKey {
#[inline]
@@ -1536,6 +1553,7 @@ impl From<&Self> for SerializedXOnlyPublicKey {
fn from(borrowed: &Self) -> Self { *borrowed }
}
+#[cfg(feature = "hex")]
impl fmt::Debug for SerializedXOnlyPublicKey {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -1671,6 +1689,7 @@ pub mod error {
/// Error returned while constructing public key from string.
#[derive(Debug, Clone, PartialEq, Eq)]
+ #[cfg(feature = "hex")]
pub enum ParsePublicKeyError {
/// Error originated while parsing string.
Encoding(FromSliceError),
@@ -1680,11 +1699,13 @@ pub mod error {
InvalidHexLength(usize),
}
+ #[cfg(feature = "hex")]
impl From<Infallible> for ParsePublicKeyError {
#[inline]
fn from(never: Infallible) -> Self { match never {} }
}
+ #[cfg(feature = "hex")]
impl fmt::Display for ParsePublicKeyError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -1697,6 +1718,7 @@ pub mod error {
}
}
+ #[cfg(feature = "hex")]
#[cfg(feature = "std")]
impl std::error::Error for ParsePublicKeyError {
#[inline]
@@ -1713,6 +1735,7 @@ pub mod error {
///
/// [`FullPublicKey`]: super::FullPublicKey
#[derive(Debug, Clone, PartialEq, Eq)]
+ #[cfg(feature = "hex")]
pub enum ParseFullPublicKeyError {
/// secp256k1 Error.
Secp256k1(secp256k1::Error),
@@ -1720,11 +1743,13 @@ pub mod error {
Hex(hex::DecodeFixedLengthBytesError),
}
+ #[cfg(feature = "hex")]
impl From<Infallible> for ParseFullPublicKeyError {
#[inline]
fn from(never: Infallible) -> Self { match never {} }
}
+ #[cfg(feature = "hex")]
impl fmt::Display for ParseFullPublicKeyError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -1735,6 +1760,7 @@ pub mod error {
}
}
+ #[cfg(feature = "hex")]
#[cfg(feature = "std")]
impl std::error::Error for ParseFullPublicKeyError {
#[inline]
@@ -1975,14 +2001,20 @@ impl<'a> Arbitrary<'a> for XOnlyPublicKey {
#[cfg(test)]
mod tests {
+ #[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
use alloc::string::ToString;
+ #[cfg(feature = "hex")]
+ #[cfg(feature = "alloc")]
+ use alloc::format;
+ #[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
- use alloc::{format, vec};
+ use alloc::vec;
use super::*;
#[test]
+ #[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
fn pubkey_hash() {
let pk = "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af"
@@ -1995,6 +2027,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
fn wpubkey_hash() {
let pk = "032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af"
@@ -2009,6 +2042,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "hex")]
#[cfg(feature = "serde")]
#[cfg(feature = "alloc")]
fn skey_serde() {
@@ -2054,6 +2088,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "hex")]
fn pubkey_to_sort_key() {
let key1 = "02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8"
.parse::<LegacyPublicKey>()
@@ -2075,6 +2110,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
fn pubkey_sort() {
struct Vector {
@@ -2196,6 +2232,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "hex")]
fn public_key_from_str_wrong_length() {
// Sanity checks, we accept string length 130 digits.
let s = "042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b133";
@@ -2214,6 +2251,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "hex")]
fn public_key_from_str_invalid_str() {
// Ensuring test cases fail when LegacyPublicKey::from_str is used on invalid keys
let s = "042e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af191923a2964c177f5b5923ae500fca49e99492d534aa3759d6b25a8bc971b142";
@@ -2278,6 +2316,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "hex")]
fn xonly_pubkey_from_bytes() {
let key_bytes = &hex::decode_to_array::<32>(
"5b1e57ec453cd33fdc7cfc901450a3931fd315422558f2fb7fefb064e6e7d60d",
@@ -2290,6 +2329,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "hex")]
fn xonly_pubkey_to_inner() {
let key_bytes = &hex::decode_to_array::<32>(
"5b1e57ec453cd33fdc7cfc901450a3931fd315422558f2fb7fefb064e6e7d60d",
@@ -2303,6 +2343,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "hex")]
#[cfg(feature = "alloc")]
fn keypair_from_str_roundtrip() {
#[cfg(feature = "rand")]
diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs
index 9c85ca80..443ff5cc 100644
--- a/crypto/src/lib.rs
+++ b/crypto/src/lib.rs
@@ -15,6 +15,7 @@ extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
+#[cfg(feature = "hex")]
pub extern crate hex;
pub extern crate base58;
diff --git a/crypto/src/taproot.rs b/crypto/src/taproot.rs
index 7188b777..9f39ecf0 100644
--- a/crypto/src/taproot.rs
+++ b/crypto/src/taproot.rs
@@ -9,21 +9,27 @@ use alloc::vec::Vec;
use core::borrow::Borrow;
use core::fmt;
use core::ops::Deref;
+#[cfg(feature = "hex")]
use core::str::FromStr;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use internals::array::ArrayExt;
#[cfg(feature = "alloc")]
+#[cfg(feature = "hex")]
use internals::impl_to_hex_from_lower_hex;
pub use self::into_iter::IntoIter;
+#[cfg(feature = "hex")]
use crate::hex;
use crate::sighash::{InvalidSighashTypeError, TapSighashType};
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(no_inline)]
-pub use self::error::{ParseSignatureError, SigFromSliceError};
+pub use self::error::SigFromSliceError;
+#[cfg(feature = "hex")]
+#[doc(no_inline)]
+pub use self::error::ParseSignatureError;
const MAX_LEN: usize = 65; // 64 for sig, 1B sighash flag
@@ -97,24 +103,28 @@ impl Signature {
}
}
+#[cfg(feature = "hex")]
impl fmt::Display for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.serialize(), f)
}
}
+#[cfg(feature = "hex")]
impl fmt::LowerHex for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::LowerHex::fmt(&self.serialize(), f)
}
}
+#[cfg(feature = "hex")]
impl fmt::UpperHex for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::UpperHex::fmt(&self.serialize(), f)
}
}
+#[cfg(feature = "hex")]
impl FromStr for Signature {
type Err = ParseSignatureError;
@@ -176,9 +186,11 @@ impl SerializedSignature {
#[inline]
pub fn iter(&self) -> core::slice::Iter<'_, u8> { self.into_iter() }
+ #[cfg(feature = "hex")]
fn is_default(&self) -> bool { self.len() != MAX_LEN }
#[inline]
+ #[cfg(feature = "hex")]
fn fmt_internal(&self, f: &mut fmt::Formatter, case: hex::Case) -> fmt::Result {
if self.is_default() {
hex::fmt_hex_exact!(f, MAX_LEN - 1, self, case)
@@ -204,21 +216,37 @@ impl SerializedSignature {
}
impl fmt::Debug for SerializedSignature {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) }
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ #[cfg(feature = "hex")]
+ {
+ fmt::Display::fmt(self, f)
+ }
+ #[cfg(not(feature = "hex"))]
+ {
+ for b in self {
+ write!(f, "{:02x}", b)?;
+ }
+ Ok(())
+ }
+ }
}
+#[cfg(feature = "hex")]
impl fmt::Display for SerializedSignature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) }
}
+#[cfg(feature = "hex")]
impl fmt::LowerHex for SerializedSignature {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_internal(f, hex::Case::Lower) }
}
#[cfg(feature = "alloc")]
+#[cfg(feature = "hex")]
impl_to_hex_from_lower_hex!(SerializedSignature, |signature: &SerializedSignature| signature.len
* 2);
+#[cfg(feature = "hex")]
impl fmt::UpperHex for SerializedSignature {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.fmt_internal(f, hex::Case::Upper) }
@@ -329,7 +357,7 @@ mod into_iter {
/// Created by [`IntoIterator::into_iter`] method.
// allowed because of https://github.com/rust-lang/rust/issues/98348
#[allow(missing_copy_implementations)]
- #[derive(Debug, Clone)]
+ #[derive(Clone, Debug)]
pub struct IntoIter {
signature: SerializedSignature,
// invariant: pos <= signature.len()
@@ -468,6 +496,7 @@ pub mod error {
/// Error encountered while parsing a Taproot signature from a string.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
+ #[cfg(feature = "hex")]
pub enum ParseSignatureError {
/// Hex string invalid length error.
InvalidLength(usize),
@@ -477,10 +506,12 @@ pub mod error {
Decode(SigFromSliceError),
}
+ #[cfg(feature = "hex")]
impl From<Infallible> for ParseSignatureError {
fn from(never: Infallible) -> Self { match never {} }
}
+ #[cfg(feature = "hex")]
impl fmt::Display for ParseSignatureError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
@@ -495,6 +526,7 @@ pub mod error {
}
}
+ #[cfg(feature = "hex")]
#[cfg(feature = "std")]
impl std::error::Error for ParseSignatureError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
@@ -522,6 +554,7 @@ impl<'a> Arbitrary<'a> for Signature {
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
+ #[cfg(feature = "hex")]
use alloc::string::ToString;
use super::*;
@@ -558,6 +591,7 @@ mod tests {
}
#[cfg(feature = "alloc")]
+ #[cfg(feature = "hex")]
const SIG_STRINGS: &[&str] = &[
// default sighash type
"abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab",
@@ -570,6 +604,7 @@ mod tests {
#[test]
#[cfg(feature = "alloc")]
+ #[cfg(feature = "hex")]
fn signature_hex_roundtrip() {
for &want in SIG_STRINGS {
let sig = want.parse::<Signature>().unwrap();
@@ -579,6 +614,7 @@ mod tests {
}
#[test]
+ #[cfg(feature = "hex")]
fn signature_hex_default_error() {
let sig_hex = "abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab00";
let parse_err = sig_hex.parse::<Signature>().unwrap_err();
@@ -590,6 +626,7 @@ mod tests {
#[test]
#[cfg(feature = "alloc")]
+ #[cfg(feature = "hex")]
fn serialized_signature_hex() {
for &want in SIG_STRINGS {
let sig = want.parse::<Signature>().unwrap();
@@ -597,4 +634,17 @@ mod tests {
assert_eq!(got, want);
}
}
+
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn serialized_signature_debug() {
+ let bytes = [0xab; 64];
+ let sig = Signature::from_slice(&bytes).unwrap();
+ let ser_sig = SerializedSignature::from_signature(sig);
+ let sig_string = alloc::format!("{:?}", ser_sig);
+ assert_eq!(
+ sig_string,
+ "abababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab"
+ );
+ }
}
Why this scored 19/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.