Remove hex_conservative from U256 serde
What changed, and why it matters
This commit changes how a 256-bit unsigned integer (U256) is converted to and from human-readable formats like JSON when the optional 'serde' feature is enabled. Previously it used a helper crate called hex_conservative; now it uses parsing code already available in the project's own 'units' module. The goal is to reduce unnecessary dependencies, not to fix a security bug. There is no direct evidence in the commit or supplied references that this fixes a vulnerability.
No immediate security action required. Treat as a normal refactoring/dependency cleanup commit. If reviewing further, verify that parse_int::hex_u128_unprefixed correctly rejects non-hex characters and that the 64-character length check covers all intended inputs, including the visit_bytes fallback path.
Security signals we found
No security-relevant signals in commit title or message
No mention of vulnerability, CVE, bug bounty, or researcher attribution
Change is dependency reduction / compilation feature cleanup
Deserializer still validates length (64 hex chars) and delegates invalid values to serde errors
Evidence from the diff
The patch in bitcoin/src/pow.rs replaces the serde human-readable (de)serialization path for U256. Serialization now uses a DisplayHex wrapper and deserialization splits the 64-character hex string into two 32-character halves, parsing each as a big-endian u128 via units::parse_int::hex_u128_unprefixed, constructing U256(upper, lower). It removes the dependency on crate::hex::decode_to_array and the hex_conservative feature gating. The change is a dependency/refactoring commit; no bounds check, error handling, or parsing correctness flaw is visible in the diff.
Changed components
bitcoin/src/pow.rsU256 serde Serialize/Deserialize implementationshuman-readable serde path for U256Inspect captured patch +10 / −13
diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs
index 6903825e..6a53bd7b 100644
--- a/bitcoin/src/pow.rs
+++ b/bitcoin/src/pow.rs
@@ -1126,10 +1126,10 @@ impl_hex!(fmt::LowerHex, hex_unstable::Case::Lower);
impl_hex!(fmt::UpperHex, hex_unstable::Case::Upper);
#[cfg(feature = "serde")]
-impl crate::serde::Serialize for U256 {
+impl serde::Serialize for U256 {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
- S: crate::serde::Serializer,
+ S: serde::Serializer,
{
struct DisplayHex(U256);
@@ -1147,10 +1147,9 @@ 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 crate::hex;
- use crate::serde::de;
+impl<'de> serde::Deserialize<'de> for U256 {
+ fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
+ use serde::de;
if d.is_human_readable() {
struct HexVisitor;
@@ -1170,10 +1169,12 @@ impl<'de> crate::serde::Deserialize<'de> for U256 {
return Err(de::Error::invalid_length(s.len(), &self));
}
- let b = hex::decode_to_array::<32>(s)
+ let upper = parse_int::hex_u128_unprefixed(&s[..32])
+ .map_err(|_| de::Error::invalid_value(de::Unexpected::Str(s), &self))?;
+ let lower = parse_int::hex_u128_unprefixed(&s[32..])
.map_err(|_| de::Error::invalid_value(de::Unexpected::Str(s), &self))?;
- Ok(U256::from_be_bytes(b))
+ Ok(U256(upper, lower))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
@@ -1181,11 +1182,7 @@ impl<'de> crate::serde::Deserialize<'de> for U256 {
E: de::Error,
{
if let Ok(hex) = core::str::from_utf8(v) {
- let b = hex::decode_to_array::<32>(hex).map_err(|_| {
- de::Error::invalid_value(de::Unexpected::Str(hex), &self)
- })?;
-
- Ok(U256::from_be_bytes(b))
+ self.visit_str(hex)
} else {
Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self))
}
Why this scored 16/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.