@@ -43,174 +43,3 @@ pub trait MessageHash {
4343
4444pub mod poseidon;
4545pub mod top_level_poseidon;
46-
47- /// Splits a list of bytes into smaller fixed-size bit chunks.
48- ///
49- /// Each byte in the input slice is divided into `chunk_size`-bit chunks,
50- /// starting from the least significant bits. The `chunk_size` must divide 8 exactly
51- /// (i.e., valid values are 1, 2, 4, or 8), since each byte contains 8 bits.
52- ///
53- /// # Arguments
54- /// - `bytes`: A slice of bytes to be chunked.
55- /// - `chunk_size`: The size (in bits) of each output chunk.
56- ///
57- /// # Returns
58- /// A vector of `u8` values where each element is a `chunk_size`-bit chunk
59- /// from the original input. The number of chunks returned is: `bytes.len() * (8 / chunk_size)`
60- ///
61- /// # Example
62- /// ```text
63- /// // Input: [0b01101100]
64- /// // Chunk size: 2
65- /// // Output: [0b00, 0b11, 0b10, 0b01] (from least to most significant)
66- /// let chunks = bytes_to_chunks(&[0b01101100], 2);
67- /// assert_eq!(chunks, vec![0b00, 0b11, 0b10, 0b01]);
68- /// ```
69- #[ must_use]
70- #[ inline]
71- pub fn bytes_to_chunks ( bytes : & [ u8 ] , chunk_size : usize ) -> Vec < u8 > {
72- // Only the chunk sizes 1, 2, 4, or 8 are valid.
73- //
74- // This avoids invalid bit manipulations and guarantees predictable output length.
75- assert ! (
76- matches!( chunk_size, 1 | 2 | 4 | 8 ) ,
77- "chunk_size must be 1, 2, 4, or 8"
78- ) ;
79-
80- // Calculate how many chunks each byte will produce and preallocate exactly.
81- let chunks_per_byte = 8 / chunk_size;
82- let mut out = Vec :: with_capacity ( bytes. len ( ) * chunks_per_byte) ;
83-
84- // Fast paths per chunk size
85- match chunk_size {
86- 8 => {
87- // Copy as-is.
88- out. extend_from_slice ( bytes) ;
89- }
90- 4 => {
91- // Low nibble, then high nibble.
92- for & b in bytes {
93- out. push ( b & 0x0F ) ;
94- out. push ( b >> 4 ) ;
95- }
96- }
97- 2 => {
98- // 4 two-bit chunks: bits [1:0], [3:2], [5:4], [7:6].
99- for & b in bytes {
100- out. push ( b & 0b11 ) ;
101- out. push ( ( b >> 2 ) & 0b11 ) ;
102- out. push ( ( b >> 4 ) & 0b11 ) ;
103- out. push ( ( b >> 6 ) & 0b11 ) ;
104- }
105- }
106- 1 => {
107- // 8 one-bit chunks (LSB to MSB).
108- for & b in bytes {
109- out. push ( b & 1 ) ;
110- out. push ( ( b >> 1 ) & 1 ) ;
111- out. push ( ( b >> 2 ) & 1 ) ;
112- out. push ( ( b >> 3 ) & 1 ) ;
113- out. push ( ( b >> 4 ) & 1 ) ;
114- out. push ( ( b >> 5 ) & 1 ) ;
115- out. push ( ( b >> 6 ) & 1 ) ;
116- out. push ( ( b >> 7 ) & 1 ) ;
117- }
118- }
119- _ => unreachable ! ( ) ,
120- }
121-
122- out
123- }
124-
125- #[ cfg( test) ]
126- mod tests {
127- use super :: bytes_to_chunks;
128- use proptest:: prelude:: * ;
129-
130- #[ test]
131- fn test_bytes_to_chunks ( ) {
132- // In this test, we check that `bytes_to_chunks` works as expected
133-
134- let byte_a: u8 = 0b0110_1100 ;
135- let byte_b: u8 = 0b1010_0110 ;
136-
137- let bytes = [ byte_a, byte_b] ;
138- let expected_chunks = [ 0b00 , 0b11 , 0b10 , 0b01 , 0b10 , 0b01 , 0b10 , 0b10 ] ;
139-
140- let chunks = bytes_to_chunks ( & bytes, 2 ) ;
141-
142- assert_eq ! ( chunks. len( ) , 8 ) ;
143-
144- for i in 0 ..chunks. len ( ) {
145- assert_eq ! ( chunks[ i] , expected_chunks[ i] ) ;
146- }
147-
148- // now test chunk size 8
149- let chunks = bytes_to_chunks ( & bytes, 8 ) ;
150-
151- assert_eq ! ( chunks. len( ) , 2 ) ;
152- assert_eq ! ( chunks[ 0 ] , byte_a) ;
153- assert_eq ! ( chunks[ 1 ] , byte_b) ;
154- }
155-
156- proptest ! {
157- #[ test]
158- fn prop_bytes_to_chunks_matches_manual_bit_extraction(
159- // Random byte vector length between 0 and 32
160- bytes in proptest:: collection:: vec( any:: <u8 >( ) , 0 ..32 ) ,
161- // Random valid chunk size: 1, 2, 4, or 8 bits
162- chunk_size in prop_oneof![ Just ( 1usize ) , Just ( 2 ) , Just ( 4 ) , Just ( 8 ) ] ,
163- ) {
164- // This is the implementation we want to verify for correctness.
165- let chunks = bytes_to_chunks( & bytes, chunk_size) ;
166-
167- // Precompute the expected output manually
168- //
169- // We will generate `expected` by extracting `chunk_size` bits at a time
170- // from each byte, starting from the least-significant bits.
171-
172- // Expected number of chunks per byte
173- let chunks_per_byte = 8 / chunk_size;
174-
175- // Preallocate output vector
176- let mut expected = Vec :: with_capacity( bytes. len( ) * chunks_per_byte) ;
177-
178- // Manual extraction logic
179- for & b in & bytes {
180- for i in 0 ..chunks_per_byte {
181- // Shift right by i * chunk_size to bring target bits to LSB.
182- let shifted = b >> ( i * chunk_size) ;
183-
184- // Mask off only chunk_size bits (special-case chunk_size == 8).
185- let mask = if chunk_size == 8 {
186- 0xFF
187- } else {
188- ( 1u8 << chunk_size) - 1
189- } ;
190-
191- expected. push( shifted & mask) ;
192- }
193- }
194-
195- // The number of chunks should match exactly.
196- prop_assert_eq!(
197- chunks. len( ) ,
198- expected. len( ) ,
199- "Length mismatch for chunk_size = {}: got {}, expected {}" ,
200- chunk_size,
201- chunks. len( ) ,
202- expected. len( )
203- ) ;
204-
205- // Each chunk should be identical to the expected manual result.
206- prop_assert_eq!(
207- chunks. as_slice( ) ,
208- expected. as_slice( ) ,
209- "Chunk data mismatch for chunk_size = {}: got {:?}, expected {:?}" ,
210- chunk_size,
211- chunks. as_slice( ) ,
212- expected. as_slice( )
213- ) ;
214- }
215- }
216- }
0 commit comments