Merge rust-bitcoin/rust-bitcoin#6851: units: Remove amount parsing limit
What changed, and why it matters
This commit removes a 50-character limit on strings that can be parsed as Bitcoin amounts. The limit was originally added as a basic defense against denial-of-service (DoS) attacks using very long inputs. The maintainers decided the limit was arbitrary and that DoS protection is not this library's responsibility. The change means slightly longer strings can now be parsed, but the actual numeric range checks remain in place, so extremely large values still produce an out-of-range error.
Review whether downstream callers rely on rust-bitcoin to enforce input length limits. If the library is used to parse untrusted input directly, consider adding an application-level length cap or timeout, since the library no longer provides one. No immediate patch is required unless a concrete DoS vector is demonstrated.
Security signals we found
Removal of an explicit input-length DoS guard
Maintainer statement that the removed limit was intended as DoS protection
No replacement length limit or mitigation introduced in the diff
Numeric out-of-range checks remain, limiting accepted monetary values
Evidence from the diff
The patch deletes INPUT_STRING_LEN_LIMIT (50) and the InputTooLargeError error variant from units/src/amount. parse_signed_to_satoshi no longer rejects strings longer than 50 characters. The remaining numeric overflow/out-of-range checks still cap the accepted value. Public re-exports and tests are updated accordingly. The PR description explicitly states the limit was a DoS protection and ‘this sort of attack vector is not really our concern.’
Changed components
units/src/amount/mod.rsunits/src/amount/error.rsbitcoin/src/lib.rsprimitives/tests/api.rsunits/src/amount/tests.rsunits/tests/api.rsInspect captured patch +9 / −78
### bitcoin/src/lib.rs
@@ -247,7 +247,7 @@ pub mod amount {
#[doc(no_inline)]
pub use self::error::{
- AmountDecoderError, BadPositionError, InputTooLargeError, InvalidCharacterError,
+ AmountDecoderError, BadPositionError, InvalidCharacterError,
MissingDenominationError, MissingDigitsError, OutOfRangeError, ParseAmountError,
ParseDenominationError, ParseError, PossiblyConfusingDenominationError, TooPreciseError,
UnknownDenominationError,
@@ -256,7 +256,7 @@ pub mod amount {
/// Error types for bitcoin amounts.
pub mod error {
pub use units::amount::error::{
- AmountDecoderError, BadPositionError, InputTooLargeError, InvalidCharacterError,
+ AmountDecoderError, BadPositionError, InvalidCharacterError,
MissingDenominationError, MissingDigitsError, OutOfRangeError, ParseAmountError,
ParseDenominationError, ParseError, PossiblyConfusingDenominationError,
TooPreciseError, UnknownDenominationError,
### primitives/tests/api.rs
@@ -609,7 +609,7 @@ fn p_consistent_exports_units_amount() {
#[test]
fn p_consistent_exports_units_amount_error() {
use bitcoin_primitives::amount::error::{
- InputTooLargeError, InvalidCharacterError, MissingDenominationError, MissingDigitsError,
+ InvalidCharacterError, MissingDenominationError, MissingDigitsError,
OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError,
PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError,
};
### units/src/amount/error.rs
@@ -8,7 +8,7 @@ use core::fmt;
use internals::error::InputString;
use internals::write_err;
-use super::{SignedAmount, INPUT_STRING_LEN_LIMIT};
+use super::SignedAmount;
use crate::parse_int::{PrefixedHexError, UnprefixedHexError};
/// Error returned when parsing an amount with denomination fails.
@@ -67,8 +67,6 @@ pub(crate) enum ParseAmountErrorInner {
TooPrecise(TooPreciseError),
/// A digit was expected but not found.
MissingDigits(MissingDigitsError),
- /// Input string was too large.
- InputTooLarge(InputTooLargeError),
/// Invalid character in input.
InvalidCharacter(InvalidCharacterError),
/// A valid character is in an invalid position.
@@ -93,7 +91,6 @@ impl fmt::Display for ParseAmountError {
E::OutOfRange(ref error) => write_err!(f, "amount out of range"; error),
E::TooPrecise(ref error) => write_err!(f, "amount has a too high precision"; error),
E::MissingDigits(ref error) => write_err!(f, "the input has too few digits"; error),
- E::InputTooLarge(ref error) => write_err!(f, "the input is too large"; error),
E::InvalidCharacter(ref error) => {
write_err!(f, "invalid character in the input"; error)
}
@@ -112,7 +109,6 @@ impl std::error::Error for ParseAmountError {
match self.0 {
E::TooPrecise(ref error) => Some(error),
- E::InputTooLarge(ref error) => Some(error),
E::OutOfRange(ref error) => Some(error),
E::MissingDigits(ref error) => Some(error),
E::InvalidCharacter(ref error) => Some(error),
@@ -236,44 +232,6 @@ impl std::error::Error for TooPreciseError {
}
}
-/// Error returned when the input string is too large.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct InputTooLargeError {
- pub(super) len: usize,
-}
-
-impl From<Infallible> for InputTooLargeError {
- #[inline]
- fn from(never: Infallible) -> Self { match never {} }
-}
-
-impl fmt::Display for InputTooLargeError {
- #[inline]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self.len - INPUT_STRING_LEN_LIMIT {
- 1 => write!(
- f,
- "the input is one character longer than the maximum allowed length ({})",
- INPUT_STRING_LEN_LIMIT
- ),
- n => write!(
- f,
- "the input is {} characters longer than the maximum allowed length ({})",
- n, INPUT_STRING_LEN_LIMIT
- ),
- }
- }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for InputTooLargeError {
- #[inline]
- fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- let Self { len: _ } = self;
- None
- }
-}
-
/// Error returned when digits were expected in the input but there were none.
///
/// In particular, this is currently returned when the string is empty or only contains the minus sign.
@@ -608,16 +566,6 @@ mod tests {
};
}
- // InputTooLargeError
- // one char too long
- let long_input = alloc::format!("{} BTC", "1".repeat(51));
- let e = Amount::from_str(&long_input).unwrap_err();
- assert_amount_err!(e, InputTooLarge, "error should be InputTooLargeError");
- // n chars too long
- let long_input = alloc::format!("{} BTC", "1".repeat(52));
- let e = Amount::from_str(&long_input).unwrap_err();
- assert_amount_err!(e, InputTooLarge, "error should be InputTooLargeError");
-
// InvalidCharacterError
// invalid character in amount string
let e = Amount::from_str("12x34 BTC").unwrap_err();
### units/src/amount/mod.rs
@@ -77,7 +77,7 @@ pub use self::{
pub use self::error::AmountDecoderError;
#[doc(no_inline)]
pub use self::error::{
- BadPositionError, InputTooLargeError, InvalidCharacterError, MissingDenominationError,
+ BadPositionError, InvalidCharacterError, MissingDenominationError,
MissingDigitsError, OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError,
PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError,
};
@@ -249,8 +249,6 @@ fn is_too_precise(s: &str, precision: usize) -> Option<usize> {
}
}
-const INPUT_STRING_LEN_LIMIT: usize = 50;
-
/// Parses a decimal string in the given denomination into a satoshi value and a
/// [`bool`] indicator for a negative amount.
///
@@ -264,9 +262,6 @@ fn parse_signed_to_satoshi(
return Err(MissingDigitsError { kind: MissingDigitsKind::Empty })
.map_err(InnerParseError::MissingDigits);
}
- if s.len() > INPUT_STRING_LEN_LIMIT {
- return Err(InnerParseError::InputTooLarge(s.len()));
- }
let is_negative = s.starts_with('-');
if is_negative {
@@ -388,7 +383,6 @@ enum InnerParseError {
Overflow { is_negative: bool },
TooPrecise(TooPreciseError),
MissingDigits(MissingDigitsError),
- InputTooLarge(usize),
InvalidCharacter(InvalidCharacterError),
BadPosition(BadPositionError),
}
@@ -409,8 +403,6 @@ impl InnerParseError {
})),
Self::TooPrecise(e) => ParseAmountError(ParseAmountErrorInner::TooPrecise(e)),
Self::MissingDigits(e) => ParseAmountError(ParseAmountErrorInner::MissingDigits(e)),
- Self::InputTooLarge(len) =>
- ParseAmountError(ParseAmountErrorInner::InputTooLarge(InputTooLargeError { len })),
Self::InvalidCharacter(e) =>
ParseAmountError(ParseAmountErrorInner::InvalidCharacter(e)),
Self::BadPosition(e) => ParseAmountError(ParseAmountErrorInner::BadPosition(e)),
### units/src/amount/tests.rs
@@ -413,8 +413,6 @@ fn floating_point() {
#[test]
#[allow(clippy::inconsistent_digit_grouping)] // Group to show 100,000,000 sats per bitcoin.
fn parsing() {
- use super::ParseAmountError as E;
-
let den_btc = Denomination::Bitcoin;
let den_sat = Denomination::Satoshi;
let p = Amount::from_str_in;
@@ -497,15 +495,10 @@ fn parsing() {
assert_eq!(p("2100000000000000.", den_sat), Ok(sat(21_000_000__000_000_00)));
assert_eq!(p("21000000", den_btc), Ok(sat(21_000_000__000_000_00)));
- // exactly 50 chars.
- assert_eq!(
- p("100000000000000.0000000000000000000000000000000000", Denomination::Bitcoin),
- Err(amt_err(ParseAmountErrorInner::OutOfRange(OutOfRangeError::too_big(false))))
- );
- // more than 50 chars.
+ // Contrived example to show that there is no limit on string length.
assert_eq!(
- p("100000000000000.00000000000000000000000000000000000", Denomination::Bitcoin),
- Err(E(ParseAmountErrorInner::InputTooLarge(InputTooLargeError { len: 51 })))
+ p("0000000000000000000000000000000000000000000000000000000000000000000001", Denomination::Bitcoin),
+ Ok(Amount::ONE_BTC),
);
}
### units/tests/api.rs
@@ -133,7 +133,6 @@ struct Default {
// These derives are the policy of `rust-bitcoin` not Rust API guidelines.
#[derive(Debug, Clone, PartialEq, Eq)] // All public types implement Debug (C-DEBUG).
struct Errors {
- a: amount::error::InputTooLargeError,
b: amount::error::InvalidCharacterError,
c: amount::error::MissingDenominationError,
d: amount::error::MissingDigitsError,
@@ -257,7 +256,6 @@ fn c_good_err_display() {
fn assert_display<T: fmt::Display>() {}
- assert_display::<amount::error::InputTooLargeError>();
assert_display::<amount::error::InvalidCharacterError>();
assert_display::<amount::error::MissingDenominationError>();
assert_display::<amount::error::MissingDigitsError>();
@@ -401,7 +399,7 @@ fn p_consistent_exports_amount() {
#[test]
fn p_consistent_exports_amount_error() {
use bitcoin_units::amount::error::{
- BadPositionError, InputTooLargeError, InvalidCharacterError, MissingDenominationError,
+ BadPositionError, InvalidCharacterError, MissingDenominationError,
MissingDigitsError, OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError,
PossiblyConfusingDenominationError, TooPreciseError, UnknownDenominationError,
};Why this scored 25/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.