consensus_encoding: introduce encoder traits and byte encoder
What changed, and why it matters
This commit adds new Rust traits and helper types for encoding Bitcoin data into bytes. It is purely a new internal API design change with no existing code using it yet, and it does not change any behavior that could affect security.
No security action needed. Review as normal code-quality/API-design change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces Encodable and Encoder<'e> traits plus BytesEncoder and ArrayEncoder newtypes in a new consensus_encoding crate. These are foundational abstractions for future consensus serialization work. The diff only adds new code; it does not modify existing serialization logic, parse untrusted input, or introduce unsafe Rust. The lifetime 'e on Encoder is a forward-compatibility marker for Rust GAT features.
Changed components
consensus_encoding/src/encode/encoders.rsconsensus_encoding/src/encode/mod.rsconsensus_encoding/src/lib.rsInspect captured patch +102 / −0
diff --git a/consensus_encoding/src/encode/encoders.rs b/consensus_encoding/src/encode/encoders.rs
new file mode 100644
index 00000000..d4ebb147
--- /dev/null
+++ b/consensus_encoding/src/encode/encoders.rs
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Collection of "standard encoders".
+//!
+//! These encoders should not be used directly. Instead, when implementing the
+//! [`super::Encodable`] trait on a type, you should define a newtype around one
+//! or more of these encoders, and pass through the [`Encoder`] implementation
+//! to your newtype. This avoids leaking encoding implementation details to the
+//! users of your type.
+//!
+
+/// An encoder for a single byte slice.
+use super::Encoder;
+
+/// An encoder for a single byte slice.
+pub struct BytesEncoder<'sl> {
+ sl: Option<&'sl [u8]>,
+}
+
+impl<'sl> BytesEncoder<'sl> {
+ /// Constructs a byte encoder which encodes the given byte slice, with no length
+ /// prefix.
+ pub fn without_length_prefix(sl: &'sl [u8]) -> Self { Self { sl: Some(sl) } }
+}
+
+impl<'e, 'sl> Encoder<'e> for BytesEncoder<'sl> {
+ fn current_chunk(&self) -> Option<&[u8]> {
+ self.sl
+ }
+
+ fn advance(&mut self)-> bool {
+ self.sl = None;
+ false
+ }
+}
+
+/// An encoder for a single array.
+pub struct ArrayEncoder<const N: usize> {
+ arr: Option<[u8; N]>,
+}
+
+impl<const N: usize> ArrayEncoder<N> {
+ /// Constructs an encoder which encodes the array with no length prefix.
+ pub fn without_length_prefix(arr: [u8; N]) -> Self { Self { arr: Some(arr) } }
+}
+
+impl<'e, const N: usize> Encoder<'e> for ArrayEncoder<N> {
+ fn current_chunk(&self) -> Option<&[u8]> {
+ self.arr.as_ref().map(|x| &x[..])
+ }
+
+
+ fn advance(&mut self)-> bool {
+ self.arr = None;
+ false
+ }
+}
diff --git a/consensus_encoding/src/encode/mod.rs b/consensus_encoding/src/encode/mod.rs
new file mode 100644
index 00000000..1801bab0
--- /dev/null
+++ b/consensus_encoding/src/encode/mod.rs
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: CC0-1.0
+
+//! Consensus Encoding Traits
+
+pub mod encoders;
+
+/// A Bitcoin object which can be consensus-encoded.
+///
+/// To encode something, use the [`Self::encoder`] method to obtain a
+/// [`Self::Encoder`], which will behave like an iterator yielding
+/// byte slices.
+pub trait Encodable {
+ /// The encoder associated with this type. Conceptually, the encoder is like
+ /// an iterator which yields byte slices.
+ type Encoder<'s>: Encoder<'s> where Self: 's;
+
+ /// Constructs a "default encoder" for the type.
+ fn encoder(&self) -> Self::Encoder<'_>;
+}
+
+/// An encoder for a consensus-encodable object.
+pub trait Encoder<'e> {
+ /// Yields the current encoded byteslice.
+ ///
+ /// Will always return the same value until [`Self::advance`] is called.
+ ///
+ /// Returns `None` if the encoder is exhausted. Once this method returns `None`,
+ /// all subsequent calls will return `None`.
+ fn current_chunk(&self) -> Option<&[u8]>;
+
+ /// Moves the encoder to its next state.
+ ///
+ /// Does not need to be called when the encoder is first created. (In fact, if it
+ /// is called, this will discard the first chunk of encoded data.)
+ ///
+ /// Returns `true` if the the next call to [`Self::current_chunk`] will return data.
+ /// Returns `false` otherwise. It is fine to ignore the return value of this method
+ /// and just call `current_chunk` to see if it works.
+ fn advance(&mut self) -> bool;
+}
diff --git a/consensus_encoding/src/lib.rs b/consensus_encoding/src/lib.rs
index 6ea4435f..17aee65b 100644
--- a/consensus_encoding/src/lib.rs
+++ b/consensus_encoding/src/lib.rs
@@ -13,3 +13,8 @@
#![warn(missing_docs)]
#![warn(deprecated_in_future)]
#![doc(test(attr(warn(unused))))]
+
+mod encode;
+
+pub use self::encode::encoders::{ArrayEncoder, BytesEncoder};
+pub use self::encode::{Encodable, Encoder};
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.