units: Hide amount decoder error internals
What changed, and why it matters
This commit is a defensive API-hardening change, not a fix for an active security bug. It hides the internal details of an error type used when decoding Bitcoin amounts, so future library changes cannot accidentally expose or depend on those internals. It does not change how amounts are actually decoded or validated.
No urgent action required. Treat as a normal dependency update. If you maintain code that matched on `AmountDecoderError` variants or relied on `From<UnexpectedEofError>`, update it because those APIs are no longer public.
Security signals we found
API hardening / encapsulation of error type internals
Removal of public From trait impl that exposed internal error variants
No change to cryptographic, consensus, or arithmetic validation logic
Evidence from the diff
The patch refactors AmountDecoderError from a public #[non_exhaustive] enum into a public struct wrapping a private pub(super) enum (AmountDecoderErrorInner). It removes public From<UnexpectedEofError> conversion and replaces public enum variant constructors with pub(super) factory methods (eof, out_of_range). Call sites in unsigned.rs are updated to use those factories. This follows a new project policy for decoder error types: hidden internals, no From impls, private constructors. The observable decoding behavior (EOF handling, out-of-range checks, little-endian u64 parsing) is unchanged.
Changed components
units/src/amount/error.rsunits/src/amount/unsigned.rsAmountDecoderError typeAmountDecoder implInspect captured patch +31 / −16
diff --git a/units/src/amount/error.rs b/units/src/amount/error.rs
index 1d54c180..c888647b 100644
--- a/units/src/amount/error.rs
+++ b/units/src/amount/error.rs
@@ -394,8 +394,24 @@ impl std::error::Error for PossiblyConfusingDenominationError {
/// An error consensus decoding an `Amount`.
#[cfg(feature = "encoding")]
#[derive(Debug, Clone, PartialEq, Eq)]
-#[non_exhaustive]
-pub enum AmountDecoderError {
+pub struct AmountDecoderError(pub(super) AmountDecoderErrorInner);
+
+#[cfg(feature = "encoding")]
+impl AmountDecoderError {
+ /// Constructs an EOF error.
+ pub(super) fn eof(e: encoding::UnexpectedEofError) -> Self {
+ Self(AmountDecoderErrorInner::UnexpectedEof(e))
+ }
+
+ /// Constructs an out of range (`Amount::from_sat`) error.
+ pub(super) fn out_of_range(e: OutOfRangeError) -> Self {
+ Self(AmountDecoderErrorInner::OutOfRange(e))
+ }
+}
+
+#[cfg(feature = "encoding")]
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(super) enum AmountDecoderErrorInner {
/// Not enough bytes given to decoder.
UnexpectedEof(encoding::UnexpectedEofError),
/// Decoded amount is too big.
@@ -407,17 +423,14 @@ impl From<Infallible> for AmountDecoderError {
fn from(never: Infallible) -> Self { match never {} }
}
-#[cfg(feature = "encoding")]
-impl From<encoding::UnexpectedEofError> for AmountDecoderError {
- fn from(e: encoding::UnexpectedEofError) -> Self { Self::UnexpectedEof(e) }
-}
-
#[cfg(feature = "encoding")]
impl fmt::Display for AmountDecoderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match *self {
- Self::UnexpectedEof(ref e) => write_err!(f, "decode error"; e),
- Self::OutOfRange(ref e) => write_err!(f, "decode error"; e),
+ use AmountDecoderErrorInner as E;
+
+ match self.0 {
+ E::UnexpectedEof(ref e) => write_err!(f, "decode error"; e),
+ E::OutOfRange(ref e) => write_err!(f, "decode error"; e),
}
}
}
@@ -425,9 +438,11 @@ impl fmt::Display for AmountDecoderError {
#[cfg(all(feature = "std", feature = "encoding"))]
impl std::error::Error for AmountDecoderError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
- match *self {
- Self::UnexpectedEof(ref e) => Some(e),
- Self::OutOfRange(ref e) => Some(e),
+ use AmountDecoderErrorInner as E;
+
+ match self.0 {
+ E::UnexpectedEof(ref e) => Some(e),
+ E::OutOfRange(ref e) => Some(e),
}
}
}
diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs
index 51fa072b..fa47d158 100644
--- a/units/src/amount/unsigned.rs
+++ b/units/src/amount/unsigned.rs
@@ -589,13 +589,13 @@ impl encoding::Decoder for AmountDecoder {
#[inline]
fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
- Ok(self.0.push_bytes(bytes)?)
+ self.0.push_bytes(bytes).map_err(AmountDecoderError::eof)
}
#[inline]
fn end(self) -> Result<Self::Output, Self::Error> {
- let a = u64::from_le_bytes(self.0.end()?);
- Ok(Amount::from_sat(a).map_err(AmountDecoderError::OutOfRange)?)
+ let a = u64::from_le_bytes(self.0.end().map_err(AmountDecoderError::eof)?);
+ Amount::from_sat(a).map_err(AmountDecoderError::out_of_range)
}
}
Why this scored 18/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.