consensus_encoding: introduce tuple encoders
What changed, and why it matters
This commit adds new helper types for combining small data encoders into larger ones when writing Bitcoin data. It is purely a code organization and feature addition change. There is no indication it fixes a security bug or introduces a vulnerability.
No security action required. Review as normal code-quality change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces Encoder2/3/4/6 composite encoder structs in consensus_encoding/src/encode/encoders.rs and re-exports them. These wrap existing Encoder implementations to encode tuples of 2, 3, 4, or 6 objects sequentially. It also updates three Cargo.toml lint comments to allow struct_field_names. No existing behavior is modified; only new abstractions are added.
Changed components
consensus_encoding/src/encode/encoders.rsconsensus_encoding/src/lib.rsconsensus_encoding/Cargo.tomlprimitives/Cargo.tomlunits/Cargo.tomlInspect captured patch +102 / −4
diff --git a/consensus_encoding/Cargo.toml b/consensus_encoding/Cargo.toml
index 3951898b..899631d3 100644
--- a/consensus_encoding/Cargo.toml
+++ b/consensus_encoding/Cargo.toml
@@ -132,7 +132,7 @@ stable_sort_primitive = "warn"
str_split_at_newline = "warn"
string_add_assign = "warn"
struct_excessive_bools = "warn"
-struct_field_names = "warn"
+struct_field_names = "allow" # dumb
too_many_lines = "warn"
transmute_ptr_to_ptr = "warn"
trivially_copy_pass_by_ref = "warn"
diff --git a/consensus_encoding/src/encode/encoders.rs b/consensus_encoding/src/encode/encoders.rs
index d4ebb147..d1ec7b32 100644
--- a/consensus_encoding/src/encode/encoders.rs
+++ b/consensus_encoding/src/encode/encoders.rs
@@ -55,3 +55,99 @@ impl<'e, const N: usize> Encoder<'e> for ArrayEncoder<N> {
false
}
}
+
+/// An encoder which encodes two objects, one after the other.
+pub struct Encoder2<A, B> {
+ enc_idx: usize,
+ enc_1: A,
+ enc_2: B,
+}
+
+impl<A, B> Encoder2<A, B> {
+ /// Constructs a new composite encoder.
+ pub fn new(enc_1: A, enc_2: B) -> Self { Self { enc_idx: 0, enc_1, enc_2 } }
+}
+
+impl<'e, A: Encoder<'e>, B: Encoder<'e>> Encoder<'e> for Encoder2<A, B> {
+ fn current_chunk(&self) -> Option<&[u8]> {
+ if self.enc_idx == 0 {
+ self.enc_1.current_chunk()
+ } else {
+ self.enc_2.current_chunk()
+ }
+ }
+
+ fn advance(&mut self) -> bool {
+ if self.enc_idx == 0 {
+ if !self.enc_1.advance() {
+ self.enc_idx += 1;
+ }
+ true
+ } else {
+ self.enc_2.advance()
+ }
+ }
+}
+
+// For now we implement every higher encoder by composing Encoder2s, because
+// I'm lazy and this is trivial both to write and to review. For efficiency, we
+// should eventually unroll all of these. There are only a couple of them. The
+// unrolled versions should be macro-izable, if we want to do that.
+
+/// An encoder which encodes three objects, one after the other.
+pub struct Encoder3<A, B, C> {
+ inner: Encoder2<Encoder2<A, B>, C>,
+}
+
+impl<A, B, C> Encoder3<A, B, C> {
+ /// Constructs a new composite encoder.
+ pub fn new(enc_1: A, enc_2: B, enc_3: C) -> Self {
+ Self { inner: Encoder2::new(Encoder2::new(enc_1, enc_2), enc_3) }
+ }
+}
+
+impl<'e, A: Encoder<'e>, B: Encoder<'e>, C: Encoder<'e>> Encoder<'e> for Encoder3<A, B, C> {
+ fn current_chunk(&self) -> Option<&[u8]> { self.inner.current_chunk() }
+ fn advance(&mut self) -> bool { self.inner.advance() }
+}
+
+/// An encoder which encodes four objects, one after the other.
+pub struct Encoder4<A, B, C, D> {
+ inner: Encoder2<Encoder2<A, B>, Encoder2<C, D>>,
+}
+
+impl<A, B, C, D> Encoder4<A, B, C, D> {
+ /// Constructs a new composite encoder.
+ pub fn new(enc_1: A, enc_2: B, enc_3: C, enc_4: D) -> Self {
+ Self { inner: Encoder2::new(Encoder2::new(enc_1, enc_2), Encoder2::new(enc_3, enc_4)) }
+ }
+}
+
+impl<'e, A: Encoder<'e>, B: Encoder<'e>, C: Encoder<'e>, D: Encoder<'e>> Encoder<'e> for Encoder4<A, B, C, D> {
+ fn current_chunk(&self) -> Option<&[u8]> { self.inner.current_chunk() }
+ fn advance(&mut self) -> bool { self.inner.advance() }
+}
+
+/// An encoder which encodes six objects, one after the other.
+pub struct Encoder6<A, B, C, D, E, F> {
+ inner: Encoder2<Encoder3<A, B, C>, Encoder3<D, E, F>>,
+}
+
+impl<A, B, C, D, E, F> Encoder6<A, B, C, D, E, F> {
+ /// Constructs a new composite encoder.
+ pub fn new(enc_1: A, enc_2: B, enc_3: C, enc_4: D, enc_5: E, enc_6: F) -> Self {
+ Self {
+ inner: Encoder2::new(
+ Encoder3::new(enc_1, enc_2, enc_3),
+ Encoder3::new(enc_4, enc_5, enc_6),
+ ),
+ }
+ }
+}
+
+impl<'e, A: Encoder<'e>, B: Encoder<'e>, C: Encoder<'e>, D: Encoder<'e>, E: Encoder<'e>, F: Encoder<'e>> Encoder<'e>
+ for Encoder6<A, B, C, D, E, F>
+{
+ fn current_chunk(&self) -> Option<&[u8]> { self.inner.current_chunk() }
+ fn advance(&mut self) -> bool { self.inner.advance() }
+}
diff --git a/consensus_encoding/src/lib.rs b/consensus_encoding/src/lib.rs
index 17aee65b..8f7e7551 100644
--- a/consensus_encoding/src/lib.rs
+++ b/consensus_encoding/src/lib.rs
@@ -16,5 +16,7 @@
mod encode;
-pub use self::encode::encoders::{ArrayEncoder, BytesEncoder};
+pub use self::encode::encoders::{
+ ArrayEncoder, BytesEncoder, Encoder2, Encoder3, Encoder4, Encoder6,
+};
pub use self::encode::{Encodable, Encoder};
diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml
index f273e6d1..4709c975 100644
--- a/primitives/Cargo.toml
+++ b/primitives/Cargo.toml
@@ -145,7 +145,7 @@ stable_sort_primitive = "warn"
str_split_at_newline = "warn"
string_add_assign = "warn"
struct_excessive_bools = "warn"
-struct_field_names = "allow" # TODO: Triggers warning for `witness_elements`.
+struct_field_names = "allow" # dumb
too_many_lines = "warn"
transmute_ptr_to_ptr = "warn"
trivially_copy_pass_by_ref = "warn"
diff --git a/units/Cargo.toml b/units/Cargo.toml
index 64a2877a..428c6bf3 100644
--- a/units/Cargo.toml
+++ b/units/Cargo.toml
@@ -143,7 +143,7 @@ stable_sort_primitive = "warn"
str_split_at_newline = "warn"
string_add_assign = "warn"
struct_excessive_bools = "warn"
-struct_field_names = "warn"
+struct_field_names = "allow" # dumb
too_many_lines = "warn"
transmute_ptr_to_ptr = "warn"
trivially_copy_pass_by_ref = "warn"
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.