consensus_encoding: Introduce decode_from_slice_unbounded
What changed, and why it matters
This commit changes how a Bitcoin library decodes byte slices. Previously, a function called decode_from_slice silently accepted extra bytes after a valid object. Now it rejects leftover bytes, and a new function decode_from_slice_unbounded is provided for cases where callers intentionally want to read only part of the input. This is a defensive correctness fix that prevents accidental acceptance of malformed or padded data, but it also changes the public API behavior and error types, which could break downstream code.
Review downstream callers of decode_from_slice to ensure they do not depend on the previous silent acceptance of trailing bytes; update error handling to match the new DecodeError type. Prefer decode_from_slice_unbounded only when intentionally parsing a prefix of a larger buffer. Consider whether this API change warrants a version bump and changelog note because it is a breaking change.
Security signals we found
Strict input validation: decode_from_slice now rejects unconsumed trailing bytes, preventing malleability or parsing ambiguity
API behavior change: existing callers relying on silent acceptance of extra bytes will now receive errors
Error-type change: return type of decode_from_slice changed from Decoder::Error to DecodeError<Decoder::Error>, a breaking public API change
New unconsumed-byte error variant improves diagnostics and explicit handling
No memory-safety bug evident; change is a semantic hardening of deserialization
Evidence from the diff
The patch modifies bitcoin_consensus_encoding so that decode_from_slice
Changed components
consensus_encoding/src/decode/mod.rsconsensus_encoding/src/lib.rsconsensus_encoding/tests/decode.rsprimitives/src/hex_codec.rsapi/consensus_encoding/*.txt public API snapshotsInspect captured patch +183 / −11
diff --git a/api/consensus_encoding/all-features.txt b/api/consensus_encoding/all-features.txt
index 70ec630f..cbff19ad 100644
--- a/api/consensus_encoding/all-features.txt
+++ b/api/consensus_encoding/all-features.txt
@@ -289,20 +289,35 @@ impl<D> core::marker::Send for bitcoin_consensus_encoding::ReadError<D> where D:
impl<D> core::marker::Sync for bitcoin_consensus_encoding::ReadError<D> where D: core::marker::Sync
impl<D> core::marker::Unpin for bitcoin_consensus_encoding::ReadError<D> where D: core::marker::Unpin
impl<D> core::marker::UnsafeUnpin for bitcoin_consensus_encoding::ReadError<D> where D: core::marker::UnsafeUnpin
+impl<Err: core::clone::Clone> core::clone::Clone for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err: core::clone::Clone> core::clone::Clone for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err: core::cmp::Eq> core::cmp::Eq for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err: core::cmp::Eq> core::cmp::Eq for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err: core::cmp::PartialEq> core::cmp::PartialEq for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err: core::cmp::PartialEq> core::cmp::PartialEq for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err> core::convert::From<core::convert::Infallible> for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err> core::convert::From<core::convert::Infallible> for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err> core::error::Error for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::error::Error + 'static
impl<Err> core::error::Error for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::error::Error + 'static
+impl<Err> core::fmt::Display for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::fmt::Display
impl<Err> core::fmt::Display for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::fmt::Display + core::fmt::Debug
+impl<Err> core::marker::Freeze for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Freeze
impl<Err> core::marker::Freeze for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::marker::Freeze
+impl<Err> core::marker::Send for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Send
impl<Err> core::marker::Send for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::marker::Send
+impl<Err> core::marker::StructuralPartialEq for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err> core::marker::StructuralPartialEq for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err> core::marker::Sync for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Sync
impl<Err> core::marker::Sync for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::marker::Sync
+impl<Err> core::marker::Unpin for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Unpin
impl<Err> core::marker::Unpin for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::marker::Unpin
+impl<Err> core::marker::UnsafeUnpin for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::UnsafeUnpin
impl<Err> core::marker::UnsafeUnpin for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::marker::UnsafeUnpin
+impl<Err> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::panic::unwind_safe::RefUnwindSafe
impl<Err> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::panic::unwind_safe::RefUnwindSafe
+impl<Err> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::panic::unwind_safe::UnwindSafe
impl<Err> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::panic::unwind_safe::UnwindSafe
impl<T: bitcoin_consensus_encoding::Decodable> bitcoin_consensus_encoding::Decoder for bitcoin_consensus_encoding::VecDecoder<T>
impl<T: bitcoin_consensus_encoding::Decodable> bitcoin_consensus_encoding::VecDecoder<T>
@@ -344,6 +359,8 @@ impl<const N: usize> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consens
impl<const N: usize> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consensus_encoding::ArrayEncoder<N>
impl<const N: usize> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::ArrayDecoder<N>
impl<const N: usize> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::ArrayEncoder<N>
+pub bitcoin_consensus_encoding::DecodeError::Parse(Err)
+pub bitcoin_consensus_encoding::DecodeError::Unconsumed(bitcoin_consensus_encoding::decode::UnconsumedError)
pub bitcoin_consensus_encoding::Decoder2Error::First(A)
pub bitcoin_consensus_encoding::Decoder2Error::Second(B)
pub bitcoin_consensus_encoding::Decoder3Error::First(A)
@@ -378,6 +395,7 @@ pub const fn bitcoin_consensus_encoding::Encoder3<A, B, C>::new(enc_1: A, enc_2:
pub const fn bitcoin_consensus_encoding::Encoder4<A, B, C, D>::new(enc_1: A, enc_2: B, enc_3: C, enc_4: D) -> Self
pub const fn bitcoin_consensus_encoding::Encoder6<A, B, C, D, E, F>::new(enc_1: A, enc_2: B, enc_3: C, enc_4: D, enc_5: E, enc_6: F) -> Self
pub const fn bitcoin_consensus_encoding::VecDecoder<T>::new() -> Self
+pub enum bitcoin_consensus_encoding::DecodeError<Err>
pub enum bitcoin_consensus_encoding::Decoder2Error<A, B>
pub enum bitcoin_consensus_encoding::Decoder3Error<A, B, C>
pub enum bitcoin_consensus_encoding::Decoder4Error<A, B, C, D>
@@ -432,6 +450,11 @@ pub fn bitcoin_consensus_encoding::CompactSizeEncoder::fmt(&self, f: &mut core::
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::len(&self) -> usize
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::new(value: usize) -> Self
pub fn bitcoin_consensus_encoding::Decodable::decoder() -> Self::Decoder
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::clone(&self) -> bitcoin_consensus_encoding::DecodeError<Err>
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::eq(&self, other: &bitcoin_consensus_encoding::DecodeError<Err>) -> bool
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::from(never: core::convert::Infallible) -> Self
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::source(&self) -> core::option::Option<&(dyn core::error::Error + 'static)>
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::end(self) -> core::result::Result<Self::Output, Self::Error>
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::push_bytes(&mut self, bytes: &mut &[u8]) -> core::result::Result<bool, Self::Error>
@@ -524,7 +547,8 @@ pub fn bitcoin_consensus_encoding::VecDecoderError<Err>::source(&self) -> core::
pub fn bitcoin_consensus_encoding::decode_from_read<T, R>(reader: R) -> core::result::Result<T, bitcoin_consensus_encoding::ReadError<<<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error>> where T: bitcoin_consensus_encoding::Decodable, R: std::io::BufRead
pub fn bitcoin_consensus_encoding::decode_from_read_unbuffered<T, R>(reader: R) -> core::result::Result<T, bitcoin_consensus_encoding::ReadError<<<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error>> where T: bitcoin_consensus_encoding::Decodable, R: std::io::Read
pub fn bitcoin_consensus_encoding::decode_from_read_unbuffered_with<T, R, const BUFFER_SIZE: usize>(reader: R) -> core::result::Result<T, bitcoin_consensus_encoding::ReadError<<<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error>> where T: bitcoin_consensus_encoding::Decodable, R: std::io::Read
-pub fn bitcoin_consensus_encoding::decode_from_slice<T>(bytes: &[u8]) -> core::result::Result<T, <<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error> where T: bitcoin_consensus_encoding::Decodable
+pub fn bitcoin_consensus_encoding::decode_from_slice<T: bitcoin_consensus_encoding::Decodable>(bytes: &[u8]) -> core::result::Result<T, bitcoin_consensus_encoding::DecodeError<<<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error>>
+pub fn bitcoin_consensus_encoding::decode_from_slice_unbounded<T>(bytes: &mut &[u8]) -> core::result::Result<T, <<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error> where T: bitcoin_consensus_encoding::Decodable
pub fn bitcoin_consensus_encoding::encode_to_vec<T>(object: &T) -> alloc::vec::Vec<u8> where T: bitcoin_consensus_encoding::Encodable + ?core::marker::Sized
pub fn bitcoin_consensus_encoding::encode_to_writer<T, W>(object: &T, writer: W) -> core::result::Result<(), std::io::error::Error> where T: bitcoin_consensus_encoding::Encodable + ?core::marker::Sized, W: std::io::Write
pub fn bitcoin_consensus_encoding::flush_to_vec<T>(encoder: &mut T) -> alloc::vec::Vec<u8> where T: bitcoin_consensus_encoding::Encoder + ?core::marker::Sized
diff --git a/api/consensus_encoding/alloc-only.txt b/api/consensus_encoding/alloc-only.txt
index 87dc09d9..022651f1 100644
--- a/api/consensus_encoding/alloc-only.txt
+++ b/api/consensus_encoding/alloc-only.txt
@@ -270,19 +270,33 @@ impl<A: core::fmt::Debug, B: core::fmt::Debug, C: core::fmt::Debug> core::fmt::D
impl<A: core::fmt::Debug, B: core::fmt::Debug, C: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::Encoder3<A, B, C>
impl<A: core::fmt::Debug, B: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::Decoder2Error<A, B>
impl<A: core::fmt::Debug, B: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::Encoder2<A, B>
+impl<Err: core::clone::Clone> core::clone::Clone for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err: core::clone::Clone> core::clone::Clone for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err: core::cmp::Eq> core::cmp::Eq for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err: core::cmp::Eq> core::cmp::Eq for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err: core::cmp::PartialEq> core::cmp::PartialEq for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err: core::cmp::PartialEq> core::cmp::PartialEq for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err> core::convert::From<core::convert::Infallible> for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err> core::convert::From<core::convert::Infallible> for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err> core::fmt::Display for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::fmt::Display
impl<Err> core::fmt::Display for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::fmt::Display + core::fmt::Debug
+impl<Err> core::marker::Freeze for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Freeze
impl<Err> core::marker::Freeze for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::marker::Freeze
+impl<Err> core::marker::Send for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Send
impl<Err> core::marker::Send for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::marker::Send
+impl<Err> core::marker::StructuralPartialEq for bitcoin_consensus_encoding::DecodeError<Err>
impl<Err> core::marker::StructuralPartialEq for bitcoin_consensus_encoding::VecDecoderError<Err>
+impl<Err> core::marker::Sync for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Sync
impl<Err> core::marker::Sync for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::marker::Sync
+impl<Err> core::marker::Unpin for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Unpin
impl<Err> core::marker::Unpin for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::marker::Unpin
+impl<Err> core::marker::UnsafeUnpin for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::UnsafeUnpin
impl<Err> core::marker::UnsafeUnpin for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::marker::UnsafeUnpin
+impl<Err> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::panic::unwind_safe::RefUnwindSafe
impl<Err> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::panic::unwind_safe::RefUnwindSafe
+impl<Err> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::panic::unwind_safe::UnwindSafe
impl<Err> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::VecDecoderError<Err> where Err: core::panic::unwind_safe::UnwindSafe
impl<T: bitcoin_consensus_encoding::Decodable> bitcoin_consensus_encoding::Decoder for bitcoin_consensus_encoding::VecDecoder<T>
impl<T: bitcoin_consensus_encoding::Decodable> bitcoin_consensus_encoding::VecDecoder<T>
@@ -324,6 +338,8 @@ impl<const N: usize> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consens
impl<const N: usize> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consensus_encoding::ArrayEncoder<N>
impl<const N: usize> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::ArrayDecoder<N>
impl<const N: usize> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::ArrayEncoder<N>
+pub bitcoin_consensus_encoding::DecodeError::Parse(Err)
+pub bitcoin_consensus_encoding::DecodeError::Unconsumed(bitcoin_consensus_encoding::decode::UnconsumedError)
pub bitcoin_consensus_encoding::Decoder2Error::First(A)
pub bitcoin_consensus_encoding::Decoder2Error::Second(B)
pub bitcoin_consensus_encoding::Decoder3Error::First(A)
@@ -356,6 +372,7 @@ pub const fn bitcoin_consensus_encoding::Encoder3<A, B, C>::new(enc_1: A, enc_2:
pub const fn bitcoin_consensus_encoding::Encoder4<A, B, C, D>::new(enc_1: A, enc_2: B, enc_3: C, enc_4: D) -> Self
pub const fn bitcoin_consensus_encoding::Encoder6<A, B, C, D, E, F>::new(enc_1: A, enc_2: B, enc_3: C, enc_4: D, enc_5: E, enc_6: F) -> Self
pub const fn bitcoin_consensus_encoding::VecDecoder<T>::new() -> Self
+pub enum bitcoin_consensus_encoding::DecodeError<Err>
pub enum bitcoin_consensus_encoding::Decoder2Error<A, B>
pub enum bitcoin_consensus_encoding::Decoder3Error<A, B, C>
pub enum bitcoin_consensus_encoding::Decoder4Error<A, B, C, D>
@@ -407,6 +424,10 @@ pub fn bitcoin_consensus_encoding::CompactSizeEncoder::fmt(&self, f: &mut core::
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::len(&self) -> usize
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::new(value: usize) -> Self
pub fn bitcoin_consensus_encoding::Decodable::decoder() -> Self::Decoder
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::clone(&self) -> bitcoin_consensus_encoding::DecodeError<Err>
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::eq(&self, other: &bitcoin_consensus_encoding::DecodeError<Err>) -> bool
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::from(never: core::convert::Infallible) -> Self
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::end(self) -> core::result::Result<Self::Output, Self::Error>
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::push_bytes(&mut self, bytes: &mut &[u8]) -> core::result::Result<bool, Self::Error>
@@ -488,7 +509,8 @@ pub fn bitcoin_consensus_encoding::VecDecoderError<Err>::clone(&self) -> bitcoin
pub fn bitcoin_consensus_encoding::VecDecoderError<Err>::eq(&self, other: &bitcoin_consensus_encoding::VecDecoderError<Err>) -> bool
pub fn bitcoin_consensus_encoding::VecDecoderError<Err>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_consensus_encoding::VecDecoderError<Err>::from(never: core::convert::Infallible) -> Self
-pub fn bitcoin_consensus_encoding::decode_from_slice<T>(bytes: &[u8]) -> core::result::Result<T, <<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error> where T: bitcoin_consensus_encoding::Decodable
+pub fn bitcoin_consensus_encoding::decode_from_slice<T: bitcoin_consensus_encoding::Decodable>(bytes: &[u8]) -> core::result::Result<T, bitcoin_consensus_encoding::DecodeError<<<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error>>
+pub fn bitcoin_consensus_encoding::decode_from_slice_unbounded<T>(bytes: &mut &[u8]) -> core::result::Result<T, <<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error> where T: bitcoin_consensus_encoding::Decodable
pub fn bitcoin_consensus_encoding::encode_to_vec<T>(object: &T) -> alloc::vec::Vec<u8> where T: bitcoin_consensus_encoding::Encodable + ?core::marker::Sized
pub fn bitcoin_consensus_encoding::flush_to_vec<T>(encoder: &mut T) -> alloc::vec::Vec<u8> where T: bitcoin_consensus_encoding::Encoder + ?core::marker::Sized
pub fn core::option::Option<T>::advance(&mut self) -> bool
diff --git a/api/consensus_encoding/no-features.txt b/api/consensus_encoding/no-features.txt
index 1902a5f5..0bea4d74 100644
--- a/api/consensus_encoding/no-features.txt
+++ b/api/consensus_encoding/no-features.txt
@@ -231,6 +231,20 @@ impl<A: core::fmt::Debug, B: core::fmt::Debug, C: core::fmt::Debug> core::fmt::D
impl<A: core::fmt::Debug, B: core::fmt::Debug, C: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::Encoder3<A, B, C>
impl<A: core::fmt::Debug, B: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::Decoder2Error<A, B>
impl<A: core::fmt::Debug, B: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::Encoder2<A, B>
+impl<Err: core::clone::Clone> core::clone::Clone for bitcoin_consensus_encoding::DecodeError<Err>
+impl<Err: core::cmp::Eq> core::cmp::Eq for bitcoin_consensus_encoding::DecodeError<Err>
+impl<Err: core::cmp::PartialEq> core::cmp::PartialEq for bitcoin_consensus_encoding::DecodeError<Err>
+impl<Err: core::fmt::Debug> core::fmt::Debug for bitcoin_consensus_encoding::DecodeError<Err>
+impl<Err> core::convert::From<core::convert::Infallible> for bitcoin_consensus_encoding::DecodeError<Err>
+impl<Err> core::fmt::Display for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::fmt::Display
+impl<Err> core::marker::Freeze for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Freeze
+impl<Err> core::marker::Send for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Send
+impl<Err> core::marker::StructuralPartialEq for bitcoin_consensus_encoding::DecodeError<Err>
+impl<Err> core::marker::Sync for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Sync
+impl<Err> core::marker::Unpin for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::Unpin
+impl<Err> core::marker::UnsafeUnpin for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::marker::UnsafeUnpin
+impl<Err> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::panic::unwind_safe::RefUnwindSafe
+impl<Err> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::DecodeError<Err> where Err: core::panic::unwind_safe::UnwindSafe
impl<T: bitcoin_consensus_encoding::Encodable> bitcoin_consensus_encoding::Encoder for bitcoin_consensus_encoding::SliceEncoder<'_, T>
impl<T: bitcoin_consensus_encoding::Encoder> bitcoin_consensus_encoding::Encoder for core::option::Option<T>
impl<const N: usize> bitcoin_consensus_encoding::ArrayDecoder<N>
@@ -259,6 +273,8 @@ impl<const N: usize> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consens
impl<const N: usize> core::panic::unwind_safe::RefUnwindSafe for bitcoin_consensus_encoding::ArrayEncoder<N>
impl<const N: usize> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::ArrayDecoder<N>
impl<const N: usize> core::panic::unwind_safe::UnwindSafe for bitcoin_consensus_encoding::ArrayEncoder<N>
+pub bitcoin_consensus_encoding::DecodeError::Parse(Err)
+pub bitcoin_consensus_encoding::DecodeError::Unconsumed(bitcoin_consensus_encoding::decode::UnconsumedError)
pub bitcoin_consensus_encoding::Decoder2Error::First(A)
pub bitcoin_consensus_encoding::Decoder2Error::Second(B)
pub bitcoin_consensus_encoding::Decoder3Error::First(A)
@@ -289,6 +305,7 @@ pub const fn bitcoin_consensus_encoding::Encoder2<A, B>::new(enc_1: A, enc_2: B)
pub const fn bitcoin_consensus_encoding::Encoder3<A, B, C>::new(enc_1: A, enc_2: B, enc_3: C) -> Self
pub const fn bitcoin_consensus_encoding::Encoder4<A, B, C, D>::new(enc_1: A, enc_2: B, enc_3: C, enc_4: D) -> Self
pub const fn bitcoin_consensus_encoding::Encoder6<A, B, C, D, E, F>::new(enc_1: A, enc_2: B, enc_3: C, enc_4: D, enc_5: E, enc_6: F) -> Self
+pub enum bitcoin_consensus_encoding::DecodeError<Err>
pub enum bitcoin_consensus_encoding::Decoder2Error<A, B>
pub enum bitcoin_consensus_encoding::Decoder3Error<A, B, C>
pub enum bitcoin_consensus_encoding::Decoder4Error<A, B, C, D>
@@ -330,6 +347,10 @@ pub fn bitcoin_consensus_encoding::CompactSizeEncoder::fmt(&self, f: &mut core::
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::len(&self) -> usize
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::new(value: usize) -> Self
pub fn bitcoin_consensus_encoding::Decodable::decoder() -> Self::Decoder
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::clone(&self) -> bitcoin_consensus_encoding::DecodeError<Err>
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::eq(&self, other: &bitcoin_consensus_encoding::DecodeError<Err>) -> bool
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub fn bitcoin_consensus_encoding::DecodeError<Err>::from(never: core::convert::Infallible) -> Self
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::end(self) -> core::result::Result<Self::Output, Self::Error>
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::push_bytes(&mut self, bytes: &mut &[u8]) -> core::result::Result<bool, Self::Error>
@@ -398,7 +419,8 @@ pub fn bitcoin_consensus_encoding::SliceEncoder<'e, T>::without_length_prefix(sl
pub fn bitcoin_consensus_encoding::UnexpectedEofError::clone(&self) -> bitcoin_consensus_encoding::UnexpectedEofError
pub fn bitcoin_consensus_encoding::UnexpectedEofError::eq(&self, other: &bitcoin_consensus_encoding::UnexpectedEofError) -> bool
pub fn bitcoin_consensus_encoding::UnexpectedEofError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub fn bitcoin_consensus_encoding::decode_from_slice<T>(bytes: &[u8]) -> core::result::Result<T, <<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error> where T: bitcoin_consensus_encoding::Decodable
+pub fn bitcoin_consensus_encoding::decode_from_slice<T: bitcoin_consensus_encoding::Decodable>(bytes: &[u8]) -> core::result::Result<T, bitcoin_consensus_encoding::DecodeError<<<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error>>
+pub fn bitcoin_consensus_encoding::decode_from_slice_unbounded<T>(bytes: &mut &[u8]) -> core::result::Result<T, <<T as bitcoin_consensus_encoding::Decodable>::Decoder as bitcoin_consensus_encoding::Decoder>::Error> where T: bitcoin_consensus_encoding::Decodable
pub fn core::option::Option<T>::advance(&mut self) -> bool
pub fn core::option::Option<T>::current_chunk(&self) -> &[u8]
pub macro bitcoin_consensus_encoding::encoder_newtype!
diff --git a/consensus_encoding/src/decode/mod.rs b/consensus_encoding/src/decode/mod.rs
index 06ee6dcf..175c66f2 100644
--- a/consensus_encoding/src/decode/mod.rs
+++ b/consensus_encoding/src/decode/mod.rs
@@ -2,6 +2,11 @@
//! Consensus Decoding Traits
+use core::convert::Infallible;
+use core::fmt;
+
+use internals::write_err;
+
pub mod decoders;
/// A Bitcoin object which can be consensus-decoded using a push decoder.
@@ -103,16 +108,40 @@ pub trait Decoder: Sized {
/// # Errors
///
/// Returns an error if the decoder encounters an error while
+/// parsing the data, including insufficient data. This function
+/// also errors if the provided slice is not completely consumed
+/// during decode.
+pub fn decode_from_slice<T: Decodable>(bytes: &[u8]) -> Result<T, DecodeError<<T::Decoder as Decoder>::Error>> {
+ let mut remaining = bytes;
+ let data = decode_from_slice_unbounded::<T>(&mut remaining)
+ .map_err(DecodeError::Parse)?;
+
+ if remaining.is_empty() {
+ Ok(data)
+ } else {
+ Err(DecodeError::Unconsumed(UnconsumedError()))
+ }
+}
+
+/// Decodes an object from an unbounded byte slice.
+///
+/// Unlike [`decode_from_slice`], this function will not error if the slice
+/// contains additional bytes that are not required to decode.
+/// Furthermore, the byte slice reference provided to this function will be
+/// updated based on the consumed data, returning the unconsumed bytes.
+///
+/// # Errors
+///
+/// Returns an error if the decoder encounters an error while
/// parsing the data, including insufficient data.
-pub fn decode_from_slice<T>(bytes: &[u8]) -> Result<T, <T::Decoder as Decoder>::Error>
+pub fn decode_from_slice_unbounded<T>(bytes: &mut &[u8]) -> Result<T, <T::Decoder as Decoder>::Error>
where
T: Decodable,
{
let mut decoder = T::decoder();
- let mut remaining = bytes;
- while !remaining.is_empty() {
- if !decoder.push_bytes(&mut remaining)? {
+ while !bytes.is_empty() {
+ if !decoder.push_bytes(bytes)? {
break;
}
}
@@ -285,3 +314,62 @@ where
impl<D> From<std::io::Error> for ReadError<D> {
fn from(e: std::io::Error) -> Self { Self::Io(e) }
}
+
+/// An error that can occur when decoding from a byte slice.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum DecodeError<Err> {
+ /// Provided slice failed to correctly decode as a type.
+ Parse(Err),
+ /// Bytes remained unconsumed after completing decoding.
+ Unconsumed(UnconsumedError),
+}
+
+impl<Err> From<Infallible> for DecodeError<Err> {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl<Err> fmt::Display for DecodeError<Err>
+where
+ Err: fmt::Display
+{
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::Parse(ref e) => write_err!(f, "error parsing encoded object"; e),
+ Self::Unconsumed(ref e) => write_err!(f, "unconsumed"; e)
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl<Err> std::error::Error for DecodeError<Err>
+where
+ Err: std::error::Error + 'static
+{
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::Parse(ref e) => Some(e),
+ Self::Unconsumed(ref e) => Some(e),
+ }
+ }
+}
+
+/// Bytes remained unconsumed after completing decoding.
+// This is just to give us the ability to add details in a
+// non-breaking way if we want to at some stage.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct UnconsumedError();
+
+impl From<Infallible> for UnconsumedError {
+ fn from(never: Infallible) -> Self { match never {} }
+}
+
+impl fmt::Display for UnconsumedError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "data not consumed entirely when decoding")
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for UnconsumedError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
+}
diff --git a/consensus_encoding/src/lib.rs b/consensus_encoding/src/lib.rs
index 7dd04784..53d4203f 100644
--- a/consensus_encoding/src/lib.rs
+++ b/consensus_encoding/src/lib.rs
@@ -71,7 +71,7 @@ pub use self::decode::decoders::{
pub use self::decode::{
decode_from_read, decode_from_read_unbuffered, decode_from_read_unbuffered_with, ReadError,
};
-pub use self::decode::{decode_from_slice, Decodable, Decoder};
+pub use self::decode::{decode_from_slice, decode_from_slice_unbounded, Decodable, Decoder, DecodeError};
pub use self::encode::encoders::{
ArrayEncoder, ArrayRefEncoder, BytesEncoder, CompactSizeEncoder, Encoder2, Encoder3, Encoder4,
Encoder6, SliceEncoder,
diff --git a/consensus_encoding/tests/decode.rs b/consensus_encoding/tests/decode.rs
index c76c1e6d..62c5bef3 100644
--- a/consensus_encoding/tests/decode.rs
+++ b/consensus_encoding/tests/decode.rs
@@ -12,7 +12,7 @@ use bitcoin_consensus_encoding::{
use bitcoin_consensus_encoding::{ByteVecDecoder, VecDecoder, VecDecoderError};
#[cfg(feature = "std")]
use bitcoin_consensus_encoding::{decode_from_read, decode_from_read_unbuffered, ReadError};
-use bitcoin_consensus_encoding::decode_from_slice;
+use bitcoin_consensus_encoding::{decode_from_slice, decode_from_slice_unbounded, DecodeError};
const EMPTY: &[u8] = &[];
@@ -271,9 +271,20 @@ fn decode_from_slice_unexpected_eof() {
fn decode_from_slice_extra_data() {
let data = [1, 2, 3, 4, 5];
let result: Result<TestArray, _> = decode_from_slice(&data);
+ assert!(result.is_err());
+ let err = result.unwrap_err();
+ assert!(matches!(err, DecodeError::Unconsumed(_)));
+}
+
+#[test]
+fn decode_from_slice_unbounded_extra_data() {
+ let data = [1, 2, 3, 4, 5];
+ let bytes = &mut data.as_slice();
+ let result: Result<TestArray, _> = decode_from_slice_unbounded(bytes);
assert!(result.is_ok());
let decoded = result.unwrap();
assert_eq!(decoded.0, [1, 2, 3, 4]);
+ assert_eq!(bytes.len(), 1);
}
#[test]
diff --git a/primitives/src/hex_codec.rs b/primitives/src/hex_codec.rs
index b4edf8d2..a36c5a3f 100644
--- a/primitives/src/hex_codec.rs
+++ b/primitives/src/hex_codec.rs
@@ -52,6 +52,7 @@ impl<T: Encodable + Decodable> HexPrimitive<'_, T> {
// Flush buffer to decoder
decoder
.push_bytes(&mut (buffer.as_slice()))
+ .map_err(encoding::DecodeError::Parse)
.map_err(ParsePrimitiveError::Decode)?;
index = 0;
}
@@ -62,9 +63,13 @@ impl<T: Encodable + Decodable> HexPrimitive<'_, T> {
// Flush remaining buffer to decoder
decoder
.push_bytes(&mut (&buffer[..index]))
+ .map_err(encoding::DecodeError::Parse)
.map_err(ParsePrimitiveError::Decode)?;
- decoder.end().map_err(ParsePrimitiveError::Decode)
+ decoder
+ .end()
+ .map_err(encoding::DecodeError::Parse)
+ .map_err(ParsePrimitiveError::Decode)
}
/// Writes an Encodable object to the given formatter in the requested case.
@@ -157,7 +162,7 @@ pub(crate) enum ParsePrimitiveError<T: Decodable> {
/// Encountered an invalid hex character
InvalidChar(hex_unstable::InvalidCharError),
/// A decode error from `consensus_encoding`
- Decode(<T::Decoder as Decoder>::Error),
+ Decode(encoding::DecodeError<<T::Decoder as encoding::Decoder>::Error>),
}
impl<T: Decodable> fmt::Debug for ParsePrimitiveError<T> {
Why this scored 34/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.