12
12
//! with the MSB of the first word set as a marker
13
13
//!
14
14
//! The resulting words are serialized in little-endian byte order before hashing.
15
- //! The Blake2s-256 digest is then truncated to 224 bits to fit within a `Felt` .
15
+ //! The Blake2s-256 digest is then packed into a `Felt` using all 256 bits modulo the field prime .
16
16
//!
17
17
//! ## Reference Implementation
18
18
//!
19
19
//! This implementation follows the Cairo specification:
20
- //! - [Cairo Blake2s implementation](https://github.com/starkware-libs/cairo-lang/blob/ab8be40403a7634ba296c467b26b8bd945ba5cfa /src/starkware/cairo/common/cairo_blake2s/blake2s.cairo)
20
+ //! - [Cairo Blake2s implementation](https://github.com/starkware-libs/cairo-lang/blob/master /src/starkware/cairo/common/cairo_blake2s/blake2s.cairo)
21
21
22
22
use crate :: felt:: Felt ;
23
23
use blake2:: Blake2s256 ;
@@ -36,7 +36,7 @@ impl StarkHash for Blake2Felt252 {
36
36
}
37
37
38
38
fn hash_array ( felts : & [ Felt ] ) -> Felt {
39
- Self :: encode_felt252_data_and_calc_224_bit_blake_hash ( felts)
39
+ Self :: encode_felt252_data_and_calc_blake_hash ( felts)
40
40
}
41
41
42
42
fn hash_single ( felt : & Felt ) -> Felt {
@@ -84,14 +84,14 @@ impl Blake2Felt252 {
84
84
unpacked_u32s
85
85
}
86
86
87
- /// Packs the first 7 little-endian 32-bit words (28 bytes) of `bytes`
88
- /// into a single 224 -bit Felt.
89
- fn pack_224_le_to_felt ( bytes : & [ u8 ] ) -> Felt {
90
- assert ! ( bytes. len( ) >= 28 , "need at least 28 bytes to pack 7 words" ) ;
87
+ /// Packs the first 8 little-endian 32-bit words (32 bytes) of `bytes`
88
+ /// into a single 252 -bit Felt.
89
+ fn pack_256_le_to_felt ( bytes : & [ u8 ] ) -> Felt {
90
+ assert ! ( bytes. len( ) >= 32 , "need at least 32 bytes to pack 8 words" ) ;
91
91
92
- // 1) copy your 28 -byte LE-hash into the low 28 bytes of a 32-byte buffer.
92
+ // 1) copy your 32 -byte LE-hash into the low 32 bytes of a 32-byte buffer.
93
93
let mut buf = [ 0u8 ; 32 ] ;
94
- buf[ ..28 ] . copy_from_slice ( & bytes[ ..28 ] ) ;
94
+ buf[ ..32 ] . copy_from_slice ( & bytes[ ..32 ] ) ;
95
95
96
96
// 2) interpret the whole 32-byte buffer as a little-endian Felt.
97
97
Felt :: from_bytes_le ( & buf)
@@ -101,14 +101,14 @@ impl Blake2Felt252 {
101
101
let mut hasher = Blake2s256 :: new ( ) ;
102
102
hasher. update ( data) ;
103
103
let hash32 = hasher. finalize ( ) ;
104
- Self :: pack_224_le_to_felt ( hash32. as_slice ( ) )
104
+ Self :: pack_256_le_to_felt ( hash32. as_slice ( ) )
105
105
}
106
106
107
107
/// Encodes a slice of `Felt` values into 32-bit words exactly as Cairo's
108
108
/// [`encode_felt252_to_u32s`](https://github.com/starkware-libs/cairo-lang/blob/ab8be40403a7634ba296c467b26b8bd945ba5cfa/src/starkware/cairo/common/cairo_blake2s/blake2s.cairo)
109
109
/// hint does, then hashes the resulting byte stream with Blake2s-256 and
110
- /// returns the 224 -bit truncated digest as a `Felt`.
111
- pub fn encode_felt252_data_and_calc_224_bit_blake_hash ( data : & [ Felt ] ) -> Felt {
110
+ /// returns the full 256 -bit digest as a `Felt`.
111
+ pub fn encode_felt252_data_and_calc_blake_hash ( data : & [ Felt ] ) -> Felt {
112
112
// 1) Unpack each Felt into 2 or 8 u32.
113
113
let u32_words = Self :: encode_felts_to_u32s ( data) ;
114
114
@@ -127,6 +127,7 @@ impl Blake2Felt252 {
127
127
mod tests {
128
128
use super :: * ;
129
129
use crate :: felt:: Felt ;
130
+ use rstest:: rstest;
130
131
131
132
/// Test two-limb encoding for a small Felt (< 2^63) into high and low 32-bit words.
132
133
#[ test]
@@ -154,49 +155,94 @@ mod tests {
154
155
assert_eq ! ( words, expected) ;
155
156
}
156
157
157
- /// Test packing of a 28 -byte little-endian buffer into a 224 -bit Felt.
158
+ /// Test packing of a 32 -byte little-endian buffer into a 256 -bit Felt.
158
159
#[ test]
159
- fn test_pack_224_le_to_felt_roundtrip ( ) {
160
- // Create a 28-byte buffer with values 1..28 and pad to 32 bytes .
160
+ fn test_pack_256_le_to_felt_basic ( ) {
161
+ // Test with small values that won't trigger modular reduction .
161
162
let mut buf = [ 0u8 ; 32 ] ;
162
- for i in 0 ..28 {
163
- buf[ i] = ( i + 1 ) as u8 ;
164
- }
165
- let f = Blake2Felt252 :: pack_224_le_to_felt ( & buf) ;
163
+ buf[ 0 ] = 0x01 ;
164
+ buf[ 1 ] = 0x02 ;
165
+ buf[ 2 ] = 0x03 ;
166
+ buf[ 3 ] = 0x04 ;
167
+ // Leave the rest as zeros.
168
+
169
+ let f = Blake2Felt252 :: pack_256_le_to_felt ( & buf) ;
166
170
let out = f. to_bytes_le ( ) ;
167
171
168
- // Low 28 bytes must match the input buffer.
169
- assert_eq ! ( & out[ ..28 ] , & buf[ ..28 ] ) ;
170
- // High 4 bytes must remain zero.
171
- assert_eq ! ( & out[ 28 ..] , & [ 0 , 0 , 0 , 0 ] ) ;
172
+ // For small values, the first few bytes should match exactly.
173
+ assert_eq ! ( out[ 0 ] , 0x01 ) ;
174
+ assert_eq ! ( out[ 1 ] , 0x02 ) ;
175
+ assert_eq ! ( out[ 2 ] , 0x03 ) ;
176
+ assert_eq ! ( out[ 3 ] , 0x04 ) ;
177
+
178
+ // Test that the packing formula works correctly for a simple case.
179
+ let expected = Felt :: from ( 0x01 )
180
+ + Felt :: from ( 0x02 ) * Felt :: from ( 1u64 << 8 )
181
+ + Felt :: from ( 0x03 ) * Felt :: from ( 1u64 << 16 )
182
+ + Felt :: from ( 0x04 ) * Felt :: from ( 1u64 << 24 ) ;
183
+ assert_eq ! ( f, expected) ;
184
+
185
+ // Test with a value that exceeds the field prime P to verify modular reduction.
186
+ // Create a 32-byte buffer with all 0xFF bytes, representing 2^256 - 1.
187
+ let max_buf = [ 0xFF_u8 ; 32 ] ;
188
+ let f_max = Blake2Felt252 :: pack_256_le_to_felt ( & max_buf) ;
189
+
190
+ // The result should be (2^256 - 1) mod P.
191
+ // Since 2^256 = Felt::TWO.pow(256), we can compute this value directly.
192
+ // This tests that modular reduction works correctly when exceeding the field prime.
193
+ let two_pow_256_minus_one = Felt :: TWO . pow ( 256u32 ) - Felt :: ONE ;
194
+ assert_eq ! ( f_max, two_pow_256_minus_one) ;
172
195
}
173
196
174
- /// Test that pack_224_le_to_felt panics when input is shorter than 28 bytes.
197
+ /// Test that pack_256_le_to_felt panics when input is shorter than 32 bytes.
175
198
#[ test]
176
- #[ should_panic( expected = "need at least 28 bytes" ) ]
177
- fn test_pack_224_le_to_felt_too_short ( ) {
178
- let too_short = [ 0u8 ; 27 ] ;
179
- Blake2Felt252 :: pack_224_le_to_felt ( & too_short) ;
199
+ #[ should_panic( expected = "need at least 32 bytes to pack 8 words " ) ]
200
+ fn test_pack_256_le_to_felt_too_short ( ) {
201
+ let too_short = [ 0u8 ; 31 ] ;
202
+ Blake2Felt252 :: pack_256_le_to_felt ( & too_short) ;
180
203
}
181
204
182
- /// Test that hashing a single zero Felt produces the expected 224 -bit Blake2s digest.
205
+ /// Test that hashing a single zero Felt produces the expected 256 -bit Blake2s digest.
183
206
#[ test]
184
207
fn test_hash_single_zero ( ) {
185
208
let zero = Felt :: from_hex_unchecked ( "0" ) ;
186
209
let hash = Blake2Felt252 :: hash_single ( & zero) ;
187
- let expected =
188
- Felt :: from_hex_unchecked ( "71a2f9bc7c9df9dc4ca0e7a1c5908d5eff88af963c3264f412dbdf50" ) ;
210
+ let expected = Felt :: from_hex_unchecked (
211
+ "5768af071a2f8df7c9df9dc4ca0e7a1c5908d5eff88af963c3264f412dbdf43" ,
212
+ ) ;
189
213
assert_eq ! ( hash, expected) ;
190
214
}
191
215
192
- /// Test that hashing an array of Felts [1, 2] produces the expected 224 -bit Blake2s digest.
216
+ /// Test that hashing an array of Felts [1, 2] produces the expected 256 -bit Blake2s digest.
193
217
#[ test]
194
218
fn test_hash_array_one_two ( ) {
195
219
let one = Felt :: from_hex_unchecked ( "1" ) ;
196
220
let two = Felt :: from_hex_unchecked ( "2" ) ;
197
221
let hash = Blake2Felt252 :: hash_array ( & [ one, two] ) ;
198
- let expected =
199
- Felt :: from_hex_unchecked ( "a14b223236366f30e9c77b6e56c8835de7dc5aee36957d4384cce67b" ) ;
222
+ let expected = Felt :: from_hex_unchecked (
223
+ "5534c03a14b214436366f30e9c77b6e56c8835de7dc5aee36957d4384cce66d" ,
224
+ ) ;
200
225
assert_eq ! ( hash, expected) ;
201
226
}
227
+
228
+ /// Test the encode_felt252_data_and_calc_blake_hash function
229
+ /// with the same result as the Cairo v0.14 version.
230
+ #[ rstest]
231
+ #[ case:: empty( vec![ ] , "874258848688468311465623299960361657518391155660316941922502367727700287818" ) ]
232
+ #[ case:: boundary_under_2_63( vec![ Felt :: from( ( 1u64 << 63 ) - 1 ) ] , "94160078030592802631039216199460125121854007413180444742120780261703604445" ) ]
233
+ #[ case:: boundary_at_2_63( vec![ Felt :: from( 1u64 << 63 ) ] , "318549634615606806810268830802792194529205864650702991817600345489579978482" ) ]
234
+ #[ case:: very_large_felt( vec![ Felt :: from_hex_unchecked( "800000000000011000000000000000000000000000000000000000000000000" ) ] , "3505594194634492896230805823524239179921427575619914728883524629460058657521" ) ]
235
+ #[ case:: mixed_small_large( vec![ Felt :: from( 42 ) , Felt :: from( 1u64 << 63 ) , Felt :: from( 1337 ) ] , "1127477916086913892828040583976438888091205536601278656613505514972451246501" ) ]
236
+ #[ case:: max_u64( vec![ Felt :: from( u64 :: MAX ) ] , "3515074221976790747383295076946184515593027667350620348239642126105984996390" ) ]
237
+ fn test_encode_felt252_data_and_calc_blake_hash (
238
+ #[ case] input : Vec < Felt > ,
239
+ #[ case] expected_result : & str ,
240
+ ) {
241
+ let result = Blake2Felt252 :: encode_felt252_data_and_calc_blake_hash ( & input) ;
242
+ let expected = Felt :: from_dec_str ( expected_result) . unwrap ( ) ;
243
+ assert_eq ! (
244
+ result, expected,
245
+ "rust_implementation: {result:?} != cairo_implementation: {expected:?}"
246
+ ) ;
247
+ }
202
248
}
0 commit comments