What changed, and why it matters
This commit adds a new feature: the ability to parse a 256-bit unsigned integer (U256) from a decimal string, similar to how you might type a large number into a program. It is a straightforward feature addition with no obvious security bug. The code carefully checks for empty input, invalid digits, non-ASCII characters, and overflow beyond the maximum U256 value.
No security action required. This is a benign feature addition. Routine code review and testing of the new FromStr behavior (including edge cases like empty strings, leading zeros, maximum values, and invalid inputs) is sufficient.
Security signals we found
New parsing code added with explicit overflow checks
Input validation for empty string, invalid digits, and non-ASCII encoding
No unsafe blocks, no unwrap/expect, no panic paths introduced in the diff
Evidence from the diff
The patch implements core::str::FromStr for the U256 type in bitcoin/src/pow.rs. It splits the input string into 38-digit chunks (the largest chunk fitting in a u128), parses each chunk, and combines them using checked multiplication/addition via overflowing_mul/overflowing_add. It introduces a ParseU256Error enum to report empty input, invalid encoding, invalid digits, and overflow. The implementation appears to handle edge cases (empty string, overflow, invalid UTF-8 chunks) correctly and does not introduce memory safety issues or panic paths visible in the diff.
Changed components
bitcoin/src/pow.rsU256 typeParseU256Error typeInspect captured patch +77 / −1
diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs
index 2fa66e4f..8682d600 100644
--- a/bitcoin/src/pow.rs
+++ b/bitcoin/src/pow.rs
@@ -8,7 +8,7 @@
use core::ops::{Add, Div, Mul, Not, Rem, Shl, Shr, Sub};
use core::{cmp, fmt};
-use internals::impl_to_hex_from_lower_hex;
+use internals::{impl_to_hex_from_lower_hex, write_err};
use io::{BufRead, Write};
use units::parse_int::{self, ParseIntError, PrefixedHexError, UnprefixedHexError};
@@ -953,6 +953,41 @@ impl fmt::Debug for U256 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:#x}", self) }
}
+// 10^38 is the largest power of 10 that fits in a u128
+const POW10_38: u128 = 10_u128.pow(38);
+impl core::str::FromStr for U256 {
+ type Err = ParseU256Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let mut result = Self::ZERO;
+
+ if s.is_empty() {
+ return Err(ParseU256Error::Empty);
+ }
+
+ for chunk in s.as_bytes().rchunks(38).rev() {
+ let chunk_str = core::str::from_utf8(chunk)
+ .map_err(ParseU256Error::InvalidEncoding)?;
+
+ let val: u128 = chunk_str
+ .parse()
+ .map_err(ParseU256Error::InvalidDigit)?;
+
+ // Shift decimals and add chunk
+ let (res, carry1) = result.overflowing_mul(POW10_38.into());
+ let (res, carry2) = res.overflowing_add(val.into());
+
+ if carry1 | carry2 {
+ return Err(ParseU256Error::Overflow);
+ }
+
+ result = res;
+ }
+
+ Ok(result)
+ }
+}
+
macro_rules! impl_hex {
($hex:path, $case:expr) => {
impl $hex for U256 {
@@ -1068,6 +1103,47 @@ fn split_in_half(a: [u8; 32]) -> ([u8; 16], [u8; 16]) {
(high, low)
}
+/// Error returned when parsing a [`U256`] from a string.
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[non_exhaustive]
+enum ParseU256Error {
+ /// Numeric value exceeded [`U256::MAX`].
+ Overflow,
+ /// Parsed string was empty.
+ Empty,
+ /// Failed parsing a target from an integer string.
+ InvalidDigit(core::num::ParseIntError),
+ /// Failed parsing due to non-ASCII encoding on the string.
+ InvalidEncoding(core::str::Utf8Error),
+}
+
+impl From<core::convert::Infallible> for ParseU256Error {
+ fn from(never: core::convert::Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for ParseU256Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::Overflow => write!(f, "parsed value exceeded unsigned 256-bit range"),
+ Self::Empty => write!(f, "parsed string is empty"),
+ Self::InvalidEncoding(ref e) => write_err!(f, "parsed number contained non-ascii chars"; e),
+ Self::InvalidDigit(ref e) => write_err!(f, "parsed number contained invalid digit"; e),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for ParseU256Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::Overflow => None,
+ Self::Empty => None,
+ Self::InvalidEncoding(ref e) => Some(e),
+ Self::InvalidDigit(ref e) => Some(e),
+ }
+ }
+}
+
#[cfg(kani)]
impl kani::Arbitrary for U256 {
fn any() -> Self {
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.