Allow underscores as separators in Amount values
What changed, and why it matters
This commit is a routine feature addition, not a security fix. It lets users type Bitcoin amounts with underscore separators (like 1_000 sat) when parsing strings, and rejects invalid placements such as a leading underscore or two underscores in a row. There is no indication of a vulnerability being patched.
No security action needed; treat as normal feature review.
Security signals we found
No security-relevant signals in commit message or diff
No bounds-checking or memory-safety changes
No cryptographic or consensus code touched
Evidence from the diff
The change extends the Amount/SignedAmount FromStr parser in rust-bitcoin to accept ‘_’ as a digit-group separator. It adds a new BadPositionError variant, updates error plumbing, and adds unit tests for valid and invalid underscore placement. The parser already rejected invalid characters and out-of-range values; this only widens accepted input in a controlled way.
Changed components
units/src/amount/mod.rsunits/src/amount/error.rsunits/src/amount/tests.rsInspect captured patch +128 / −3
diff --git a/api/units/all-features.txt b/api/units/all-features.txt
index e78fb377..e6d5ba5b 100644
--- a/api/units/all-features.txt
+++ b/api/units/all-features.txt
@@ -79,6 +79,7 @@ impl core::clone::Clone for bitcoin_units::Weight
impl core::clone::Clone for bitcoin_units::amount::Denomination
impl core::clone::Clone for bitcoin_units::amount::Display
impl core::clone::Clone for bitcoin_units::amount::error::AmountDecoderError
+impl core::clone::Clone for bitcoin_units::amount::error::BadPositionError
impl core::clone::Clone for bitcoin_units::amount::error::InputTooLargeError
impl core::clone::Clone for bitcoin_units::amount::error::InvalidCharacterError
impl core::clone::Clone for bitcoin_units::amount::error::MissingDenominationError
@@ -131,6 +132,7 @@ impl core::cmp::Eq for bitcoin_units::SignedAmount
impl core::cmp::Eq for bitcoin_units::Weight
impl core::cmp::Eq for bitcoin_units::amount::Denomination
impl core::cmp::Eq for bitcoin_units::amount::error::AmountDecoderError
+impl core::cmp::Eq for bitcoin_units::amount::error::BadPositionError
impl core::cmp::Eq for bitcoin_units::amount::error::InputTooLargeError
impl core::cmp::Eq for bitcoin_units::amount::error::InvalidCharacterError
impl core::cmp::Eq for bitcoin_units::amount::error::MissingDenominationError
@@ -197,6 +199,7 @@ impl core::cmp::PartialEq for bitcoin_units::SignedAmount
impl core::cmp::PartialEq for bitcoin_units::Weight
impl core::cmp::PartialEq for bitcoin_units::amount::Denomination
impl core::cmp::PartialEq for bitcoin_units::amount::error::AmountDecoderError
+impl core::cmp::PartialEq for bitcoin_units::amount::error::BadPositionError
impl core::cmp::PartialEq for bitcoin_units::amount::error::InputTooLargeError
impl core::cmp::PartialEq for bitcoin_units::amount::error::InvalidCharacterError
impl core::cmp::PartialEq for bitcoin_units::amount::error::MissingDenominationError
@@ -264,6 +267,8 @@ impl core::convert::From<bitcoin_units::Amount> for bitcoin_units::result::NumOp
impl core::convert::From<bitcoin_units::BlockTime> for u32
impl core::convert::From<bitcoin_units::SignedAmount> for bitcoin_units::result::NumOpResult<bitcoin_units::SignedAmount>
impl core::convert::From<bitcoin_units::Weight> for u64
+impl core::convert::From<bitcoin_units::amount::error::BadPositionError> for bitcoin_units::amount::error::ParseAmountError
+impl core::convert::From<bitcoin_units::amount::error::BadPositionError> for bitcoin_units::amount::error::ParseError
impl core::convert::From<bitcoin_units::amount::error::InputTooLargeError> for bitcoin_units::amount::error::ParseAmountError
impl core::convert::From<bitcoin_units::amount::error::InputTooLargeError> for bitcoin_units::amount::error::ParseError
impl core::convert::From<bitcoin_units::amount::error::InvalidCharacterError> for bitcoin_units::amount::error::ParseAmountError
@@ -361,6 +366,7 @@ impl core::default::Default for bitcoin_units::sequence::Sequence
impl core::default::Default for bitcoin_units::sequence::SequenceDecoder
impl core::default::Default for bitcoin_units::time::BlockTimeDecoder
impl core::error::Error for bitcoin_units::amount::error::AmountDecoderError
+impl core::error::Error for bitcoin_units::amount::error::BadPositionError
impl core::error::Error for bitcoin_units::amount::error::InputTooLargeError
impl core::error::Error for bitcoin_units::amount::error::InvalidCharacterError
impl core::error::Error for bitcoin_units::amount::error::MissingDigitsError
@@ -401,6 +407,7 @@ impl core::fmt::Debug for bitcoin_units::Weight
impl core::fmt::Debug for bitcoin_units::amount::Denomination
impl core::fmt::Debug for bitcoin_units::amount::Display
impl core::fmt::Debug for bitcoin_units::amount::error::AmountDecoderError
+impl core::fmt::Debug for bitcoin_units::amount::error::BadPositionError
impl core::fmt::Debug for bitcoin_units::amount::error::InputTooLargeError
impl core::fmt::Debug for bitcoin_units::amount::error::InvalidCharacterError
impl core::fmt::Debug for bitcoin_units::amount::error::MissingDenominationError
@@ -452,6 +459,7 @@ impl core::fmt::Display for bitcoin_units::Weight
impl core::fmt::Display for bitcoin_units::amount::Denomination
impl core::fmt::Display for bitcoin_units::amount::Display
impl core::fmt::Display for bitcoin_units::amount::error::AmountDecoderError
+impl core::fmt::Display for bitcoin_units::amount::error::BadPositionError
impl core::fmt::Display for bitcoin_units::amount::error::InputTooLargeError
impl core::fmt::Display for bitcoin_units::amount::error::InvalidCharacterError
impl core::fmt::Display for bitcoin_units::amount::error::MissingDigitsError
@@ -551,6 +559,7 @@ impl core::marker::Freeze for bitcoin_units::amount::AmountEncoder
impl core::marker::Freeze for bitcoin_units::amount::Denomination
impl core::marker::Freeze for bitcoin_units::amount::Display
impl core::marker::Freeze for bitcoin_units::amount::error::AmountDecoderError
+impl core::marker::Freeze for bitcoin_units::amount::error::BadPositionError
impl core::marker::Freeze for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Freeze for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Freeze for bitcoin_units::amount::error::MissingDenominationError
@@ -614,6 +623,7 @@ impl core::marker::Send for bitcoin_units::amount::AmountEncoder
impl core::marker::Send for bitcoin_units::amount::Denomination
impl core::marker::Send for bitcoin_units::amount::Display
impl core::marker::Send for bitcoin_units::amount::error::AmountDecoderError
+impl core::marker::Send for bitcoin_units::amount::error::BadPositionError
impl core::marker::Send for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Send for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Send for bitcoin_units::amount::error::MissingDenominationError
@@ -674,6 +684,7 @@ impl core::marker::StructuralPartialEq for bitcoin_units::SignedAmount
impl core::marker::StructuralPartialEq for bitcoin_units::Weight
impl core::marker::StructuralPartialEq for bitcoin_units::amount::Denomination
impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::AmountDecoderError
+impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::BadPositionError
impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::MissingDenominationError
@@ -729,6 +740,7 @@ impl core::marker::Sync for bitcoin_units::amount::AmountEncoder
impl core::marker::Sync for bitcoin_units::amount::Denomination
impl core::marker::Sync for bitcoin_units::amount::Display
impl core::marker::Sync for bitcoin_units::amount::error::AmountDecoderError
+impl core::marker::Sync for bitcoin_units::amount::error::BadPositionError
impl core::marker::Sync for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Sync for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Sync for bitcoin_units::amount::error::MissingDenominationError
@@ -792,6 +804,7 @@ impl core::marker::Unpin for bitcoin_units::amount::AmountEncoder
impl core::marker::Unpin for bitcoin_units::amount::Denomination
impl core::marker::Unpin for bitcoin_units::amount::Display
impl core::marker::Unpin for bitcoin_units::amount::error::AmountDecoderError
+impl core::marker::Unpin for bitcoin_units::amount::error::BadPositionError
impl core::marker::Unpin for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Unpin for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Unpin for bitcoin_units::amount::error::MissingDenominationError
@@ -1081,6 +1094,7 @@ impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::AmountEn
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::Denomination
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::Display
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::AmountDecoderError
+impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::BadPositionError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::InputTooLargeError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::InvalidCharacterError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::MissingDenominationError
@@ -1144,6 +1158,7 @@ impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::AmountEncod
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::Denomination
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::Display
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::AmountDecoderError
+impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::BadPositionError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::InputTooLargeError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::InvalidCharacterError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::MissingDenominationError
@@ -1919,6 +1934,9 @@ pub fn bitcoin_units::amount::error::AmountDecoderError::eq(&self, other: &bitco
pub fn bitcoin_units::amount::error::AmountDecoderError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_units::amount::error::AmountDecoderError::from(never: core::convert::Infallible) -> Self
pub fn bitcoin_units::amount::error::AmountDecoderError::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
+pub fn bitcoin_units::amount::error::BadPositionError::clone(&self) -> bitcoin_units::amount::error::BadPositionError
+pub fn bitcoin_units::amount::error::BadPositionError::eq(&self, other: &bitcoin_units::amount::error::BadPositionError) -> bool
+pub fn bitcoin_units::amount::error::BadPositionError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_units::amount::error::InputTooLargeError::clone(&self) -> bitcoin_units::amount::error::InputTooLargeError
pub fn bitcoin_units::amount::error::InputTooLargeError::eq(&self, other: &bitcoin_units::amount::error::InputTooLargeError) -> bool
pub fn bitcoin_units::amount::error::InputTooLargeError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
@@ -1941,6 +1959,7 @@ pub fn bitcoin_units::amount::error::ParseAmountError::clone(&self) -> bitcoin_u
pub fn bitcoin_units::amount::error::ParseAmountError::eq(&self, other: &bitcoin_units::amount::error::ParseAmountError) -> bool
pub fn bitcoin_units::amount::error::ParseAmountError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_units::amount::error::ParseAmountError::from(never: core::convert::Infallible) -> Self
+pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::BadPositionError) -> Self
pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::InputTooLargeError) -> Self
pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::InvalidCharacterError) -> Self
pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::MissingDigitsError) -> Self
@@ -1955,6 +1974,7 @@ pub fn bitcoin_units::amount::error::ParseDenominationError::source(&self) -> co
pub fn bitcoin_units::amount::error::ParseError::clone(&self) -> bitcoin_units::amount::error::ParseError
pub fn bitcoin_units::amount::error::ParseError::eq(&self, other: &bitcoin_units::amount::error::ParseError) -> bool
pub fn bitcoin_units::amount::error::ParseError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::BadPositionError) -> Self
pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::InputTooLargeError) -> Self
pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::InvalidCharacterError) -> Self
pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::MissingDigitsError) -> Self
@@ -2537,6 +2557,7 @@ pub struct bitcoin_units::amount::ParseAmountError(_)
pub struct bitcoin_units::amount::ParseError(_)
pub struct bitcoin_units::amount::SignedAmount(_)
pub struct bitcoin_units::amount::error::AmountDecoderError(_)
+pub struct bitcoin_units::amount::error::BadPositionError
pub struct bitcoin_units::amount::error::InputTooLargeError
pub struct bitcoin_units::amount::error::InvalidCharacterError
pub struct bitcoin_units::amount::error::MissingDigitsError
diff --git a/api/units/alloc-only.txt b/api/units/alloc-only.txt
index 2a309184..04191097 100644
--- a/api/units/alloc-only.txt
+++ b/api/units/alloc-only.txt
@@ -52,6 +52,7 @@ impl core::clone::Clone for bitcoin_units::SignedAmount
impl core::clone::Clone for bitcoin_units::Weight
impl core::clone::Clone for bitcoin_units::amount::Denomination
impl core::clone::Clone for bitcoin_units::amount::Display
+impl core::clone::Clone for bitcoin_units::amount::error::BadPositionError
impl core::clone::Clone for bitcoin_units::amount::error::InputTooLargeError
impl core::clone::Clone for bitcoin_units::amount::error::InvalidCharacterError
impl core::clone::Clone for bitcoin_units::amount::error::MissingDenominationError
@@ -98,6 +99,7 @@ impl core::cmp::Eq for bitcoin_units::FeeRate
impl core::cmp::Eq for bitcoin_units::SignedAmount
impl core::cmp::Eq for bitcoin_units::Weight
impl core::cmp::Eq for bitcoin_units::amount::Denomination
+impl core::cmp::Eq for bitcoin_units::amount::error::BadPositionError
impl core::cmp::Eq for bitcoin_units::amount::error::InputTooLargeError
impl core::cmp::Eq for bitcoin_units::amount::error::InvalidCharacterError
impl core::cmp::Eq for bitcoin_units::amount::error::MissingDenominationError
@@ -158,6 +160,7 @@ impl core::cmp::PartialEq for bitcoin_units::FeeRate
impl core::cmp::PartialEq for bitcoin_units::SignedAmount
impl core::cmp::PartialEq for bitcoin_units::Weight
impl core::cmp::PartialEq for bitcoin_units::amount::Denomination
+impl core::cmp::PartialEq for bitcoin_units::amount::error::BadPositionError
impl core::cmp::PartialEq for bitcoin_units::amount::error::InputTooLargeError
impl core::cmp::PartialEq for bitcoin_units::amount::error::InvalidCharacterError
impl core::cmp::PartialEq for bitcoin_units::amount::error::MissingDenominationError
@@ -220,6 +223,8 @@ impl core::convert::From<bitcoin_units::Amount> for bitcoin_units::result::NumOp
impl core::convert::From<bitcoin_units::BlockTime> for u32
impl core::convert::From<bitcoin_units::SignedAmount> for bitcoin_units::result::NumOpResult<bitcoin_units::SignedAmount>
impl core::convert::From<bitcoin_units::Weight> for u64
+impl core::convert::From<bitcoin_units::amount::error::BadPositionError> for bitcoin_units::amount::error::ParseAmountError
+impl core::convert::From<bitcoin_units::amount::error::BadPositionError> for bitcoin_units::amount::error::ParseError
impl core::convert::From<bitcoin_units::amount::error::InputTooLargeError> for bitcoin_units::amount::error::ParseAmountError
impl core::convert::From<bitcoin_units::amount::error::InputTooLargeError> for bitcoin_units::amount::error::ParseError
impl core::convert::From<bitcoin_units::amount::error::InvalidCharacterError> for bitcoin_units::amount::error::ParseAmountError
@@ -312,6 +317,7 @@ impl core::fmt::Debug for bitcoin_units::SignedAmount
impl core::fmt::Debug for bitcoin_units::Weight
impl core::fmt::Debug for bitcoin_units::amount::Denomination
impl core::fmt::Debug for bitcoin_units::amount::Display
+impl core::fmt::Debug for bitcoin_units::amount::error::BadPositionError
impl core::fmt::Debug for bitcoin_units::amount::error::InputTooLargeError
impl core::fmt::Debug for bitcoin_units::amount::error::InvalidCharacterError
impl core::fmt::Debug for bitcoin_units::amount::error::MissingDenominationError
@@ -357,6 +363,7 @@ impl core::fmt::Display for bitcoin_units::SignedAmount
impl core::fmt::Display for bitcoin_units::Weight
impl core::fmt::Display for bitcoin_units::amount::Denomination
impl core::fmt::Display for bitcoin_units::amount::Display
+impl core::fmt::Display for bitcoin_units::amount::error::BadPositionError
impl core::fmt::Display for bitcoin_units::amount::error::InputTooLargeError
impl core::fmt::Display for bitcoin_units::amount::error::InvalidCharacterError
impl core::fmt::Display for bitcoin_units::amount::error::MissingDigitsError
@@ -448,6 +455,7 @@ impl core::marker::Freeze for bitcoin_units::SignedAmount
impl core::marker::Freeze for bitcoin_units::Weight
impl core::marker::Freeze for bitcoin_units::amount::Denomination
impl core::marker::Freeze for bitcoin_units::amount::Display
+impl core::marker::Freeze for bitcoin_units::amount::error::BadPositionError
impl core::marker::Freeze for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Freeze for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Freeze for bitcoin_units::amount::error::MissingDenominationError
@@ -495,6 +503,7 @@ impl core::marker::Send for bitcoin_units::SignedAmount
impl core::marker::Send for bitcoin_units::Weight
impl core::marker::Send for bitcoin_units::amount::Denomination
impl core::marker::Send for bitcoin_units::amount::Display
+impl core::marker::Send for bitcoin_units::amount::error::BadPositionError
impl core::marker::Send for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Send for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Send for bitcoin_units::amount::error::MissingDenominationError
@@ -541,6 +550,7 @@ impl core::marker::StructuralPartialEq for bitcoin_units::FeeRate
impl core::marker::StructuralPartialEq for bitcoin_units::SignedAmount
impl core::marker::StructuralPartialEq for bitcoin_units::Weight
impl core::marker::StructuralPartialEq for bitcoin_units::amount::Denomination
+impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::BadPositionError
impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::MissingDenominationError
@@ -588,6 +598,7 @@ impl core::marker::Sync for bitcoin_units::SignedAmount
impl core::marker::Sync for bitcoin_units::Weight
impl core::marker::Sync for bitcoin_units::amount::Denomination
impl core::marker::Sync for bitcoin_units::amount::Display
+impl core::marker::Sync for bitcoin_units::amount::error::BadPositionError
impl core::marker::Sync for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Sync for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Sync for bitcoin_units::amount::error::MissingDenominationError
@@ -635,6 +646,7 @@ impl core::marker::Unpin for bitcoin_units::SignedAmount
impl core::marker::Unpin for bitcoin_units::Weight
impl core::marker::Unpin for bitcoin_units::amount::Denomination
impl core::marker::Unpin for bitcoin_units::amount::Display
+impl core::marker::Unpin for bitcoin_units::amount::error::BadPositionError
impl core::marker::Unpin for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Unpin for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Unpin for bitcoin_units::amount::error::MissingDenominationError
@@ -908,6 +920,7 @@ impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::SignedAmount
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::Weight
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::Denomination
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::Display
+impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::BadPositionError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::InputTooLargeError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::InvalidCharacterError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::MissingDenominationError
@@ -955,6 +968,7 @@ impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::SignedAmount
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::Weight
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::Denomination
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::Display
+impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::BadPositionError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::InputTooLargeError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::InvalidCharacterError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::MissingDenominationError
@@ -1655,6 +1669,9 @@ pub fn bitcoin_units::amount::Denomination::hash<__H: core::hash::Hasher>(&self,
pub fn bitcoin_units::amount::Display::clone(&self) -> bitcoin_units::amount::Display
pub fn bitcoin_units::amount::Display::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_units::amount::Display::show_denomination(self) -> Self
+pub fn bitcoin_units::amount::error::BadPositionError::clone(&self) -> bitcoin_units::amount::error::BadPositionError
+pub fn bitcoin_units::amount::error::BadPositionError::eq(&self, other: &bitcoin_units::amount::error::BadPositionError) -> bool
+pub fn bitcoin_units::amount::error::BadPositionError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_units::amount::error::InputTooLargeError::clone(&self) -> bitcoin_units::amount::error::InputTooLargeError
pub fn bitcoin_units::amount::error::InputTooLargeError::eq(&self, other: &bitcoin_units::amount::error::InputTooLargeError) -> bool
pub fn bitcoin_units::amount::error::InputTooLargeError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
@@ -1677,6 +1694,7 @@ pub fn bitcoin_units::amount::error::ParseAmountError::clone(&self) -> bitcoin_u
pub fn bitcoin_units::amount::error::ParseAmountError::eq(&self, other: &bitcoin_units::amount::error::ParseAmountError) -> bool
pub fn bitcoin_units::amount::error::ParseAmountError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_units::amount::error::ParseAmountError::from(never: core::convert::Infallible) -> Self
+pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::BadPositionError) -> Self
pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::InputTooLargeError) -> Self
pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::InvalidCharacterError) -> Self
pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::MissingDigitsError) -> Self
@@ -1689,6 +1707,7 @@ pub fn bitcoin_units::amount::error::ParseDenominationError::from(never: core::c
pub fn bitcoin_units::amount::error::ParseError::clone(&self) -> bitcoin_units::amount::error::ParseError
pub fn bitcoin_units::amount::error::ParseError::eq(&self, other: &bitcoin_units::amount::error::ParseError) -> bool
pub fn bitcoin_units::amount::error::ParseError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::BadPositionError) -> Self
pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::InputTooLargeError) -> Self
pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::InvalidCharacterError) -> Self
pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::MissingDigitsError) -> Self
@@ -2129,6 +2148,7 @@ pub struct bitcoin_units::amount::OutOfRangeError
pub struct bitcoin_units::amount::ParseAmountError(_)
pub struct bitcoin_units::amount::ParseError(_)
pub struct bitcoin_units::amount::SignedAmount(_)
+pub struct bitcoin_units::amount::error::BadPositionError
pub struct bitcoin_units::amount::error::InputTooLargeError
pub struct bitcoin_units::amount::error::InvalidCharacterError
pub struct bitcoin_units::amount::error::MissingDigitsError
diff --git a/api/units/no-features.txt b/api/units/no-features.txt
index 356f2769..8b860555 100644
--- a/api/units/no-features.txt
+++ b/api/units/no-features.txt
@@ -52,6 +52,7 @@ impl core::clone::Clone for bitcoin_units::SignedAmount
impl core::clone::Clone for bitcoin_units::Weight
impl core::clone::Clone for bitcoin_units::amount::Denomination
impl core::clone::Clone for bitcoin_units::amount::Display
+impl core::clone::Clone for bitcoin_units::amount::error::BadPositionError
impl core::clone::Clone for bitcoin_units::amount::error::InputTooLargeError
impl core::clone::Clone for bitcoin_units::amount::error::InvalidCharacterError
impl core::clone::Clone for bitcoin_units::amount::error::MissingDenominationError
@@ -98,6 +99,7 @@ impl core::cmp::Eq for bitcoin_units::FeeRate
impl core::cmp::Eq for bitcoin_units::SignedAmount
impl core::cmp::Eq for bitcoin_units::Weight
impl core::cmp::Eq for bitcoin_units::amount::Denomination
+impl core::cmp::Eq for bitcoin_units::amount::error::BadPositionError
impl core::cmp::Eq for bitcoin_units::amount::error::InputTooLargeError
impl core::cmp::Eq for bitcoin_units::amount::error::InvalidCharacterError
impl core::cmp::Eq for bitcoin_units::amount::error::MissingDenominationError
@@ -158,6 +160,7 @@ impl core::cmp::PartialEq for bitcoin_units::FeeRate
impl core::cmp::PartialEq for bitcoin_units::SignedAmount
impl core::cmp::PartialEq for bitcoin_units::Weight
impl core::cmp::PartialEq for bitcoin_units::amount::Denomination
+impl core::cmp::PartialEq for bitcoin_units::amount::error::BadPositionError
impl core::cmp::PartialEq for bitcoin_units::amount::error::InputTooLargeError
impl core::cmp::PartialEq for bitcoin_units::amount::error::InvalidCharacterError
impl core::cmp::PartialEq for bitcoin_units::amount::error::MissingDenominationError
@@ -220,6 +223,8 @@ impl core::convert::From<bitcoin_units::Amount> for bitcoin_units::result::NumOp
impl core::convert::From<bitcoin_units::BlockTime> for u32
impl core::convert::From<bitcoin_units::SignedAmount> for bitcoin_units::result::NumOpResult<bitcoin_units::SignedAmount>
impl core::convert::From<bitcoin_units::Weight> for u64
+impl core::convert::From<bitcoin_units::amount::error::BadPositionError> for bitcoin_units::amount::error::ParseAmountError
+impl core::convert::From<bitcoin_units::amount::error::BadPositionError> for bitcoin_units::amount::error::ParseError
impl core::convert::From<bitcoin_units::amount::error::InputTooLargeError> for bitcoin_units::amount::error::ParseAmountError
impl core::convert::From<bitcoin_units::amount::error::InputTooLargeError> for bitcoin_units::amount::error::ParseError
impl core::convert::From<bitcoin_units::amount::error::InvalidCharacterError> for bitcoin_units::amount::error::ParseAmountError
@@ -289,6 +294,7 @@ impl core::fmt::Debug for bitcoin_units::SignedAmount
impl core::fmt::Debug for bitcoin_units::Weight
impl core::fmt::Debug for bitcoin_units::amount::Denomination
impl core::fmt::Debug for bitcoin_units::amount::Display
+impl core::fmt::Debug for bitcoin_units::amount::error::BadPositionError
impl core::fmt::Debug for bitcoin_units::amount::error::InputTooLargeError
impl core::fmt::Debug for bitcoin_units::amount::error::InvalidCharacterError
impl core::fmt::Debug for bitcoin_units::amount::error::MissingDenominationError
@@ -334,6 +340,7 @@ impl core::fmt::Display for bitcoin_units::SignedAmount
impl core::fmt::Display for bitcoin_units::Weight
impl core::fmt::Display for bitcoin_units::amount::Denomination
impl core::fmt::Display for bitcoin_units::amount::Display
+impl core::fmt::Display for bitcoin_units::amount::error::BadPositionError
impl core::fmt::Display for bitcoin_units::amount::error::InputTooLargeError
impl core::fmt::Display for bitcoin_units::amount::error::InvalidCharacterError
impl core::fmt::Display for bitcoin_units::amount::error::MissingDigitsError
@@ -425,6 +432,7 @@ impl core::marker::Freeze for bitcoin_units::SignedAmount
impl core::marker::Freeze for bitcoin_units::Weight
impl core::marker::Freeze for bitcoin_units::amount::Denomination
impl core::marker::Freeze for bitcoin_units::amount::Display
+impl core::marker::Freeze for bitcoin_units::amount::error::BadPositionError
impl core::marker::Freeze for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Freeze for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Freeze for bitcoin_units::amount::error::MissingDenominationError
@@ -472,6 +480,7 @@ impl core::marker::Send for bitcoin_units::SignedAmount
impl core::marker::Send for bitcoin_units::Weight
impl core::marker::Send for bitcoin_units::amount::Denomination
impl core::marker::Send for bitcoin_units::amount::Display
+impl core::marker::Send for bitcoin_units::amount::error::BadPositionError
impl core::marker::Send for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Send for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Send for bitcoin_units::amount::error::MissingDenominationError
@@ -518,6 +527,7 @@ impl core::marker::StructuralPartialEq for bitcoin_units::FeeRate
impl core::marker::StructuralPartialEq for bitcoin_units::SignedAmount
impl core::marker::StructuralPartialEq for bitcoin_units::Weight
impl core::marker::StructuralPartialEq for bitcoin_units::amount::Denomination
+impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::BadPositionError
impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::StructuralPartialEq for bitcoin_units::amount::error::MissingDenominationError
@@ -565,6 +575,7 @@ impl core::marker::Sync for bitcoin_units::SignedAmount
impl core::marker::Sync for bitcoin_units::Weight
impl core::marker::Sync for bitcoin_units::amount::Denomination
impl core::marker::Sync for bitcoin_units::amount::Display
+impl core::marker::Sync for bitcoin_units::amount::error::BadPositionError
impl core::marker::Sync for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Sync for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Sync for bitcoin_units::amount::error::MissingDenominationError
@@ -612,6 +623,7 @@ impl core::marker::Unpin for bitcoin_units::SignedAmount
impl core::marker::Unpin for bitcoin_units::Weight
impl core::marker::Unpin for bitcoin_units::amount::Denomination
impl core::marker::Unpin for bitcoin_units::amount::Display
+impl core::marker::Unpin for bitcoin_units::amount::error::BadPositionError
impl core::marker::Unpin for bitcoin_units::amount::error::InputTooLargeError
impl core::marker::Unpin for bitcoin_units::amount::error::InvalidCharacterError
impl core::marker::Unpin for bitcoin_units::amount::error::MissingDenominationError
@@ -885,6 +897,7 @@ impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::SignedAmount
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::Weight
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::Denomination
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::Display
+impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::BadPositionError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::InputTooLargeError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::InvalidCharacterError
impl core::panic::unwind_safe::RefUnwindSafe for bitcoin_units::amount::error::MissingDenominationError
@@ -932,6 +945,7 @@ impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::SignedAmount
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::Weight
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::Denomination
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::Display
+impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::BadPositionError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::InputTooLargeError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::InvalidCharacterError
impl core::panic::unwind_safe::UnwindSafe for bitcoin_units::amount::error::MissingDenominationError
@@ -1617,6 +1631,9 @@ pub fn bitcoin_units::amount::Denomination::hash<__H: core::hash::Hasher>(&self,
pub fn bitcoin_units::amount::Display::clone(&self) -> bitcoin_units::amount::Display
pub fn bitcoin_units::amount::Display::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_units::amount::Display::show_denomination(self) -> Self
+pub fn bitcoin_units::amount::error::BadPositionError::clone(&self) -> bitcoin_units::amount::error::BadPositionError
+pub fn bitcoin_units::amount::error::BadPositionError::eq(&self, other: &bitcoin_units::amount::error::BadPositionError) -> bool
+pub fn bitcoin_units::amount::error::BadPositionError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_units::amount::error::InputTooLargeError::clone(&self) -> bitcoin_units::amount::error::InputTooLargeError
pub fn bitcoin_units::amount::error::InputTooLargeError::eq(&self, other: &bitcoin_units::amount::error::InputTooLargeError) -> bool
pub fn bitcoin_units::amount::error::InputTooLargeError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
@@ -1639,6 +1656,7 @@ pub fn bitcoin_units::amount::error::ParseAmountError::clone(&self) -> bitcoin_u
pub fn bitcoin_units::amount::error::ParseAmountError::eq(&self, other: &bitcoin_units::amount::error::ParseAmountError) -> bool
pub fn bitcoin_units::amount::error::ParseAmountError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_units::amount::error::ParseAmountError::from(never: core::convert::Infallible) -> Self
+pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::BadPositionError) -> Self
pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::InputTooLargeError) -> Self
pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::InvalidCharacterError) -> Self
pub fn bitcoin_units::amount::error::ParseAmountError::from(value: bitcoin_units::amount::error::MissingDigitsError) -> Self
@@ -1651,6 +1669,7 @@ pub fn bitcoin_units::amount::error::ParseDenominationError::from(never: core::c
pub fn bitcoin_units::amount::error::ParseError::clone(&self) -> bitcoin_units::amount::error::ParseError
pub fn bitcoin_units::amount::error::ParseError::eq(&self, other: &bitcoin_units::amount::error::ParseError) -> bool
pub fn bitcoin_units::amount::error::ParseError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::BadPositionError) -> Self
pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::InputTooLargeError) -> Self
pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::InvalidCharacterError) -> Self
pub fn bitcoin_units::amount::error::ParseError::from(e: bitcoin_units::amount::error::MissingDigitsError) -> Self
@@ -2066,6 +2085,7 @@ pub struct bitcoin_units::amount::OutOfRangeError
pub struct bitcoin_units::amount::ParseAmountError(_)
pub struct bitcoin_units::amount::ParseError(_)
pub struct bitcoin_units::amount::SignedAmount(_)
+pub struct bitcoin_units::amount::error::BadPositionError
pub struct bitcoin_units::amount::error::InputTooLargeError
pub struct bitcoin_units::amount::error::InvalidCharacterError
pub struct bitcoin_units::amount::error::MissingDigitsError
diff --git a/units/src/amount/error.rs b/units/src/amount/error.rs
index 2914c051..fc60b318 100644
--- a/units/src/amount/error.rs
+++ b/units/src/amount/error.rs
@@ -60,6 +60,10 @@ impl From<InvalidCharacterError> for ParseError {
fn from(e: InvalidCharacterError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
}
+impl From<BadPositionError> for ParseError {
+ fn from(e: BadPositionError) -> Self { Self(ParseErrorInner::Amount(e.into())) }
+}
+
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
@@ -100,6 +104,8 @@ pub(crate) enum ParseAmountErrorInner {
InputTooLarge(InputTooLargeError),
/// Invalid character in input.
InvalidCharacter(InvalidCharacterError),
+ /// A valid character is in an invalid position.
+ BadPosition(BadPositionError),
}
impl From<TooPreciseError> for ParseAmountError {
@@ -120,6 +126,10 @@ impl From<InvalidCharacterError> for ParseAmountError {
}
}
+impl From<BadPositionError> for ParseAmountError {
+ fn from(value: BadPositionError) -> Self { Self(ParseAmountErrorInner::BadPosition(value)) }
+}
+
impl From<Infallible> for ParseAmountError {
fn from(never: Infallible) -> Self { match never {} }
}
@@ -139,7 +149,8 @@ impl fmt::Display for ParseAmountError {
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)
- }
+ },
+ E::BadPosition(ref error) => write_err!(f, "valid character in bad position"; error)
}
}
}
@@ -155,6 +166,7 @@ impl std::error::Error for ParseAmountError {
E::OutOfRange(ref error) => Some(error),
E::MissingDigits(ref error) => Some(error),
E::InvalidCharacter(ref error) => Some(error),
+ E::BadPosition(ref error) => Some(error),
}
}
}
@@ -322,6 +334,36 @@ impl fmt::Display for InvalidCharacterError {
#[cfg(feature = "std")]
impl std::error::Error for InvalidCharacterError {}
+/// Error returned when a valid character (e.g. '_') is in an invalid/bad position.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct BadPositionError {
+ pub(super) char: char,
+ pub(super) position: usize,
+}
+
+impl fmt::Display for BadPositionError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self.char {
+ '_' => match self.position {
+ 0 => f.write_str("the input amount is prefixed with an underscore (_)"),
+ // Position 1 can only occur when the amount begins with a minus (-), since an underscore
+ // at position 0 will cause an error with position 0, and this error marks the position of
+ // the second underscore.
+ 1 => f.write_str("the input amount is prefixed with an underscore (_)"),
+ _ => f.write_str("there are consecutive underscores (_) in the input"),
+ },
+ c => write!(
+ f,
+ "The character '{}' is at a bad position: {}",
+ c, self.position
+ ),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for BadPositionError {}
+
/// An error during amount parsing.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
diff --git a/units/src/amount/mod.rs b/units/src/amount/mod.rs
index b363d9ea..f1778391 100644
--- a/units/src/amount/mod.rs
+++ b/units/src/amount/mod.rs
@@ -28,7 +28,7 @@ use arbitrary::{Arbitrary, Unstructured};
use self::error::{
InputTooLargeError, InvalidCharacterError, MissingDenominationError, MissingDigitsError,
MissingDigitsKind, ParseAmountErrorInner, ParseErrorInner, PossiblyConfusingDenominationError,
- TooPreciseError, UnknownDenominationError,
+ TooPreciseError, UnknownDenominationError, BadPositionError,
};
#[rustfmt::skip] // Keep public re-exports separate.
@@ -259,6 +259,8 @@ fn parse_signed_to_satoshi(
};
let mut decimals = None;
+ // The number of consecutive underscores
+ let mut underscores = None;
let mut value: i64 = 0; // as satoshis
for (i, c) in s.char_indices() {
match c {
@@ -280,10 +282,22 @@ fn parse_signed_to_satoshi(
position: i + usize::from(is_negative),
})),
};
+ underscores = None;
}
+ '_' if i == 0 =>
+ // Leading underscore
+ return Err(InnerParseError::BadPosition(BadPositionError { char: '_', position: i + usize::from(is_negative) })),
+ '_' => match underscores {
+ None => underscores = Some(1),
+ // Consecutive underscores
+ _ => return Err(InnerParseError::BadPosition(BadPositionError { char: '_', position: i + usize::from(is_negative) })),
+ },
'.' => match decimals {
None if max_decimals <= 0 => break,
- None => decimals = Some(0),
+ None => {
+ decimals = Some(0);
+ underscores = None;
+ },
// Double decimal dot.
_ =>
return Err(InnerParseError::InvalidCharacter(InvalidCharacterError {
@@ -323,6 +337,7 @@ enum InnerParseError {
MissingDigits(MissingDigitsError),
InputTooLarge(usize),
InvalidCharacter(InvalidCharacterError),
+ BadPosition(BadPositionError),
}
impl From<Infallible> for InnerParseError {
@@ -340,6 +355,7 @@ impl InnerParseError {
ParseAmountError(ParseAmountErrorInner::InputTooLarge(InputTooLargeError { len })),
Self::InvalidCharacter(e) =>
ParseAmountError(ParseAmountErrorInner::InvalidCharacter(e)),
+ Self::BadPosition(e) => ParseAmountError(ParseAmountErrorInner::BadPosition(e)),
}
}
}
diff --git a/units/src/amount/tests.rs b/units/src/amount/tests.rs
index 37e0770a..87306182 100644
--- a/units/src/amount/tests.rs
+++ b/units/src/amount/tests.rs
@@ -726,6 +726,9 @@ fn from_str() {
scase("2100000000000001 SAT", Err(OutOfRangeError::too_big(true)));
case("21000001 BTC", Err(OutOfRangeError::too_big(false)));
case("18446744073709551616 sat", Err(OutOfRangeError::too_big(false)));
+ case("_1000 sat", Err(BadPositionError { char: '_', position: 0 }));
+ case("10__00 sat", Err(BadPositionError { char: '_', position: 3 }));
+ scase("-_10_00 sat", Err(BadPositionError { char: '_', position: 1 }));
ok_case(".5 bits", sat(50));
ok_scase("-.5 bits", ssat(-50));
@@ -736,6 +739,9 @@ fn from_str() {
ok_case("21000000 BTC", Amount::MAX);
ok_scase("21000000 BTC", SignedAmount::MAX);
ok_scase("-21000000 BTC", SignedAmount::MIN);
+ ok_case("1_000 sat", sat(1000));
+ ok_case("1_0_0_0_0_0_0 satoshi", sat(1_000_000));
+ ok_scase("-0_._0_10_00 BTC", ssat(-1_000_000));
}
#[cfg(feature = "alloc")]
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.