Move internals::compact_size encoding into CompactSizeEncoder
What changed, and why it matters
This commit is a routine internal code cleanup. It moves the logic for encoding Bitcoin 'compact size' numbers from one internal helper module into a dedicated encoder type, then updates call sites to use the new location. There is no security fix or behavior change intended.
No security action required; treat as normal refactoring. Reviewers may verify that all compact-size encoding outputs remain byte-for-byte identical, which the moved tests support.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change relocates encode and encoded_size from internals::compact_size to consensus_encoding::CompactSizeEncoder, deletes the old public functions from internals, and updates callers in bitcoin and primitives crates. The encoding rules (1/3/5/9 byte ranges) and byte layout remain identical. Existing public functions varint_size/varint_size_u64 are deprecated but kept as thin wrappers. Tests are moved and adjusted for usize vs u64 platform differences. No vulnerability is patched.
Changed components
bitcoin/src/blockdata/block.rsbitcoin/src/blockdata/transaction.rsbitcoin/src/consensus/encode.rsbitcoin/src/psbt/serialize.rsconsensus_encoding/src/encode/encoders.rsinternals/src/compact_size.rsprimitives/src/lib.rsprimitives/src/transaction.rsprimitives/src/witness.rsInspect captured patch +173 / −179
diff --git a/api/consensus_encoding/all-features.txt b/api/consensus_encoding/all-features.txt
index ee67d857..35d374b5 100644
--- a/api/consensus_encoding/all-features.txt
+++ b/api/consensus_encoding/all-features.txt
@@ -293,6 +293,7 @@ pub const fn bitcoin_consensus_encoding::ByteVecDecoder::new() -> Self
pub const fn bitcoin_consensus_encoding::BytesEncoder<'sl>::without_length_prefix(sl: &'sl [u8]) -> Self
pub const fn bitcoin_consensus_encoding::CompactSizeDecoder::new() -> Self
pub const fn bitcoin_consensus_encoding::CompactSizeDecoder::new_with_limit(limit: usize) -> Self
+pub const fn bitcoin_consensus_encoding::CompactSizeEncoder::encoded_size(value: usize) -> usize
pub const fn bitcoin_consensus_encoding::Decoder2<A, B>::new(first: A, second: B) -> Self
pub const fn bitcoin_consensus_encoding::Decoder3<A, B, C>::new(dec_1: A, dec_2: B, dec_3: C) -> Self
pub const fn bitcoin_consensus_encoding::Decoder4<A, B, C, D>::new(dec_1: A, dec_2: B, dec_3: C, dec_4: D) -> Self
diff --git a/api/consensus_encoding/alloc-only.txt b/api/consensus_encoding/alloc-only.txt
index 9b859154..1528e3bf 100644
--- a/api/consensus_encoding/alloc-only.txt
+++ b/api/consensus_encoding/alloc-only.txt
@@ -272,6 +272,7 @@ pub const fn bitcoin_consensus_encoding::ByteVecDecoder::new() -> Self
pub const fn bitcoin_consensus_encoding::BytesEncoder<'sl>::without_length_prefix(sl: &'sl [u8]) -> Self
pub const fn bitcoin_consensus_encoding::CompactSizeDecoder::new() -> Self
pub const fn bitcoin_consensus_encoding::CompactSizeDecoder::new_with_limit(limit: usize) -> Self
+pub const fn bitcoin_consensus_encoding::CompactSizeEncoder::encoded_size(value: usize) -> usize
pub const fn bitcoin_consensus_encoding::Decoder2<A, B>::new(first: A, second: B) -> Self
pub const fn bitcoin_consensus_encoding::Decoder3<A, B, C>::new(dec_1: A, dec_2: B, dec_3: C) -> Self
pub const fn bitcoin_consensus_encoding::Decoder4<A, B, C, D>::new(dec_1: A, dec_2: B, dec_3: C, dec_4: D) -> Self
diff --git a/api/consensus_encoding/no-features.txt b/api/consensus_encoding/no-features.txt
index a11b4608..253aaa7f 100644
--- a/api/consensus_encoding/no-features.txt
+++ b/api/consensus_encoding/no-features.txt
@@ -215,6 +215,7 @@ pub const fn bitcoin_consensus_encoding::ArrayEncoder<N>::without_length_prefix(
pub const fn bitcoin_consensus_encoding::BytesEncoder<'sl>::without_length_prefix(sl: &'sl [u8]) -> Self
pub const fn bitcoin_consensus_encoding::CompactSizeDecoder::new() -> Self
pub const fn bitcoin_consensus_encoding::CompactSizeDecoder::new_with_limit(limit: usize) -> Self
+pub const fn bitcoin_consensus_encoding::CompactSizeEncoder::encoded_size(value: usize) -> usize
pub const fn bitcoin_consensus_encoding::Decoder2<A, B>::new(first: A, second: B) -> Self
pub const fn bitcoin_consensus_encoding::Decoder3<A, B, C>::new(dec_1: A, dec_2: B, dec_3: C) -> Self
pub const fn bitcoin_consensus_encoding::Decoder4<A, B, C, D>::new(dec_1: A, dec_2: B, dec_3: C, dec_4: D) -> Self
diff --git a/api/internals/all-features.txt b/api/internals/all-features.txt
index 5e41c489..2684d1e7 100644
--- a/api/internals/all-features.txt
+++ b/api/internals/all-features.txt
@@ -94,12 +94,9 @@ impl<const N: usize, T> bitcoin_internals::array::ArrayExt for [T; N]
pub bitcoin_internals::script::PushDataLenLen::Four = 4
pub bitcoin_internals::script::PushDataLenLen::One = 1
pub bitcoin_internals::script::PushDataLenLen::Two = 2
-pub const bitcoin_internals::compact_size::MAX_ENCODABLE_VALUE: u64
-pub const bitcoin_internals::compact_size::MAX_ENCODING_SIZE: usize
pub const fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::as_slice(&self) -> &[T]
pub const fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::from_slice(slice: &[T]) -> Self
pub const fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::new() -> Self
-pub const fn bitcoin_internals::compact_size::encoded_size_const(value: u64) -> usize
pub const fn bitcoin_internals::const_casts::i16_to_i64(value: i16) -> i64
pub const fn bitcoin_internals::const_casts::u16_to_u32(value: u16) -> u32
pub const fn bitcoin_internals::const_casts::u16_to_u64(value: u16) -> u64
@@ -139,8 +136,6 @@ pub fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::hash<H: core::hash::Hashe
pub fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::pop(&mut self) -> core::option::Option<T>
pub fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::push(&mut self, element: T)
pub fn bitcoin_internals::compact_size::decode_unchecked(slice: &mut &[u8]) -> u64
-pub fn bitcoin_internals::compact_size::encode(value: impl bitcoin_internals::ToU64) -> bitcoin_internals::array_vec::ArrayVec<u8, MAX_ENCODING_SIZE>
-pub fn bitcoin_internals::compact_size::encoded_size(value: impl bitcoin_internals::ToU64) -> usize
pub fn bitcoin_internals::error::input_string::CannotParse<'_, T>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_internals::error::input_string::InputString::clone(&self) -> bitcoin_internals::error::input_string::InputString
pub fn bitcoin_internals::error::input_string::InputString::cmp(&self, other: &bitcoin_internals::error::input_string::InputString) -> core::cmp::Ordering
diff --git a/api/internals/alloc-only.txt b/api/internals/alloc-only.txt
index 11461afd..9c9155bf 100644
--- a/api/internals/alloc-only.txt
+++ b/api/internals/alloc-only.txt
@@ -85,12 +85,9 @@ impl<const N: usize, T> bitcoin_internals::array::ArrayExt for [T; N]
pub bitcoin_internals::script::PushDataLenLen::Four = 4
pub bitcoin_internals::script::PushDataLenLen::One = 1
pub bitcoin_internals::script::PushDataLenLen::Two = 2
-pub const bitcoin_internals::compact_size::MAX_ENCODABLE_VALUE: u64
-pub const bitcoin_internals::compact_size::MAX_ENCODING_SIZE: usize
pub const fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::as_slice(&self) -> &[T]
pub const fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::from_slice(slice: &[T]) -> Self
pub const fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::new() -> Self
-pub const fn bitcoin_internals::compact_size::encoded_size_const(value: u64) -> usize
pub const fn bitcoin_internals::const_casts::i16_to_i64(value: i16) -> i64
pub const fn bitcoin_internals::const_casts::u16_to_u32(value: u16) -> u32
pub const fn bitcoin_internals::const_casts::u16_to_u64(value: u16) -> u64
@@ -128,8 +125,6 @@ pub fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::hash<H: core::hash::Hashe
pub fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::pop(&mut self) -> core::option::Option<T>
pub fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::push(&mut self, element: T)
pub fn bitcoin_internals::compact_size::decode_unchecked(slice: &mut &[u8]) -> u64
-pub fn bitcoin_internals::compact_size::encode(value: impl bitcoin_internals::ToU64) -> bitcoin_internals::array_vec::ArrayVec<u8, MAX_ENCODING_SIZE>
-pub fn bitcoin_internals::compact_size::encoded_size(value: impl bitcoin_internals::ToU64) -> usize
pub fn bitcoin_internals::error::input_string::CannotParse<'_, T>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_internals::error::input_string::InputString::clone(&self) -> bitcoin_internals::error::input_string::InputString
pub fn bitcoin_internals::error::input_string::InputString::cmp(&self, other: &bitcoin_internals::error::input_string::InputString) -> core::cmp::Ordering
diff --git a/api/internals/no-features.txt b/api/internals/no-features.txt
index 2e5175c0..6b1533d5 100644
--- a/api/internals/no-features.txt
+++ b/api/internals/no-features.txt
@@ -82,12 +82,9 @@ impl<const N: usize, T> bitcoin_internals::array::ArrayExt for [T; N]
pub bitcoin_internals::script::PushDataLenLen::Four = 4
pub bitcoin_internals::script::PushDataLenLen::One = 1
pub bitcoin_internals::script::PushDataLenLen::Two = 2
-pub const bitcoin_internals::compact_size::MAX_ENCODABLE_VALUE: u64
-pub const bitcoin_internals::compact_size::MAX_ENCODING_SIZE: usize
pub const fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::as_slice(&self) -> &[T]
pub const fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::from_slice(slice: &[T]) -> Self
pub const fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::new() -> Self
-pub const fn bitcoin_internals::compact_size::encoded_size_const(value: u64) -> usize
pub const fn bitcoin_internals::const_casts::i16_to_i64(value: i16) -> i64
pub const fn bitcoin_internals::const_casts::u16_to_u32(value: u16) -> u32
pub const fn bitcoin_internals::const_casts::u16_to_u64(value: u16) -> u64
@@ -125,8 +122,6 @@ pub fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::hash<H: core::hash::Hashe
pub fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::pop(&mut self) -> core::option::Option<T>
pub fn bitcoin_internals::array_vec::ArrayVec<T, CAP>::push(&mut self, element: T)
pub fn bitcoin_internals::compact_size::decode_unchecked(slice: &mut &[u8]) -> u64
-pub fn bitcoin_internals::compact_size::encode(value: impl bitcoin_internals::ToU64) -> bitcoin_internals::array_vec::ArrayVec<u8, MAX_ENCODING_SIZE>
-pub fn bitcoin_internals::compact_size::encoded_size(value: impl bitcoin_internals::ToU64) -> usize
pub fn bitcoin_internals::error::input_string::CannotParse<'_, T>::fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub fn bitcoin_internals::error::input_string::InputString::clone(&self) -> bitcoin_internals::error::input_string::InputString
pub fn bitcoin_internals::error::input_string::InputString::cmp(&self, other: &bitcoin_internals::error::input_string::InputString) -> core::cmp::Ordering
diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs
index 069fdbba..160daf65 100644
--- a/bitcoin/src/blockdata/block.rs
+++ b/bitcoin/src/blockdata/block.rs
@@ -10,7 +10,8 @@
use core::convert::Infallible;
use core::fmt;
-use internals::{compact_size, ToU64};
+use encoding::CompactSizeEncoder;
+use internals::ToU64;
use io::{BufRead, Write};
use crate::consensus::encode::{self, Decodable, Encodable, WriteExt as _};
@@ -180,7 +181,7 @@ impl BlockCheckedExt for Block<Checked> {
fn total_size(&self) -> usize {
let mut size = Header::SIZE;
- size += compact_size::encoded_size(self.transactions().len());
+ size += CompactSizeEncoder::encoded_size(self.transactions().len());
size += self.transactions().iter().map(|tx| tx.total_size()).sum::<usize>();
size
@@ -226,7 +227,7 @@ impl BlockCheckedExt for Block<Checked> {
fn block_base_size(transactions: &[Transaction]) -> usize {
let mut size = Header::SIZE;
- size += compact_size::encoded_size(transactions.len());
+ size += CompactSizeEncoder::encoded_size(transactions.len());
size += transactions.iter().map(|tx| tx.base_size()).sum::<usize>();
size
diff --git a/bitcoin/src/blockdata/transaction.rs b/bitcoin/src/blockdata/transaction.rs
index 61bd5513..d792489f 100644
--- a/bitcoin/src/blockdata/transaction.rs
+++ b/bitcoin/src/blockdata/transaction.rs
@@ -14,7 +14,8 @@ use core::fmt;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
-use internals::{compact_size, const_casts, write_err, ToU64};
+use encoding::CompactSizeEncoder;
+use internals::{const_casts, write_err, ToU64};
use io::{BufRead, Write};
use super::Weight;
@@ -172,7 +173,7 @@ internal_macros::define_extension_trait! {
fn base_size(&self) -> usize {
let mut size = OutPoint::SIZE;
- size += compact_size::encoded_size(self.script_sig.len());
+ size += CompactSizeEncoder::encoded_size(self.script_sig.len());
size += self.script_sig.len();
size + Sequence::SIZE
@@ -249,7 +250,7 @@ internal_macros::define_extension_trait! {
/// Returns the total number of bytes that this script pubkey would contribute to a transaction.
fn size_from_script_pubkey(script_pubkey: &ScriptPubKey) -> usize {
let len = script_pubkey.len();
- Amount::SIZE + compact_size::encoded_size(len) + len
+ Amount::SIZE + CompactSizeEncoder::encoded_size(len) + len
}
/// Extension functionality for the [`Transaction`] type.
@@ -390,10 +391,10 @@ impl TransactionExt for Transaction {
fn base_size(&self) -> usize {
let mut size: usize = 4; // Serialized length of a u32 for the version number.
- size += compact_size::encoded_size(self.inputs.len());
+ size += CompactSizeEncoder::encoded_size(self.inputs.len());
size += self.inputs.iter().map(|input| input.base_size()).sum::<usize>();
- size += compact_size::encoded_size(self.outputs.len());
+ size += CompactSizeEncoder::encoded_size(self.outputs.len());
size += self.outputs.iter().map(|output| output.size()).sum::<usize>();
size + absolute::LockTime::SIZE
@@ -408,14 +409,14 @@ impl TransactionExt for Transaction {
size += 2; // 1 byte for the marker and 1 for the flag.
}
- size += compact_size::encoded_size(self.inputs.len());
+ size += CompactSizeEncoder::encoded_size(self.inputs.len());
size += self
.inputs
.iter()
.map(|input| if uses_segwit { input.total_size() } else { input.base_size() })
.sum::<usize>();
- size += compact_size::encoded_size(self.outputs.len());
+ size += CompactSizeEncoder::encoded_size(self.outputs.len());
size += self.outputs.iter().map(|output| output.size()).sum::<usize>();
size + absolute::LockTime::SIZE
@@ -877,7 +878,7 @@ where
let (output_count, output_scripts_size) =
output_script_lens.into_iter().fold((0, 0), |(count, scripts_size), script_len| {
- (count + 1, scripts_size + script_len + compact_size::encoded_size(script_len))
+ (count + 1, scripts_size + script_len + CompactSizeEncoder::encoded_size(script_len))
});
predict_weight_internal(
@@ -899,8 +900,8 @@ const fn predict_weight_internal(
// The value field of a TxOut is 8 bytes.
let output_size = 8 * output_count + output_scripts_size;
let non_input_size = 4 // version
- + compact_size::encoded_size_const(input_count as u64) // Can't use ToU64 in const context.
- + compact_size::encoded_size_const(output_count as u64)
+ + CompactSizeEncoder::encoded_size(input_count) // Can't use ToU64 in const context.
+ + CompactSizeEncoder::encoded_size(output_count)
+ output_size
+ 4; // locktime
let weight = if inputs_with_witnesses == 0 {
@@ -939,7 +940,7 @@ pub const fn predict_weight_from_slices(
i = 0;
while i < output_script_lens.len() {
let script_len = output_script_lens[i];
- output_scripts_size += script_len + compact_size::encoded_size_const(script_len as u64);
+ output_scripts_size += script_len + CompactSizeEncoder::encoded_size(script_len);
i += 1;
}
diff --git a/bitcoin/src/consensus/encode.rs b/bitcoin/src/consensus/encode.rs
index baa50030..bde3f84f 100644
--- a/bitcoin/src/consensus/encode.rs
+++ b/bitcoin/src/consensus/encode.rs
@@ -17,9 +17,10 @@
use core::any::TypeId;
use core::{cmp, mem, slice};
+use encoding::{CompactSizeEncoder, Encoder};
use hashes::{sha256, sha256d, Hash};
use hex::DisplayHex as _;
-use internals::{compact_size, ToU64};
+use internals::ToU64;
use io::{BufRead, Cursor, Read, Write};
use super::IterReader;
@@ -184,8 +185,9 @@ impl<W: Write + ?Sized> WriteExt for W {
}
#[inline]
fn emit_compact_size(&mut self, v: impl ToU64) -> Result<usize, io::Error> {
- let encoded = compact_size::encode(v.to_u64());
- self.emit_slice(&encoded)?;
+ let encoder = CompactSizeEncoder::new(v.to_u64().try_into().unwrap_or(usize::MAX));
+ let encoded = encoder.current_chunk();
+ self.emit_slice(encoded)?;
Ok(encoded.len())
}
}
@@ -349,19 +351,28 @@ impl_int_encodable!(i32, read_i32, emit_i32);
impl_int_encodable!(i64, read_i64, emit_i64);
/// Returns 1 for 0..=0xFC, 3 for 0xFD..=(2^16-1), 5 for 0x10000..=(2^32-1), and 9 otherwise.
+#[deprecated(since = "0.33.0", note = "use `consensus_encoding::CompactSizeEncoder::encoded_size` instead")]
#[inline]
pub const fn varint_size_u64(v: u64) -> usize {
+ const LIMIT: u64 = if core::mem::size_of::<usize>() <= 8 {
+ usize::MAX as u64 // Cast is ok, because usize is <= the size of u64
+ } else {
+ u64::MAX
+ };
+
+ #[allow(unreachable_patterns)] // Disable lint because this match is reachable on < 64 bit platforms
match v {
- 0..=0xFC => 1,
- 0xFD..=0xFFFF => 3,
- 0x10000..=0xFFFFFFFF => 5,
- _ => 9,
+ 0..=LIMIT => encoding::CompactSizeEncoder::encoded_size(v as usize), // cast is ok because we just checked bounds
+ _ => encoding::CompactSizeEncoder::encoded_size(usize::MAX),
}
}
/// Returns 1 for 0..=0xFC, 3 for 0xFD..=(2^16-1), 5 for 0x10000..=(2^32-1), and 9 otherwise.
+#[deprecated(since = "0.33.0", note = "use `consensus_encoding::CompactSizeEncoder::encoded_size` instead")]
#[inline]
-pub fn varint_size(v: impl ToU64) -> usize { varint_size_u64(v.to_u64()) }
+pub fn varint_size(v: impl ToU64) -> usize {
+ encoding::CompactSizeEncoder::encoded_size(v.to_u64().try_into().unwrap_or(usize::MAX))
+}
impl Encodable for bool {
#[inline]
@@ -807,16 +818,17 @@ mod tests {
assert_eq!(encode(0xFD), [0xFDu8, 0xFD, 0]);
assert_eq!(encode(0xFFF), [0xFDu8, 0xFF, 0xF]);
assert_eq!(encode(0xF0F0F0F), [0xFEu8, 0xF, 0xF, 0xF, 0xF]);
+ #[cfg(target_pointer_width = "64")]
assert_eq!(encode(0xF0F0F0F0F0E0), vec![0xFFu8, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0, 0],);
assert_eq!(test_varint_encode(0xFF, &0x100000000_u64.to_le_bytes()).unwrap(), 0x100000000,);
assert_eq!(test_varint_encode(0xFE, &0x10000_u64.to_le_bytes()).unwrap(), 0x10000);
assert_eq!(test_varint_encode(0xFD, &0xFD_u64.to_le_bytes()).unwrap(), 0xFD);
// Test that length calc is working correctly
- fn test_varint_len(varint: u64, expected: usize) {
+ fn test_varint_len(varint: usize, expected: usize) {
let mut encoder = vec![];
assert_eq!(encoder.emit_compact_size(varint).unwrap(), expected);
- assert_eq!(varint_size(varint), expected);
+ assert_eq!(encoding::CompactSizeEncoder::encoded_size(varint), expected);
}
test_varint_len(0, 1);
test_varint_len(0xFC, 1);
@@ -824,8 +836,11 @@ mod tests {
test_varint_len(0xFFFF, 3);
test_varint_len(0x10000, 5);
test_varint_len(0xFFFFFFFF, 5);
- test_varint_len(0xFFFFFFFF + 1, 9);
- test_varint_len(u64::MAX, 9);
+ #[cfg(target_pointer_width = "64")]
+ {
+ test_varint_len(0xFFFFFFFF + 1, 9);
+ test_varint_len(u64::MAX as usize, 9);
+ }
}
#[test]
diff --git a/bitcoin/src/psbt/serialize.rs b/bitcoin/src/psbt/serialize.rs
index 8d68df88..6f393577 100644
--- a/bitcoin/src/psbt/serialize.rs
+++ b/bitcoin/src/psbt/serialize.rs
@@ -5,8 +5,8 @@
//! Traits to serialize PSBT values to and from raw bytes
//! according to the BIP-0174 specification.
+use encoding::CompactSizeEncoder;
use hashes::{hash160, ripemd160, sha256, sha256d};
-use internals::compact_size;
#[allow(unused)] // MSRV polyfill
use internals::slice::SliceExt;
@@ -389,7 +389,7 @@ impl Serialize for TapTree {
let capacity = self
.script_leaves()
.map(|l| {
- l.script().len() + compact_size::encoded_size(l.script().len()) // script version
+ l.script().len() + CompactSizeEncoder::encoded_size(l.script().len()) // script version
+ 1 // merkle branch
+ 1 // leaf version
})
diff --git a/consensus_encoding/src/encode/encoders.rs b/consensus_encoding/src/encode/encoders.rs
index 735e5929..7a0e4614 100644
--- a/consensus_encoding/src/encode/encoders.rs
+++ b/consensus_encoding/src/encode/encoders.rs
@@ -11,11 +11,10 @@
//! For implementing these newtypes, we provide the [`encoder_newtype`] macro.
//!
-use internals::array_vec::ArrayVec;
-use internals::compact_size;
-
use super::{Encodable, Encoder};
+use internals::array_vec::ArrayVec;
+
/// The maximum length of a compact size encoding.
const SIZE: usize = 9;
@@ -233,8 +232,51 @@ impl CompactSizeEncoder {
/// 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)) }
+ Self { buf: Some(Self::encode(value)) }
+ }
+
+ /// Returns the number of bytes used to encode this `CompactSize` value.
+ ///
+ /// # Returns
+ ///
+ /// - 1 for 0..=0xFC
+ /// - 3 for 0xFD..=(2^16-1)
+ /// - 5 for 0x10000..=(2^32-1)
+ /// - 9 otherwise.
+ #[inline]
+ pub const fn encoded_size(value: usize) -> usize {
+ match value {
+ 0..=0xFC => 1,
+ 0xFD..=0xFFFF => 3,
+ 0x10000..=0xFFFF_FFFF => 5,
+ _ => 9,
+ }
+ }
+
+ /// Encodes `CompactSize` without allocating.
+ #[inline]
+ fn encode(value: usize) -> ArrayVec<u8, SIZE> {
+ let mut res = ArrayVec::<u8, SIZE>::new();
+ match value {
+ 0..=0xFC => {
+ res.push(value as u8); // Cast ok because of match.
+ }
+ 0xFD..=0xFFFF => {
+ let v = value as u16; // Cast ok because of match.
+ res.push(0xFD);
+ res.extend_from_slice(&v.to_le_bytes());
+ }
+ 0x10000..=0xFFFF_FFFF => {
+ let v = value as u32; // Cast ok because of match.
+ res.push(0xFE);
+ res.extend_from_slice(&v.to_le_bytes());
+ }
+ _ => {
+ res.push(0xFF);
+ res.extend_from_slice(&value.to_le_bytes());
+ }
+ }
+ res
}
}
@@ -620,6 +662,55 @@ 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);
+ // Should be encoded as the value as a u8.
+ let want = [v as u8];
+ let got = CompactSizeEncoder::encode(v);
+ assert_eq!(got.as_slice().len(), 1); // sanity check
+ assert_eq!(got.as_slice(), want);
+ }
+ }
+
+ macro_rules! check_encode {
+ ($($test_name:ident, $size:expr, $value:expr, $want:expr);* $(;)?) => {
+ $(
+ #[test]
+ fn $test_name() {
+ let value = $value as usize; // Because default integer type is i32.
+ assert_eq!(CompactSizeEncoder::encoded_size(value), $size);
+ let got = CompactSizeEncoder::encode(value);
+ assert_eq!(got.as_slice().len(), $size); // sanity check
+ assert_eq!(got.as_slice(), &$want);
+ }
+ )*
+ }
+ }
+
+ check_encode! {
+ // 3 byte encoding.
+ encoded_value_3_byte_lower_bound, 3, 0xFD, [0xFD, 0xFD, 0x00]; // 0x00FD
+ encoded_value_3_byte_endianness, 3, 0xABCD, [0xFD, 0xCD, 0xAB];
+ encoded_value_3_byte_upper_bound, 3, 0xFFFF, [0xFD, 0xFF, 0xFF];
+ // 5 byte encoding.
+ encoded_value_5_byte_lower_bound, 5, 0x0001_0000, [0xFE, 0x00, 0x00, 0x01, 0x00];
+ encoded_value_5_byte_endianness, 5, 0x0123_4567, [0xFE, 0x67, 0x45, 0x23, 0x01];
+ 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
+ #[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_upper_bound, 9, u64::MAX, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
+ }
+
#[test]
fn iter_encoder() {
let test_array = TestArray([1u8, 2, 3, 4]);
diff --git a/internals/src/compact_size.rs b/internals/src/compact_size.rs
index 473f31ee..81821ab6 100644
--- a/internals/src/compact_size.rs
+++ b/internals/src/compact_size.rs
@@ -7,75 +7,6 @@
//!
//! [`CompactSize`]: <https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer>
-use crate::array_vec::ArrayVec;
-use crate::ToU64;
-
-/// The maximum size of a serialized object in bytes or number of elements
-/// (for eg vectors) when the size is encoded as `CompactSize`.
-///
-/// This is `MAX_SIZE` in Bitcoin Core.
-// Issue: https://github.com/rust-bitcoin/rust-bitcoin/issues/3264
-pub const MAX_ENCODABLE_VALUE: u64 = 0x0200_0000;
-
-/// The maximum length of an encoding.
-pub const MAX_ENCODING_SIZE: usize = 9;
-
-/// Returns the number of bytes used to encode this `CompactSize` value.
-///
-/// # Returns
-///
-/// - 1 for 0..=0xFC
-/// - 3 for 0xFD..=(2^16-1)
-/// - 5 for 0x10000..=(2^32-1)
-/// - 9 otherwise.
-#[inline]
-pub fn encoded_size(value: impl ToU64) -> usize { encoded_size_const(value.to_u64()) }
-
-/// Returns the number of bytes used to encode this `CompactSize` value (in const context).
-///
-/// # Returns
-///
-/// - 1 for 0..=0xFC
-/// - 3 for 0xFD..=(2^16-1)
-/// - 5 for 0x10000..=(2^32-1)
-/// - 9 otherwise.
-#[inline]
-pub const fn encoded_size_const(value: u64) -> usize {
- match value {
- 0..=0xFC => 1,
- 0xFD..=0xFFFF => 3,
- 0x10000..=0xFFFF_FFFF => 5,
- _ => 9,
- }
-}
-
-/// Encodes `CompactSize` without allocating.
-#[inline]
-pub fn encode(value: impl ToU64) -> ArrayVec<u8, MAX_ENCODING_SIZE> {
- let value = value.to_u64();
- let mut res = ArrayVec::<u8, MAX_ENCODING_SIZE>::new();
- match value {
- 0..=0xFC => {
- res.push(value as u8); // Cast ok because of match.
- }
- 0xFD..=0xFFFF => {
- let v = value as u16; // Cast ok because of match.
- res.push(0xFD);
- res.extend_from_slice(&v.to_le_bytes());
- }
- 0x10000..=0xFFFF_FFFF => {
- let v = value as u32; // Cast ok because of match.
- res.push(0xFE);
- res.extend_from_slice(&v.to_le_bytes());
- }
- _ => {
- res.push(0xFF);
- res.extend_from_slice(&value.to_le_bytes());
- }
- }
- res
-}
-
/// Gets the compact size encoded value from `slice` and moves slice past the encoding.
///
/// Caller to guarantee that the encoding is well formed. Well formed is defined as:
@@ -138,20 +69,6 @@ pub fn decode_unchecked(slice: &mut &[u8]) -> u64 {
mod tests {
use super::*;
- #[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 u32;
- assert_eq!(encoded_size(v), 1);
- // Should be encoded as the value as a u8.
- let want = [v as u8];
- let got = encode(v);
- assert_eq!(got.as_slice().len(), 1); // sanity check
- assert_eq!(got.as_slice(), want);
- }
- }
-
#[test]
fn decode_value_1_byte() {
// Check lower bound, upper bound.
@@ -164,35 +81,6 @@ mod tests {
}
}
- macro_rules! check_encode {
- ($($test_name:ident, $size:expr, $value:expr, $want:expr);* $(;)?) => {
- $(
- #[test]
- fn $test_name() {
- let value = $value as u64; // Because default integer type is i32.
- let got = encode(value);
- assert_eq!(got.as_slice().len(), $size); // sanity check
- assert_eq!(got.as_slice(), &$want);
- }
- )*
- }
- }
-
- check_encode! {
- // 3 byte encoding.
- encoded_value_3_byte_lower_bound, 3, 0xFD, [0xFD, 0xFD, 0x00]; // 0x00FD
- encoded_value_3_byte_endianness, 3, 0xABCD, [0xFD, 0xCD, 0xAB];
- encoded_value_3_byte_upper_bound, 3, 0xFFFF, [0xFD, 0xFF, 0xFF];
- // 5 byte encoding.
- encoded_value_5_byte_lower_bound, 5, 0x0001_0000, [0xFE, 0x00, 0x00, 0x01, 0x00];
- encoded_value_5_byte_endianness, 5, 0x0123_4567, [0xFE, 0x67, 0x45, 0x23, 0x01];
- encoded_value_5_byte_upper_bound, 5, 0xFFFF_FFFF, [0xFE, 0xFF, 0xFF, 0xFF, 0xFF];
- // 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_upper_bound, 9, u64::MAX, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
- }
-
macro_rules! check_decode {
($($test_name:ident, $size:expr, $want:expr, $encoded:expr);* $(;)?) => {
$(
diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs
index f550eae4..7a2b3d56 100644
--- a/primitives/src/lib.rs
+++ b/primitives/src/lib.rs
@@ -104,3 +104,15 @@ mod prelude {
#[cfg(all(feature = "alloc", target_has_atomic = "ptr"))]
pub use alloc::sync;
}
+
+#[cfg(feature = "alloc")]
+use encoding::Encoder;
+#[cfg(feature = "alloc")]
+use internals::array_vec::ArrayVec;
+
+// Encode a compact size to a slice without allocating
+#[cfg(feature = "alloc")]
+pub(crate) fn compact_size_encode(value: usize) -> ArrayVec<u8, 9> {
+ let encoder = encoding::CompactSizeEncoder::new(value);
+ ArrayVec::from_slice(encoder.current_chunk())
+}
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index a796e5ce..5dd2fd77 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -26,8 +26,6 @@ use encoding::{
#[cfg(feature = "alloc")]
use hashes::sha256d;
use internals::array::ArrayExt as _;
-#[cfg(feature = "alloc")]
-use internals::compact_size;
use internals::write_err;
#[cfg(feature = "serde")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
@@ -279,14 +277,14 @@ fn hash_transaction(tx: &Transaction, uses_segwit_serialization: bool) -> sha256
// Encode inputs (excluding witness data) with leading compact size encoded int.
let input_len = tx.inputs.len();
- enc.input(compact_size::encode(input_len).as_slice());
+ enc.input(crate::compact_size_encode(input_len).as_slice());
for input in &tx.inputs {
// Encode each input same as we do in `Encodable for TxIn`.
enc.input(input.previous_output.txid.as_byte_array());
enc.input(&input.previous_output.vout.to_le_bytes());
let script_sig_bytes = input.script_sig.as_bytes();
- enc.input(compact_size::encode(script_sig_bytes.len()).as_slice());
+ enc.input(crate::compact_size_encode(script_sig_bytes.len()).as_slice());
enc.input(script_sig_bytes);
enc.input(&input.sequence.0.to_le_bytes());
@@ -294,13 +292,13 @@ fn hash_transaction(tx: &Transaction, uses_segwit_serialization: bool) -> sha256
// Encode outputs with leading compact size encoded int.
let output_len = tx.outputs.len();
- enc.input(compact_size::encode(output_len).as_slice());
+ enc.input(crate::compact_size_encode(output_len).as_slice());
for output in &tx.outputs {
// Encode each output same as we do in `Encodable for TxOut`.
enc.input(&output.amount.to_sat().to_le_bytes());
let script_pubkey_bytes = output.script_pubkey.as_bytes();
- enc.input(compact_size::encode(script_pubkey_bytes.len()).as_slice());
+ enc.input(crate::compact_size_encode(script_pubkey_bytes.len()).as_slice());
enc.input(script_pubkey_bytes);
}
@@ -308,9 +306,9 @@ fn hash_transaction(tx: &Transaction, uses_segwit_serialization: bool) -> sha256
// BIP-0141 (SegWit) transaction serialization also includes the witness data.
for input in &tx.inputs {
// Same as `Encodable for Witness`.
- enc.input(compact_size::encode(input.witness.len()).as_slice());
+ enc.input(crate::compact_size_encode(input.witness.len()).as_slice());
for element in &input.witness {
- enc.input(compact_size::encode(element.len()).as_slice());
+ enc.input(crate::compact_size_encode(element.len()).as_slice());
enc.input(element);
}
}
diff --git a/primitives/src/witness.rs b/primitives/src/witness.rs
index ff0547f9..a9448843 100644
--- a/primitives/src/witness.rs
+++ b/primitives/src/witness.rs
@@ -76,14 +76,14 @@ impl Witness {
let index_size = witness_elements * 4;
let content_size = slice
.iter()
- .map(|elem| elem.as_ref().len() + compact_size::encoded_size(elem.as_ref().len()))
+ .map(|elem| elem.as_ref().len() + CompactSizeEncoder::encoded_size(elem.as_ref().len()))
.sum();
let mut content = alloc::vec![0u8; content_size + index_size];
let mut cursor = 0usize;
for (i, elem) in slice.iter().enumerate() {
encode_cursor(&mut content, content_size, i, cursor);
- let encoded = compact_size::encode(elem.as_ref().len());
+ let encoded = crate::compact_size_encode(elem.as_ref().len());
let encoded_size = encoded.as_slice().len();
content[cursor..cursor + encoded_size].copy_from_slice(encoded.as_slice());
cursor += encoded_size;
@@ -121,12 +121,12 @@ impl Witness {
pub fn size(&self) -> usize {
let mut size: usize = 0;
- size += compact_size::encoded_size(self.witness_elements);
+ size += CompactSizeEncoder::encoded_size(self.witness_elements);
size += self
.iter()
.map(|witness_element| {
let len = witness_element.len();
- compact_size::encoded_size(len) + len
+ CompactSizeEncoder::encoded_size(len) + len
})
.sum::<usize>();
@@ -151,7 +151,7 @@ impl Witness {
fn push_slice(&mut self, new_element: &[u8]) {
self.witness_elements += 1;
let previous_content_end = self.indices_start;
- let encoded = compact_size::encode(new_element.len());
+ let encoded = crate::compact_size_encode(new_element.len());
let encoded_size = encoded.as_slice().len();
let current_content_len = self.content.len();
let new_item_total_len = encoded_size + new_element.len();
@@ -424,10 +424,10 @@ impl Decoder for WitnessDecoder {
encode_cursor(&mut self.content, 0, self.element_idx, position_after_rotation);
// Re-encode the length back into the buffer.
- let encoded_size = compact_size::encoded_size(element_length);
+ let encoded_size = CompactSizeEncoder::encoded_size(element_length);
let required_len = self.cursor + encoded_size + element_length;
self.resize_if_needed(required_len);
- let encoded_compact_size = compact_size::encode(element_length);
+ let encoded_compact_size = crate::compact_size_encode(element_length);
self.content[self.cursor..self.cursor + encoded_size]
.copy_from_slice(&encoded_compact_size);
self.cursor += encoded_size;
@@ -1326,8 +1326,8 @@ mod test {
#[cfg(feature = "alloc")]
fn decode_max_length() {
let mut encoded = Vec::new();
- encoded.extend_from_slice(compact_size::encode(1usize).as_slice());
- encoded.extend_from_slice(compact_size::encode(4_000_000usize).as_slice());
+ encoded.extend_from_slice(crate::compact_size_encode(1usize).as_slice());
+ encoded.extend_from_slice(crate::compact_size_encode(4_000_000usize).as_slice());
encoded.resize(encoded.len() + 4_000_000, 0u8);
let mut slice = encoded.as_slice();
@@ -1337,8 +1337,8 @@ mod test {
assert_eq!(witness[0].len(), 4_000_000);
let mut encoded = Vec::new();
- encoded.extend_from_slice(compact_size::encode(1usize).as_slice());
- encoded.extend_from_slice(compact_size::encode(4_000_001usize).as_slice());
+ encoded.extend_from_slice(crate::compact_size_encode(1usize).as_slice());
+ encoded.extend_from_slice(crate::compact_size_encode(4_000_001usize).as_slice());
let mut slice = encoded.as_slice();
let mut decoder = WitnessDecoder::new();
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.