What changed, and why it matters
This commit adds decoding logic for several Bitcoin unit types (amounts, block heights, lock times, sequence numbers, block times) so they can be read from a byte stream, mirroring existing encoding logic. It is a routine feature addition, not a security fix.
No security action required; review as normal feature code.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch implements the encoding::Decoder and encoding::Decodable traits for Amount, BlockHeight, BlockTime, LockTime, and Sequence. Each decoder uses a fixed-size array decoder (4 or 8 bytes), reads little-endian bytes, and constructs the type via existing constructors. For Amount, an AmountDecoderError enum is added to handle EOF and out-of-range cases. The change is gated behind the encoding feature and is symmetric to the already-present Encodable implementations.
Changed components
units/src/amount/error.rsunits/src/amount/mod.rsunits/src/amount/unsigned.rsunits/src/block.rsunits/src/locktime/absolute/mod.rsunits/src/sequence.rsunits/src/time.rsInspect captured patch +184 / −2
diff --git a/units/src/amount/error.rs b/units/src/amount/error.rs
index e0946d15..1d54c180 100644
--- a/units/src/amount/error.rs
+++ b/units/src/amount/error.rs
@@ -137,8 +137,9 @@ impl fmt::Display for ParseAmountError {
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),
+ E::InvalidCharacter(ref error) => {
+ write_err!(f, "invalid character in the input"; error)
+ }
}
}
}
@@ -389,3 +390,44 @@ impl fmt::Display for PossiblyConfusingDenominationError {
impl std::error::Error for PossiblyConfusingDenominationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
+
+/// An error consensus decoding an `Amount`.
+#[cfg(feature = "encoding")]
+#[derive(Debug, Clone, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum AmountDecoderError {
+ /// Not enough bytes given to decoder.
+ UnexpectedEof(encoding::UnexpectedEofError),
+ /// Decoded amount is too big.
+ OutOfRange(OutOfRangeError),
+}
+
+#[cfg(feature = "encoding")]
+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),
+ }
+ }
+}
+
+#[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),
+ }
+ }
+}
diff --git a/units/src/amount/mod.rs b/units/src/amount/mod.rs
index bc2e79c4..f90b7b59 100644
--- a/units/src/amount/mod.rs
+++ b/units/src/amount/mod.rs
@@ -37,6 +37,9 @@ pub use self::{
signed::SignedAmount,
unsigned::Amount,
};
+#[cfg(feature = "encoding")]
+#[doc(no_inline)]
+pub use self::error::AmountDecoderError;
#[doc(no_inline)]
pub use self::error::{OutOfRangeError, ParseAmountError, ParseDenominationError, ParseError};
diff --git a/units/src/amount/unsigned.rs b/units/src/amount/unsigned.rs
index 80438dd5..51fa072b 100644
--- a/units/src/amount/unsigned.rs
+++ b/units/src/amount/unsigned.rs
@@ -12,6 +12,8 @@ use arbitrary::{Arbitrary, Unstructured};
use internals::const_casts;
use NumOpResult as R;
+#[cfg(feature = "encoding")]
+use super::error::AmountDecoderError;
use super::error::{ParseAmountErrorInner, ParseErrorInner};
use super::{
parse_signed_to_satoshi, split_amount_and_denomination, Denomination, Display, DisplayStyle,
@@ -576,6 +578,33 @@ impl encoding::Encodable for Amount {
}
}
+/// The decoder for the [`Amount`] type.
+#[cfg(feature = "encoding")]
+pub struct AmountDecoder(encoding::ArrayDecoder<8>);
+
+#[cfg(feature = "encoding")]
+impl encoding::Decoder for AmountDecoder {
+ type Output = Amount;
+ type Error = AmountDecoderError;
+
+ #[inline]
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ Ok(self.0.push_bytes(bytes)?)
+ }
+
+ #[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)?)
+ }
+}
+
+#[cfg(feature = "encoding")]
+impl encoding::Decodable for Amount {
+ type Decoder = AmountDecoder;
+ fn decoder() -> Self::Decoder { AmountDecoder(encoding::ArrayDecoder::<8>::new()) }
+}
+
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for Amount {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
diff --git a/units/src/block.rs b/units/src/block.rs
index abc65721..6f16c102 100644
--- a/units/src/block.rs
+++ b/units/src/block.rs
@@ -157,6 +157,33 @@ impl encoding::Encodable for BlockHeight {
}
}
+/// The decoder for the [`BlockHeight`] type.
+#[cfg(feature = "encoding")]
+pub struct BlockHeightDecoder(encoding::ArrayDecoder<4>);
+
+#[cfg(feature = "encoding")]
+impl encoding::Decoder for BlockHeightDecoder {
+ type Output = BlockHeight;
+ type Error = encoding::UnexpectedEofError;
+
+ #[inline]
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ self.0.push_bytes(bytes)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ let n = u32::from_le_bytes(self.0.end()?);
+ Ok(BlockHeight::from_u32(n))
+ }
+}
+
+#[cfg(feature = "encoding")]
+impl encoding::Decodable for BlockHeight {
+ type Decoder = BlockHeightDecoder;
+ fn decoder() -> Self::Decoder { BlockHeightDecoder(encoding::ArrayDecoder::<4>::new()) }
+}
+
impl_u32_wrapper! {
/// An unsigned block interval.
///
diff --git a/units/src/locktime/absolute/mod.rs b/units/src/locktime/absolute/mod.rs
index 9cc9bfec..b6be9235 100644
--- a/units/src/locktime/absolute/mod.rs
+++ b/units/src/locktime/absolute/mod.rs
@@ -417,6 +417,33 @@ impl encoding::Encodable for LockTime {
}
}
+/// The decoder for the [`LockTime`] type.
+#[cfg(feature = "encoding")]
+pub struct LockTimeDecoder(encoding::ArrayDecoder<4>);
+
+#[cfg(feature = "encoding")]
+impl encoding::Decoder for LockTimeDecoder {
+ type Output = LockTime;
+ type Error = encoding::UnexpectedEofError;
+
+ #[inline]
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ self.0.push_bytes(bytes)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ let n = u32::from_le_bytes(self.0.end()?);
+ Ok(LockTime::from_consensus(n))
+ }
+}
+
+#[cfg(feature = "encoding")]
+impl encoding::Decodable for LockTime {
+ type Decoder = LockTimeDecoder;
+ fn decoder() -> Self::Decoder { LockTimeDecoder(encoding::ArrayDecoder::<4>::new()) }
+}
+
impl From<Height> for LockTime {
#[inline]
fn from(h: Height) -> Self { LockTime::Blocks(h) }
diff --git a/units/src/sequence.rs b/units/src/sequence.rs
index 8c2e4967..814192b5 100644
--- a/units/src/sequence.rs
+++ b/units/src/sequence.rs
@@ -280,6 +280,33 @@ impl encoding::Encodable for Sequence {
}
}
+/// The decoder for the [`Sequence`] type.
+#[cfg(feature = "encoding")]
+pub struct SequenceDecoder(encoding::ArrayDecoder<4>);
+
+#[cfg(feature = "encoding")]
+impl encoding::Decoder for SequenceDecoder {
+ type Output = Sequence;
+ type Error = encoding::UnexpectedEofError;
+
+ #[inline]
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ self.0.push_bytes(bytes)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ let n = u32::from_le_bytes(self.0.end()?);
+ Ok(Sequence::from_consensus(n))
+ }
+}
+
+#[cfg(feature = "encoding")]
+impl encoding::Decodable for Sequence {
+ type Decoder = SequenceDecoder;
+ fn decoder() -> Self::Decoder { SequenceDecoder(encoding::ArrayDecoder::<4>::new()) }
+}
+
#[cfg(feature = "arbitrary")]
#[cfg(feature = "alloc")]
impl<'a> Arbitrary<'a> for Sequence {
diff --git a/units/src/time.rs b/units/src/time.rs
index 5ee2990e..1f5945fe 100644
--- a/units/src/time.rs
+++ b/units/src/time.rs
@@ -87,6 +87,33 @@ impl encoding::Encodable for BlockTime {
}
}
+/// The decoder for the [`BlockTime`] type.
+#[cfg(feature = "encoding")]
+pub struct BlockTimeDecoder(encoding::ArrayDecoder<4>);
+
+#[cfg(feature = "encoding")]
+impl encoding::Decoder for BlockTimeDecoder {
+ type Output = BlockTime;
+ type Error = encoding::UnexpectedEofError;
+
+ #[inline]
+ fn push_bytes(&mut self, bytes: &mut &[u8]) -> Result<bool, Self::Error> {
+ self.0.push_bytes(bytes)
+ }
+
+ #[inline]
+ fn end(self) -> Result<Self::Output, Self::Error> {
+ let t = u32::from_le_bytes(self.0.end()?);
+ Ok(BlockTime::from_u32(t))
+ }
+}
+
+#[cfg(feature = "encoding")]
+impl encoding::Decodable for BlockTime {
+ type Decoder = BlockTimeDecoder;
+ fn decoder() -> Self::Decoder { BlockTimeDecoder(encoding::ArrayDecoder::<4>::new()) }
+}
+
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for BlockTime {
#[inline]
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.