What changed, and why it matters
This commit swaps in a newer, stable version of a helper library (hex-conservative 1.0.0) for public-facing parts of the rust-bitcoin 'primitives' crate, while keeping the older version for internal-only uses. It is a dependency/API-stability cleanup, not a fix for a known security bug. There is no direct evidence in the commit that it addresses an exploit or vulnerability.
Treat as a normal maintenance/API-stability commit. Review downstream lockfiles and public API usage for renamed error types; no security patch or incident response is indicated by the commit itself.
Security signals we found
Dependency version bump (hex-conservative 0.3.0 -> 1.0.0 for public API)
Removal of unused FromHex imports
Public error/type names changed (HexToArrayError -> DecodeFixedLengthBytesError, HexToBytesError -> DecodeVariableLengthBytesError)
Feature-gate cleanup for serde/hex
Evidence from the diff
The change introduces hex-conservative 1.0.0 as ‘hex-stable’ and renames the existing 0.3.0 dependency to ‘hex-unstable’ in primitives/Cargo.toml. Public API items now expose types from the stable 1.0.0 release (e.g., DecodeFixedLengthBytesError, DecodeVariableLengthBytesError, decode_to_array, decode_to_vec), while non-public/internal formatting still uses the 0.3.0 crate. Several unused ‘use hex::FromHex’ imports are removed. The serde feature no longer enables hex/serde on the unstable crate. This is a semver/API-hygiene change ahead of primitives 1.0.0.
Changed components
primitives/Cargo.tomlprimitives/src/lib.rsprimitives/src/hash_types/generic.rsprimitives/src/transaction.rsprimitives/src/witness.rsprimitives/src/script/mod.rsprimitives/src/block.rsInspect captured patch +35 / −39
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index 7aafa642..354329ce 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -142,6 +142,7 @@ dependencies = [
"bitcoin-units",
"bitcoin_hashes 0.17.0",
"hex-conservative 0.3.0",
+ "hex-conservative 1.0.0",
"hex_lit",
"serde",
"serde_json",
@@ -242,9 +243,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55"
dependencies = [
"arrayvec",
- "serde",
]
+[[package]]
+name = "hex-conservative"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ee770c000993d17c185713463d5ebfbd1af9afae4c17cc295640104383bfbf0"
+
[[package]]
name = "hex_lit"
version = "0.1.1"
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 2def4487..92012fef 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -141,6 +141,7 @@ dependencies = [
"bitcoin-units",
"bitcoin_hashes 0.17.0",
"hex-conservative 0.3.0",
+ "hex-conservative 1.0.0",
"hex_lit",
"serde",
"serde_json",
@@ -244,9 +245,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4afe881d0527571892c4034822e59bb10c6c991cce6abe8199b6f5cf10766f55"
dependencies = [
"arrayvec",
- "serde",
]
+[[package]]
+name = "hex-conservative"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ee770c000993d17c185713463d5ebfbd1af9afae4c17cc295640104383bfbf0"
+
[[package]]
name = "hex_lit"
version = "0.1.1"
diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml
index 0c105bf1..505e04c9 100644
--- a/primitives/Cargo.toml
+++ b/primitives/Cargo.toml
@@ -14,11 +14,11 @@ exclude = ["tests", "contrib"]
[features]
default = ["std", "hex"]
-std = ["alloc", "hashes/std", "hex?/std", "internals/std", "units/std", "arrayvec/std"]
-alloc = ["hashes/alloc", "hex?/alloc", "internals/alloc", "units/alloc"]
-serde = ["dep:serde", "hashes/serde", "hex?/serde", "internals/serde", "units/serde", "alloc", "hex"]
+std = ["alloc", "hashes/std", "hex-stable?/std", "hex-unstable?/std", "internals/std", "units/std", "arrayvec/std"]
+alloc = ["hashes/alloc", "hex-stable?/alloc", "hex-unstable?/alloc", "internals/alloc", "units/alloc"]
+serde = ["dep:serde", "hashes/serde", "internals/serde", "units/serde", "alloc", "hex"]
arbitrary = ["dep:arbitrary", "units/arbitrary"]
-hex = ["dep:hex", "hashes/hex", "internals/hex"]
+hex = ["dep:hex-stable", "dep:hex-unstable", "hashes/hex", "internals/hex"]
[dependencies]
encoding = { package = "bitcoin-consensus-encoding", path = "../consensus_encoding", version = "1.0.0-rc.1", default-features = false }
@@ -28,7 +28,8 @@ units = { package = "bitcoin-units", path = "../units", version = "1.0.0-rc.2",
arrayvec = { version = "0.7.2", default-features = false }
arbitrary = { version = "1.4.1", optional = true }
-hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true }
+hex-stable = { package = "hex-conservative", version = "1.0.0", default-features = false, optional = true }
+hex-unstable = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true }
serde = { version = "1.0.195", default-features = false, features = ["derive", "alloc"], optional = true }
[dev-dependencies]
diff --git a/primitives/src/block.rs b/primitives/src/block.rs
index d306dc8e..9b1172b7 100644
--- a/primitives/src/block.rs
+++ b/primitives/src/block.rs
@@ -244,7 +244,7 @@ impl Header {
impl fmt::Display for Header {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use fmt::Write as _;
- use hex::DisplayHex as _;
+ use hex_unstable::DisplayHex as _;
let mut buf = arrayvec::ArrayString::<160>::new();
write!(
diff --git a/primitives/src/hash_types/block_hash.rs b/primitives/src/hash_types/block_hash.rs
index 438b7b5a..0c6ad61c 100644
--- a/primitives/src/hash_types/block_hash.rs
+++ b/primitives/src/hash_types/block_hash.rs
@@ -11,8 +11,6 @@ use core::str;
use arbitrary::{Arbitrary, Unstructured};
use encoding::Encodable;
use hashes::sha256d;
-#[cfg(feature = "hex")]
-use hex::FromHex as _;
use internals::write_err;
/// A bitcoin block hash.
diff --git a/primitives/src/hash_types/generic.rs b/primitives/src/hash_types/generic.rs
index e017681b..3ba0883c 100644
--- a/primitives/src/hash_types/generic.rs
+++ b/primitives/src/hash_types/generic.rs
@@ -29,7 +29,7 @@ super::impl_serde!(HashType, LEN);
super::impl_bytelike_traits!(HashType, LEN);
#[cfg(feature = "hex")]
-hex::impl_fmt_traits! {
+hex_unstable::impl_fmt_traits! {
#[display_backward(REVERSE)]
impl fmt_traits for HashType {
const LENGTH: usize = LEN;
@@ -38,10 +38,10 @@ hex::impl_fmt_traits! {
#[cfg(feature = "hex")]
impl str::FromStr for HashType {
- type Err = hex::HexToArrayError;
+ type Err = hex::DecodeFixedLengthBytesError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- let mut bytes = <[u8; LEN]>::from_hex(s)?;
+ let mut bytes = crate::hex::decode_to_array(s)?;
if REVERSE {
bytes.reverse();
diff --git a/primitives/src/hash_types/ntxid.rs b/primitives/src/hash_types/ntxid.rs
index f7ab1e80..df7a22bc 100644
--- a/primitives/src/hash_types/ntxid.rs
+++ b/primitives/src/hash_types/ntxid.rs
@@ -10,8 +10,6 @@ use core::str;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::sha256d;
-#[cfg(feature = "hex")]
-use hex::FromHex as _;
/// A "normalized TXID".
///
diff --git a/primitives/src/hash_types/script_hash.rs b/primitives/src/hash_types/script_hash.rs
index ae7ed390..4bb64167 100644
--- a/primitives/src/hash_types/script_hash.rs
+++ b/primitives/src/hash_types/script_hash.rs
@@ -10,8 +10,6 @@ use core::str;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::hash160;
-#[cfg(feature = "hex")]
-use hex::FromHex as _;
use crate::script::{Script, ScriptHashableTag, MAX_REDEEM_SCRIPT_SIZE};
diff --git a/primitives/src/hash_types/transaction_merkle_node.rs b/primitives/src/hash_types/transaction_merkle_node.rs
index 685fdca8..a2aa1e02 100644
--- a/primitives/src/hash_types/transaction_merkle_node.rs
+++ b/primitives/src/hash_types/transaction_merkle_node.rs
@@ -10,8 +10,6 @@ use core::str;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::sha256d;
-#[cfg(feature = "hex")]
-use hex::FromHex as _;
use internals::write_err;
/// A hash of the Merkle tree branch or root for transactions.
diff --git a/primitives/src/hash_types/txid.rs b/primitives/src/hash_types/txid.rs
index 4246bce5..b0d9fbf1 100644
--- a/primitives/src/hash_types/txid.rs
+++ b/primitives/src/hash_types/txid.rs
@@ -12,8 +12,6 @@ use core::str;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::sha256d;
-#[cfg(feature = "hex")]
-use hex::FromHex as _;
#[cfg(doc)]
use crate::OutPoint;
diff --git a/primitives/src/hash_types/witness_commitment.rs b/primitives/src/hash_types/witness_commitment.rs
index 46fdd61a..f4332e96 100644
--- a/primitives/src/hash_types/witness_commitment.rs
+++ b/primitives/src/hash_types/witness_commitment.rs
@@ -10,8 +10,6 @@ use core::str;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::sha256d;
-#[cfg(feature = "hex")]
-use hex::FromHex as _;
/// A hash corresponding to the witness structure commitment in the coinbase transaction.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
diff --git a/primitives/src/hash_types/witness_merkle_node.rs b/primitives/src/hash_types/witness_merkle_node.rs
index 9fc6d4d6..49b9ed12 100644
--- a/primitives/src/hash_types/witness_merkle_node.rs
+++ b/primitives/src/hash_types/witness_merkle_node.rs
@@ -10,8 +10,6 @@ use core::str;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::sha256d;
-#[cfg(feature = "hex")]
-use hex::FromHex as _;
/// A hash corresponding to the Merkle tree root for witness data.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
diff --git a/primitives/src/hash_types/witness_script_hash.rs b/primitives/src/hash_types/witness_script_hash.rs
index 927f626a..5e4c915d 100644
--- a/primitives/src/hash_types/witness_script_hash.rs
+++ b/primitives/src/hash_types/witness_script_hash.rs
@@ -10,8 +10,6 @@ use core::str;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::sha256;
-#[cfg(feature = "hex")]
-use hex::FromHex as _;
use crate::script::{WitnessScript, MAX_WITNESS_SCRIPT_SIZE};
diff --git a/primitives/src/hash_types/wtxid.rs b/primitives/src/hash_types/wtxid.rs
index 6aaa2af1..482623f5 100644
--- a/primitives/src/hash_types/wtxid.rs
+++ b/primitives/src/hash_types/wtxid.rs
@@ -12,8 +12,6 @@ use core::str;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use hashes::sha256d;
-#[cfg(feature = "hex")]
-use hex::FromHex as _;
/// A bitcoin witness transaction ID.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs
index 6c351342..ac217b9a 100644
--- a/primitives/src/lib.rs
+++ b/primitives/src/lib.rs
@@ -30,6 +30,9 @@ extern crate std;
#[macro_use]
extern crate serde;
+#[cfg(feature = "hex")]
+pub extern crate hex_stable as hex;
+
#[doc(hidden)]
pub mod _export {
/// A re-export of `core::*`.
diff --git a/primitives/src/script/mod.rs b/primitives/src/script/mod.rs
index c81d4beb..c65ec729 100644
--- a/primitives/src/script/mod.rs
+++ b/primitives/src/script/mod.rs
@@ -12,7 +12,7 @@ use core::fmt;
use core::marker::PhantomData;
#[cfg(feature = "hex")]
-use hex::DisplayHex;
+use hex_unstable::DisplayHex;
use internals::script::{self, PushDataLenLen};
use crate::prelude::rc::Rc;
@@ -473,8 +473,6 @@ impl<'de, T> serde::Deserialize<'de> for ScriptBuf<T> {
{
use core::fmt::Formatter;
- use hex::FromHex;
-
if deserializer.is_human_readable() {
struct Visitor<T>(PhantomData<T>);
impl<T> serde::de::Visitor<'_> for Visitor<T> {
@@ -488,7 +486,7 @@ impl<'de, T> serde::Deserialize<'de> for ScriptBuf<T> {
where
E: serde::de::Error,
{
- let v = Vec::from_hex(v).map_err(E::custom)?;
+ let v = hex::decode_to_vec(v).map_err(E::custom)?;
Ok(ScriptBuf::from(v))
}
}
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index 1a9aadd7..daf392f5 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -1259,7 +1259,7 @@ impl<'de> Deserialize<'de> for OutPoint {
#[cfg(feature = "hex")]
pub enum ParseOutPointError {
/// Error in TXID part.
- Txid(hex::HexToArrayError),
+ Txid(hex::DecodeFixedLengthBytesError),
/// Error in vout part.
Vout(parse_int::ParseIntError),
/// Error in general format.
diff --git a/primitives/src/witness.rs b/primitives/src/witness.rs
index 22c522e6..c79cf09c 100644
--- a/primitives/src/witness.rs
+++ b/primitives/src/witness.rs
@@ -17,7 +17,7 @@ use encoding::{
Encodable, Encoder, Encoder2, LengthPrefixExceedsMaxError,
};
#[cfg(feature = "hex")]
-use hex::{error::HexToBytesError, FromHex};
+use hex::DecodeVariableLengthBytesError;
use internals::slice::SliceExt;
use internals::wrap_debug::WrapDebug;
use internals::{compact_size, write_err};
@@ -240,14 +240,14 @@ impl Witness {
///
/// This function will return an error if any of the hex strings are invalid.
#[cfg(feature = "hex")]
- pub fn from_hex<I, T>(iter: I) -> Result<Self, HexToBytesError>
+ pub fn from_hex<I, T>(iter: I) -> Result<Self, DecodeVariableLengthBytesError>
where
I: IntoIterator<Item = T>,
T: AsRef<str>,
{
let result = iter
.into_iter()
- .map(|hex_str| Vec::from_hex(hex_str.as_ref()))
+ .map(|hex_str| crate::hex::decode_to_vec(hex_str.as_ref()))
.collect::<Result<Vec<_>, _>>()?;
Ok(Self::from_slice(&result))
@@ -525,7 +525,7 @@ impl fmt::Debug for Witness {
&WrapDebug(|f| {
#[cfg(feature = "hex")]
{
- f.debug_list().entries(self.iter().map(hex::DisplayHex::as_hex)).finish()
+ f.debug_list().entries(self.iter().map(hex_unstable::DisplayHex::as_hex)).finish()
}
#[cfg(not(feature = "hex"))]
{
@@ -634,7 +634,7 @@ impl<'de> serde::Deserialize<'de> for Witness {
self,
mut a: A,
) -> Result<Self::Value, A::Error> {
- use hex::{FromHex, HexToBytesError as E};
+ use hex_unstable::{FromHex, HexToBytesError as E};
use serde::de::{self, Unexpected};
let mut ret = match a.size_hint() {
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.