units: Add hex parsing for numeric wrapper types
What changed, and why it matters
This commit adds new public helper functions that let several numeric wrapper types in the rust-bitcoin 'units' crate be created from hexadecimal strings (with or without a '0x' prefix). It is a straightforward feature addition with no security-relevant behavior change; the underlying hex parsing already existed in the crate.
No security action required. Reviewers may optionally verify that the new constructors return the expected error types and that documentation comments correctly distinguish prefixed vs unprefixed behavior.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch introduces from_hex and from_unprefixed_hex constructors for BlockHeight, BlockInterval, BlockTime, Weight, relative::NumberOfBlocks, and relative::NumberOf512Seconds. Each new method delegates to existing parse_int::hex_u{16,32,64}_{prefixed,unprefixed} parsers and wraps the result in the corresponding newtype constructor. No existing logic is modified, no unsafe code is added, and no new dependencies are introduced. The commit message frames this as a missing formatting/parsing symmetry feature, not a security fix.
Changed components
units/src/block.rsunits/src/locktime/relative/mod.rsunits/src/time.rsunits/src/weight.rsInspect captured patch +132 / −3
diff --git a/units/src/block.rs b/units/src/block.rs
index fd85f8c9..1969d670 100644
--- a/units/src/block.rs
+++ b/units/src/block.rs
@@ -25,6 +25,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[cfg(doc)]
use crate::locktime;
use crate::locktime::{absolute, relative};
+use crate::parse_int::{self, PrefixedHexError, UnprefixedHexError};
macro_rules! impl_u32_wrapper {
{
@@ -34,6 +35,34 @@ macro_rules! impl_u32_wrapper {
$(#[$($type_attrs)*])*
$type_vis struct $newtype($inner_vis u32);
+ impl $newtype {
+ #[doc = "Constructs a new `"]
+ #[doc = stringify!($newtype)]
+ #[doc = "` from an unprefixed hex string.\n\n"]
+ #[doc = "# Errors\n\n"]
+ #[doc = "If the input string is not a valid hex representation of a `"]
+ #[doc = stringify!($newtype)]
+ #[doc = "` or it does not include the `0x` prefix."]
+ #[inline]
+ pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
+ let block_height = parse_int::hex_u32_prefixed(s)?;
+ Ok(Self(block_height))
+ }
+
+ #[doc = "Constructs a new `"]
+ #[doc = stringify!($newtype)]
+ #[doc = "` from a prefixed hex string.\n\n"]
+ #[doc = "# Errors\n\n"]
+ #[doc = "If the input string is not a valid hex representation of a `"]
+ #[doc = stringify!($newtype)]
+ #[doc = "` or if it includes the `0x` prefix."]
+ #[inline]
+ pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
+ let block_height = parse_int::hex_u32_unprefixed(s)?;
+ Ok(Self(block_height))
+ }
+ }
+
impl fmt::Display for $newtype {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) }
}
diff --git a/units/src/locktime/relative/mod.rs b/units/src/locktime/relative/mod.rs
index f1ffa79b..5dc27425 100644
--- a/units/src/locktime/relative/mod.rs
+++ b/units/src/locktime/relative/mod.rs
@@ -16,7 +16,8 @@ use internals::const_casts;
#[cfg(doc)]
use crate::relative;
-use crate::{parse_int, BlockHeight, BlockMtp, Sequence};
+use crate::parse_int::{self, PrefixedHexError, UnprefixedHexError};
+use crate::{BlockHeight, BlockMtp, Sequence};
#[rustfmt::skip] // Keep public re-exports separate.
#[doc(no_inline)]
@@ -430,6 +431,30 @@ impl NumberOfBlocks {
self.0 as u32 // cast safety: u32 is wider than u16 on all architectures
}
+ /// Constructs a new `NumberOfBlocks` from a prefixed hex string.
+ ///
+ /// # Errors
+ ///
+ /// If the input string is not a valid hex representation of a block count or it does not
+ /// include the `0x` prefix.
+ #[inline]
+ pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
+ let block_count = parse_int::hex_u16_prefixed(s)?;
+ Ok(Self::from_height(block_count))
+ }
+
+ /// Constructs a new `NumberOfBlocks` from an unprefixed hex string.
+ ///
+ /// # Errors
+ ///
+ /// If the input string is not a valid hex representation of a block count or if it includes
+ /// the `0x` prefix.
+ #[inline]
+ pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
+ let block_count = parse_int::hex_u16_unprefixed(s)?;
+ Ok(Self::from_height(block_count))
+ }
+
/// Returns true if an output locked by height can be spent in the next block.
///
/// # Errors
@@ -554,6 +579,30 @@ impl NumberOf512Seconds {
(1u32 << 22) | self.0 as u32 // cast safety: u32 is wider than u16 on all architectures
}
+ /// Constructs a new `NumberOf512Seconds` from a prefixed hex string.
+ ///
+ /// # Errors
+ ///
+ /// If the input string is not a valid hex representation of a number of 512 second intervals
+ /// or it does not include the `0x` prefix.
+ #[inline]
+ pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
+ let block_count = parse_int::hex_u16_prefixed(s)?;
+ Ok(Self::from_512_second_intervals(block_count))
+ }
+
+ /// Constructs a new `NumberOf512Seconds` from an unprefixed hex string.
+ ///
+ /// # Errors
+ ///
+ /// If the input string is not a valid hex representation of a number of 512 second intervals
+ /// or if it includes the `0x` prefix.
+ #[inline]
+ pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
+ let block_count = parse_int::hex_u16_unprefixed(s)?;
+ Ok(Self::from_512_second_intervals(block_count))
+ }
+
/// Returns true if an output locked by time can be spent in the next block.
///
/// # Errors
diff --git a/units/src/time.rs b/units/src/time.rs
index 74eac17a..9a16ed25 100644
--- a/units/src/time.rs
+++ b/units/src/time.rs
@@ -18,7 +18,7 @@ use internals::write_err;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use crate::parse_int;
+use crate::parse_int::{self, PrefixedHexError, UnprefixedHexError};
mod encapsulate {
/// A Bitcoin block timestamp.
@@ -49,6 +49,32 @@ mod encapsulate {
#[doc(inline)]
pub use encapsulate::BlockTime;
+impl BlockTime {
+ /// Constructs a new `BlockTime` from a prefixed hex string.
+ ///
+ /// # Errors
+ ///
+ /// If the input string is not a valid hex representation of a blocktime or it does not include
+ /// the `0x` prefix.
+ #[inline]
+ pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
+ let block_time = parse_int::hex_u32_prefixed(s)?;
+ Ok(Self::from_u32(block_time))
+ }
+
+ /// Constructs a new `BlockTime` from an unprefixed hex string.
+ ///
+ /// # Errors
+ ///
+ /// If the input string is not a valid hex representation of a blocktime or if it includes the
+ /// `0x` prefix.
+ #[inline]
+ pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
+ let block_time = parse_int::hex_u32_unprefixed(s)?;
+ Ok(Self::from_u32(block_time))
+ }
+}
+
crate::internal_macros::impl_fmt_traits_for_u32_wrapper!(BlockTime, to_u32);
impl fmt::Display for BlockTime {
diff --git a/units/src/weight.rs b/units/src/weight.rs
index 8fc6f4bc..e26ba601 100644
--- a/units/src/weight.rs
+++ b/units/src/weight.rs
@@ -10,7 +10,8 @@ use arbitrary::{Arbitrary, Unstructured};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
-use crate::{parse_int, Amount, FeeRate, NumOpResult};
+use crate::parse_int::{self, PrefixedHexError, UnprefixedHexError};
+use crate::{Amount, FeeRate, NumOpResult};
/// The factor that non-witness serialization data is multiplied by during weight calculation.
pub const WITNESS_SCALE_FACTOR: usize = 4;
@@ -109,6 +110,30 @@ impl Weight {
Self::from_wu(non_witness_size * Self::WITNESS_SCALE_FACTOR)
}
+ /// Constructs a new `Weight` from a prefixed hex string.
+ ///
+ /// # Errors
+ ///
+ /// If the input string is not a valid hex representation of a weight in weight units or it
+ /// does not include the `0x` prefix.
+ #[inline]
+ pub fn from_hex(s: &str) -> Result<Self, PrefixedHexError> {
+ let weight = parse_int::hex_u64_prefixed(s)?;
+ Ok(Self::from_wu(weight))
+ }
+
+ /// Constructs a new `Weight` from an unprefixed hex string.
+ ///
+ /// # Errors
+ ///
+ /// If the input string is not a valid hex representation of a weight in weight units or if
+ /// it includes the `0x` prefix.
+ #[inline]
+ pub fn from_unprefixed_hex(s: &str) -> Result<Self, UnprefixedHexError> {
+ let weight = parse_int::hex_u64_unprefixed(s)?;
+ Ok(Self::from_wu(weight))
+ }
+
/// Converts to kilo weight units rounding down.
pub const fn to_kwu_floor(self) -> u64 { self.to_wu() / 1000 }
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.