hashes: Introduce hex stable and remove unecessary uses of hex_unstable
What changed, and why it matters
This commit is a routine dependency and API cleanup in the rust-bitcoin project's hashing crate. It swaps an unstable hex-parsing helper for a newly released stable version and updates the error types that downstream code sees. There is no direct evidence of a security vulnerability being fixed; it is best understood as a maintainability and API-stability improvement.
No immediate security action is required. Treat as a normal dependency/API-maintenance update. Review downstream code that pattern-matches on the old HexToArrayError type, as it will need to handle DecodeFixedLengthBytesError after upgrading.
Security signals we found
Dependency bump to a stable release of hex-conservative
Public error type changed from unstable to stable in macro-generated FromStr implementations
No explicit security claim, CVE, or bug fix in commit message or diff
Evidence from the diff
The commit updates bitcoin_hashes to depend on hex-conservative 1.0.0 (stable) alongside the existing 0.3.0 (unstable) version. Public macros that implement FromStr for hash types now return the stable DecodeFixedLengthBytesError instead of the unstable HexToArrayError. Internal formatting code still uses the unstable crate’s fmt_hex_exact macro. Test code switches from Vec::from_hex to hex::decode_to_vec. The change narrows exposure of unstable error types in the public API but does not, on its own, alter cryptographic behavior or fix a concrete bug.
Changed components
hashes crate public macros (impl_hex_string_traits)hashes crate Cargo.toml dependency declarationshashes/src/hkdf test moduleAPI lockfile (api/hashes/all-features.txt)Inspect captured patch +66 / −43
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index 71bfda54..12cf52dd 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -190,6 +190,7 @@ dependencies = [
"bitcoin-internals",
"cpufeatures",
"hex-conservative 0.3.0",
+ "hex-conservative 1.0.0",
"serde",
"serde_test",
]
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index 38abf0f6..d42dd63f 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -189,6 +189,7 @@ dependencies = [
"bitcoin-internals",
"cpufeatures",
"hex-conservative 0.3.0",
+ "hex-conservative 1.0.0",
"serde",
"serde_test",
]
diff --git a/api/hashes/all-features.txt b/api/hashes/all-features.txt
index e0db12e0..5c43e8a9 100644
--- a/api/hashes/all-features.txt
+++ b/api/hashes/all-features.txt
@@ -720,6 +720,7 @@ pub const fn bitcoin_hashes::siphash24::Hash::from_bytes_ref(bytes: &[u8; 8]) ->
pub const fn bitcoin_hashes::siphash24::Hash::to_byte_array(self) -> [u8; 8]
pub const fn bitcoin_hashes::siphash24::HashEngine::with_keys(k0: u64, k1: u64) -> Self
pub extern crate bitcoin_hashes::hex
+pub extern crate bitcoin_hashes::hex_unstable
pub extern crate bitcoin_hashes::serde
pub fn bitcoin_hashes::Hash::as_byte_array(&self) -> &Self::Bytes
pub fn bitcoin_hashes::Hash::from_byte_array(bytes: Self::Bytes) -> Self
@@ -1202,7 +1203,7 @@ pub type bitcoin_hashes::HmacSha256 = bitcoin_hashes::hmac::Hmac<bitcoin_hashes:
pub type bitcoin_hashes::HmacSha512 = bitcoin_hashes::hmac::Hmac<bitcoin_hashes::sha512::Hash>
pub type bitcoin_hashes::Sha256t<T> = bitcoin_hashes::sha256t::Hash<T>
pub type bitcoin_hashes::hash160::Hash::Bytes = [u8; 20]
-pub type bitcoin_hashes::hash160::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::hash160::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::hash160::HashEngine::Bytes = [u8; 20]
pub type bitcoin_hashes::hash160::HashEngine::Hash = bitcoin_hashes::hash160::Hash
pub type bitcoin_hashes::hmac::Hmac<T>::Bytes = <T as bitcoin_hashes::Hash>::Bytes
@@ -1212,44 +1213,44 @@ pub type bitcoin_hashes::hmac::HmacEngine<T>::Hash = bitcoin_hashes::hmac::Hmac<
pub type bitcoin_hashes::macros::serde_details::BytesVisitor<ValueT, N>::Value = ValueT
pub type bitcoin_hashes::macros::serde_details::HexVisitor<ValueT>::Value = ValueT
pub type bitcoin_hashes::muhash::Hash::Bytes = [u8; 384]
-pub type bitcoin_hashes::muhash::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::muhash::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::ripemd160::Hash::Bytes = [u8; 20]
-pub type bitcoin_hashes::ripemd160::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::ripemd160::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::ripemd160::HashEngine::Bytes = [u8; 20]
pub type bitcoin_hashes::ripemd160::HashEngine::Hash = bitcoin_hashes::ripemd160::Hash
pub type bitcoin_hashes::sha1::Hash::Bytes = [u8; 20]
-pub type bitcoin_hashes::sha1::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::sha1::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::sha1::HashEngine::Bytes = [u8; 20]
pub type bitcoin_hashes::sha1::HashEngine::Hash = bitcoin_hashes::sha1::Hash
pub type bitcoin_hashes::sha256::Hash::Bytes = [u8; 32]
-pub type bitcoin_hashes::sha256::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::sha256::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::sha256::HashEngine::Bytes = [u8; 32]
pub type bitcoin_hashes::sha256::HashEngine::Hash = bitcoin_hashes::sha256::Hash
pub type bitcoin_hashes::sha256d::Hash::Bytes = [u8; 32]
-pub type bitcoin_hashes::sha256d::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::sha256d::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::sha256d::HashEngine::Bytes = [u8; 32]
pub type bitcoin_hashes::sha256d::HashEngine::Hash = bitcoin_hashes::sha256d::Hash
pub type bitcoin_hashes::sha256t::Hash<T>::Bytes = [u8; 32]
-pub type bitcoin_hashes::sha256t::Hash<T>::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::sha256t::Hash<T>::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::sha256t::HashEngine<T>::Bytes = [u8; 32]
pub type bitcoin_hashes::sha256t::HashEngine<T>::Hash = bitcoin_hashes::sha256t::Hash<T>
pub type bitcoin_hashes::sha384::Hash::Bytes = [u8; 48]
-pub type bitcoin_hashes::sha384::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::sha384::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::sha384::HashEngine::Bytes = [u8; 48]
pub type bitcoin_hashes::sha384::HashEngine::Hash = bitcoin_hashes::sha384::Hash
pub type bitcoin_hashes::sha3_256::Hash::Bytes = [u8; 32]
-pub type bitcoin_hashes::sha3_256::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::sha3_256::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::sha3_256::HashEngine::Bytes = [u8; 32]
pub type bitcoin_hashes::sha3_256::HashEngine::Hash = bitcoin_hashes::sha3_256::Hash
pub type bitcoin_hashes::sha512::Hash::Bytes = [u8; 64]
-pub type bitcoin_hashes::sha512::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::sha512::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::sha512::HashEngine::Bytes = [u8; 64]
pub type bitcoin_hashes::sha512::HashEngine::Hash = bitcoin_hashes::sha512::Hash
pub type bitcoin_hashes::sha512_256::Hash::Bytes = [u8; 32]
-pub type bitcoin_hashes::sha512_256::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::sha512_256::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::sha512_256::HashEngine::Bytes = [u8; 64]
pub type bitcoin_hashes::sha512_256::HashEngine::Hash = bitcoin_hashes::sha512_256::Hash
pub type bitcoin_hashes::siphash24::Hash::Bytes = [u8; 8]
-pub type bitcoin_hashes::siphash24::Hash::Err = hex_conservative::error::HexToArrayError
+pub type bitcoin_hashes::siphash24::Hash::Err = hex_conservative::error::DecodeFixedLengthBytesError
pub type bitcoin_hashes::siphash24::HashEngine::Bytes = [u8; 8]
pub type bitcoin_hashes::siphash24::HashEngine::Hash = bitcoin_hashes::siphash24::Hash
diff --git a/base58/rbmt.toml b/base58/rbmt.toml
index a44a78fd..f674c3e5 100644
--- a/base58/rbmt.toml
+++ b/base58/rbmt.toml
@@ -12,3 +12,8 @@ features_with_std = []
# Features to test without the `std` feature.
# Tests each feature alone, all pairs, and all together.
features_without_std = ["alloc"]
+
+[lint]
+allowed_duplicates = [
+ "hex-conservative",
+]
diff --git a/hashes/Cargo.toml b/hashes/Cargo.toml
index acfe2e53..a58bfe16 100644
--- a/hashes/Cargo.toml
+++ b/hashes/Cargo.toml
@@ -15,9 +15,10 @@ exclude = ["tests", "contrib"]
[features]
default = ["std"]
-std = ["alloc", "hex?/std"]
-alloc = ["hex?/alloc"]
+std = ["alloc", "hex-stable/std", "hex-unstable/std"]
+alloc = ["hex-stable/alloc", "hex-unstable/alloc"]
serde = ["dep:serde", "hex"]
+hex = ["dep:hex-stable", "dep:hex-unstable"]
# Smaller (but slower) implementation of sha256, sha512 and ripemd160
small-hash = []
@@ -25,10 +26,13 @@ small-hash = []
internals = { package = "bitcoin-internals", path = "../internals", version = "0.5.0" }
encoding = { package = "bitcoin-consensus-encoding", path = "../consensus_encoding", version = "0.1.0", default-features = false }
-hex = { package = "hex-conservative", version = "0.3.0", default-features = false, optional = true }
serde = { version = "1.0.195", default-features = false, optional = true }
cpufeatures = { version = "0.2", optional = true }
+# You likely don't want to enable these directly, use hex instead.
+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 }
+
[dev-dependencies]
serde_test = "1.0.19"
diff --git a/hashes/rbmt.toml b/hashes/rbmt.toml
index 735cbbac..6d07c1a3 100644
--- a/hashes/rbmt.toml
+++ b/hashes/rbmt.toml
@@ -12,3 +12,8 @@ features_with_std = ["serde", "small-hash"]
# Features to test without the `std` feature.
# Tests each feature alone, all pairs, and all together.
features_without_std = ["alloc", "serde", "small-hash"]
+
+[lint]
+allowed_duplicates = [
+ "hex-conservative",
+]
diff --git a/hashes/src/hkdf/mod.rs b/hashes/src/hkdf/mod.rs
index d1326890..88ab0a87 100644
--- a/hashes/src/hkdf/mod.rs
+++ b/hashes/src/hkdf/mod.rs
@@ -145,16 +145,17 @@ impl<T: HashEngine> fmt::Debug for Hkdf<T> {
#[cfg(feature = "alloc")]
#[cfg(feature = "hex")]
mod tests {
- use hex::prelude::{DisplayHex, FromHex};
+ use hex_unstable::DisplayHex;
use super::*;
+ use crate::hex;
use crate::sha256;
#[test]
fn rfc5869_basic() {
- let salt = Vec::from_hex("000102030405060708090a0b0c").unwrap();
- let ikm = Vec::from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
- let info = Vec::from_hex("f0f1f2f3f4f5f6f7f8f9").unwrap();
+ let salt = hex::decode_to_vec("000102030405060708090a0b0c").unwrap();
+ let ikm = hex::decode_to_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
+ let info = hex::decode_to_vec("f0f1f2f3f4f5f6f7f8f9").unwrap();
let hkdf = Hkdf::<sha256::HashEngine>::new(&salt, &ikm);
let mut okm = [0u8; 42];
@@ -168,13 +169,13 @@ mod tests {
#[test]
fn rfc5869_longer_inputs_outputs() {
- let salt = Vec::from_hex(
+ let salt = hex::decode_to_vec(
"606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
).unwrap();
- let ikm = Vec::from_hex(
+ let ikm = hex::decode_to_vec(
"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f"
).unwrap();
- let info = Vec::from_hex(
+ let info = hex::decode_to_vec(
"b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"
).unwrap();
@@ -190,9 +191,9 @@ mod tests {
#[test]
fn too_long_okm() {
- let salt = Vec::from_hex("000102030405060708090a0b0c").unwrap();
- let ikm = Vec::from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
- let info = Vec::from_hex("f0f1f2f3f4f5f6f7f8f9").unwrap();
+ let salt = hex::decode_to_vec("000102030405060708090a0b0c").unwrap();
+ let ikm = hex::decode_to_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
+ let info = hex::decode_to_vec("f0f1f2f3f4f5f6f7f8f9").unwrap();
let hkdf = Hkdf::<sha256::HashEngine>::new(&salt, &ikm);
let mut okm = [0u8; 256 * 32];
@@ -203,9 +204,9 @@ mod tests {
#[test]
fn short_okm() {
- let salt = Vec::from_hex("000102030405060708090a0b0c").unwrap();
- let ikm = Vec::from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
- let info = Vec::from_hex("f0f1f2f3f4f5f6f7f8f9").unwrap();
+ let salt = hex::decode_to_vec("000102030405060708090a0b0c").unwrap();
+ let ikm = hex::decode_to_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
+ let info = hex::decode_to_vec("f0f1f2f3f4f5f6f7f8f9").unwrap();
let hkdf = Hkdf::<sha256::HashEngine>::new(&salt, &ikm);
let mut okm = [0u8; 1];
@@ -216,9 +217,9 @@ mod tests {
#[test]
fn alloc_wrapper() {
- let salt = Vec::from_hex("000102030405060708090a0b0c").unwrap();
- let ikm = Vec::from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
- let info = Vec::from_hex("f0f1f2f3f4f5f6f7f8f9").unwrap();
+ let salt = hex::decode_to_vec("000102030405060708090a0b0c").unwrap();
+ let ikm = hex::decode_to_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
+ let info = hex::decode_to_vec("f0f1f2f3f4f5f6f7f8f9").unwrap();
let hkdf = Hkdf::<sha256::HashEngine>::new(&salt, &ikm);
let okm = hkdf.expand_to_len(&info, 42).unwrap();
@@ -231,8 +232,8 @@ mod tests {
#[test]
fn debug() {
- let salt = Vec::from_hex("000102030405060708090a0b0c").unwrap();
- let ikm = Vec::from_hex("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
+ let salt = hex::decode_to_vec("000102030405060708090a0b0c").unwrap();
+ let ikm = hex::decode_to_vec("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap();
let hkdf = Hkdf::<sha256::HashEngine>::new(&salt, &ikm);
let debug = alloc::format!("{:?}", hkdf);
diff --git a/hashes/src/lib.rs b/hashes/src/lib.rs
index 5b28c138..c827e9e4 100644
--- a/hashes/src/lib.rs
+++ b/hashes/src/lib.rs
@@ -81,7 +81,10 @@ extern crate serde_test;
/// Re-export the `hex-conservative` crate.
#[cfg(feature = "hex")]
-pub extern crate hex;
+pub extern crate hex_stable as hex;
+#[cfg(feature = "hex")]
+pub extern crate hex_unstable;
+
#[doc(hidden)]
pub mod _export {
diff --git a/hashes/src/macros.rs b/hashes/src/macros.rs
index 739f9a5c..7a76cbe3 100644
--- a/hashes/src/macros.rs
+++ b/hashes/src/macros.rs
@@ -261,12 +261,10 @@ macro_rules! impl_bytelike_traits {
macro_rules! impl_hex_string_traits {
($ty:ident, $len:expr, $reverse:expr $(, $gen:ident: $gent:ident)*) => {
impl<$($gen: $gent),*> $crate::_export::_core::str::FromStr for $ty<$($gen),*> {
- type Err = $crate::hex::HexToArrayError;
+ type Err = $crate::hex::DecodeFixedLengthBytesError;
fn from_str(s: &str) -> $crate::_export::_core::result::Result<Self, Self::Err> {
- use $crate::hex::FromHex;
-
- let mut bytes = <[u8; { $len }]>::from_hex(s)?;
+ let mut bytes = $crate::hex::decode_to_array::<$len>(s)?;
if $reverse {
bytes.reverse();
}
@@ -281,21 +279,21 @@ macro_rules! impl_hex_string_traits {
fn fmt(&self, f: &mut $crate::_export::_core::fmt::Formatter) -> $crate::_export::_core::fmt::Result {
if $reverse {
let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter().rev();
- $crate::hex::fmt_hex_exact!(f, ($len), bytes, $case)
+ $crate::hex_unstable::fmt_hex_exact!(f, ($len), bytes, $case)
} else {
let bytes = $crate::_export::_core::borrow::Borrow::<[u8]>::borrow(self).iter();
- $crate::hex::fmt_hex_exact!(f, ($len), bytes, $case)
+ $crate::hex_unstable::fmt_hex_exact!(f, ($len), bytes, $case)
}
}
}
}
impl<$($gen: $gent),*> $crate::_export::_core::fmt::LowerHex for $ty<$($gen),*> {
- impl_case_hex!($crate::hex::Case::Lower);
+ impl_case_hex!($crate::hex_unstable::Case::Lower);
}
impl<$($gen: $gent),*> $crate::_export::_core::fmt::UpperHex for $ty<$($gen),*> {
- impl_case_hex!($crate::hex::Case::Upper);
+ impl_case_hex!($crate::hex_unstable::Case::Upper);
}
impl<$($gen: $gent),*> $crate::_export::_core::fmt::Display for $ty<$($gen),*> {
diff --git a/hashes/src/sha3_256/mod.rs b/hashes/src/sha3_256/mod.rs
index 77b9e8f8..7edbd0a5 100644
--- a/hashes/src/sha3_256/mod.rs
+++ b/hashes/src/sha3_256/mod.rs
@@ -227,4 +227,3 @@ impl crate::HashEngine for HashEngine {
Hash(out)
}
}
-
diff --git a/io/rbmt.toml b/io/rbmt.toml
index b47ff04c..9b5f1a5b 100644
--- a/io/rbmt.toml
+++ b/io/rbmt.toml
@@ -12,3 +12,8 @@ features_with_std = ["hashes"]
# Features to test without the `std` feature.
# Tests each feature alone, all pairs, and all together.
features_without_std = ["alloc", "hashes"]
+
+[lint]
+allowed_duplicates = [
+ "hex-conservative",
+]
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.