primitives: Implement ExactSizeEncoder for WitnessEncoder
What changed, and why it matters
This commit adds a missing 'ExactSizeEncoder' implementation for the WitnessEncoder in the rust-bitcoin library. Previously, WitnessEncoder only implemented the basic Encoder trait, even though the underlying encoders already supported exact-size reporting. The change uses an existing macro to automatically provide the exact-size capability and adds tests to verify the reported length matches the actual encoded bytes. There is no direct security vulnerability being fixed; it is a correctness and API-completeness improvement.
No urgent security action required. Treat as a normal library correctness update. Consumers using ExactSizeEncoder with WitnessEncoder can now rely on precise length reporting; consider updating if they previously worked around the missing impl.
Security signals we found
Missing trait implementation could have caused callers relying on ExactSizeEncoder to use fallback length estimation or fail to compile
No memory safety issue, no input validation bypass, no cryptographic weakness evident in diff
Change is API-completeness/correctness rather than a vulnerability patch
Evidence from the diff
The patch replaces the manual WitnessEncoder struct and Encoder impl with the encoder_newtype_exact! macro, which also derives ExactSizeEncoder. The underlying Encoder2<CompactSizeEncoder, BytesEncoder<’e>> already supported exact length, so the new impl simply exposes that capability. A test is added confirming witness.encoder().len() equals encoding::encode_to_vec(witness).len() across empty witnesses, zero-length elements, multi-element witnesses, and CompactSize one-byte boundaries for both element length (252/253 bytes) and element count (253 elements).
Changed components
bitcoin_primitives::witness::WitnessEncoderprimitives/src/witness.rsbitcoin_consensus_encoding::encode::ExactSizeEncoderInspect captured patch +55 / −20
diff --git a/primitives/api/all-features.txt b/primitives/api/all-features.txt
index fd5b884f..a1b5ef3d 100644
--- a/primitives/api/all-features.txt
+++ b/primitives/api/all-features.txt
@@ -5166,10 +5166,12 @@ pub unsafe fn bitcoin_primitives::witness::WitnessDecoder::clone_to_uninit(&self
impl<T> core::convert::From<T> for bitcoin_primitives::witness::WitnessDecoder
pub fn bitcoin_primitives::witness::WitnessDecoder::from(t: T) -> T
pub struct bitcoin_primitives::witness::WitnessDecoderError(_)
-pub struct bitcoin_primitives::witness::WitnessEncoder<'e>(_)
-impl bitcoin_consensus_encoding::encode::Encoder for bitcoin_primitives::witness::WitnessEncoder<'_>
-pub fn bitcoin_primitives::witness::WitnessEncoder<'_>::advance(&mut self) -> bitcoin_consensus_encoding::encode::EncoderStatus
-pub fn bitcoin_primitives::witness::WitnessEncoder<'_>::current_chunk(&self) -> &[u8]
+pub struct bitcoin_primitives::witness::WitnessEncoder<'e>(_, _)
+impl<'e> bitcoin_consensus_encoding::encode::Encoder for bitcoin_primitives::witness::WitnessEncoder<'e>
+pub fn bitcoin_primitives::witness::WitnessEncoder<'e>::advance(&mut self) -> bitcoin_consensus_encoding::encode::EncoderStatus
+pub fn bitcoin_primitives::witness::WitnessEncoder<'e>::current_chunk(&self) -> &[u8]
+impl<'e> bitcoin_consensus_encoding::encode::ExactSizeEncoder for bitcoin_primitives::witness::WitnessEncoder<'e>
+pub fn bitcoin_primitives::witness::WitnessEncoder<'e>::len(&self) -> usize
impl<'e> core::clone::Clone for bitcoin_primitives::witness::WitnessEncoder<'e>
pub fn bitcoin_primitives::witness::WitnessEncoder<'e>::clone(&self) -> bitcoin_primitives::witness::WitnessEncoder<'e>
impl<'e> core::fmt::Debug for bitcoin_primitives::witness::WitnessEncoder<'e>
diff --git a/primitives/api/alloc-only.txt b/primitives/api/alloc-only.txt
index 25f18fda..cf02e6a4 100644
--- a/primitives/api/alloc-only.txt
+++ b/primitives/api/alloc-only.txt
@@ -4844,10 +4844,12 @@ pub unsafe fn bitcoin_primitives::witness::WitnessDecoder::clone_to_uninit(&self
impl<T> core::convert::From<T> for bitcoin_primitives::witness::WitnessDecoder
pub fn bitcoin_primitives::witness::WitnessDecoder::from(t: T) -> T
pub struct bitcoin_primitives::witness::WitnessDecoderError(_)
-pub struct bitcoin_primitives::witness::WitnessEncoder<'e>(_)
-impl bitcoin_consensus_encoding::encode::Encoder for bitcoin_primitives::witness::WitnessEncoder<'_>
-pub fn bitcoin_primitives::witness::WitnessEncoder<'_>::advance(&mut self) -> bitcoin_consensus_encoding::encode::EncoderStatus
-pub fn bitcoin_primitives::witness::WitnessEncoder<'_>::current_chunk(&self) -> &[u8]
+pub struct bitcoin_primitives::witness::WitnessEncoder<'e>(_, _)
+impl<'e> bitcoin_consensus_encoding::encode::Encoder for bitcoin_primitives::witness::WitnessEncoder<'e>
+pub fn bitcoin_primitives::witness::WitnessEncoder<'e>::advance(&mut self) -> bitcoin_consensus_encoding::encode::EncoderStatus
+pub fn bitcoin_primitives::witness::WitnessEncoder<'e>::current_chunk(&self) -> &[u8]
+impl<'e> bitcoin_consensus_encoding::encode::ExactSizeEncoder for bitcoin_primitives::witness::WitnessEncoder<'e>
+pub fn bitcoin_primitives::witness::WitnessEncoder<'e>::len(&self) -> usize
impl<'e> core::clone::Clone for bitcoin_primitives::witness::WitnessEncoder<'e>
pub fn bitcoin_primitives::witness::WitnessEncoder<'e>::clone(&self) -> bitcoin_primitives::witness::WitnessEncoder<'e>
impl<'e> core::fmt::Debug for bitcoin_primitives::witness::WitnessEncoder<'e>
diff --git a/primitives/src/witness.rs b/primitives/src/witness.rs
index 9e294883..b13b21f6 100644
--- a/primitives/src/witness.rs
+++ b/primitives/src/witness.rs
@@ -13,7 +13,7 @@ use arbitrary::{Arbitrary, Unstructured};
use encoding::Decoder4;
use encoding::{
self, BytesEncoder, CompactSizeDecoder, CompactSizeEncoder, Decoder as _, DecoderStatus,
- Encoder2, EncoderStatus,
+ Encoder2,
};
#[cfg(feature = "hex")]
use hex::DecodeVariableLengthBytesError;
@@ -284,7 +284,7 @@ impl encoding::Encode for Witness {
let witness_elements =
BytesEncoder::without_length_prefix(&self.content[..self.indices_start]);
- WitnessEncoder(Encoder2::new(num_elements, witness_elements))
+ WitnessEncoder::new(Encoder2::new(num_elements, witness_elements))
}
}
@@ -292,16 +292,10 @@ impl encoding::Decode for Witness {
type Decoder = WitnessDecoder;
}
-/// The encoder for the [`Witness`] type.
-#[derive(Debug, Clone)]
-pub struct WitnessEncoder<'e>(Encoder2<CompactSizeEncoder, BytesEncoder<'e>>);
-
-impl encoding::Encoder for WitnessEncoder<'_> {
- #[inline]
- fn current_chunk(&self) -> &[u8] { self.0.current_chunk() }
-
- #[inline]
- fn advance(&mut self) -> EncoderStatus { self.0.advance() }
+encoding::encoder_newtype_exact! {
+ /// The encoder for the [`Witness`] type.
+ #[derive(Debug, Clone)]
+ pub struct WitnessEncoder<'e>(Encoder2<CompactSizeEncoder, BytesEncoder<'e>>);
}
/// The decoder for the [`Witness`] type.
@@ -1738,6 +1732,43 @@ mod test {
assert_eq!(witness.size(), encoding::encode_to_vec(&witness).len());
}
+ #[test]
+ #[cfg(feature = "alloc")]
+ fn witness_encoder_len_matches_encoding_length() {
+ use encoding::{Encode as _, ExactSizeEncoder as _};
+
+ // ExactSizeEncoder::len on a fresh encoder must be equal to total encoded length
+ fn assert_exact_len(witness: &Witness) {
+ let encoded_len = encoding::encode_to_vec(witness).len();
+ assert_eq!(witness.encoder().len(), encoded_len);
+ }
+
+ // empty witness: encodes as a single CompactSize zero byte.
+ assert_exact_len(&Witness::new());
+
+ // Single zero-length element.
+ let mut witness = Witness::new();
+ witness.push([0u8; 0]);
+ assert_exact_len(&witness);
+
+ // Multiple elements of differing sizes.
+ let witness =
+ Witness::from_iter([[1u8, 2, 3].as_slice(), [4u8, 5].as_slice(), [6u8].as_slice()]);
+ assert_exact_len(&witness);
+
+ // Element length crossing the CompactSize one-byte boundary (252 -> 253).
+ let mut witness = Witness::new();
+ witness.push([0u8; 252]);
+ assert_exact_len(&witness);
+ witness.push([0u8; 253]);
+ assert_exact_len(&witness);
+
+ // Element count crossing the CompactSize one-byte boundary: 253 elements
+ // makes the leading count prefix three bytes instead of one.
+ let witness = (0..253u32).map(|_| [0xABu8].as_slice()).collect::<Witness>();
+ assert_exact_len(&witness);
+ }
+
#[test]
fn decode_value_1_byte() {
// Check lower bound, upper bound.
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.