bitcoin: Remove uses of HexToArrayError
What changed, and why it matters
This commit is a routine internal cleanup in the rust-bitcoin library. It swaps one hex-decoding helper for another equivalent one so the library no longer depends on an 'unstable' public API from a dependency. There is no indication this fixes a security bug or changes behavior visible to users in a dangerous way.
No security action required; review as normal dependency/API hygiene.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch removes all uses of hex_unstable::HexToArrayError and hex_unstable::FromHex, replacing them with crate-local hex::decode_to_array and hex::DecodeFixedLengthBytesError. Affected code paths are PublicKey/CompressedPublicKey parsing, internal array-newtype macros, and U256 serde deserialization. The change is API-stabilization work; it does not alter hex parsing semantics or add/remove validation.
Changed components
bitcoin/src/crypto/key.rsbitcoin/src/internal_macros.rsbitcoin/src/pow.rsInspect captured patch +23 / −26
diff --git a/bitcoin/src/crypto/key.rs b/bitcoin/src/crypto/key.rs
index 93ed7bb9..772bba3b 100644
--- a/bitcoin/src/crypto/key.rs
+++ b/bitcoin/src/crypto/key.rs
@@ -11,13 +11,13 @@ use core::ops;
use core::str::FromStr;
use hashes::hash160;
-use hex_unstable::{FromHex, HexToArrayError};
use internals::array::ArrayExt;
use internals::array_vec::ArrayVec;
use internals::{impl_to_hex_from_lower_hex, write_err};
use io::{Read, Write};
use crate::crypto::ecdsa;
+use crate::hex::{self, DecodeFixedLengthBytesError};
use crate::internal_macros::impl_asref_push_bytes;
use crate::network::NetworkKind;
use crate::prelude::{DisplayHex, String, Vec};
@@ -704,16 +704,16 @@ impl FromStr for PublicKey {
fn from_str(s: &str) -> Result<Self, ParsePublicKeyError> {
match s.len() {
66 => {
- let bytes = <[u8; 33]>::from_hex(s).map_err(|e| match e {
- HexToArrayError::InvalidChar(e) => ParsePublicKeyError::InvalidChar(e),
- HexToArrayError::InvalidLength(_) => unreachable!("length checked already"),
+ let bytes = hex::decode_to_array::<33>(s).map_err(|e| match e {
+ DecodeFixedLengthBytesError::InvalidChar(e) => ParsePublicKeyError::InvalidChar(e),
+ DecodeFixedLengthBytesError::InvalidLength(_) => unreachable!("length checked already"),
})?;
Ok(Self::from_slice(&bytes)?)
}
130 => {
- let bytes = <[u8; 65]>::from_hex(s).map_err(|e| match e {
- HexToArrayError::InvalidChar(e) => ParsePublicKeyError::InvalidChar(e),
- HexToArrayError::InvalidLength(_) => unreachable!("length checked already"),
+ let bytes = hex::decode_to_array::<65>(s).map_err(|e| match e {
+ DecodeFixedLengthBytesError::InvalidChar(e) => ParsePublicKeyError::InvalidChar(e),
+ DecodeFixedLengthBytesError::InvalidLength(_) => unreachable!("length checked already"),
})?;
Ok(Self::from_slice(&bytes)?)
}
@@ -849,7 +849,7 @@ impl FromStr for CompressedPublicKey {
type Err = ParseCompressedPublicKeyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- Self::from_slice(&<[u8; 33]>::from_hex(s)?).map_err(Into::into)
+ Self::from_slice(&hex::decode_to_array::<33>(s)?).map_err(Into::into)
}
}
@@ -1503,7 +1503,7 @@ pub enum ParsePublicKeyError {
/// Error originated while parsing string.
Encoding(FromSliceError),
/// Hex decoding error.
- InvalidChar(hex_unstable::InvalidCharError),
+ InvalidChar(hex::error::InvalidCharError),
/// `PublicKey` hex should be 66 or 130 digits long.
InvalidHexLength(usize),
}
@@ -1544,7 +1544,7 @@ pub enum ParseCompressedPublicKeyError {
/// secp256k1 Error.
Secp256k1(secp256k1::Error),
/// hex to array conversion error.
- Hex(hex_unstable::HexToArrayError),
+ Hex(hex::DecodeFixedLengthBytesError),
}
impl From<Infallible> for ParseCompressedPublicKeyError {
@@ -1574,8 +1574,8 @@ impl From<secp256k1::Error> for ParseCompressedPublicKeyError {
fn from(e: secp256k1::Error) -> Self { Self::Secp256k1(e) }
}
-impl From<hex_unstable::HexToArrayError> for ParseCompressedPublicKeyError {
- fn from(e: hex_unstable::HexToArrayError) -> Self { Self::Hex(e) }
+impl From<hex::DecodeFixedLengthBytesError> for ParseCompressedPublicKeyError {
+ fn from(e: hex::DecodeFixedLengthBytesError) -> Self { Self::Hex(e) }
}
/// SegWit public keys must always be compressed.
@@ -1910,13 +1910,13 @@ mod tests {
.unwrap();
let key2 = PublicKey::from_secp_uncompressed(key1.to_inner());
let arrayvec1 = ArrayVec::from_slice(
- &<[u8; 33]>::from_hex(
+ &hex::decode_to_array::<33>(
"02ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f8",
)
.unwrap(),
);
let expected1 = SortKey(arrayvec1);
- let arrayvec2 = ArrayVec::from_slice(&<[u8; 65]>::from_hex(
+ let arrayvec2 = ArrayVec::from_slice(&hex::decode_to_array::<65>(
"04ff12471208c14bd580709cb2358d98975247d8765f92bc25eab3b2763ed605f81794e7f3d5e420641a3bc690067df5541470c966cbca8c694bf39aa16d836918",
).unwrap());
let expected2 = SortKey(arrayvec2);
@@ -2102,7 +2102,6 @@ mod tests {
let res = s.parse::<PublicKey>();
assert!(res.is_err());
if let Err(ParsePublicKeyError::InvalidChar(err)) = res {
- assert_eq!(err.invalid_char(), b'g');
assert_eq!(err.pos(), 129);
} else {
panic!("expected ParsePublicKeyError::InvalidChar");
@@ -2113,7 +2112,6 @@ mod tests {
let res = s.parse::<PublicKey>();
assert!(res.is_err());
if let Err(ParsePublicKeyError::InvalidChar(err)) = res {
- assert_eq!(err.invalid_char(), b'g');
assert_eq!(err.pos(), 65);
} else {
panic!("expected ParsePublicKeyError::InvalidChar");
@@ -2131,7 +2129,7 @@ mod tests {
#[test]
fn xonly_pubkey_from_bytes() {
- let key_bytes = &<[u8; 32]>::from_hex(
+ let key_bytes = &hex::decode_to_array::<32>(
"5b1e57ec453cd33fdc7cfc901450a3931fd315422558f2fb7fefb064e6e7d60d",
)
.expect("Failed to convert hex string to byte array");
@@ -2143,7 +2141,7 @@ mod tests {
#[test]
fn xonly_pubkey_to_inner() {
- let key_bytes = &<[u8; 32]>::from_hex(
+ let key_bytes = &hex::decode_to_array::<32>(
"5b1e57ec453cd33fdc7cfc901450a3931fd315422558f2fb7fefb064e6e7d60d",
)
.expect("Failed to convert hex string to byte array");
@@ -2160,7 +2158,7 @@ mod tests {
let keypair = Keypair::generate(&mut rand::rng());
#[cfg(not(all(feature = "rand", feature = "std")))]
let keypair = {
- let bytes = <[u8; 32]>::from_hex(
+ let bytes = hex::decode_to_array::<32>(
"1ede31b0e7e47c2afc65ffd158b1b1b9d3b752bba8fd117dc8b9e944a390e8d9",
)
.unwrap();
diff --git a/bitcoin/src/internal_macros.rs b/bitcoin/src/internal_macros.rs
index 4f940be3..211d2ec8 100644
--- a/bitcoin/src/internal_macros.rs
+++ b/bitcoin/src/internal_macros.rs
@@ -55,8 +55,8 @@ macro_rules! impl_array_newtype_stringify {
($t:ident, $len:literal) => {
impl $t {
/// Constructs a new `Self` from a hex string.
- pub fn from_hex(s: &str) -> Result<Self, hex_unstable::HexToArrayError> {
- Ok($t(hex_unstable::FromHex::from_hex(s)?))
+ pub fn from_hex(s: &str) -> Result<Self, $crate::hex::DecodeFixedLengthBytesError> {
+ Ok($t($crate::hex::decode_to_array(s)?))
}
}
@@ -87,7 +87,7 @@ macro_rules! impl_array_newtype_stringify {
}
impl core::str::FromStr for $t {
- type Err = hex_unstable::HexToArrayError;
+ type Err = $crate::hex::DecodeFixedLengthBytesError;
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> { Self::from_hex(s) }
}
diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs
index 23ec04a7..fcb73465 100644
--- a/bitcoin/src/pow.rs
+++ b/bitcoin/src/pow.rs
@@ -1147,8 +1147,7 @@ impl crate::serde::Serialize for U256 {
#[cfg(feature = "serde")]
impl<'de> crate::serde::Deserialize<'de> for U256 {
fn deserialize<D: crate::serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
- use hex_unstable::FromHex;
-
+ use crate::hex;
use crate::serde::de;
if d.is_human_readable() {
@@ -1169,7 +1168,7 @@ impl<'de> crate::serde::Deserialize<'de> for U256 {
return Err(de::Error::invalid_length(s.len(), &self));
}
- let b = <[u8; 32]>::from_hex(s)
+ let b = hex::decode_to_array::<32>(s)
.map_err(|_| de::Error::invalid_value(de::Unexpected::Str(s), &self))?;
Ok(U256::from_be_bytes(b))
@@ -1180,7 +1179,7 @@ impl<'de> crate::serde::Deserialize<'de> for U256 {
E: de::Error,
{
if let Ok(hex) = core::str::from_utf8(v) {
- let b = <[u8; 32]>::from_hex(hex).map_err(|_| {
+ let b = hex::decode_to_array::<32>(hex).map_err(|_| {
de::Error::invalid_value(de::Unexpected::Str(hex), &self)
})?;
Why this scored 17/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.