Use usize for CompactSizeEncoder::new
What changed, and why it matters
This commit is a small API cleanup, not a security fix. It changes a Bitcoin data-encoding helper so it accepts ordinary memory sizes (usize) instead of a special internal type. The only behavioral change is on hypothetical future 128-bit computers, where impossibly large values would be encoded as the maximum allowed u64 value. On all current systems the output is unchanged.
No security action required; treat as a normal API refactor. Review downstream callers if they relied on passing non-usize types to CompactSizeEncoder::new.
Security signals we found
Public API no longer exposes internal ToU64 trait
Defensive clamping of out-of-range usize values to u64::MAX on hypothetical >64-bit platforms
No memory safety, cryptographic, or consensus-critical logic changed
Evidence from the diff
CompactSizeEncoder::new previously took impl ToU64 from bitcoin_internals, which is not allowed in the public API of the consensus_encoding crate. The patch changes the parameter to usize and uses try_into().unwrap_or(u64::MAX) before calling compact_size::encode. This removes an internal trait from the public API and adds a defensive fallback for platforms where usize > 64 bits. Existing callers in transaction.rs are updated to drop unnecessary as u64 casts. Tests are updated and a conditional test for >64-bit usize is added.
Changed components
bitcoin_consensus_encoding::CompactSizeEncoderconsensus_encoding/src/encode/encoders.rsprimitives/src/transaction.rsInspect captured patch +42 / −21
diff --git a/api/consensus_encoding/all-features.txt b/api/consensus_encoding/all-features.txt
index 916422ba..89633094 100644
--- a/api/consensus_encoding/all-features.txt
+++ b/api/consensus_encoding/all-features.txt
@@ -326,7 +326,7 @@ pub fn bitcoin_consensus_encoding::CompactSizeDecoderError::eq(&self, other: &bi
pub fn bitcoin_consensus_encoding::CompactSizeDecoderError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::advance(&mut self) -> bool
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::current_chunk(&self) -> &[u8]
-pub fn bitcoin_consensus_encoding::CompactSizeEncoder::new(value: impl bitcoin_internals::ToU64) -> Self
+pub fn bitcoin_consensus_encoding::CompactSizeEncoder::new(value: usize) -> Self
pub fn bitcoin_consensus_encoding::Decodable::decoder() -> Self::Decoder
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::end(self) -> core::result::Result<Self::Output, Self::Error>
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::push_bytes(&mut self, bytes: &mut &[u8]) -> core::result::Result<bool, Self::Error>
diff --git a/api/consensus_encoding/alloc-only.txt b/api/consensus_encoding/alloc-only.txt
index 418a7742..ebef75d3 100644
--- a/api/consensus_encoding/alloc-only.txt
+++ b/api/consensus_encoding/alloc-only.txt
@@ -303,7 +303,7 @@ pub fn bitcoin_consensus_encoding::CompactSizeDecoderError::eq(&self, other: &bi
pub fn bitcoin_consensus_encoding::CompactSizeDecoderError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::advance(&mut self) -> bool
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::current_chunk(&self) -> &[u8]
-pub fn bitcoin_consensus_encoding::CompactSizeEncoder::new(value: impl bitcoin_internals::ToU64) -> Self
+pub fn bitcoin_consensus_encoding::CompactSizeEncoder::new(value: usize) -> Self
pub fn bitcoin_consensus_encoding::Decodable::decoder() -> Self::Decoder
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::end(self) -> core::result::Result<Self::Output, Self::Error>
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::push_bytes(&mut self, bytes: &mut &[u8]) -> core::result::Result<bool, Self::Error>
diff --git a/api/consensus_encoding/no-features.txt b/api/consensus_encoding/no-features.txt
index 1b14319b..4b09a48b 100644
--- a/api/consensus_encoding/no-features.txt
+++ b/api/consensus_encoding/no-features.txt
@@ -237,7 +237,7 @@ pub fn bitcoin_consensus_encoding::CompactSizeDecoderError::eq(&self, other: &bi
pub fn bitcoin_consensus_encoding::CompactSizeDecoderError::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::advance(&mut self) -> bool
pub fn bitcoin_consensus_encoding::CompactSizeEncoder::current_chunk(&self) -> &[u8]
-pub fn bitcoin_consensus_encoding::CompactSizeEncoder::new(value: impl bitcoin_internals::ToU64) -> Self
+pub fn bitcoin_consensus_encoding::CompactSizeEncoder::new(value: usize) -> Self
pub fn bitcoin_consensus_encoding::Decodable::decoder() -> Self::Decoder
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::end(self) -> core::result::Result<Self::Output, Self::Error>
pub fn bitcoin_consensus_encoding::Decoder2<A, B>::push_bytes(&mut self, bytes: &mut &[u8]) -> core::result::Result<bool, Self::Error>
diff --git a/consensus_encoding/src/encode/encoders.rs b/consensus_encoding/src/encode/encoders.rs
index 0aac982a..b31207b2 100644
--- a/consensus_encoding/src/encode/encoders.rs
+++ b/consensus_encoding/src/encode/encoders.rs
@@ -12,7 +12,7 @@
//!
use internals::array_vec::ArrayVec;
-use internals::{compact_size, ToU64};
+use internals::compact_size;
use super::{Encodable, Encoder};
@@ -226,7 +226,16 @@ pub struct CompactSizeEncoder {
impl CompactSizeEncoder {
/// Constructs a new `CompactSizeEncoder`.
- pub fn new(value: impl ToU64) -> Self { Self { buf: Some(compact_size::encode(value)) } }
+ ///
+ /// 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 {
+ let enc_value = value.try_into().unwrap_or(u64::MAX);
+ Self { buf: Some(compact_size::encode(enc_value)) }
+ }
}
impl Encoder for CompactSizeEncoder {
@@ -554,42 +563,54 @@ mod tests {
#[test]
fn encode_compact_size() {
// 1-byte
- let mut e = CompactSizeEncoder::new(0x10u64);
+ let mut e = CompactSizeEncoder::new(0x10usize);
assert_eq!(e.current_chunk(), &[0x10][..]);
assert!(!e.advance());
assert!(e.current_chunk().is_empty());
- let mut e = CompactSizeEncoder::new(0xFCu64);
+ let mut e = CompactSizeEncoder::new(0xFCusize);
assert_eq!(e.current_chunk(), &[0xFC][..]);
assert!(!e.advance());
assert!(e.current_chunk().is_empty());
// 0xFD + u16
- let mut e = CompactSizeEncoder::new(0x00FDu64);
+ let mut e = CompactSizeEncoder::new(0x00FDusize);
assert_eq!(e.current_chunk(), &[0xFD, 0xFD, 0x00][..]);
assert!(!e.advance());
assert!(e.current_chunk().is_empty());
- let mut e = CompactSizeEncoder::new(0x0FFFu64);
+ let mut e = CompactSizeEncoder::new(0x0FFFusize);
assert_eq!(e.current_chunk(), &[0xFD, 0xFF, 0x0F][..]);
assert!(!e.advance());
assert!(e.current_chunk().is_empty());
// 0xFE + u32
- let mut e = CompactSizeEncoder::new(0x0001_0000u64);
+ let mut e = CompactSizeEncoder::new(0x0001_0000usize);
assert_eq!(e.current_chunk(), &[0xFE, 0x00, 0x00, 0x01, 0x00][..]);
assert!(!e.advance());
assert!(e.current_chunk().is_empty());
- let mut e = CompactSizeEncoder::new(0x0F0F_0F0Fu64);
+ let mut e = CompactSizeEncoder::new(0x0F0F_0F0Fusize);
assert_eq!(e.current_chunk(), &[0xFE, 0x0F, 0x0F, 0x0F, 0x0F][..]);
assert!(!e.advance());
assert!(e.current_chunk().is_empty());
// 0xFF + u64
- let mut e = CompactSizeEncoder::new(0x0000_F0F0_F0F0_F0E0u64);
- assert_eq!(e.current_chunk(), &[0xFF, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00][..]);
- assert!(!e.advance());
- assert!(e.current_chunk().is_empty());
+ // This test only runs on systems with >= 64 bit usize.
+ if core::mem::size_of::<usize>() >= 8 {
+ let mut e = CompactSizeEncoder::new(0x0000_F0F0_F0F0_F0E0u64 as usize);
+ assert_eq!(e.current_chunk(), &[0xFF, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0x00, 0x00][..]);
+ assert!(!e.advance());
+ assert!(e.current_chunk().is_empty());
+ }
+
+ // > u64::MAX encodes as u64::MAX.
+ // This test only runs on systems with > 64 bit usize.
+ if core::mem::size_of::<usize>() > 8 {
+ let mut e = CompactSizeEncoder::new((u128::from(u64::MAX) + 5) as usize);
+ assert_eq!(e.current_chunk(), &[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF][..]);
+ assert!(!e.advance());
+ assert!(e.current_chunk().is_empty());
+ }
}
}
diff --git a/consensus_encoding/tests/encode.rs b/consensus_encoding/tests/encode.rs
index ed887e33..f86b63ba 100644
--- a/consensus_encoding/tests/encode.rs
+++ b/consensus_encoding/tests/encode.rs
@@ -153,19 +153,19 @@ fn encode_slice_encoder_mixed_empty_and_data() {
#[test]
fn encode_compact_size_boundary_values() {
// Test CompactSizeEncoder with boundary values.
- let mut encoder = CompactSizeEncoder::new(252u32);
+ let mut encoder = CompactSizeEncoder::new(252usize);
assert_eq!(encoder.current_chunk(), &[252]);
assert!(!encoder.advance());
- let mut encoder = CompactSizeEncoder::new(253u32);
+ let mut encoder = CompactSizeEncoder::new(253usize);
assert_eq!(encoder.current_chunk(), &[0xFD, 253, 0]);
assert!(!encoder.advance());
- let mut encoder = CompactSizeEncoder::new(0x10000u32);
+ let mut encoder = CompactSizeEncoder::new(0x10000usize);
assert_eq!(encoder.current_chunk(), &[0xFE, 0, 0, 1, 0]);
assert!(!encoder.advance());
- let mut encoder = CompactSizeEncoder::new(0u32);
+ let mut encoder = CompactSizeEncoder::new(0usize);
assert_eq!(encoder.current_chunk(), &[0]);
assert!(!encoder.advance());
}
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index d7312e99..593b2740 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -330,11 +330,11 @@ impl Encodable for Transaction {
fn encoder(&self) -> Self::Encoder<'_> {
let version = self.version.encoder();
let inputs = Encoder2::new(
- CompactSizeEncoder::new(self.inputs.len() as u64),
+ CompactSizeEncoder::new(self.inputs.len()),
SliceEncoder::without_length_prefix(self.inputs.as_ref()),
);
let outputs = Encoder2::new(
- CompactSizeEncoder::new(self.outputs.len() as u64),
+ CompactSizeEncoder::new(self.outputs.len()),
SliceEncoder::without_length_prefix(self.outputs.as_ref()),
);
let lock_time = self.lock_time.encoder();
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.