units: Add U256 hex parsing as pub(crate) in parse_int
What changed, and why it matters
This commit is a code cleanup inside the rust-bitcoin library. It moves the logic for parsing 256-bit unsigned integers from hexadecimal strings into a shared internal module, without changing what the library exposes to users. The change is purely internal refactoring.
No security action required; treat as routine refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors U256 hex parsing by relocating it from units/src/pow.rs into units/src/parse_int.rs as pub(crate) helpers (hex_u256_prefixed, hex_u256_unprefixed, hex_u256_unchecked). It makes the U256 struct and constructors pub(crate) in include/u256.rs and delegates pow.rs’s from_hex/from_unprefixed_hex to the new parse_int functions. The public API is unchanged; no new behavior or security fix is introduced.
Changed components
units/src/parse_int.rsunits/src/pow.rsinclude/u256.rsInspect captured patch +41 / −29
diff --git a/include/u256.rs b/include/u256.rs
index 0f8346cb..40954fc3 100644
--- a/include/u256.rs
+++ b/include/u256.rs
@@ -10,7 +10,7 @@
/// Big-endian 256 bit integer type.
// (high, low): u.0 contains the high bits, u.1 contains the low bits.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
-struct U256(u128, u128);
+pub(crate) struct U256(u128, u128);
#[allow(dead_code)]
impl U256 {
@@ -22,7 +22,7 @@ impl U256 {
const ONE: Self = Self(0, 1);
/// Constructs a new `U256` from a big-endian array of `u8`s.
- fn from_be_bytes(a: [u8; 32]) -> Self {
+ pub(crate) fn from_be_bytes(a: [u8; 32]) -> Self {
let (high, low) = split_in_half(a);
let big = u128::from_be_bytes(high);
let little = u128::from_be_bytes(low);
@@ -30,7 +30,7 @@ impl U256 {
}
/// Constructs a new `U256` from a little-endian array of `u8`s.
- fn from_le_bytes(a: [u8; 32]) -> Self {
+ pub(crate) fn from_le_bytes(a: [u8; 32]) -> Self {
let (high, low) = split_in_half(a);
let little = u128::from_le_bytes(high);
let big = u128::from_le_bytes(low);
@@ -599,7 +599,7 @@ impl<'de> serde::Deserialize<'de> for U256 {
/// Error returned when parsing a [`U256`] from a string.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
-enum ParseU256Error {
+pub(crate) enum ParseU256Error {
/// Numeric value exceeded [`U256::MAX`].
Overflow,
/// Parsed string was empty.
diff --git a/units/src/parse_int.rs b/units/src/parse_int.rs
index fac1cda9..a5167317 100644
--- a/units/src/parse_int.rs
+++ b/units/src/parse_int.rs
@@ -342,6 +342,40 @@ parse_hex_for!(
fn hex_u128_unchecked();
);
+pub(crate) fn hex_u256_prefixed(s: &str) -> Result<crate::pow::U256, PrefixedHexError> {
+ let checked = hex_remove_prefix(s)?;
+ hex_u256_unchecked(checked)
+ .map_err(error::PrefixedHexErrorInner::ParseInt)
+ .map_err(PrefixedHexError)
+}
+
+pub(crate) fn hex_u256_unprefixed(s: &str) -> Result<crate::pow::U256, UnprefixedHexError> {
+ let checked = hex_check_unprefixed(s)?;
+ hex_u256_unchecked(checked)
+ .map_err(error::UnprefixedHexErrorInner::ParseInt)
+ .map_err(UnprefixedHexError)
+}
+
+pub(crate) fn hex_u256_unchecked(s: &str) -> Result<crate::pow::U256, ParseIntError> {
+ let (high, low) = if s.len() <= 32 {
+ let low = hex_u128_unchecked(s)?;
+ (0, low)
+ } else {
+ let high_len = s.len() - 32;
+ let high_s = &s[..high_len];
+ let low_s = &s[high_len..];
+
+ let high = hex_u128_unchecked(high_s)?;
+ let low = hex_u128_unchecked(low_s)?;
+ (high, low)
+ };
+
+ let mut bytes = [0u8; 32];
+ bytes[..16].copy_from_slice(&low.to_le_bytes());
+ bytes[16..].copy_from_slice(&high.to_le_bytes());
+ Ok(crate::pow::U256::from_le_bytes(bytes))
+}
+
/// Strips the hex prefix off `s` if one is present.
#[inline]
pub(crate) fn hex_remove_optional_prefix(s: &str) -> &str {
diff --git a/units/src/pow.rs b/units/src/pow.rs
index 1c7ccee6..d106af96 100644
--- a/units/src/pow.rs
+++ b/units/src/pow.rs
@@ -10,7 +10,7 @@ use arbitrary::{Arbitrary, Unstructured};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
-use crate::parse_int::{self, ParseIntError, PrefixedHexError, UnprefixedHexError};
+use crate::parse_int::{self, PrefixedHexError, UnprefixedHexError};
/// Implement traits and methods shared by `Target` and `Work`.
macro_rules! do_impl {
@@ -416,33 +416,11 @@ include!("../../include/u256.rs");
impl U256 {
/// Constructs a new `U256` from a prefixed hex string.
- fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
- let checked = parse_int::hex_remove_prefix(s)?;
- Ok(Self::from_hex_internal(checked)?)
- }
+ fn from_hex(s: &str) -> Result<Self, PrefixedHexError> { parse_int::hex_u256_prefixed(s) }
/// Constructs a new `U256` from an unprefixed hex string.
fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
- let checked = parse_int::hex_check_unprefixed(s)?;
- Ok(Self::from_hex_internal(checked)?)
- }
-
- // Caller to ensure `s` does not contain a prefix.
- fn from_hex_internal(s: &str) -> Result<Self, ParseIntError> {
- let (high, low) = if s.len() <= 32 {
- let low = parse_int::hex_u128_unchecked(s)?;
- (0, low)
- } else {
- let high_len = s.len() - 32;
- let high_s = &s[..high_len];
- let low_s = &s[high_len..];
-
- let high = parse_int::hex_u128_unchecked(high_s)?;
- let low = parse_int::hex_u128_unchecked(low_s)?;
- (high, low)
- };
-
- Ok(Self(high, low))
+ parse_int::hex_u256_unprefixed(s)
}
}
Why this scored 12/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.