consensus_encoding: add a u64 constructor to compact size encoder
What changed, and why it matters
This commit adds a new way to create a compact-size encoder using a u64 value, and slightly tightens the existing usize constructor. It is a routine API improvement, not a security fix. There is no evidence in the commit or supplied references that this resolves any vulnerability.
No security action required. Review as normal code-quality/API change.
Security signals we found
No security-relevant signals detected in the commit diff or message.
Change is an API addition/refactor, not a patch for a known vulnerability.
Evidence from the diff
The patch introduces CompactSizeEncoder::new_u64(value: u64) for encoding arbitrary u64 compact-size integers (e.g., service flags) on all platforms. It also changes CompactSizeEncoder::new(value: usize) to explicitly convert via u64::try_from and saturate to u64::MAX, and moves the public re-export of CompactSizeEncoder from encode::encoders to the crate root. The internal encode() function now takes u64 instead of usize. Tests are updated to exercise u64 values directly. No bug fix, bounds-check bypass, or vulnerability remediation is visible in the diff.
Changed components
consensus_encoding/src/compact_size.rsconsensus_encoding/src/encode/encoders.rsconsensus_encoding/src/lib.rsconsensus_encoding/tests/encode.rsInspect captured patch +49 / −21
diff --git a/consensus_encoding/src/compact_size.rs b/consensus_encoding/src/compact_size.rs
index 2c9b9817..54995591 100644
--- a/consensus_encoding/src/compact_size.rs
+++ b/consensus_encoding/src/compact_size.rs
@@ -27,14 +27,35 @@ pub struct CompactSizeEncoder {
}
impl CompactSizeEncoder {
- /// Constructs a new `CompactSizeEncoder`.
+ /// Constructs a new `CompactSizeEncoder` for a length prefix.
///
- /// Encodings are defined only for the range of u64. On systems where usize is
- /// larger than u64, it will be possible to call this method with out-of-range
- /// values. In such cases we will ignore the passed value and encode [`u64::MAX`].
- /// But even on such exotic systems, we expect users to pass the length of an
- /// in-memory object, meaning that such large values are impossible to obtain.
- pub fn new(value: usize) -> Self { Self { buf: Some(Self::encode(value)) } }
+ /// **This is the constructor you should use in almost all cases.**
+ ///
+ /// The `usize` type is the natural Rust type for lengths and collection sizes,
+ /// which is the dominant use case for compact size encoding in the Bitcoin
+ /// protocol. Prefer this constructor whenever you are encoding the length of
+ /// a collection or a byte slice.
+ ///
+ /// Compact size encodings are defined only over the `u64` range. On exotic
+ /// platforms where `usize` is wider than 64 bits the value will be saturated
+ /// to [`u64::MAX`], but in practice any in-memory length that could actually
+ /// be passed here is well within the `u64` range.
+ ///
+ /// If you need to encode an arbitrary `u64` integer that is not a length
+ /// prefix, use [`Self::new_u64`] instead.
+ pub fn new(value: usize) -> Self {
+ Self { buf: Some(Self::encode(u64::try_from(value).unwrap_or(u64::MAX))) }
+ }
+
+ /// Constructs a new `CompactSizeEncoder` for an arbitrary `u64` integer.
+ ///
+ /// **Prefer [`Self::new`] unless you are encoding a non-length integer.**
+ ///
+ /// A small number of fields in the Bitcoin protocol are compact-size-encoded
+ /// integers that are not collection lengths (e.g. service flags). Use this
+ /// constructor for those cases, where the natural type of the value is `u64`
+ /// rather than `usize`.
+ pub fn new_u64(value: u64) -> Self { Self { buf: Some(Self::encode(value)) } }
/// Returns the number of bytes used to encode this `CompactSize` value.
///
@@ -56,7 +77,7 @@ impl CompactSizeEncoder {
/// Encodes `CompactSize` without allocating.
#[inline]
- fn encode(value: usize) -> ArrayVec<u8, SIZE> {
+ fn encode(value: u64) -> ArrayVec<u8, SIZE> {
let mut res = ArrayVec::<u8, SIZE>::new();
match value {
0..=0xFC => {
@@ -312,9 +333,8 @@ mod tests {
#[test]
fn encoded_value_1_byte() {
// Check lower bound, upper bound (and implicitly endian-ness).
- for v in [0x00, 0x01, 0x02, 0xFA, 0xFB, 0xFC] {
- let v = v as usize;
- assert_eq!(CompactSizeEncoder::encoded_size(v), 1);
+ for v in [0x00u64, 0x01, 0x02, 0xFA, 0xFB, 0xFC] {
+ assert_eq!(CompactSizeEncoder::encoded_size(v as usize), 1);
// Should be encoded as the value as a u8.
let want = [v as u8];
let got = CompactSizeEncoder::encode(v);
@@ -328,8 +348,8 @@ mod tests {
$(
#[test]
fn $test_name() {
- let value = $value as usize; // Because default integer type is i32.
- assert_eq!(CompactSizeEncoder::encoded_size(value), $size);
+ let value = $value as u64; // Because default integer type is i32.
+ assert_eq!(CompactSizeEncoder::encoded_size(value as usize), $size);
let got = CompactSizeEncoder::encode(value);
assert_eq!(got.as_slice().len(), $size); // sanity check
assert_eq!(got.as_slice(), &$want);
@@ -349,12 +369,11 @@ mod tests {
encoded_value_5_byte_upper_bound, 5, 0xFFFF_FFFF, [0xFE, 0xFF, 0xFF, 0xFF, 0xFF];
}
- // Only test on platforms with a usize that is 64 bits
+ // 9-byte encoding requires values above u32::MAX which don't fit in usize on 32-bit platforms.
#[cfg(target_pointer_width = "64")]
check_encode! {
- // 9 byte encoding.
- encoded_value_9_byte_lower_bound, 9, 0x0000_0001_0000_0000, [0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00];
- encoded_value_9_byte_endianness, 9, 0x0123_4567_89AB_CDEF, [0xFF, 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01];
+ encoded_value_9_byte_lower_bound, 9, 0x0000_0001_0000_0000u64, [0xFF, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00];
+ encoded_value_9_byte_endianness, 9, 0x0123_4567_89AB_CDEFu64, [0xFF, 0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01];
encoded_value_9_byte_upper_bound, 9, u64::MAX, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
}
diff --git a/consensus_encoding/src/encode/encoders.rs b/consensus_encoding/src/encode/encoders.rs
index 5549b22b..8139ad8a 100644
--- a/consensus_encoding/src/encode/encoders.rs
+++ b/consensus_encoding/src/encode/encoders.rs
@@ -15,7 +15,6 @@
use core::fmt;
use super::{Encodable, Encoder, ExactSizeEncoder};
-pub use crate::compact_size::CompactSizeEncoder;
/// An encoder for a single byte slice.
#[derive(Debug, Clone)]
diff --git a/consensus_encoding/src/lib.rs b/consensus_encoding/src/lib.rs
index 1f3cb203..5042ba46 100644
--- a/consensus_encoding/src/lib.rs
+++ b/consensus_encoding/src/lib.rs
@@ -60,7 +60,7 @@ mod compact_size;
mod decode;
mod encode;
-pub use self::compact_size::{CompactSizeDecoder, CompactSizeDecoderError};
+pub use self::compact_size::{CompactSizeDecoder, CompactSizeDecoderError, CompactSizeEncoder};
#[cfg(feature = "alloc")]
pub use self::compact_size::LengthPrefixExceedsMaxError;
pub use self::decode::decoders::{
@@ -75,8 +75,8 @@ pub use self::decode::{
};
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,
+ ArrayEncoder, ArrayRefEncoder, BytesEncoder, Encoder2, Encoder3, Encoder4, Encoder6,
+ SliceEncoder,
};
#[cfg(feature = "alloc")]
pub use self::encode::{encode_to_vec, flush_to_vec};
diff --git a/consensus_encoding/tests/encode.rs b/consensus_encoding/tests/encode.rs
index 75e0bdcb..61045713 100644
--- a/consensus_encoding/tests/encode.rs
+++ b/consensus_encoding/tests/encode.rs
@@ -650,6 +650,16 @@ fn encode_compact_size() {
assert!(!e.advance());
assert!(e.current_chunk().is_empty());
}
+
+ // new_u64 works on all platforms, no guard needed.
+ let mut e = CompactSizeEncoder::new_u64(0x0000_F0F0_F0F0_F0E0u64);
+ assert_eq!(
+ e.current_chunk(),
+ &[0xFF, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00][..]
+ );
+ assert_eq!(e.len(), 9);
+ assert!(!e.advance());
+ assert!(e.current_chunk().is_empty());
}
#[test]
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.