p2p: Introduce hex stable and remove unecessary uses of hex_unstable
What changed, and why it matters
This commit is a routine internal cleanup in the rust-bitcoin project's peer-to-peer (p2p) crate. It swaps some test and internal code from using an unstable version of a hex-encoding helper library to a newly released stable version. There is no user-facing behavior change, no bug fix, and no security fix visible in the diff.
No security action required. Treat as normal maintenance/dependency hygiene.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces hex-conservative 1.0.0 as a new dependency (aliased as hex-stable) in the p2p crate and replaces calls to the unstable hex crate’s FromHex trait with decode_to_vec and decode_to_array from the stable crate. A few formatting macros still use the unstable crate (now aliased as hex_unstable). The public re-export pub extern crate hex_stable as hex exposes the stable crate to downstream users. Error and feature names are updated accordingly. No logic changes are present.
Changed components
rust-bitcoin p2p crate dependency declarationsrust-bitcoin p2p crate internal hex encoding/decoding helpersrust-bitcoin p2p crate tests and documentation examplesInspect captured patch +27 / −22
diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock
index 9a08ea82..71bfda54 100644
--- a/Cargo-minimal.lock
+++ b/Cargo-minimal.lock
@@ -148,6 +148,7 @@ dependencies = [
"bitcoin-units",
"bitcoin_hashes",
"hex-conservative 0.3.0",
+ "hex-conservative 1.0.0",
"hex_lit",
]
diff --git a/Cargo-recent.lock b/Cargo-recent.lock
index cf257409..38abf0f6 100644
--- a/Cargo-recent.lock
+++ b/Cargo-recent.lock
@@ -147,6 +147,7 @@ dependencies = [
"bitcoin-units",
"bitcoin_hashes",
"hex-conservative 0.3.0",
+ "hex-conservative 1.0.0",
"hex_lit",
]
diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml
index 5d4bdeb7..fef3c969 100644
--- a/p2p/Cargo.toml
+++ b/p2p/Cargo.toml
@@ -14,7 +14,7 @@ exclude = ["tests", "contrib"]
[features]
default = ["std"]
-std = ["encoding/std", "hashes/std", "network/std", "hex/std", "internals/std", "io/std", "units/std", "bitcoin/std", "primitives/std"]
+std = ["encoding/std", "hashes/std", "network/std", "hex-stable/std", "hex-unstable/std", "internals/std", "io/std", "units/std", "bitcoin/std", "primitives/std"]
arbitrary = ["dep:arbitrary", "bitcoin/arbitrary"]
[dependencies]
@@ -23,7 +23,8 @@ encoding = { package = "bitcoin-consensus-encoding", version = "0.1.0", path = "
hashes = { package = "bitcoin_hashes", version = "0.20.0", path = "../hashes", default-features = false }
network = { package = "bitcoin-network-kind", path = "../network", version = "0.1.0", default-features = false }
primitives = { package = "bitcoin-primitives", path = "../primitives", version = "0.102.0", default-features = false }
-hex = { package = "hex-conservative", version = "0.3.0", default-features = false }
+hex-stable = { package = "hex-conservative", version = "1.0.0", default-features = false, features = ["alloc"] }
+hex-unstable = { package = "hex-conservative", version = "0.3.0", default-features = false }
internals = { package = "bitcoin-internals", path = "../internals", default-features = false }
io = { package = "bitcoin-io", version = "0.5.0", path = "../io", default-features = false }
units = { package = "bitcoin-units", path = "../units", version = "0.3.0", default-features = false }
diff --git a/p2p/src/address.rs b/p2p/src/address.rs
index 5047c886..50b36020 100644
--- a/p2p/src/address.rs
+++ b/p2p/src/address.rs
@@ -971,10 +971,10 @@ mod test {
use std::net::IpAddr;
use bitcoin::consensus::encode::{deserialize, serialize};
- use hex::FromHex;
use hex_lit::hex;
use super::*;
+ use crate::hex;
use crate::message::AddrV2Payload;
#[test]
@@ -1086,7 +1086,7 @@ mod test {
let tor_bytes =
hex!("042053cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88");
let ip = AddrV2::TorV3(
- FromHex::from_hex("53cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88")
+ hex::decode_to_array::<32>("53cd5648488c4707914182655b7664034e09e66f7e8cbf1084e654eb56c5bd88")
.unwrap(),
);
assert_eq!(serialize(&ip), tor_bytes);
@@ -1095,7 +1095,7 @@ mod test {
let i2p_bytes =
hex!("0520a2894dabaec08c0051a481a6dac88b64f98232ae42d4b6fd2fa81952dfe36a87");
let ip = AddrV2::I2p(
- FromHex::from_hex("a2894dabaec08c0051a481a6dac88b64f98232ae42d4b6fd2fa81952dfe36a87")
+ hex::decode_to_array::<32>("a2894dabaec08c0051a481a6dac88b64f98232ae42d4b6fd2fa81952dfe36a87")
.unwrap(),
);
assert_eq!(serialize(&ip), i2p_bytes);
diff --git a/p2p/src/bip152.rs b/p2p/src/bip152.rs
index e44db477..c9a85b4f 100644
--- a/p2p/src/bip152.rs
+++ b/p2p/src/bip152.rs
@@ -242,13 +242,13 @@ impl ShortId {
impl core::fmt::LowerHex for ShortId {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
- hex::display::fmt_hex_exact!(f, 6, &self.0, hex::Case::Lower)
+ hex_unstable::display::fmt_hex_exact!(f, 6, &self.0, hex_unstable::Case::Lower)
}
}
impl core::fmt::UpperHex for ShortId {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
- hex::display::fmt_hex_exact!(f, 6, &self.0, hex::Case::Upper)
+ hex_unstable::display::fmt_hex_exact!(f, 6, &self.0, hex_unstable::Case::Upper)
}
}
@@ -1000,7 +1000,6 @@ mod test {
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::merkle_tree::TxMerkleNode;
- use hex::FromHex;
use primitives::locktime::absolute;
use primitives::{
transaction, Amount, BlockChecked, BlockTime, CompactTarget, OutPoint, ScriptPubKeyBuf,
@@ -1008,6 +1007,7 @@ mod test {
};
use super::*;
+ use crate::hex;
fn dummy_tx(nonce: &[u8]) -> Transaction {
let dummy_txid = Txid::from_byte_array(hashes::sha256::Hash::hash(nonce).to_byte_array());
@@ -1060,8 +1060,8 @@ mod test {
#[test]
fn compact_block_vector() {
// Tested with Elements implementation of compact blocks.
- let raw_block = Vec::<u8>::from_hex("000000206c750a364035aefd5f81508a08769975116d9195312ee4520dceac39e1fdc62c4dc67473b8e354358c1e610afeaff7410858bd45df43e2940f8a62bd3d5e3ac943c2975cffff7f200000000002020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04016b0101ffffffff020006062a0100000001510000000000000000266a24aa21a9ed4a3d9f3343dafcc0d6f6d4310f2ee5ce273ed34edca6c75db3a73e7f368734200120000000000000000000000000000000000000000000000000000000000000000000000000020000000001021fc20ba2bd745507b8e00679e3b362558f9457db374ca28ffa5243f4c23a4d5f00000000171600147c9dea14ffbcaec4b575e03f05ceb7a81cd3fcbffdffffff915d689be87b43337f42e26033df59807b768223368f189a023d0242d837768900000000171600147c9dea14ffbcaec4b575e03f05ceb7a81cd3fcbffdffffff0200cdf5050000000017a9146803c72d9154a6a20f404bed6d3dcee07986235a8700e1f5050000000017a9144e6a4c7cb5b5562904843bdf816342f4db9f5797870247304402205e9bf6e70eb0e4b495bf483fd8e6e02da64900f290ef8aaa64bb32600d973c450220670896f5d0e5f33473e5f399ab680cc1d25c2d2afd15abd722f04978f28be887012103e4e4d9312b2261af508b367d8ba9be4f01b61d6d6e78bec499845b4f410bcf2702473044022045ac80596a6ac9c8c572f94708709adaf106677221122e08daf8b9741a04f66a022003ccd52a3b78f8fd08058fc04fc0cffa5f4c196c84eae9e37e2a85babe731b57012103e4e4d9312b2261af508b367d8ba9be4f01b61d6d6e78bec499845b4f410bcf276a000000").unwrap();
- let raw_compact = Vec::<u8>::from_hex("000000206c750a364035aefd5f81508a08769975116d9195312ee4520dceac39e1fdc62c4dc67473b8e354358c1e610afeaff7410858bd45df43e2940f8a62bd3d5e3ac943c2975cffff7f2000000000a4df3c3744da89fa010a6979e971450100020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04016b0101ffffffff020006062a0100000001510000000000000000266a24aa21a9ed4a3d9f3343dafcc0d6f6d4310f2ee5ce273ed34edca6c75db3a73e7f368734200120000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
+ let raw_block = hex::decode_to_vec("000000206c750a364035aefd5f81508a08769975116d9195312ee4520dceac39e1fdc62c4dc67473b8e354358c1e610afeaff7410858bd45df43e2940f8a62bd3d5e3ac943c2975cffff7f200000000002020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04016b0101ffffffff020006062a0100000001510000000000000000266a24aa21a9ed4a3d9f3343dafcc0d6f6d4310f2ee5ce273ed34edca6c75db3a73e7f368734200120000000000000000000000000000000000000000000000000000000000000000000000000020000000001021fc20ba2bd745507b8e00679e3b362558f9457db374ca28ffa5243f4c23a4d5f00000000171600147c9dea14ffbcaec4b575e03f05ceb7a81cd3fcbffdffffff915d689be87b43337f42e26033df59807b768223368f189a023d0242d837768900000000171600147c9dea14ffbcaec4b575e03f05ceb7a81cd3fcbffdffffff0200cdf5050000000017a9146803c72d9154a6a20f404bed6d3dcee07986235a8700e1f5050000000017a9144e6a4c7cb5b5562904843bdf816342f4db9f5797870247304402205e9bf6e70eb0e4b495bf483fd8e6e02da64900f290ef8aaa64bb32600d973c450220670896f5d0e5f33473e5f399ab680cc1d25c2d2afd15abd722f04978f28be887012103e4e4d9312b2261af508b367d8ba9be4f01b61d6d6e78bec499845b4f410bcf2702473044022045ac80596a6ac9c8c572f94708709adaf106677221122e08daf8b9741a04f66a022003ccd52a3b78f8fd08058fc04fc0cffa5f4c196c84eae9e37e2a85babe731b57012103e4e4d9312b2261af508b367d8ba9be4f01b61d6d6e78bec499845b4f410bcf276a000000").unwrap();
+ let raw_compact = hex::decode_to_vec("000000206c750a364035aefd5f81508a08769975116d9195312ee4520dceac39e1fdc62c4dc67473b8e354358c1e610afeaff7410858bd45df43e2940f8a62bd3d5e3ac943c2975cffff7f2000000000a4df3c3744da89fa010a6979e971450100020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04016b0101ffffffff020006062a0100000001510000000000000000266a24aa21a9ed4a3d9f3343dafcc0d6f6d4310f2ee5ce273ed34edca6c75db3a73e7f368734200120000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
let block: Block = deserialize(&raw_block).unwrap();
let block = block.assume_checked(None);
diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs
index 66de9a3a..12426883 100644
--- a/p2p/src/lib.rs
+++ b/p2p/src/lib.rs
@@ -28,6 +28,8 @@ extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
+pub extern crate hex_stable as hex;
+
use alloc::borrow::ToOwned;
use alloc::string::String;
use core::borrow::{Borrow, BorrowMut};
@@ -39,7 +41,6 @@ use core::{fmt, ops};
use arbitrary::{Arbitrary, Unstructured};
use bitcoin::consensus::encode::{self, Decodable, Encodable};
use encoding::{ArrayDecoder, ArrayEncoder};
-use hex::FromHex;
use internals::{impl_to_hex_from_lower_hex, write_err};
use io::{BufRead, Write};
use network::{Network, TestnetVersion};
@@ -437,7 +438,7 @@ impl FromStr for Magic {
type Err = ParseMagicError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
- match <[u8; 4]>::from_hex(s) {
+ match hex::decode_to_array::<4>(s) {
Ok(magic) => Ok(Self::from_bytes(magic)),
Err(e) => Err(ParseMagicError { error: e, magic: s.to_owned() }),
}
@@ -476,7 +477,7 @@ impl TryFrom<Magic> for Network {
impl fmt::Display for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?;
+ hex_unstable::fmt_hex_exact!(f, 4, &self.0, hex_unstable::Case::Lower)?;
Ok(())
}
}
@@ -487,7 +488,7 @@ impl fmt::Debug for Magic {
impl fmt::LowerHex for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?;
+ hex_unstable::fmt_hex_exact!(f, 4, &self.0, hex_unstable::Case::Lower)?;
Ok(())
}
}
@@ -495,7 +496,7 @@ impl_to_hex_from_lower_hex!(Magic, |_| 8);
impl fmt::UpperHex for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Upper)?;
+ hex_unstable::fmt_hex_exact!(f, 4, &self.0, hex_unstable::Case::Upper)?;
Ok(())
}
}
@@ -611,7 +612,7 @@ impl BorrowMut<[u8; 4]> for Magic {
#[non_exhaustive]
pub struct ParseMagicError {
/// The error that occurred when parsing the string.
- error: hex::HexToArrayError,
+ error: hex::DecodeFixedLengthBytesError,
/// The byte string that failed to parse.
magic: String,
}
diff --git a/p2p/src/merkle_tree.rs b/p2p/src/merkle_tree.rs
index 35efd03d..daeeb9e8 100644
--- a/p2p/src/merkle_tree.rs
+++ b/p2p/src/merkle_tree.rs
@@ -49,12 +49,12 @@ impl MerkleBlock {
/// # Examples
///
/// ```rust
- /// use hex::FromHex;
+ /// use bitcoin_p2p_messages::hex;
/// use bitcoin_p2p_messages::merkle_tree::MerkleBlock;
/// use primitives::{Block, Txid};
///
/// // Block 80000
- /// let block_bytes = Vec::from_hex("01000000ba8b9cda965dd8e536670f9ddec10e53aab14b20bacad2\
+ /// let block_bytes = hex::decode_to_vec("01000000ba8b9cda965dd8e536670f9ddec10e53aab14b20bacad2\
/// 7b9137190000000000190760b278fe7b8565fda3b968b918d5fd997f993b23674c0af3b6fde300b38f33\
/// a5914ce6ed5b1b01e32f5702010000000100000000000000000000000000000000000000000000000000\
/// 00000000000000ffffffff0704e6ed5b1b014effffffff0100f2052a01000000434104b68a50eaa0287e\
@@ -738,11 +738,12 @@ impl<'a> Arbitrary<'a> for MerkleBlock {
mod tests {
use core::cmp;
- use hex::{DisplayHex, FromHex};
+ use hex_unstable::DisplayHex;
use hex_lit::hex;
use primitives::block::Unchecked;
use super::*;
+ use crate::hex;
// `bloc` in hex.
const PRNG_SEED: usize = 0x626C_6F63;
@@ -895,7 +896,7 @@ mod tests {
// `gettxoutproof '["220ebc64e21abece964927322cba69180ed853bb187fbc6923bac7d010b9d87a"]'`
let mb_hex = include_str!("../tests/data/merkle_block.hex");
- let bytes = Vec::from_hex(mb_hex).unwrap();
+ let bytes = hex::decode_to_vec(mb_hex).unwrap();
let mb: MerkleBlock = encoding::decode_from_slice(&bytes).unwrap();
assert_eq!(get_block_13b8a().block_hash(), mb.header.block_hash());
assert_eq!(
@@ -986,7 +987,7 @@ mod tests {
fn get_block_13b8a() -> Block<Checked> {
let block_hex = include_str!("../tests/data/block_13b8a.hex");
let block: Block<Unchecked> =
- encoding::decode_from_slice(&Vec::from_hex(block_hex).unwrap()).unwrap();
+ encoding::decode_from_slice(&hex::decode_to_vec(block_hex).unwrap()).unwrap();
block.validate().expect("block should be valid")
}
@@ -1063,7 +1064,7 @@ mod tests {
// Get the proof from a bitcoind by running in the terminal:
// $ TXID="5a4ebf66822b0b2d56bd9dc64ece0bc38ee7844a23ff1d7320a88c5fdb2ad3e2"
// $ bitcoin-cli gettxoutproof [\"$TXID\"]
- let mb_bytes = Vec::from_hex("01000000ba8b9cda965dd8e536670f9ddec10e53aab14b20bacad27b913719\
+ let mb_bytes = hex::decode_to_vec("01000000ba8b9cda965dd8e536670f9ddec10e53aab14b20bacad27b913719\
0000000000190760b278fe7b8565fda3b968b918d5fd997f993b23674c0af3b6fde300b38f33a5914ce6ed5b\
1b01e32f570200000002252bf9d75c4f481ebb6278d708257d1f12beb6dd30301d26c623f789b2ba6fc0e2d3\
2adb5f8ca820731dff234a84e78ec30bce4ec69dbd562d0b2b8266bf4e5a0105").unwrap();
Why this scored 15/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.