Merge rust-bitcoin/rust-bitcoin#4262: primitives: Make `hex` optional

a5f904559d primitives: Make hex optional (Tobin C. Harding)

Pull request description:

  Make the `hex` dependency optional. This means not implementing
      `Display` for some types if `hex` is not enabled and only implementing
      `Debug`.

      Also without `hex` enabled:

      - We loose the ability to parse an `OutPoint` from string because we
        can no longer parse `Txid`.

      - We loose the hex formatting of witness elements.

      Note also that `primitives` builds with the `serde` feature even if
      `hex?/serde` is excluded from the `serde` feature. I found this
      surprising.

  Close: #4183

ACKs for top commit:
  apoelstra:
    ACK a5f904559d3b5d2dfbcbed8b1746305e32103fee; successfully ran local tests

Tree-SHA512: c105e089508036af8251cb923f3eda163b8f2d6151ea043aa1489edb6ce396975d1f598a240f4ca6e48f6ef780774f7d86cb70ec9399f3d2ff87c0ac53ceee91
This commit is contained in:
merge-script 2025-04-15 15:45:42 +00:00
commit 93e2000b98
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
9 changed files with 67 additions and 17 deletions

View File

@ -213,6 +213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55" checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"serde",
] ]
[[package]] [[package]]

View File

@ -215,6 +215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55" checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"serde",
] ]
[[package]] [[package]]

View File

@ -31,7 +31,7 @@ hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = fa
hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["alloc"] } hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["alloc"] }
internals = { package = "bitcoin-internals", path = "../internals", features = ["alloc", "hex"] } internals = { package = "bitcoin-internals", path = "../internals", features = ["alloc", "hex"] }
io = { package = "bitcoin-io", path = "../io", default-features = false, features = ["alloc", "hashes"] } io = { package = "bitcoin-io", path = "../io", default-features = false, features = ["alloc", "hashes"] }
primitives = { package = "bitcoin-primitives", path = "../primitives", default-features = false, features = ["alloc"] } primitives = { package = "bitcoin-primitives", path = "../primitives", default-features = false, features = ["alloc", "hex"] }
secp256k1 = { version = "0.30.0", default-features = false, features = ["hashes", "alloc", "rand"] } secp256k1 = { version = "0.30.0", default-features = false, features = ["hashes", "alloc", "rand"] }
units = { package = "bitcoin-units", path = "../units", default-features = false, features = ["alloc"] } units = { package = "bitcoin-units", path = "../units", default-features = false, features = ["alloc"] }

View File

@ -15,19 +15,20 @@ rust-version = "1.63.0"
exclude = ["tests", "contrib"] exclude = ["tests", "contrib"]
[features] [features]
default = ["std"] default = ["std", "hex"]
std = ["alloc", "hashes/std", "hex/std", "internals/std", "units/std"] std = ["alloc", "hashes/std", "hex?/std", "internals/std", "units/std"]
alloc = ["hashes/alloc", "hex/alloc", "internals/alloc", "units/alloc"] alloc = ["hashes/alloc", "hex?/alloc", "internals/alloc", "units/alloc"]
serde = ["dep:serde", "hashes/serde", "internals/serde", "units/serde", "alloc"] serde = ["dep:serde", "hashes/serde", "hex?/serde", "internals/serde", "units/serde", "alloc", "hex"]
arbitrary = ["dep:arbitrary", "units/arbitrary"] arbitrary = ["dep:arbitrary", "units/arbitrary"]
hex = ["dep:hex", "hashes/hex", "internals/hex"]
[dependencies] [dependencies]
hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false, features = ["hex"] } hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false }
hex = { package = "hex-conservative", version = "0.3.0", default-features = false } internals = { package = "bitcoin-internals", path = "../internals" }
internals = { package = "bitcoin-internals", path = "../internals", features = ["hex"] }
units = { package = "bitcoin-units", path = "../units", default-features = false } units = { package = "bitcoin-units", path = "../units", default-features = false }
arbitrary = { version = "1.4", optional = true } arbitrary = { version = "1.4", optional = true }
hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true }
serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"], optional = true } serde = { version = "1.0.103", default-features = false, features = ["derive", "alloc"], optional = true }
[dev-dependencies] [dev-dependencies]

View File

@ -310,7 +310,10 @@ hashes::hash_newtype! {
pub struct WitnessCommitment(sha256d::Hash); pub struct WitnessCommitment(sha256d::Hash);
} }
#[cfg(feature = "hex")]
hashes::impl_hex_for_newtype!(BlockHash, WitnessCommitment); hashes::impl_hex_for_newtype!(BlockHash, WitnessCommitment);
#[cfg(not(feature = "hex"))]
hashes::impl_debug_only_for_newtype!(BlockHash, WitnessCommitment);
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
hashes::impl_serde_for_newtype!(BlockHash, WitnessCommitment); hashes::impl_serde_for_newtype!(BlockHash, WitnessCommitment);

View File

@ -11,6 +11,9 @@ hashes::hash_newtype! {
pub struct WitnessMerkleNode(sha256d::Hash); pub struct WitnessMerkleNode(sha256d::Hash);
} }
#[cfg(feature = "hex")]
hashes::impl_hex_for_newtype!(TxMerkleNode, WitnessMerkleNode); hashes::impl_hex_for_newtype!(TxMerkleNode, WitnessMerkleNode);
#[cfg(not(feature = "hex"))]
hashes::impl_debug_only_for_newtype!(TxMerkleNode, WitnessMerkleNode);
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
hashes::impl_serde_for_newtype!(TxMerkleNode, WitnessMerkleNode); hashes::impl_serde_for_newtype!(TxMerkleNode, WitnessMerkleNode);

View File

@ -10,6 +10,7 @@ use core::convert::Infallible;
use core::fmt; use core::fmt;
use hashes::{hash160, sha256}; use hashes::{hash160, sha256};
#[cfg(feature = "hex")]
use hex::DisplayHex; use hex::DisplayHex;
use internals::script::{self, PushDataLenLen}; use internals::script::{self, PushDataLenLen};
@ -49,7 +50,10 @@ hashes::hash_newtype! {
pub struct WScriptHash(sha256::Hash); pub struct WScriptHash(sha256::Hash);
} }
#[cfg(feature = "hex")]
hashes::impl_hex_for_newtype!(ScriptHash, WScriptHash); hashes::impl_hex_for_newtype!(ScriptHash, WScriptHash);
#[cfg(not(feature = "hex"))]
hashes::impl_debug_only_for_newtype!(ScriptHash, WScriptHash);
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
hashes::impl_serde_for_newtype!(ScriptHash, WScriptHash); hashes::impl_serde_for_newtype!(ScriptHash, WScriptHash);
@ -425,6 +429,7 @@ impl fmt::Display for ScriptBuf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self.as_script(), f) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self.as_script(), f) }
} }
#[cfg(feature = "hex")]
impl fmt::LowerHex for Script { impl fmt::LowerHex for Script {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -432,15 +437,19 @@ impl fmt::LowerHex for Script {
} }
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg(feature = "hex")]
internals::impl_to_hex_from_lower_hex!(Script, |script: &Self| script.len() * 2); internals::impl_to_hex_from_lower_hex!(Script, |script: &Self| script.len() * 2);
#[cfg(feature = "hex")]
impl fmt::LowerHex for ScriptBuf { impl fmt::LowerHex for ScriptBuf {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self.as_script(), f) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self.as_script(), f) }
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg(feature = "hex")]
internals::impl_to_hex_from_lower_hex!(ScriptBuf, |script_buf: &Self| script_buf.len() * 2); internals::impl_to_hex_from_lower_hex!(ScriptBuf, |script_buf: &Self| script_buf.len() * 2);
#[cfg(feature = "hex")]
impl fmt::UpperHex for Script { impl fmt::UpperHex for Script {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -448,6 +457,7 @@ impl fmt::UpperHex for Script {
} }
} }
#[cfg(feature = "hex")]
impl fmt::UpperHex for ScriptBuf { impl fmt::UpperHex for ScriptBuf {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(self.as_script(), f) } fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(self.as_script(), f) }
@ -756,17 +766,25 @@ mod tests {
fn script_display() { fn script_display() {
let script = Script::from_bytes(&[0xa1, 0xb2, 0xc3]); let script = Script::from_bytes(&[0xa1, 0xb2, 0xc3]);
assert_eq!(format!("{}", script), "OP_LESSTHANOREQUAL OP_CSV OP_RETURN_195"); assert_eq!(format!("{}", script), "OP_LESSTHANOREQUAL OP_CSV OP_RETURN_195");
#[cfg(feature = "hex")]
{
assert_eq!(format!("{:x}", script), "a1b2c3"); assert_eq!(format!("{:x}", script), "a1b2c3");
assert_eq!(format!("{:X}", script), "A1B2C3"); assert_eq!(format!("{:X}", script), "A1B2C3");
} }
}
#[test] #[test]
fn scriptbuf_display() { fn scriptbuf_display() {
let script_buf = ScriptBuf::from(vec![0xa1, 0xb2, 0xc3]); let script_buf = ScriptBuf::from(vec![0xa1, 0xb2, 0xc3]);
assert_eq!(format!("{}", script_buf), "OP_LESSTHANOREQUAL OP_CSV OP_RETURN_195"); assert_eq!(format!("{}", script_buf), "OP_LESSTHANOREQUAL OP_CSV OP_RETURN_195");
#[cfg(feature = "hex")]
{
assert_eq!(format!("{:x}", script_buf), "a1b2c3"); assert_eq!(format!("{:x}", script_buf), "a1b2c3");
assert_eq!(format!("{:X}", script_buf), "A1B2C3"); assert_eq!(format!("{:X}", script_buf), "A1B2C3");
} }
}
#[test] #[test]
fn cow_script_to_scriptbuf() { fn cow_script_to_scriptbuf() {

View File

@ -13,6 +13,7 @@
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use core::cmp; use core::cmp;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg(feature = "hex")]
use core::convert::Infallible; use core::convert::Infallible;
use core::fmt; use core::fmt;
@ -20,9 +21,13 @@ use core::fmt;
use arbitrary::{Arbitrary, Unstructured}; use arbitrary::{Arbitrary, Unstructured};
use hashes::sha256d; use hashes::sha256d;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use internals::{compact_size, write_err}; use internals::compact_size;
#[cfg(feature = "hex")]
use internals::write_err;
#[cfg(feature = "hex")]
use units::parse;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use units::{parse, Amount, Weight}; use units::{Amount, Weight};
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
use crate::locktime::absolute; use crate::locktime::absolute;
@ -389,6 +394,7 @@ impl OutPoint {
pub const COINBASE_PREVOUT: Self = Self { txid: Txid::COINBASE_PREVOUT, vout: u32::MAX }; pub const COINBASE_PREVOUT: Self = Self { txid: Txid::COINBASE_PREVOUT, vout: u32::MAX };
} }
#[cfg(feature = "hex")]
impl fmt::Display for OutPoint { impl fmt::Display for OutPoint {
#[inline] #[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -397,6 +403,7 @@ impl fmt::Display for OutPoint {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg(feature = "hex")]
impl core::str::FromStr for OutPoint { impl core::str::FromStr for OutPoint {
type Err = ParseOutPointError; type Err = ParseOutPointError;
@ -424,6 +431,7 @@ impl core::str::FromStr for OutPoint {
/// ///
/// Does not permit leading zeroes or non-digit characters. /// Does not permit leading zeroes or non-digit characters.
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg(feature = "hex")]
fn parse_vout(s: &str) -> Result<u32, ParseOutPointError> { fn parse_vout(s: &str) -> Result<u32, ParseOutPointError> {
if s.len() > 1 { if s.len() > 1 {
let first = s.chars().next().unwrap(); let first = s.chars().next().unwrap();
@ -438,6 +446,7 @@ fn parse_vout(s: &str) -> Result<u32, ParseOutPointError> {
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive] #[non_exhaustive]
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg(feature = "hex")]
pub enum ParseOutPointError { pub enum ParseOutPointError {
/// Error in TXID part. /// Error in TXID part.
Txid(hex::HexToArrayError), Txid(hex::HexToArrayError),
@ -452,12 +461,14 @@ pub enum ParseOutPointError {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg(feature = "hex")]
impl From<Infallible> for ParseOutPointError { impl From<Infallible> for ParseOutPointError {
#[inline] #[inline]
fn from(never: Infallible) -> Self { match never {} } fn from(never: Infallible) -> Self { match never {} }
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[cfg(feature = "hex")]
impl fmt::Display for ParseOutPointError { impl fmt::Display for ParseOutPointError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ParseOutPointError as E; use ParseOutPointError as E;
@ -473,6 +484,7 @@ impl fmt::Display for ParseOutPointError {
} }
#[cfg(feature = "std")] #[cfg(feature = "std")]
#[cfg(feature = "hex")]
impl std::error::Error for ParseOutPointError { impl std::error::Error for ParseOutPointError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use ParseOutPointError as E; use ParseOutPointError as E;
@ -500,7 +512,10 @@ hashes::hash_newtype! {
pub struct Wtxid(sha256d::Hash); pub struct Wtxid(sha256d::Hash);
} }
#[cfg(feature = "hex")]
hashes::impl_hex_for_newtype!(Txid, Wtxid); hashes::impl_hex_for_newtype!(Txid, Wtxid);
#[cfg(not(feature = "hex"))]
hashes::impl_debug_only_for_newtype!(Txid, Wtxid);
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
hashes::impl_serde_for_newtype!(Txid, Wtxid); hashes::impl_serde_for_newtype!(Txid, Wtxid);
@ -699,6 +714,7 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "hex")]
fn outpoint_from_str() { fn outpoint_from_str() {
// Check format errors // Check format errors
let mut outpoint_str = "0".repeat(64); // No ":" let mut outpoint_str = "0".repeat(64); // No ":"
@ -725,6 +741,7 @@ mod tests {
} }
#[test] #[test]
#[cfg(feature = "hex")]
fn canonical_vout() { fn canonical_vout() {
assert_eq!(parse_vout("0").unwrap(), 0); assert_eq!(parse_vout("0").unwrap(), 0);
assert_eq!(parse_vout("1").unwrap(), 1); assert_eq!(parse_vout("1").unwrap(), 1);

View File

@ -9,7 +9,6 @@ use core::ops::Index;
#[cfg(feature = "arbitrary")] #[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured}; use arbitrary::{Arbitrary, Unstructured};
use hex::DisplayHex;
use internals::compact_size; use internals::compact_size;
use internals::slice::SliceExt; use internals::slice::SliceExt;
use internals::wrap_debug::WrapDebug; use internals::wrap_debug::WrapDebug;
@ -314,7 +313,7 @@ impl<T: core::borrow::Borrow<[u8]>> PartialEq<Witness> for alloc::sync::Arc<[T]>
/// Debug implementation that displays the witness as a structured output containing: /// Debug implementation that displays the witness as a structured output containing:
/// - Number of witness elements /// - Number of witness elements
/// - Total bytes across all elements /// - Total bytes across all elements
/// - List of hex-encoded witness elements /// - List of hex-encoded witness elements if `hex` features is enabled.
#[allow(clippy::missing_fields_in_debug)] // We don't want to show `indices_start`. #[allow(clippy::missing_fields_in_debug)] // We don't want to show `indices_start`.
impl fmt::Debug for Witness { impl fmt::Debug for Witness {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@ -326,7 +325,14 @@ impl fmt::Debug for Witness {
.field( .field(
"elements", "elements",
&WrapDebug(|f| { &WrapDebug(|f| {
f.debug_list().entries(self.iter().map(DisplayHex::as_hex)).finish() #[cfg(feature = "hex")]
{
f.debug_list().entries(self.iter().map(hex::DisplayHex::as_hex)).finish()
}
#[cfg(not(feature = "hex"))]
{
f.debug_list().entries(self.iter()).finish()
}
}), }),
) )
.finish() .finish()