Merge rust-bitcoin/rust-bitcoin#6897: consensus_encoding: fix overcounting bug in the composite encoders exact size len calculation
What changed, and why it matters
This commit fixes a counting bug in a Rust Bitcoin library. The library has combined encoders that bundle multiple pieces of data together. A method called len() is supposed to report how many bytes are left to encode, but it was incorrectly reporting the total size of all pieces even after some pieces were already processed. The fix makes it only count the pieces that have not yet been processed. The bug could cause callers that rely on len() for buffer sizing or progress checks to over-allocate or misjudge remaining work, but it is a correctness fix rather than a clear-cut security vulnerability.
Upgrade to a version of rust-bitcoin consensus_encoding that includes this fix. If you maintain downstream code that calls ExactSizeEncoder::len() on composite encoders, review any logic that used the previous overcounted value for buffer allocation, serialization bounds, or progress tracking. No immediate exploit is evident, but correctness-sensitive consensus code should not rely on violated contracts.
Security signals we found
Violation of documented API contract (ExactSizeEncoder::len)
Potential incorrect buffer-size or progress decisions by downstream callers
Composite encoder state not fully isolated in size reporting before fix
Regression test added to prevent reintroduction
Evidence from the diff
The ExactSizeEncoder::len() contract requires returning the number of bytes remaining in the encoder. Composite encoders Encoder2/3/4/6 violated this by unconditionally summing all sub-encoder lengths via 0 $(+ self.$enc_field.len())*. The patch changes the macro-generated implementation to only add lengths for sub-encoders whose index is greater than or equal to the current index (self.cur_idx <= $enc_idx), so exhausted sub-encoders no longer contribute. A regression test verifies that after advancing past the first sub-encoder, len() returns only the second sub-encoder’s remaining bytes.
Changed components
consensus_encoding/src/encode/encoders.rsEncoder2/Encoder3/Encoder4/Encoder6 composite encodersExactSizeEncoder::len implementationInspect captured patch +19 / −1
### consensus_encoding/CHANGELOG.md
@@ -2,6 +2,10 @@
## [Unreleased]
+- Fix `ExactSizeEncoder::len()` overcounting in composite encoders (`Encoder2`/`3`/`4`/`6`) after
+ sub-encoders are exhausted. The `len()` method now correctly reports only the remaining bytes
+ rather than unconditionally summing all sub-encoder lengths.
+
## [1.2.0] - 2026-08-11
- Expose lower level encoder/decoder interfaces [#6690](https://github.com/rust-bitcoin/rust-bitcoin/pull/6690)
### consensus_encoding/src/encode/encoders.rs
@@ -244,7 +244,9 @@ macro_rules! define_encoder_n {
{
#[inline]
fn len(&self) -> usize {
- 0 $(+ self.$enc_field.len())*
+ let mut total = 0;
+ $(if self.cur_idx <= $enc_idx { total += self.$enc_field.len(); })*
+ total
}
}
};
### consensus_encoding/tests/encode.rs
@@ -569,3 +569,15 @@ fn drain_hex_multi_chunk() {
let hex = bitcoin_consensus_encoding::drain_to_hex(encoder, hex::Case::Lower);
assert_eq!(hex, "deadbeef");
}
+
+#[test]
+fn check_encoder_composite_exact_size_len_contract() {
+ // After advancing past the first sub-encoder, len() should reflect
+ // only the remaining bytes from the second sub-encoder.
+ let mut e = Encoder2::new(
+ ArrayEncoder::without_length_prefix([0xAA]),
+ ArrayEncoder::without_length_prefix([0xBB, 0xCC]),
+ );
+ let _ = e.advance();
+ assert_eq!(e.len(), 2, "len() should be 2 after first encoder exhausted");
+}Why this scored 37/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.