diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/hash/blake2s.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/hash/blake2s.cairo index 946ffabbd5e..f80763477b0 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/hash/blake2s.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/hash/blake2s.cairo @@ -579,6 +579,10 @@ func blake2s_felts{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, blake2s_ptr: f // Note: This function can nondeterministically choose between several encodings of felts, // x < PRIME can be encoded as x + PRIME, x + 2 * PRIME, etc. The canonical encoding is // given when x < PRIME. +// TODO(alont): Write custom hints and integrate with VM. +// TODO(alont): Consider adding cases for 1 u32 (small immediates, including negatives) +// and 3 u32s (extended opcodes). +// TODO(alont): Consider unrolling this loop to avoid state copy overhead. func encode_felt252_to_u32s{range_check_ptr: felt}( packed_values_len: felt, packed_values: felt*, unpacked_u32s: felt* ) -> felt { @@ -667,23 +671,8 @@ const BLAKE2S_FINALIZE_INSTRUCTION = OFF_MINUS_1 * COUNTER_OFFSET + OFF_MINUS_3 OPCODE_EXT_OFFSET; // Computes blake2s of `input` of size `len` felts, representing 32 bits each. -// Note: this function guarantees that len > 0. func blake_with_opcode{range_check_ptr}(len: felt, data: felt*, out: felt*) { alloc_locals; - if (len == 0) { - // hash32 = [105,33,122,48, 121,144,128,148, 225,17,33,208, 66,53,74,124, - // 31,85,182,72, 44,161,165,30, 27,37,13,253, 30,208,238,249] - // as little-endian u32s: - assert [out + 0] = 813310313; // 0x307A2169 - assert [out + 1] = 2491453561; // 0x94809079 - assert [out + 2] = 3491828193; // 0xD02111E1 - assert [out + 3] = 2085238082; // 0x7C4A3542 - assert [out + 4] = 1219908895; // 0x48B6551F - assert [out + 5] = 514171180; // 0x1EA5A12C - assert [out + 6] = 4245497115; // 0xFD0D251B - assert [out + 7] = 4193177630; // 0xF9EED01E - return (); - } let (local state: felt*) = alloc(); assert state[0] = 0x6B08E647; // IV[0] ^ 0x01010020 (config: no key, 32 bytes output). @@ -696,8 +685,16 @@ func blake_with_opcode{range_check_ptr}(len: felt, data: felt*, out: felt*) { assert state[7] = 0x5BE0CD19; // Express the length in bytes, subtract the remainder for finalize. - let (_, rem) = unsigned_div_rem(len - 1, 16); - local rem = rem + 1; + local rem; + if (len == 0) { + assert rem = 0; + tempvar range_check_ptr = range_check_ptr; + } else { + let (_, r) = unsigned_div_rem(len - 1, 16); + assert rem = r + 1; + tempvar range_check_ptr = range_check_ptr; + } + local len_in_bytes = (len - rem) * 4; local range_check_ptr = range_check_ptr; @@ -745,8 +742,8 @@ func blake_with_opcode{range_check_ptr}(len: felt, data: felt*, out: felt*) { // Given `data_len` felt252s at `data`, encodes them as u32s as defined in `encode_felt252_to_u32s` // and computes the blake2s hash of the result using the dedicated opcodes. -// The result is then returned as a 224-bit felt, ignoring the last 32 bits. -func encode_felt252_data_and_calc_224_bit_blake_hash{range_check_ptr: felt}( +// The 256 bit result is then returned as a felt252 (i.e. modulo PRIME). +func encode_felt252_data_and_calc_blake_hash{range_check_ptr: felt}( data_len: felt, data: felt* ) -> (hash: felt) { alloc_locals; @@ -757,8 +754,8 @@ func encode_felt252_data_and_calc_224_bit_blake_hash{range_check_ptr: felt}( let (local blake_output: felt*) = alloc(); blake_with_opcode(len=encoded_data_len, data=encoded_data, out=blake_output); return ( - hash=blake_output[6] * 2 ** 192 + blake_output[5] * 2 ** 160 + blake_output[4] * 2 ** 128 + - blake_output[3] * 2 ** 96 + blake_output[2] * 2 ** 64 + blake_output[1] * 2 ** 32 + - blake_output[0], + hash=blake_output[7] * 2 ** 224 + blake_output[6] * 2 ** 192 + blake_output[5] * 2 ** 160 + + blake_output[4] * 2 ** 128 + blake_output[3] * 2 ** 96 + blake_output[2] * 2 ** 64 + + blake_output[1] * 2 ** 32 + blake_output[0], ); } diff --git a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo index 0b2106621dd..d5b255a8dce 100644 --- a/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo +++ b/crates/apollo_starknet_os_program/src/cairo/starkware/starknet/core/os/os.cairo @@ -46,7 +46,7 @@ from starkware.starknet.core.os.output import ( ) from starkware.starknet.core.os.state.commitment import StateEntry from starkware.starknet.core.os.state.state import OsStateUpdate, state_update -from starkware.starknet.core.os.hash.blake2s import encode_felt252_data_and_calc_224_bit_blake_hash +from starkware.starknet.core.os.hash.blake2s import encode_felt252_data_and_calc_blake_hash // Executes transactions on Starknet. func main{ diff --git a/crates/blake2s/src/lib.rs b/crates/blake2s/src/lib.rs index 21dec45bd04..90dbfeaf7ac 100644 --- a/crates/blake2s/src/lib.rs +++ b/crates/blake2s/src/lib.rs @@ -38,29 +38,48 @@ pub fn encode_felts_to_u32s(felts: Vec) -> Vec { unpacked_u32s } -/// Packs the first 7 little-endian 32-bit words (28 bytes) of `bytes` -/// into a single 224-bit Felt. -fn pack_224_le_to_felt(bytes: &[u8]) -> Felt { - assert!(bytes.len() >= 28, "need at least 28 bytes to pack 7 words"); +/// Packs the first 8 little-endian 32-bit words (32 bytes) of `bytes` into a single long Felt +/// by summing each word shifted by multiples of 32 bits: +/// +/// `result = word_0 + (word_1 << 32) + (word_2 << 64) + ... + (word_7 << 224) (mod P)` +/// +/// # Panics +/// +/// Panics if `bytes.len() < 32`. +pub fn pack_256_le_to_felt(bytes: &[u8]) -> Felt { + const BYTES_PER_WORD: usize = 4; + const WORD_COUNT: usize = 8; + assert!( + bytes.len() >= BYTES_PER_WORD * WORD_COUNT, + "pack_224_le_to_felt: need at least {} bytes, got {}", + BYTES_PER_WORD * WORD_COUNT, + bytes.len() + ); - // 1) copy your 28-byte LE-hash into the low 28 bytes of a 32-byte buffer - let mut buf = [0u8; 32]; - buf[..28].copy_from_slice(&bytes[..28]); + let mut result = Felt::ZERO; + let mut current_factor = Felt::ONE; + let shift_factor = Felt::from(1u64 << 32); + + for chunk in bytes[..BYTES_PER_WORD * WORD_COUNT].chunks_exact(BYTES_PER_WORD) { + // Each chunk is exactly 4 bytes, little-endian order + let word = u32::from_le_bytes(chunk.try_into().unwrap()); + result += Felt::from(word) * current_factor; + current_factor *= shift_factor; + } - // 2) interpret the whole 32-byte buffer as a little-endian Felt - Felt::from_bytes_le(&buf) + result } pub fn blake2s_to_felt(data: &[u8]) -> Felt { let mut hasher = Blake2s256::new(); hasher.update(data); let hash32 = hasher.finalize(); - pack_224_le_to_felt(hash32.as_slice()) + pack_256_le_to_felt(hash32.as_slice()) } /// Encodes a slice of `Felt` values into 32-bit words exactly as Cairo’s /// `encode_felt252_to_u32s` hint does, then hashes the resulting byte stream -/// with Blake2s-256 and returns the 224-bit truncated digest as a `Felt`. +/// with Blake2s-256 and returns the 256-bit truncated digest as a `Felt`. pub fn encode_felt252_data_and_calc_224_bit_blake_hash(data: &[Felt]) -> Felt { // 1) Unpack each Felt into 2 or 8 u32 limbs let u32_words = encode_felts_to_u32s(data.to_vec()); @@ -71,6 +90,6 @@ pub fn encode_felt252_data_and_calc_224_bit_blake_hash(data: &[Felt]) -> Felt { byte_stream.extend_from_slice(&word.to_le_bytes()); } - // 3) Compute Blake2s-256 over the bytes and pack the first 224 bits into a Felt + // 3) Compute Blake2s-256 over the bytes and pack the first 256 bits into a Felt blake2s_to_felt(&byte_stream) } diff --git a/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs b/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs index 78a12f01b2f..2326aac5465 100644 --- a/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs +++ b/crates/starknet_os/src/hints/hint_implementation/blake2s/blake2s_test.rs @@ -79,7 +79,7 @@ fn test_cairo_vs_rust_blake2s_implementation(#[case] test_data: Vec) { let result = initialize_and_run_cairo_0_entry_point( &runner_config, program_bytes, - "starkware.starknet.core.os.hash.blake2s.encode_felt252_data_and_calc_224_bit_blake_hash", + "starkware.starknet.core.os.hash.blake2s.encode_felt252_data_and_calc_blake_hash", &explicit_args, &implicit_args, &expected_return_values,