Repository: kkrt-labs/keth
Pull request #1390: feat: restore state root computation & abstract hash functions used
Author: login: enitrat
Created: 2025-04-22T14:32:28Z | Merged: 2025-04-24T12:27:17Z

### PR Summary: State Root Restoration and Hash Abstraction

**Closes #1389**

This PR reverts the diff-based state validation (AI-Report April 11, 2025) to restore `state_root` and `storage_root` computation in Keth, introducing hash function abstraction for Keccak, Blake2s, and future Poseidon support.

#### Key Changes
- **State Root Restoration (`state.cairo`)**: Reintroduced `state_root` and added `storage_roots` with helpers to compute MPT roots, squashing tries for unique keys.
- **Hash Abstraction**:
  - Updated `state_root`, `storage_roots`, `root` (`trie.cairo`), and related functions to include implicit hash builtins (`keccak_ptr`, `poseidon_ptr`), and `hash_function_name: felt`.
  - Added `hash_with` (`hash.cairo`) to dispatch to Keccak or Blake2s based on `hash_function_name`, replacing hardcoded `keccak256`.
  - Implemented `blake2s_bytes` and `bytes_to_bytes4_little_endian` (`bytes.cairo`) for Blake2s support.
- **Constants**: Renamed `EMPTY_ROOT`/`EMPTY_HASH` to `EMPTY_ROOT_KECCAK`/`EMPTY_HASH_KECCAK` (`fork_types.cairo`, `state.cairo`).
- **Testing**: Updated tests (`test_fork.py`, `test_state.py`, `test_trie.py`, `test_hash.py`) to validate roots with Keccak and Blake2s, including empty tries.
- **Misc**: Integrated `state_root` in `main.cairo`, updated Python runner (`runner.py`) to filter segment pointers, and added VM hints for Blake2s.
- Uses the new blake2s opcode

#### Review Focus
1. **Hash Abstraction**: Verified `root` uses `hash_with` dynamically. **Recommend**: Document `hash_function_name` values.
2. **Blake2s**: Implemented and tested. **Recommend**: Ensure `finalize_blake2s` is called once this is used.
3. **Testing**: Covers Keccak/Blake2s. **Recommend**: Test invalid `hash_function_name` and performance.

#### Implications
Enables Blake2s for intermediate commitments, ensures valid Ethereum state roots, and prepares for Poseidon. Follow-up needed for `EMPTY_ROOT_BLAKE2S` and performance optimization.

Linked issue #1389: feat: restore state root computation & abstract hash functions used
## 1. Make blake2s usable from higher-level types
- Inside ethereum.crypto.hash implement a function that computes the blake2s hash on an input `Bytes` buffer.

This will require:
- Converting the input bytes to a bytes4 array
- Running blake2s
- returning the result as a Hash32
- implementing related tests

## 2. Restore the state_root and storage_root computation

Revert the git commit that removed these functions. Once reverted, ensure our codebase still compiles.
Then, we're interested in two things:
1. Being able to compute root on tries with keccak (final root computations for withdrawal, trie, receipt tries)
2. Being able to compute root on tries with blake2s (intermediate commitments for tx-processing splits).
3. Eventually, in the future, do this operation with poseidon

From these requirements, it's clear that we need a way to abstract the hash function used in these root computations.
Thus: 
- Develop an abstraction of the hash function used in `root` and `storage_root`. This abstraction will be materialized by an extra implicit argument that is the codeoffset of the function to invoke, as well as including any required implicit arg for the execution of all supported hash functions. 
- Once the signatures have been updated, update all calls to `keccak256` in root-related computations to using a generic `hash_with` function that takes as input the buffer to hash and the hash function to use, and returns the hash using the provided hash function

Diff:
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 4e4a0f136..ffc02402b 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -18,7 +18,7 @@ jobs:
     with:
       hypothesis-profile: "ci"
       coverage-flags: "ci-unit"
-      parallelism: logical
+      parallelism: "logical"
       # Ignore ef-test run in this job (handled in the ef-tests job)
       pytest-add-params: "-m 'not slow' --ignore-glob=cairo/tests/ef_tests/"
 
diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml
index b40ee9d7a..b7066aeb8 100644
--- a/.trunk/configs/.markdownlint.yaml
+++ b/.trunk/configs/.markdownlint.yaml
@@ -1,2 +1,5 @@
 # Prettier friendly markdownlint config (all formatting rules disabled)
 extends: markdownlint/style/prettier
+
+MD024:
+  siblings_only: true
diff --git a/cairo/ethereum/cancun/fork.cairo b/cairo/ethereum/cancun/fork.cairo
index 252ca785a..7b7487730 100644
--- a/cairo/ethereum/cancun/fork.cairo
+++ b/cairo/ethereum/cancun/fork.cairo
@@ -1245,7 +1245,7 @@ func apply_body{
         ),
     );
     let none_storage_roots = OptionalMappingAddressBytes32(cast(0, MappingAddressBytes32Struct*));
-    let transactions_root = root(transaction_eth_trie, none_storage_roots);
+    let transactions_root = root(transaction_eth_trie, none_storage_roots, 'keccak256');
 
     tempvar receipt_eth_trie = EthereumTries(
         new EthereumTriesEnum(
@@ -1260,7 +1260,7 @@ func apply_body{
             ),
         ),
     );
-    let receipts_root = root(receipt_eth_trie, none_storage_roots);
+    let receipts_root = root(receipt_eth_trie, none_storage_roots, 'keccak256');
 
     tempvar withdrawals_eth_trie = EthereumTries(
         new EthereumTriesEnum(
@@ -1275,7 +1275,7 @@ func apply_body{
             withdrawal=withdrawals_trie,
         ),
     );
-    let withdrawals_root = root(withdrawals_eth_trie, none_storage_roots);
+    let withdrawals_root = root(withdrawals_eth_trie, none_storage_roots, 'keccak256');
 
     // Finalize the state, getting unique keys for main and storage tries
     finalize_state{state=state}();
@@ -1445,9 +1445,6 @@ func _process_withdrawals_inner{
     return _process_withdrawals_inner{state=state, trie=trie}(index + 1, withdrawals);
 }
 
-// @notice Given the historical blockchain and a block to execute, computes the STF on the initial state and updates the blockchain.
-// @dev: Note that the state_root of the new block is not computed in this `state_transition` function, and is replaced with a `0` instead.
-//       see `main.cairo`, the entrypoint of `Keth`.
 func state_transition{
     range_check_ptr,
     bitwise_ptr: BitwiseBuiltin*,
diff --git a/cairo/ethereum/cancun/fork_types.cairo b/cairo/ethereum/cancun/fork_types.cairo
index 770931f15..f102de839 100644
--- a/cairo/ethereum/cancun/fork_types.cairo
+++ b/cairo/ethereum/cancun/fork_types.cairo
@@ -15,7 +15,7 @@ from ethereum_types.bytes import (
 )
 from ethereum.utils.bytes import Bytes__eq__
 from ethereum_types.numeric import Uint, U256, U256Struct, bool
-from ethereum.crypto.hash import Hash32, EMPTY_ROOT, EMPTY_HASH
+from ethereum.crypto.hash import Hash32, EMPTY_ROOT_KECCAK, EMPTY_HASH_KECCAK
 from ethereum.utils.numeric import U256_to_be_bytes20
 from cairo_core.comparison import is_zero
 
@@ -190,8 +190,8 @@ struct MappingTupleAddressBytes32U256 {
 }
 
 func EMPTY_ACCOUNT() -> Account {
-    let (empty_root_ptr) = get_label_location(EMPTY_ROOT);
-    let (empty_hash_ptr) = get_label_location(EMPTY_HASH);
+    let (empty_root_ptr) = get_label_location(EMPTY_ROOT_KECCAK);
+    let (empty_hash_ptr) = get_label_location(EMPTY_HASH_KECCAK);
     tempvar balance = U256(new U256Struct(0, 0));
     let (data) = alloc();
     tempvar empty_code = OptionalBytes(new BytesStruct(data=data, len=0));
diff --git a/cairo/ethereum/cancun/state.cairo b/cairo/ethereum/cancun/state.cairo
index c0da14a85..5fef13823 100644
--- a/cairo/ethereum/cancun/state.cairo
+++ b/cairo/ethereum/cancun/state.cairo
@@ -37,7 +37,7 @@ from ethereum.cancun.fork_types import (
     ListTupleAddressBytes32,
     ListTupleAddressBytes32Struct,
 )
-from ethereum.crypto.hash import keccak256, EMPTY_HASH
+from ethereum.crypto.hash import keccak256, EMPTY_HASH_KECCAK
 from ethereum.cancun.trie import (
     get_tuple_address_bytes32_preimage_for_key,
     root,
@@ -70,7 +70,7 @@ from ethereum.cancun.trie import (
 )
 from ethereum.cancun.blocks import Withdrawal
 from ethereum_types.bytes import Bytes, Bytes32, Bytes32Struct, BytesStruct, OptionalBytes
-from ethereum.crypto.hash import EMPTY_ROOT
+from ethereum.crypto.hash import EMPTY_ROOT_KECCAK
 from ethereum_types.numeric import U256, U256Struct, Bool, bool, Uint
 from ethereum.utils.numeric import U256_le, U256_sub, U256_add, U256_mul
 from ethereum.cancun.utils.constants import U256_ZERO
@@ -656,7 +656,7 @@ func account_has_code_or_nonce{poseidon_ptr: PoseidonBuiltin*, state: State}(
     alloc_locals;
 
     let account = get_account(address);
-    let (empty_hash_ptr) = get_label_location(EMPTY_HASH);
+    let (empty_hash_ptr) = get_label_location(EMPTY_HASH_KECCAK);
     tempvar empty_hash = Bytes32(cast(empty_hash_ptr, Bytes32Struct*));
 
     if (account.value.nonce.value != 0) {
@@ -695,7 +695,7 @@ func get_storage_keys_for_address{dict_ptr: DictAccess*}(
 func account_has_storage{poseidon_ptr: PoseidonBuiltin*, state: State}(address: Address) -> bool {
     alloc_locals;
 
-    let (empty_root_ptr_) = get_label_location(EMPTY_ROOT);
+    let (empty_root_ptr_) = get_label_location(EMPTY_ROOT_KECCAK);
     let empty_root_ptr = cast(empty_root_ptr_, Bytes32Struct*);
     let account = get_account(address);
     if (empty_root_ptr.low == account.value.storage_root.value.low and
@@ -710,7 +710,7 @@ func account_has_storage{poseidon_ptr: PoseidonBuiltin*, state: State}(address:
 func is_account_empty{poseidon_ptr: PoseidonBuiltin*, state: State}(address: Address) -> bool {
     // Get the account at the address
     let account = get_account(address);
-    let (empty_hash_ptr) = get_label_location(EMPTY_HASH);
+    let (empty_hash_ptr) = get_label_location(EMPTY_HASH_KECCAK);
     tempvar empty_hash = Bytes32(cast(empty_hash_ptr, Bytes32Struct*));
 
     // Check if nonce is 0, code is empty, and balance is 0
@@ -1187,6 +1187,313 @@ func empty_transient_storage{range_check_ptr}() -> TransientStorage {
     return transient_storage;
 }
 
+// @notice Computes the storage roots of all the addresses in the state
+// @dev The input state must've been squashed for unique keys.
+func storage_roots{
+    range_check_ptr,
+    bitwise_ptr: BitwiseBuiltin*,
+    keccak_ptr: KeccakBuiltin*,
+    poseidon_ptr: PoseidonBuiltin*,
+}(state: State, hash_function_name: felt) -> MappingAddressBytes32 {
+    alloc_locals;
+
+    if (cast(state.value._main_trie.value._data.value.parent_dict, felt) != 0) {
+        raise('AssertionError');
+    }
+
+    // Create a Mapping[Address, Trie[Bytes32, U256]] that will contain the "flat" tries, where we
+    // will get the storage trie of each address
+    let (map_addr_storage_start) = default_dict_new(0);
+    tempvar map_addr_storage = MappingAddressTrieBytes32U256(
+        new MappingAddressTrieBytes32U256Struct(
+            dict_ptr_start=cast(map_addr_storage_start, AddressTrieBytes32U256DictAccess*),
+            dict_ptr=cast(map_addr_storage_start, AddressTrieBytes32U256DictAccess*),
+            parent_dict=cast(0, MappingAddressTrieBytes32U256Struct*),
+        ),
+    );
+
+    let squashed_storage_tries_start = cast(
+        state.value._storage_tries.value._data.value.dict_ptr_start, DictAccess*
+    );
+    let squashed_storage_tries_end = cast(
+        state.value._storage_tries.value._data.value.dict_ptr, DictAccess*
+    );
+    build_map_addr_storage_trie{
+        map_addr_storage=map_addr_storage, storage_tries_ptr_end=squashed_storage_tries_end
+    }(squashed_storage_tries_start);
+
+    // Squash the Mapping[address, trie[bytes32, u256]] to iterate over each address
+    let (squashed_map_addr_storage_start, squashed_map_addr_storage_end) = default_dict_finalize(
+        cast(map_addr_storage.value.dict_ptr_start, DictAccess*),
+        cast(map_addr_storage.value.dict_ptr, DictAccess*),
+        0,
+    );
+
+    tempvar map_addr_storage = MappingAddressTrieBytes32U256(
+        new MappingAddressTrieBytes32U256Struct(
+            dict_ptr_start=cast(squashed_map_addr_storage_start, AddressTrieBytes32U256DictAccess*),
+            dict_ptr=cast(squashed_map_addr_storage_end, AddressTrieBytes32U256DictAccess*),
+            parent_dict=cast(0, MappingAddressTrieBytes32U256Struct*),
+        ),
+    );
+
+    // Create a Mapping[Address, Bytes32] that will contain the storage root of each address's
+    // storage trie
+    // TODO: the EMPTY_ROOT should be relative to the hash function used!
+    let (empty_root_ptr) = get_label_location(EMPTY_ROOT_KECCAK);
+    let (map_addr_storage_root_start) = default_dict_new(cast(empty_root_ptr, felt));
+    tempvar map_addr_storage_root = MappingAddressBytes32(
+        new MappingAddressBytes32Struct(
+            dict_ptr_start=cast(map_addr_storage_root_start, AddressBytes32DictAccess*),
+            dict_ptr=cast(map_addr_storage_root_start, AddressBytes32DictAccess*),
+            parent_dict=cast(0, MappingAddressBytes32Struct*),
+        ),
+    );
+
+    // Iterate over each address, and get the root of its storage trie
+    let map_addr_storage_ptr = cast(
+        squashed_map_addr_storage_start, AddressTrieBytes32U256DictAccess*
+    );
+    let map_addr_storage_ptr_end = cast(
+        squashed_map_addr_storage_end, AddressTrieBytes32U256DictAccess*
+    );
+    build_map_addr_storage_root{
+        map_addr_storage_root=map_addr_storage_root,
+        map_addr_storage_ptr_end=map_addr_storage_ptr_end,
+    }(map_addr_storage_ptr, hash_function_name);
+
+    return map_addr_storage_root;
+}
+
+// @notice Builds a Mapping[Address, Bytes32] that contains the storage root of each address's
+// storage trie
+// @param map_addr_storage_root The mapping to write to
+// @param map_addr_storage_ptr The pointer to the current address in the Mapping[Address, Trie[Bytes32, U256]]
+// @param map_addr_storage_ptr_end The end of the Mapping[Address, Trie[Bytes32, U256]]
+func build_map_addr_storage_root{
+    range_check_ptr,
+    bitwise_ptr: BitwiseBuiltin*,
+    keccak_ptr: KeccakBuiltin*,
+    poseidon_ptr: PoseidonBuiltin*,
+    map_addr_storage_root: MappingAddressBytes32,
+    map_addr_storage_ptr_end: AddressTrieBytes32U256DictAccess*,
+}(map_addr_storage_ptr: AddressTrieBytes32U256DictAccess*, hash_function_name: felt) {
+    alloc_locals;
+
+    if (map_addr_storage_ptr == map_addr_storage_ptr_end) {
+        return ();
+    }
+
+    let address = map_addr_storage_ptr.key;
+    let storage_trie = map_addr_storage_ptr.new_value;
+
+    tempvar union_trie = EthereumTries(
+        new EthereumTriesEnum(
+            account=TrieAddressOptionalAccount(cast(0, TrieAddressOptionalAccountStruct*)),
+            storage=storage_trie,
+            transaction=TrieBytesOptionalUnionBytesLegacyTransaction(
+                cast(0, TrieBytesOptionalUnionBytesLegacyTransactionStruct*)
+            ),
+            receipt=TrieBytesOptionalUnionBytesReceipt(
+                cast(0, TrieBytesOptionalUnionBytesReceiptStruct*)
+            ),
+            withdrawal=TrieBytesOptionalUnionBytesWithdrawal(
+                cast(0, TrieBytesOptionalUnionBytesWithdrawalStruct*)
+            ),
+        ),
+    );
+
+    let storage_root = root(
+        union_trie,
+        OptionalMappingAddressBytes32(cast(0, MappingAddressBytes32Struct*)),
+        hash_function_name,
+    );
+
+    mapping_address_bytes32_write{mapping=map_addr_storage_root}(address, storage_root);
+
+    // Squash the Trie[Bytes32, U256] - it won't ever be used again.
+    let trie_ptr_start = storage_trie.value._data.value.dict_ptr_start;
+    let trie_ptr_end = storage_trie.value._data.value.dict_ptr;
+    default_dict_finalize(
+        cast(trie_ptr_start, DictAccess*),
+        cast(trie_ptr_end, DictAccess*),
+        cast(storage_trie.value.default.value, felt),
+    );
+
+    return build_map_addr_storage_root(map_addr_storage_ptr + DictAccess.SIZE, hash_function_name);
+}
+
+// @notice Builds a Mapping[Address, Trie[Bytes32, U256]] that contains the storage trie of each
+// address
+// @param map_addr_storage The mapping to write to
+// @param storage_tries_ptr The pointer to the current entry in the Trie[Tuple[Address, Bytes32], U256]
+// @param storage_tries_ptr_end The end of the Trie[Tuple[Address, Bytes32], U256]
+func build_map_addr_storage_trie{
+    range_check_ptr,
+    poseidon_ptr: PoseidonBuiltin*,
+    map_addr_storage: MappingAddressTrieBytes32U256,
+    storage_tries_ptr_end: DictAccess*,
+}(storage_tries_ptr: DictAccess*) {
+    alloc_locals;
+
+    if (storage_tries_ptr == storage_tries_ptr_end) {
+        return ();
+    }
+
+    // Skip all None values, which are deleted trie entries. We don't need them to compute
+    // the storage roots.
+    if (cast(storage_tries_ptr.new_value, felt) == 0) {
+        return build_map_addr_storage_trie(storage_tries_ptr + DictAccess.SIZE);
+    }
+
+    let tup_address_b32 = get_tuple_address_bytes32_preimage_for_key(
+        storage_tries_ptr.key, storage_tries_ptr_end
+    );
+    build_storage_trie_for_address(
+        tup_address_b32.value.address,
+        tup_address_b32.value.bytes32,
+        U256(cast(storage_tries_ptr.new_value, U256Struct*)),
+    );
+
+    return build_map_addr_storage_trie(storage_tries_ptr + DictAccess.SIZE);
+}
+
+// @notice Modifies a Trie[Bytes32, U256], the storage trie of an address, and adds it to the
+// Mapping[Address, Trie[Bytes32, U256]]
+// @param address The address to build the storage trie for
+// @param key The key to add to the storage trie
+// @param value The value to add to the storage trie
+func build_storage_trie_for_address{
+    range_check_ptr, poseidon_ptr: PoseidonBuiltin*, map_addr_storage: MappingAddressTrieBytes32U256
+}(address: Address, key: Bytes32, value: U256) {
+    alloc_locals;
+
+    let dict_ptr = cast(map_addr_storage.value.dict_ptr, DictAccess*);
+
+    // Get storage trie for address
+    let trie = mapping_address_trie_bytes32_u256_read{mapping=map_addr_storage}(address);
+
+    // Modify storage trie for address
+    trie_set_TrieBytes32U256{poseidon_ptr=poseidon_ptr, trie=trie}(key, value);
+
+    // Update the mapping address -> trie
+    mapping_address_trie_bytes32_u256_write{mapping=map_addr_storage}(address, trie);
+
+    return ();
+}
+
+// @notice Computes the state root of the state
+// @dev Squashes the main trie for unique keys, and updates the state with the new, squashed segment.
+func state_root{
+    range_check_ptr,
+    bitwise_ptr: BitwiseBuiltin*,
+    keccak_ptr: KeccakBuiltin*,
+    poseidon_ptr: PoseidonBuiltin*,
+}(state: State, hash_function_name: felt) -> Bytes32 {
+    alloc_locals;
+
+    if (cast(state.value._main_trie.value._data.value.parent_dict, felt) != 0) {
+        raise('AssertionError');
+    }
+
+    let storage_roots_ = storage_roots(state, hash_function_name);
+
+    tempvar trie_union = EthereumTries(
+        new EthereumTriesEnum(
+            account=state.value._main_trie,
+            storage=TrieBytes32U256(cast(0, TrieBytes32U256Struct*)),
+            transaction=TrieBytesOptionalUnionBytesLegacyTransaction(
+                cast(0, TrieBytesOptionalUnionBytesLegacyTransactionStruct*)
+            ),
+            receipt=TrieBytesOptionalUnionBytesReceipt(
+                cast(0, TrieBytesOptionalUnionBytesReceiptStruct*)
+            ),
+            withdrawal=TrieBytesOptionalUnionBytesWithdrawal(
+                cast(0, TrieBytesOptionalUnionBytesWithdrawalStruct*)
+            ),
+        ),
+    );
+
+    let state_root = root(
+        trie_union, OptionalMappingAddressBytes32(storage_roots_.value), hash_function_name
+    );
+    return state_root;
+}
+
+// Utils function, porting this to a module would incur a lot of refactoring due to circular imports
+func mapping_address_trie_bytes32_u256_read{
+    range_check_ptr, mapping: MappingAddressTrieBytes32U256
+}(key: Address) -> TrieBytes32U256 {
+    alloc_locals;
+    let dict_ptr = cast(mapping.value.dict_ptr, DictAccess*);
+    let (value_ptr) = dict_read{dict_ptr=dict_ptr}(key.value);
+
+    if (cast(value_ptr, felt) == 0) {
+        tempvar default = new U256Struct(0, 0);
+        let (segment_start) = default_dict_new(cast(default, felt));
+        tempvar trie_ptr = new TrieBytes32U256Struct(
+            secured=bool(1),
+            default=U256(default),
+            _data=MappingBytes32U256(
+                new MappingBytes32U256Struct(
+                    dict_ptr_start=cast(segment_start, Bytes32U256DictAccess*),
+                    dict_ptr=cast(segment_start, Bytes32U256DictAccess*),
+                    parent_dict=cast(0, MappingBytes32U256Struct*),
+                ),
+            ),
+        );
+    } else {
+        tempvar trie_ptr = cast(value_ptr, TrieBytes32U256Struct*);
+    }
+
+    tempvar mapping = MappingAddressTrieBytes32U256(
+        new MappingAddressTrieBytes32U256Struct(
+            dict_ptr_start=mapping.value.dict_ptr_start,
+            dict_ptr=cast(dict_ptr, AddressTrieBytes32U256DictAccess*),
+            parent_dict=mapping.value.parent_dict,
+        ),
+    );
+    let trie = TrieBytes32U256(trie_ptr);
+    return trie;
+}
+
+// Utils function, porting this to a module would incur a lot of refactoring due to circular imports
+func mapping_address_trie_bytes32_u256_write{
+    range_check_ptr, mapping: MappingAddressTrieBytes32U256
+}(key: Address, value: TrieBytes32U256) {
+    alloc_locals;
+    let dict_ptr = cast(mapping.value.dict_ptr, DictAccess*);
+    let value_felt = cast(value.value, felt);
+    dict_write{dict_ptr=dict_ptr}(key.value, value_felt);
+
+    tempvar mapping = MappingAddressTrieBytes32U256(
+        new MappingAddressTrieBytes32U256Struct(
+            dict_ptr_start=mapping.value.dict_ptr_start,
+            dict_ptr=cast(dict_ptr, AddressTrieBytes32U256DictAccess*),
+            parent_dict=mapping.value.parent_dict,
+        ),
+    );
+    return ();
+}
+
+// Utils function, porting this to a module would incur a lot of refactoring due to circular imports
+func mapping_address_bytes32_write{range_check_ptr, mapping: MappingAddressBytes32}(
+    key: Address, value: Bytes32
+) {
+    alloc_locals;
+    let dict_ptr = cast(mapping.value.dict_ptr, DictAccess*);
+    let value_felt = cast(value.value, felt);
+    dict_write{dict_ptr=dict_ptr}(key.value, value_felt);
+    tempvar mapping = MappingAddressBytes32(
+        new MappingAddressBytes32Struct(
+            dict_ptr_start=mapping.value.dict_ptr_start,
+            dict_ptr=cast(dict_ptr, AddressBytes32DictAccess*),
+            parent_dict=mapping.value.parent_dict,
+        ),
+    );
+    return ();
+}
+
 // @notice Finalizes a `State` struct by squashing all of its field.
 // @dev Squashing the main_trie and storage_tries not only de-duplicates the keys, but also
 //      sorts them in ascending order of tuple (key, prev_value, new_value), which is important
diff --git a/cairo/ethereum/cancun/trie.cairo b/cairo/ethereum/cancun/trie.cairo
index a286e717a..3c8090ca8 100644
--- a/cairo/ethereum/cancun/trie.cairo
+++ b/cairo/ethereum/cancun/trie.cairo
@@ -12,7 +12,7 @@ from starkware.cairo.common.memcpy import memcpy
 
 from legacy.utils.bytes import uint256_to_bytes32_little
 from legacy.utils.dict import hashdict_read, hashdict_write, dict_new_empty, dict_read, dict_squash
-from ethereum.crypto.hash import keccak256
+from ethereum.crypto.hash import hash_with
 from ethereum.utils.numeric import min
 from ethereum_rlp.rlp import encode, _encode_bytes, _encode, Extended__eq__
 from ethereum.utils.numeric import U256__eq__
@@ -476,7 +476,7 @@ struct Node {
 
 func encode_internal_node{
     range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: KeccakBuiltin*
-}(node: InternalNode) -> Extended {
+}(node: InternalNode, hash_function_name: felt) -> Extended {
     alloc_locals;
     local unencoded: Extended;
     local range_check_ptr_end;
@@ -544,16 +544,14 @@ func encode_internal_node{
         return unencoded;
     }
 
-    let hash = keccak256(encoded);
+    let hash = hash_with(encoded, hash_function_name);
     let (data) = alloc();
     uint256_to_bytes32_little(data, [hash.value]);
     let hashed = ExtendedImpl.bytes(Bytes(new BytesStruct(data, 32)));
     return hashed;
 }
 
-func encode_node{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: KeccakBuiltin*}(
-    node: Node, storage_root: Bytes
-) -> Bytes {
+func encode_node{range_check_ptr}(node: Node, storage_root: Bytes) -> Bytes {
     alloc_locals;
 
     tempvar is_none = is_zero(cast(node.value, felt));
@@ -1183,7 +1181,11 @@ func _prepare_trie{
     bitwise_ptr: BitwiseBuiltin*,
     keccak_ptr: KeccakBuiltin*,
     poseidon_ptr: PoseidonBuiltin*,
-}(trie_union: EthereumTries, storage_roots_: OptionalMappingAddressBytes32) -> MappingBytesBytes {
+}(
+    trie_union: EthereumTries,
+    storage_roots_: OptionalMappingAddressBytes32,
+    hash_function_name: felt,
+) -> MappingBytesBytes {
     alloc_locals;
 
     let (local mapping_ptr_start: BytesBytesDictAccess*) = default_dict_new(0);
@@ -1215,34 +1217,47 @@ func _prepare_trie{
         account_trie.value._data.value.dict_ptr_start,
         mapping_ptr_start,
         MappingAddressBytes32(storage_roots_.value),
+        hash_function_name,
     );
     jmp end;
 
     storage:
     let storage_trie = trie_union.value.storage;
     _prepare_trie_inner_storage(
-        storage_trie, storage_trie.value._data.value.dict_ptr_start, mapping_ptr_start
+        storage_trie,
+        storage_trie.value._data.value.dict_ptr_start,
+        mapping_ptr_start,
+        hash_function_name,
     );
     jmp end;
 
     transaction:
     let transaction_trie = trie_union.value.transaction;
     _prepare_trie_inner_transaction(
-        transaction_trie, transaction_trie.value._data.value.dict_ptr_start, mapping_ptr_start
+        transaction_trie,
+        transaction_trie.value._data.value.dict_ptr_start,
+        mapping_ptr_start,
+        hash_function_name,
     );
     jmp end;
 
     receipt:
     let receipt_trie = trie_union.value.receipt;
     _prepare_trie_inner_receipt(
-        receipt_trie, receipt_trie.value._data.value.dict_ptr_start, mapping_ptr_start
+        receipt_trie,
+        receipt_trie.value._data.value.dict_ptr_start,
+        mapping_ptr_start,
+        hash_function_name,
     );
     jmp end;
 
     withdrawal:
     let withdrawal_trie = trie_union.value.withdrawal;
     _prepare_trie_inner_withdrawal(
-        withdrawal_trie, withdrawal_trie.value._data.value.dict_ptr_start, mapping_ptr_start
+        withdrawal_trie,
+        withdrawal_trie.value._data.value.dict_ptr_start,
+        mapping_ptr_start,
+        hash_function_name,
     );
     jmp end;
 
@@ -1279,6 +1294,7 @@ func _prepare_trie_inner_account{
     dict_ptr: AddressAccountDictAccess*,
     mapping_ptr_end: BytesBytesDictAccess*,
     storage_roots_: MappingAddressBytes32,
+    hash_function_name: felt,
 ) -> BytesBytesDictAccess* {
     alloc_locals;
 
@@ -1289,7 +1305,11 @@ func _prepare_trie_inner_account{
     // Skip all None values, which are deleted trie entries
     if (cast(dict_ptr.new_value.value, felt) == 0) {
         return _prepare_trie_inner_account(
-            trie, dict_ptr + AddressAccountDictAccess.SIZE, mapping_ptr_end, storage_roots_
+            trie,
+            dict_ptr + AddressAccountDictAccess.SIZE,
+            mapping_ptr_end,
+            storage_roots_,
+            hash_function_name,
         );
     }
 
@@ -1319,7 +1339,7 @@ func _prepare_trie_inner_account{
     // TODO: Common part, factorise.
 
     if (trie.value.secured.value != 0) {
-        let key_bytes32 = keccak256(preimage);
+        let key_bytes32 = hash_with(preimage, hash_function_name);
         let key_bytes = Bytes32_to_Bytes(key_bytes32);
         tempvar range_check_ptr = range_check_ptr;
         tempvar bitwise_ptr = bitwise_ptr;
@@ -1346,6 +1366,7 @@ func _prepare_trie_inner_account{
         dict_ptr + AddressAccountDictAccess.SIZE,
         cast(mapping_dict_ptr, BytesBytesDictAccess*),
         storage_roots_,
+        hash_function_name,
     );
 }
 
@@ -1355,7 +1376,10 @@ func _prepare_trie_inner_storage{
     keccak_ptr: KeccakBuiltin*,
     poseidon_ptr: PoseidonBuiltin*,
 }(
-    trie: TrieBytes32U256, dict_ptr: Bytes32U256DictAccess*, mapping_ptr_end: BytesBytesDictAccess*
+    trie: TrieBytes32U256,
+    dict_ptr: Bytes32U256DictAccess*,
+    mapping_ptr_end: BytesBytesDictAccess*,
+    hash_function_name: felt,
 ) -> BytesBytesDictAccess* {
     alloc_locals;
 
@@ -1368,7 +1392,7 @@ func _prepare_trie_inner_storage{
     // Trie[Tuple[Address, Bytes32], U256], there should not be any None values remaining.
     if (dict_ptr.new_value.value == 0) {
         return _prepare_trie_inner_storage(
-            trie, dict_ptr + Bytes32U256DictAccess.SIZE, mapping_ptr_end
+            trie, dict_ptr + Bytes32U256DictAccess.SIZE, mapping_ptr_end, hash_function_name
         );
     }
 
@@ -1397,7 +1421,7 @@ func _prepare_trie_inner_storage{
     }
 
     if (trie.value.secured.value != 0) {
-        let key_bytes32 = keccak256(preimage);
+        let key_bytes32 = hash_with(preimage, hash_function_name);
         let key_bytes = Bytes32_to_Bytes(key_bytes32);
         tempvar range_check_ptr = range_check_ptr;
         tempvar bitwise_ptr = bitwise_ptr;
@@ -1420,7 +1444,10 @@ func _prepare_trie_inner_storage{
     );
 
     return _prepare_trie_inner_storage(
-        trie, dict_ptr + Bytes32U256DictAccess.SIZE, cast(mapping_dict_ptr, BytesBytesDictAccess*)
+        trie,
+        dict_ptr + Bytes32U256DictAccess.SIZE,
+        cast(mapping_dict_ptr, BytesBytesDictAccess*),
+        hash_function_name,
     );
 }
 
@@ -1433,6 +1460,7 @@ func _prepare_trie_inner_transaction{
     trie: TrieBytesOptionalUnionBytesLegacyTransaction,
     dict_ptr: BytesOptionalUnionBytesLegacyTransactionDictAccess*,
     mapping_ptr_end: BytesBytesDictAccess*,
+    hash_function_name: felt,
 ) -> BytesBytesDictAccess* {
     alloc_locals;
 
@@ -1451,6 +1479,7 @@ func _prepare_trie_inner_transaction{
             trie,
             dict_ptr + BytesOptionalUnionBytesLegacyTransactionDictAccess.SIZE,
             mapping_ptr_end,
+            hash_function_name,
         );
     }
 
@@ -1489,7 +1518,7 @@ func _prepare_trie_inner_transaction{
     }
 
     if (trie.value.secured.value != 0) {
-        let key_bytes32 = keccak256(preimage);
+        let key_bytes32 = hash_with(preimage, hash_function_name);
         let key_bytes = Bytes32_to_Bytes(key_bytes32);
         tempvar range_check_ptr = range_check_ptr;
         tempvar bitwise_ptr = bitwise_ptr;
@@ -1515,6 +1544,7 @@ func _prepare_trie_inner_transaction{
         trie,
         dict_ptr + BytesOptionalUnionBytesLegacyTransactionDictAccess.SIZE,
         cast(mapping_dict_ptr, BytesBytesDictAccess*),
+        hash_function_name,
     );
 }
 
@@ -1527,6 +1557,7 @@ func _prepare_trie_inner_receipt{
     trie: TrieBytesOptionalUnionBytesReceipt,
     dict_ptr: BytesOptionalUnionBytesReceiptDictAccess*,
     mapping_ptr_end: BytesBytesDictAccess*,
+    hash_function_name: felt,
 ) -> BytesBytesDictAccess* {
     alloc_locals;
 
@@ -1542,7 +1573,10 @@ func _prepare_trie_inner_receipt{
     // Skip all None values, which are deleted trie entries
     if (cast(dict_ptr.new_value.value, felt) == 0) {
         return _prepare_trie_inner_receipt(
-            trie, dict_ptr + BytesOptionalUnionBytesReceiptDictAccess.SIZE, mapping_ptr_end
+            trie,
+            dict_ptr + BytesOptionalUnionBytesReceiptDictAccess.SIZE,
+            mapping_ptr_end,
+            hash_function_name,
         );
     }
 
@@ -1581,7 +1615,7 @@ func _prepare_trie_inner_receipt{
     }
 
     if (trie.value.secured.value != 0) {
-        let key_bytes32 = keccak256(preimage);
+        let key_bytes32 = hash_with(preimage, hash_function_name);
         let key_bytes = Bytes32_to_Bytes(key_bytes32);
         tempvar range_check_ptr = range_check_ptr;
         tempvar bitwise_ptr = bitwise_ptr;
@@ -1607,6 +1641,7 @@ func _prepare_trie_inner_receipt{
         trie,
         dict_ptr + BytesOptionalUnionBytesReceiptDictAccess.SIZE,
         cast(mapping_dict_ptr, BytesBytesDictAccess*),
+        hash_function_name,
     );
 }
 
@@ -1619,6 +1654,7 @@ func _prepare_trie_inner_withdrawal{
     trie: TrieBytesOptionalUnionBytesWithdrawal,
     dict_ptr: BytesOptionalUnionBytesWithdrawalDictAccess*,
     mapping_ptr_end: BytesBytesDictAccess*,
+    hash_function_name: felt,
 ) -> BytesBytesDictAccess* {
     alloc_locals;
 
@@ -1634,7 +1670,10 @@ func _prepare_trie_inner_withdrawal{
     // Skip all None values, which are deleted trie entries
     if (cast(dict_ptr.new_value.value, felt) == 0) {
         return _prepare_trie_inner_withdrawal(
-            trie, dict_ptr + BytesOptionalUnionBytesWithdrawalDictAccess.SIZE, mapping_ptr_end
+            trie,
+            dict_ptr + BytesOptionalUnionBytesWithdrawalDictAccess.SIZE,
+            mapping_ptr_end,
+            hash_function_name,
         );
     }
 
@@ -1672,7 +1711,7 @@ func _prepare_trie_inner_withdrawal{
     }
 
     if (trie.value.secured.value != 0) {
-        let key_bytes32 = keccak256(preimage);
+        let key_bytes32 = hash_with(preimage, hash_function_name);
         let key_bytes = Bytes32_to_Bytes(key_bytes32);
         tempvar range_check_ptr = range_check_ptr;
         tempvar bitwise_ptr = bitwise_ptr;
@@ -1698,6 +1737,7 @@ func _prepare_trie_inner_withdrawal{
         trie,
         dict_ptr + BytesOptionalUnionBytesWithdrawalDictAccess.SIZE,
         cast(mapping_dict_ptr, BytesBytesDictAccess*),
+        hash_function_name,
     );
 }
 
@@ -1706,17 +1746,21 @@ func root{
     bitwise_ptr: BitwiseBuiltin*,
     keccak_ptr: KeccakBuiltin*,
     poseidon_ptr: PoseidonBuiltin*,
-}(trie_union: EthereumTries, storage_roots_: OptionalMappingAddressBytes32) -> Root {
+}(
+    trie_union: EthereumTries,
+    storage_roots_: OptionalMappingAddressBytes32,
+    hash_function_name: felt,
+) -> Root {
     alloc_locals;
 
-    let obj = _prepare_trie(trie_union, storage_roots_);
-    let patricialized = patricialize(obj, Uint(0));
-    let root_node = encode_internal_node(patricialized);
+    let obj = _prepare_trie(trie_union, storage_roots_, hash_function_name);
+    let patricialized = patricialize(obj, Uint(0), hash_function_name);
+    let root_node = encode_internal_node(patricialized, hash_function_name);
     let rlp_encoded_root_node = encode(root_node);
 
     let is_encoding_lt_32 = is_le(rlp_encoded_root_node.value.len, 31);
     if (is_encoding_lt_32 != 0) {
-        let root_hash = keccak256(rlp_encoded_root_node);
+        let root_hash = hash_with(rlp_encoded_root_node, hash_function_name);
         return root_hash;
     }
 
@@ -2134,7 +2178,7 @@ func patricialize{
     bitwise_ptr: BitwiseBuiltin*,
     keccak_ptr: KeccakBuiltin*,
     poseidon_ptr: PoseidonBuiltin*,
-}(obj: MappingBytesBytes, level: Uint) -> InternalNode {
+}(obj: MappingBytesBytes, level: Uint, hash_function_name: felt) -> InternalNode {
     alloc_locals;
 
     let len = (obj.value.dict_ptr - obj.value.dict_ptr_start) / BytesBytesDictAccess.SIZE;
@@ -2169,8 +2213,10 @@ func patricialize{
 
     if (prefix_length != 0) {
         tempvar prefix = Bytes(new BytesStruct(preimage.value.data + level.value, prefix_length));
-        let patricialized_subnode = patricialize(obj, Uint(level.value + prefix_length));
-        let encoded_subnode = encode_internal_node(patricialized_subnode);
+        let patricialized_subnode = patricialize(
+            obj, Uint(level.value + prefix_length), hash_function_name
+        );
+        let encoded_subnode = encode_internal_node(patricialized_subnode, hash_function_name);
         tempvar extension_node = ExtensionNode(new ExtensionNodeStruct(prefix, encoded_subnode));
         let internal_node = InternalNodeImpl.extension_node(extension_node);
         return internal_node;
@@ -2179,38 +2225,38 @@ func patricialize{
     let (branches, value) = _get_branches(obj, level);
     tempvar next_level = Uint(level.value + 1);
 
-    let patricialized_0 = patricialize(branches.value.data[0], next_level);
-    let encoded_0 = encode_internal_node(patricialized_0);
-    let patricialized_1 = patricialize(branches.value.data[1], next_level);
-    let encoded_1 = encode_internal_node(patricialized_1);
-    let patricialized_2 = patricialize(branches.value.data[2], next_level);
-    let encoded_2 = encode_internal_node(patricialized_2);
-    let patricialized_3 = patricialize(branches.value.data[3], next_level);
-    let encoded_3 = encode_internal_node(patricialized_3);
-    let patricialized_4 = patricialize(branches.value.data[4], next_level);
-    let encoded_4 = encode_internal_node(patricialized_4);
-    let patricialized_5 = patricialize(branches.value.data[5], next_level);
-    let encoded_5 = encode_internal_node(patricialized_5);
-    let patricialized_6 = patricialize(branches.value.data[6], next_level);
-    let encoded_6 = encode_internal_node(patricialized_6);
-    let patricialized_7 = patricialize(branches.value.data[7], next_level);
-    let encoded_7 = encode_internal_node(patricialized_7);
-    let patricialized_8 = patricialize(branches.value.data[8], next_level);
-    let encoded_8 = encode_internal_node(patricialized_8);
-    let patricialized_9 = patricialize(branches.value.data[9], next_level);
-    let encoded_9 = encode_internal_node(patricialized_9);
-    let patricialized_10 = patricialize(branches.value.data[10], next_level);
-    let encoded_10 = encode_internal_node(patricialized_10);
-    let patricialized_11 = patricialize(branches.value.data[11], next_level);
-    let encoded_11 = encode_internal_node(patricialized_11);
-    let patricialized_12 = patricialize(branches.value.data[12], next_level);
-    let encoded_12 = encode_internal_node(patricialized_12);
-    let patricialized_13 = patricialize(branches.value.data[13], next_level);
-    let encoded_13 = encode_internal_node(patricialized_13);
-    let patricialized_14 = patricialize(branches.value.data[14], next_level);
-    let encoded_14 = encode_internal_node(patricialized_14);
-    let patricialized_15 = patricialize(branches.value.data[15], next_level);
-    let encoded_15 = encode_internal_node(patricialized_15);
+    let patricialized_0 = patricialize(branches.value.data[0], next_level, hash_function_name);
+    let encoded_0 = encode_internal_node(patricialized_0, hash_function_name);
+    let patricialized_1 = patricialize(branches.value.data[1], next_level, hash_function_name);
+    let encoded_1 = encode_internal_node(patricialized_1, hash_function_name);
+    let patricialized_2 = patricialize(branches.value.data[2], next_level, hash_function_name);
+    let encoded_2 = encode_internal_node(patricialized_2, hash_function_name);
+    let patricialized_3 = patricialize(branches.value.data[3], next_level, hash_function_name);
+    let encoded_3 = encode_internal_node(patricialized_3, hash_function_name);
+    let patricialized_4 = patricialize(branches.value.data[4], next_level, hash_function_name);
+    let encoded_4 = encode_internal_node(patricialized_4, hash_function_name);
+    let patricialized_5 = patricialize(branches.value.data[5], next_level, hash_function_name);
+    let encoded_5 = encode_internal_node(patricialized_5, hash_function_name);
+    let patricialized_6 = patricialize(branches.value.data[6], next_level, hash_function_name);
+    let encoded_6 = encode_internal_node(patricialized_6, hash_function_name);
+    let patricialized_7 = patricialize(branches.value.data[7], next_level, hash_function_name);
+    let encoded_7 = encode_internal_node(patricialized_7, hash_function_name);
+    let patricialized_8 = patricialize(branches.value.data[8], next_level, hash_function_name);
+    let encoded_8 = encode_internal_node(patricialized_8, hash_function_name);
+    let patricialized_9 = patricialize(branches.value.data[9], next_level, hash_function_name);
+    let encoded_9 = encode_internal_node(patricialized_9, hash_function_name);
+    let patricialized_10 = patricialize(branches.value.data[10], next_level, hash_function_name);
+    let encoded_10 = encode_internal_node(patricialized_10, hash_function_name);
+    let patricialized_11 = patricialize(branches.value.data[11], next_level, hash_function_name);
+    let encoded_11 = encode_internal_node(patricialized_11, hash_function_name);
+    let patricialized_12 = patricialize(branches.value.data[12], next_level, hash_function_name);
+    let encoded_12 = encode_internal_node(patricialized_12, hash_function_name);
+    let patricialized_13 = patricialize(branches.value.data[13], next_level, hash_function_name);
+    let encoded_13 = encode_internal_node(patricialized_13, hash_function_name);
+    let patricialized_14 = patricialize(branches.value.data[14], next_level, hash_function_name);
+    let encoded_14 = encode_internal_node(patricialized_14, hash_function_name);
+    let patricialized_15 = patricialize(branches.value.data[15], next_level, hash_function_name);
+    let encoded_15 = encode_internal_node(patricialized_15, hash_function_name);
 
     // Squash the dicts for all the branches
     _squash_branches(branches);
diff --git a/cairo/ethereum/crypto/hash.cairo b/cairo/ethereum/crypto/hash.cairo
index 4759c4b89..e070caac8 100644
--- a/cairo/ethereum/crypto/hash.cairo
+++ b/cairo/ethereum/crypto/hash.cairo
@@ -1,21 +1,25 @@
-from ethereum_types.bytes import Bytes32, Bytes
-from legacy.utils.bytes import bytes_to_bytes8_little_endian
 from starkware.cairo.common.builtin_keccak.keccak import keccak
 from starkware.cairo.common.cairo_builtins import BitwiseBuiltin, KeccakBuiltin
 from starkware.cairo.common.uint256 import Uint256
 from starkware.cairo.common.alloc import alloc
+
 from ethereum_types.numeric import bool
+from ethereum_types.bytes import Bytes32, Bytes
+from legacy.utils.bytes import bytes_to_bytes8_little_endian, bytes_to_bytes4_little_endian
+
+from cairo_core.hash.blake2s import blake2s
 
 using Hash32 = Bytes32;
 
-EMPTY_ROOT:
+EMPTY_ROOT_KECCAK:
 dw 0x6ef8c092e64583ffa655cc1b171fe856;  // low
 dw 0x21b463e3b52f6201c0ad6c991be0485b;  // high
 
-EMPTY_HASH:
+EMPTY_HASH_KECCAK:
 dw 0xc003c7dcb27d7e923c23f7860146d2c5;  // low
 dw 0x70a4855d04d8fa7b3b2782ca53b600e5;  // high
 
+// @notice Computes the keccak256 hash of a bytes object.
 func keccak256{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: KeccakBuiltin*}(
     buffer: Bytes
 ) -> Hash32 {
@@ -28,3 +32,42 @@ func keccak256{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: Keccak
     tempvar hash = Hash32(value=value);
     return hash;
 }
+
+// @notice Computes the blake2s hash of a bytes object.
+// @dev `finalize_blake2s` must absolutely be called at the end of the program.
+func blake2s_bytes{range_check_ptr}(buffer: Bytes) -> Hash32 {
+    alloc_locals;
+    let n_bytes = buffer.value.len;
+    let (dst: felt*) = alloc();
+    bytes_to_bytes4_little_endian(dst, n_bytes, buffer.value.data);
+    let (result) = blake2s(dst, n_bytes);
+    tempvar value = new Uint256(low=result.low, high=result.high);
+    tempvar hash = Hash32(value=value);
+    return hash;
+}
+
+// @notice Computes the hash of a bytes object using the given hash function.
+// @dev This function takes as implicit arguments all possible arguments for the hash functions
+// used.
+func hash_with{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: KeccakBuiltin*}(
+    buffer: Bytes, hash_function_name: felt
+) -> Hash32 {
+    alloc_locals;
+    let n_bytes = buffer.value.len;
+
+    if (hash_function_name == 'blake2s') {
+        let (dst: felt*) = alloc();
+        bytes_to_bytes4_little_endian(dst, n_bytes, buffer.value.data);
+        let (result) = blake2s(dst, n_bytes);
+        tempvar value = new Uint256(low=result.low, high=result.high);
+        tempvar hash = Hash32(value=value);
+        return hash;
+    }
+
+    let (local dst: felt*) = alloc();
+    bytes_to_bytes8_little_endian(dst, n_bytes, buffer.value.data);
+    let (result) = keccak(dst, n_bytes);
+    tempvar value = new Uint256(low=result.low, high=result.high);
+    tempvar hash = Hash32(value=value);
+    return hash;
+}
diff --git a/cairo/ethereum_rlp/rlp.cairo b/cairo/ethereum_rlp/rlp.cairo
index 5ed0bcf60..97693b0a2 100644
--- a/cairo/ethereum_rlp/rlp.cairo
+++ b/cairo/ethereum_rlp/rlp.cairo
@@ -473,9 +473,7 @@ func encode_bytes8{range_check_ptr}(raw_bytes8: Bytes8) -> Bytes {
     return result;
 }
 
-func encode_account{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: KeccakBuiltin*}(
-    raw_account_data: Account, storage_root: Bytes
-) -> Bytes {
+func encode_account{range_check_ptr}(raw_account_data: Account, storage_root: Bytes) -> Bytes {
     alloc_locals;
     let (local dst) = alloc();
     let body_ptr = dst + PREFIX_LEN_MAX;
diff --git a/cairo/legacy/utils/bytes.cairo b/cairo/legacy/utils/bytes.cairo
index a7554bc96..80e2cd68a 100644
--- a/cairo/legacy/utils/bytes.cairo
+++ b/cairo/legacy/utils/bytes.cairo
@@ -251,6 +251,103 @@ func uint256_to_bytes32{range_check_ptr}(dst: felt*, n: Uint256) {
     return ();
 }
 
+// @notice Converts an array of bytes to an array of bytes4, little endian
+// @dev The function is sound because the number of steps is limited to 2^50 by the verifier.
+//      Consequently, `bytes4` cannot wrap around P. No range_check is needed in the main loop.
+//      Only in the final step, depending on the size of the remainder, is the total length of the
+//      output array checked.
+// @param dst The destination array.
+// @param bytes_len The number of bytes in the input array.
+// @param bytes The input array.
+func bytes_to_bytes4_little_endian{range_check_ptr}(dst: felt*, bytes_len: felt, bytes: felt*) -> (
+    ) {
+    alloc_locals;
+
+    if (bytes_len == 0) {
+        return ();
+    }
+
+    tempvar less_than_4;
+    %{ bytes_len_less_than_4 %}
+    tempvar bytes4 = dst;
+    tempvar bytes = bytes;
+
+    static_assert bytes4 == [ap - 2];
+    static_assert bytes == [ap - 1];
+
+    jmp skip_full_word_loop if less_than_4 != 0;
+
+    // Main loop done a random number of times
+    full_word_loop:
+    let bytes4 = cast([ap - 2], felt*);
+    let bytes = cast([ap - 1], felt*);
+
+    assert [bytes4] = bytes[0] + bytes[1] * 256 + bytes[2] * 256 ** 2 + bytes[3] * 256 ** 3;
+    tempvar continue_loop;
+    tempvar bytes4 = bytes4 + 1;
+    tempvar bytes = bytes + 4;
+    %{ remaining_bytes_greater_than_4 %}
+
+    jmp full_word_loop if continue_loop != 0;
+
+    skip_full_word_loop:
+    let bytes4 = cast([ap - 2], felt*);
+    let bytes = cast([ap - 1], felt*);
+
+    tempvar remaining_offset;
+    %{ remaining_bytes_jmp_offset_4 %}
+    static_assert bytes4 == [ap - 3];
+    static_assert bytes == [ap - 2];
+    jmp rel remaining_offset;
+    jmp remaining_0;
+    jmp remaining_1;
+    jmp remaining_2;
+    jmp remaining_3;
+
+    // Remaining bytes, one case per possible number of bytes
+    // Each case assert the number of bytes written to the destination array
+    // and the value of the bytes
+
+    remaining_3:
+    let dst = cast([fp - 5], felt*);
+    let bytes_len = cast([fp - 4], felt);
+    let bytes4 = cast([ap - 3], felt*);
+    let bytes = cast([ap - 2], felt*);
+    assert (bytes4 - dst) * 4 = bytes_len - 3;
+    assert [bytes4] = bytes[0] + bytes[1] * 256 + bytes[2] * 256 ** 2;
+    let range_check_ptr = [fp - 6];
+    return ();
+
+    remaining_2:
+    let dst = cast([fp - 5], felt*);
+    let bytes_len = cast([fp - 4], felt);
+    let bytes4 = cast([ap - 3], felt*);
+    let bytes = cast([ap - 2], felt*);
+    assert (bytes4 - dst) * 4 = bytes_len - 2;
+    assert [bytes4] = bytes[0] + bytes[1] * 256;
+    let range_check_ptr = [fp - 6];
+    return ();
+
+    remaining_1:
+    let dst = cast([fp - 5], felt*);
+    let bytes_len = cast([fp - 4], felt);
+    let bytes4 = cast([ap - 3], felt*);
+    let bytes = cast([ap - 2], felt*);
+    assert (bytes4 - dst) * 4 = bytes_len - 1;
+    assert [bytes4] = bytes[0];
+    let range_check_ptr = [fp - 6];
+    return ();
+
+    remaining_0:
+    let dst = cast([fp - 5], felt*);
+    let bytes_len = cast([fp - 4], felt);
+    let bytes4 = cast([ap - 3], felt*);
+    let bytes = cast([ap - 2], felt*);
+    assert (bytes4 - dst) * 4 = bytes_len;
+    let range_check_ptr = [fp - 6];
+    return ();
+}
+
 // @notice Converts an array of bytes to an array of bytes8, little endian
 // @dev The function is sound because the number of steps is limited to 2^50 by the verifier.
 //      Consequently, `bytes8` cannot wrap around P. No range_check is needed in the main loop.
diff --git a/cairo/tests/ethereum/cancun/test_fork.py b/cairo/tests/ethereum/cancun/test_fork.py
index 481ee2f23..852bb4d83 100644
--- a/cairo/tests/ethereum/cancun/test_fork.py
+++ b/cairo/tests/ethereum/cancun/test_fork.py
@@ -1,5 +1,5 @@
 from collections import defaultdict
-from dataclasses import replace
+from dataclasses import fields, replace
 from typing import Optional, Tuple
 
 from eth_abi.abi import encode
@@ -702,9 +702,12 @@ def test_apply_body(self, cairo_run, data, withdrawals: Tuple[Withdrawal, ...]):
 
         output = apply_body(**kwargs)
 
-        # We compare all but not the state root - which is not computed in Cairo
-        cairo_result.state_root = output.state_root
-        assert cairo_result == output
+        # Compare all fields except state_root
+        assert all(
+            getattr(cairo_result, field.name) == getattr(output, field.name)
+            for field in fields(cairo_result)
+            if field.name != "state_root"
+        )
         assert cairo_state == state
 
 
diff --git a/cairo/tests/ethereum/cancun/test_state.py b/cairo/tests/ethereum/cancun/test_state.py
index 3bd6e2872..5c393b0c8 100644
--- a/cairo/tests/ethereum/cancun/test_state.py
+++ b/cairo/tests/ethereum/cancun/test_state.py
@@ -1,5 +1,5 @@
 import copy
-from typing import Optional
+from typing import Mapping, Optional
 
 import pytest
 from ethereum.cancun.fork_types import EMPTY_ACCOUNT, Account, Address
@@ -30,6 +30,8 @@
     set_code,
     set_storage,
     set_transient_storage,
+    state_root,
+    storage_root,
     touch_account,
 )
 from ethereum.cancun.trie import Trie, copy_trie, root
@@ -586,6 +588,58 @@ def test_commit_transaction(
         assert transient_storage_cairo == transient_storage
 
 
+@composite
+def state_maybe_snapshot(draw):
+    """
+    Draw a state that has a 80% chance of not containing snapshots.
+    """
+    state_ = draw(state_strategy())
+    probability = draw(st.floats(min_value=0, max_value=1))
+    if probability < 0.8:
+        state_._snapshots = []
+        return state_
+    return state_
+
+
+class TestRoot:
+    @given(state=state_maybe_snapshot())
+    def test_state_root(self, cairo_run, state: State):
+        try:
+            state_root_cairo = cairo_run(
+                "state_root", state=state, hash_function_name="keccak256"
+            )
+        except Exception as e:
+            with strict_raises(type(e)):
+                state_root(state)
+            return
+        state_root_py = state_root(state)
+        assert state_root_cairo == state_root_py
+
+
+class TestStorageRoots:
+    @given(state=state_maybe_snapshot())
+    def test_storage_roots(self, cairo_run, state: State):
+        def storage_roots(state) -> Mapping[Address, Bytes32]:
+            # This assertion is made in each individual storage_root in python -
+            # but in Cairo we can only perform it once.
+            assert not state._snapshots
+            storage_roots_py = {}
+            for addr in state._storage_tries.keys():
+                storage_roots_py[addr] = storage_root(state, addr)
+            return storage_roots_py
+
+        try:
+            storage_roots_cairo = cairo_run(
+                "storage_roots", state=state, hash_function_name="keccak256"
+            )
+        except Exception as e:
+            with strict_raises(type(e)):
+                storage_roots(state)
+            return
+
+        assert storage_roots_cairo == storage_roots(state)
+
+
 class TestGetAccountCode:
     def _create_account_no_code(self, account: Account) -> Account:
         """Helper function to create a copy of an account with code set to None."""
diff --git a/cairo/tests/ethereum/cancun/test_trie.py b/cairo/tests/ethereum/cancun/test_trie.py
index a84696320..ab9071fdd 100644
--- a/cairo/tests/ethereum/cancun/test_trie.py
+++ b/cairo/tests/ethereum/cancun/test_trie.py
@@ -50,7 +50,8 @@ class TestTrie:
     @given(node=...)
     def test_encode_internal_node(self, cairo_run, node: Optional[InternalNode]):
         assert sequence_equal(
-            encode_internal_node(node), cairo_run("encode_internal_node", node)
+            encode_internal_node(node),
+            cairo_run("encode_internal_node", node, hash_function_name="keccak256"),
         )
 
     @given(node=..., storage_root=...)
@@ -161,7 +162,9 @@ def test_get_branches(self, cairo_run, obj, level):
     @pytest.mark.slow
     @given(obj=st.dictionaries(nibble, bytes32, max_size=100))
     def test_patricialize(self, cairo_run, obj: Mapping[Bytes, Bytes]):
-        assert patricialize(obj, Uint(0)) == cairo_run("patricialize", obj, Uint(0))
+        assert patricialize(obj, Uint(0)) == cairo_run(
+            "patricialize", hash_function_name="keccak256", obj=obj, level=Uint(0)
+        )
 
     @given(leaf_node=...)
     def test_internal_node_leaf_node(self, cairo_run, leaf_node: LeafNode):
@@ -219,7 +222,12 @@ def get_storage_root(address: Address) -> Root:
             get_storage_root = None
 
         try:
-            result_cairo = cairo_run("_prepare_trie", trie_with_none, storage_roots)
+            result_cairo = cairo_run(
+                "_prepare_trie",
+                trie_with_none,
+                storage_roots,
+                hash_function_name="keccak256",
+            )
         except Exception as e:
             with strict_raises(type(e)):
                 _prepare_trie(trie, get_storage_root)
@@ -263,7 +271,12 @@ def get_storage_root(address: Address) -> Root:
             get_storage_root = None
 
         try:
-            result_cairo = cairo_run("root", trie_with_none, storage_roots)
+            result_cairo = cairo_run(
+                "root",
+                hash_function_name="keccak256",
+                trie_union=trie_with_none,
+                storage_roots_=storage_roots,
+            )
         except Exception as e:
             with strict_raises(type(e)):
                 root(trie, get_storage_root)
diff --git a/cairo/tests/ethereum/crypto/test_hash.py b/cairo/tests/ethereum/crypto/test_hash.py
index b4abbb98e..7f2b2b4ea 100644
--- a/cairo/tests/ethereum/crypto/test_hash.py
+++ b/cairo/tests/ethereum/crypto/test_hash.py
@@ -1,12 +1,38 @@
+import hashlib
+
 from ethereum.crypto.hash import keccak256
 from ethereum_types.bytes import Bytes
 from hypothesis import given
 from hypothesis import strategies as st
 
+from tests.utils.strategies import small_bytes
+
 
 class TestHash:
 
     class TestKeccak256:
-        @given(buffer=st.binary(max_size=1000).map(Bytes))
+        @given(buffer=small_bytes)
         def test_keccak256(self, cairo_run, buffer: Bytes):
             assert keccak256(buffer) == cairo_run("keccak256", buffer)
+
+    class TestBlake2s:
+        @given(buffer=small_bytes)
+        def test_blake2s_bytes(self, cairo_run, buffer: Bytes):
+            assert hashlib.blake2s(buffer).digest() == cairo_run(
+                "blake2s_bytes", buffer
+            )
+
+    class TestHashWith:
+        @given(
+            buffer=small_bytes,
+            hash_function_name=st.sampled_from(["keccak256", "blake2s"]),
+        )
+        def test_hash_with(self, cairo_run, buffer: Bytes, hash_function_name: str):
+            if hash_function_name == "keccak256":
+                assert keccak256(buffer) == cairo_run(
+                    "hash_with", buffer, hash_function_name
+                )
+            elif hash_function_name == "blake2s":
+                assert hashlib.blake2s(buffer).digest() == cairo_run(
+                    "hash_with", buffer, hash_function_name
+                )
diff --git a/cairo/tests/legacy/utils/test_bytes_legacy.cairo b/cairo/tests/legacy/utils/test_bytes_legacy.cairo
index e5fec8dc9..e86b2a0ca 100644
--- a/cairo/tests/legacy/utils/test_bytes_legacy.cairo
+++ b/cairo/tests/legacy/utils/test_bytes_legacy.cairo
@@ -13,6 +13,7 @@ from legacy.utils.bytes import (
     uint256_to_bytes,
     uint256_to_bytes32,
     bytes_to_bytes8_little_endian,
+    bytes_to_bytes4_little_endian,
     bytes_to_felt,
     bytes_to_felt_le,
 )
@@ -61,6 +62,13 @@ func test__bytes_to_bytes8_little_endian{range_check_ptr}(bytes: Bytes) -> felt*
     return bytes8;
 }
 
+func test__bytes_to_bytes4_little_endian{range_check_ptr}(bytes: Bytes) -> felt* {
+    alloc_locals;
+    let (bytes4) = alloc();
+    bytes_to_bytes4_little_endian(bytes4, bytes.value.len, bytes.value.data);
+    return bytes4;
+}
+
 func test__bytes_to_felt(bytes: Bytes) -> felt {
     alloc_locals;
     let res = bytes_to_felt(bytes.value.len, bytes.value.data);
diff --git a/cairo/tests/legacy/utils/test_bytes_legacy.py b/cairo/tests/legacy/utils/test_bytes_legacy.py
index e2e279dff..69b0478ad 100644
--- a/cairo/tests/legacy/utils/test_bytes_legacy.py
+++ b/cairo/tests/legacy/utils/test_bytes_legacy.py
@@ -125,6 +125,17 @@ def test_should_return_bytes8(self, cairo_run, data: Bytes):
 
             assert bytes8_little_endian == output
 
+    class TestBytesToBytes4LittleEndian:
+        @given(data=binary(max_size=1000).map(Bytes))
+        def test_should_return_bytes4(self, cairo_run, data: Bytes):
+            bytes4_little_endian = [
+                int.from_bytes(bytes(data[i : i + 4]), "little")
+                for i in range(0, len(data), 4)
+            ]
+            assert bytes4_little_endian == cairo_run(
+                "test__bytes_to_bytes4_little_endian", bytes=data
+            )
+
     class TestBytesToFelt:
 
         @given(data=binary(min_size=0, max_size=35).map(Bytes))
diff --git a/cairo/tests/utils/args_gen.py b/cairo/tests/utils/args_gen.py
index 3d4ecda94..854bc4375 100644
--- a/cairo/tests/utils/args_gen.py
+++ b/cairo/tests/utils/args_gen.py
@@ -892,6 +892,12 @@ def _gen_arg(
         return struct_ptr
 
     if arg_type in (int, bool, U64, Uint, Bytes0, Bytes4, Bytes8, Bytes20):
+        # Case short string: arg type is int but actual type is str
+        if type(arg) is str:
+            arg = int.from_bytes(arg.encode(), "big")
+            if arg > DEFAULT_PRIME:
+                raise ValueError("String does not fit in a felt")
+
         if arg_type is int and arg < 0:
             ret_value = arg + DEFAULT_PRIME
             return tuple([ret_value]) if for_dict_key else ret_value
diff --git a/cairo/tests/utils/strategies.py b/cairo/tests/utils/strategies.py
index b48e0e7ea..86c76c566 100644
--- a/cairo/tests/utils/strategies.py
+++ b/cairo/tests/utils/strategies.py
@@ -157,7 +157,11 @@
 MAX_TUPLE_SIZE = int(os.getenv("HYPOTHESIS_MAX_TUPLE_SIZE", 10))
 
 
-small_bytes = st.binary(max_size=256)
+# Hypothesis generates examples that shrink towards 0, creating very few large examples.
+# By converting an integer instead, we maximize the amount of large examples created, while being faster.
+small_bytes = st.integers(min_value=0, max_value=2 ** (256 * 8) - 1).map(
+    lambda x: Uint(x).to_le_bytes()
+)
 code = st.binary(max_size=MAX_CODE_SIZE)
 pc = st.integers(min_value=0, max_value=MAX_CODE_SIZE * 2).map(Uint)
 
diff --git a/crates/cairo-addons/src/vm/hint_definitions/bytes_hints.rs b/crates/cairo-addons/src/vm/hint_definitions/bytes_hints.rs
index 07383e26b..15cd0c520 100644
--- a/crates/cairo-addons/src/vm/hint_definitions/bytes_hints.rs
+++ b/crates/cairo-addons/src/vm/hint_definitions/bytes_hints.rs
@@ -15,8 +15,14 @@ use cairo_vm::{
 
 use crate::vm::hints::Hint;
 
-pub const HINTS: &[fn() -> Hint] =
-    &[bytes_len_less_than_8, remaining_bytes_greater_than_8, remaining_bytes_jmp_offset];
+pub const HINTS: &[fn() -> Hint] = &[
+    bytes_len_less_than_8,
+    remaining_bytes_greater_than_8,
+    remaining_bytes_jmp_offset,
+    bytes_len_less_than_4,
+    remaining_bytes_greater_than_4,
+    remaining_bytes_jmp_offset_4,
+];
 
 pub fn bytes_len_less_than_8() -> Hint {
     Hint::new(
@@ -35,6 +41,23 @@ pub fn bytes_len_less_than_8() -> Hint {
     )
 }
 
+pub fn bytes_len_less_than_4() -> Hint {
+    Hint::new(
+        String::from("bytes_len_less_than_4"),
+        |vm: &mut VirtualMachine,
+         _exec_scopes: &mut ExecutionScopes,
+         _ids_data: &HashMap<String, HintReference>,
+         _ap_tracking: &ApTracking,
+         _constants: &HashMap<String, Felt252>|
+         -> Result<(), HintError> {
+            let bytes_len = get_integer_from_var_name("bytes_len", vm, _ids_data, _ap_tracking)?;
+            let less_than_4 = Felt252::from(bytes_len < Felt252::from(4));
+            insert_value_from_var_name("less_than_4", less_than_4, vm, _ids_data, _ap_tracking)?;
+            Ok(())
+        },
+    )
+}
+
 pub fn remaining_bytes_greater_than_8() -> Hint {
     Hint::new(
         String::from("remaining_bytes_greater_than_8"),
@@ -65,6 +88,36 @@ pub fn remaining_bytes_greater_than_8() -> Hint {
     )
 }
 
+pub fn remaining_bytes_greater_than_4() -> Hint {
+    Hint::new(
+        String::from("remaining_bytes_greater_than_4"),
+        |vm: &mut VirtualMachine,
+         _exec_scopes: &mut ExecutionScopes,
+         _ids_data: &HashMap<String, HintReference>,
+         _ap_tracking: &ApTracking,
+         _constants: &HashMap<String, Felt252>|
+         -> Result<(), HintError> {
+            let bytes_len = get_integer_from_var_name("bytes_len", vm, _ids_data, _ap_tracking)?;
+            let bytes4 = get_ptr_from_var_name("bytes4", vm, _ids_data, _ap_tracking)?;
+            let fp = vm.get_fp();
+            let dst = vm.get_relocatable((fp - 5)?)?;
+
+            let processed_bytes_len = (bytes4 - dst)? * 4;
+            let remaining_bytes_len = bytes_len - Felt252::from(processed_bytes_len);
+            let continue_loop = Felt252::from(remaining_bytes_len >= Felt252::from(4));
+
+            insert_value_from_var_name(
+                "continue_loop",
+                continue_loop,
+                vm,
+                _ids_data,
+                _ap_tracking,
+            )?;
+            Ok(())
+        },
+    )
+}
+
 pub fn remaining_bytes_jmp_offset() -> Hint {
     Hint::new(
         String::from("remaining_bytes_jmp_offset"),
@@ -94,3 +147,33 @@ pub fn remaining_bytes_jmp_offset() -> Hint {
         },
     )
 }
+
+pub fn remaining_bytes_jmp_offset_4() -> Hint {
+    Hint::new(
+        String::from("remaining_bytes_jmp_offset_4"),
+        |vm: &mut VirtualMachine,
+         _exec_scopes: &mut ExecutionScopes,
+         _ids_data: &HashMap<String, HintReference>,
+         _ap_tracking: &ApTracking,
+         _constants: &HashMap<String, Felt252>|
+         -> Result<(), HintError> {
+            let bytes_len = get_integer_from_var_name("bytes_len", vm, _ids_data, _ap_tracking)?;
+            let bytes4 = get_ptr_from_var_name("bytes4", vm, _ids_data, _ap_tracking)?;
+            let fp = vm.get_fp();
+            let dst = vm.get_relocatable((fp - 5)?)?;
+
+            let processed_bytes_len = (bytes4 - dst)? * 4;
+            let remaining_bytes_len = bytes_len - Felt252::from(processed_bytes_len);
+            let remaining_offset = remaining_bytes_len * Felt252::from(2) + 1;
+
+            insert_value_from_var_name(
+                "remaining_offset",
+                remaining_offset,
+                vm,
+                _ids_data,
+                _ap_tracking,
+            )?;
+            Ok(())
+        },
+    )
+}
diff --git a/docs/AI-REPORT.md b/docs/AI-REPORT.md
index e446e4633..329285164 100644
--- a/docs/AI-REPORT.md
+++ b/docs/AI-REPORT.md
@@ -1,5 +1,66 @@
 # AI-Reports
 
+## AI-REPORT: State Root Restoration and Hash Function Abstraction (April 23, 2025)
+
+### Context & Goal
+
+Following the previous refactor (see AI-REPORT April 11, 2025) that moved to a
+diff-based state validation aligned with EELS, this change restores the
+capability to compute the full `state_root` and `storage_root` directly within
+Keth. The primary goal is for the MPT `root` function to support multiple
+cryptographic hash functions (Keccak for final roots, Blake2s for intermediate
+in our applicative-recursion commitments, and eventually Poseidon later) for
+these computations, bringing better flexibility.
+
+### Implementation: Hash Function Abstraction
+
+The core challenge is abstracting the hash function used within the `root`
+computation (for the main state trie) and the `storage_roots` computation (for
+individual account storage tries). Cairo's language design makes traditional
+dynamic dispatch or passing function closures complex (functions take different
+arguments, we can only `call abs` on the function label, etc.)
+
+**Pattern Chosen**: We use implicit arguments to manage hash function selection
+and execution:
+
+1.  **Builtin Pointers**: Functions supporting multiple hashing functions now
+    include implicit arguments for all potentially needed hash builtins (e.g.,
+    `keccak_ptr`, `poseidon_ptr`). This ensures that regardless of the function
+    chosen at runtime, we have the right segments available.
+2.  **Hash Function Identifier & Dispatch**: The actual hash computation is
+    delegated to a central `hash_with` function (located in
+    `cairo/ethereum/crypto/hash.cairo`). This function takes an explicit
+    `hash_function_name: felt` argument (e.g., `'keccak256'`, `'blake2s'`) which
+    identifies the desired hash algorithm. Inside `hash_with`, conditional logic
+    uses this name to select the appropriate hash implementation (e.g., `keccak`
+    or `blake2s`) and use the corresponding implicit builtin pointers (like
+    `keccak_ptr`, if used). This approach avoids the complexity of using
+    `call abs` with function labels, which would require branching to manage the
+    differing implicit arguments required by each hash function. Functions like
+    `root`, `state_root` or `storage_roots` propagate the necessary builtins and
+    the `hash_function_name` down to where `hash_with` is called.
+3.  **Hash-Specific Constants**: Constants like `EMPTY_ROOT` and `EMPTY_HASH`
+    were renamed to `EMPTY_ROOT_KECCAK` and `EMPTY_HASH_KECCAK` to distinguish
+    them, as different hash functions have different default empty values.
+4.  We use the newly introduced opcode in the VM and proven by STWO to compute
+    Blake2s hashes (see cairo_core.hash.blake2s)
+
+### Changes
+
+- **`state.cairo`**: Re-implemented `state_root` and introduced `storage_roots`
+  and helper functions. These functions now include implicit arguments for
+  various hash builtins (`poseidon_ptr`, `keccak_ptr` where relevant). They will
+  be used to compute intermediate state roots (with blake2s) when splitting our
+  state transition into chunks.
+- **`fork_types.cairo`, `state.cairo`**: Renamed `EMPTY_ROOT`/`EMPTY_HASH` to
+  `EMPTY_ROOT_KECCAK`/`EMPTY_HASH_KECCAK`.
+
+### Implications
+
+The hash abstraction pattern prepares the codebase for using alternative hash
+functions like Blake2s, more efficient than Poseidon-F252, for future features
+like intermediate state commitments.
+
 ## AI-REPORT: Differentiating Null and Zero in State/Storage Diffs (April 22, 2025)
 
 ### Problem
diff --git a/python/cairo-core/src/cairo_core/hash/blake2s.cairo b/python/cairo-core/src/cairo_core/hash/blake2s.cairo
new file mode 100644
index 000000000..d0a202e7f
--- /dev/null
+++ b/python/cairo-core/src/cairo_core/hash/blake2s.cairo
@@ -0,0 +1,306 @@
+// Module adapted from starkware.cairo.common.cairo_blake2s.blake2s to use the newly introduced
+// `blake2s` opcode.
+
+// This module provides a set of functions to compute the blake2s hash function.
+//
+// This module is similar to the keccak.cairo module. See more info there.
+
+from starkware.cairo.common.alloc import alloc
+from starkware.cairo.common.cairo_blake2s.packed_blake2s import N_PACKED_INSTANCES, blake2s_compress
+from starkware.cairo.common.cairo_builtins import BitwiseBuiltin
+from starkware.cairo.common.math import assert_nn_le, split_felt, unsigned_div_rem
+from starkware.cairo.common.math_cmp import is_le
+from starkware.cairo.common.memcpy import memcpy
+from starkware.cairo.common.memset import memset
+from starkware.cairo.common.pow import pow
+from starkware.cairo.common.registers import get_fp_and_pc, get_label_location
+from starkware.cairo.common.uint256 import Uint256
+
+const INPUT_BLOCK_FELTS = 16;
+const INPUT_BLOCK_BYTES = 64;
+const STATE_SIZE_FELTS = 8;
+
+// Computes blake2s of 'input'.
+// To use this function, split the input into words of 32 bits (little endian).
+// For example, to compute blake2s('Hello world'), use:
+//   input = [1819043144, 1870078063, 6581362]
+// where:
+//   1819043144 == int.from_bytes(b'Hell', 'little')
+//   1870078063 == int.from_bytes(b'o wo', 'little')
+//   6581362 == int.from_bytes(b'rld', 'little')
+//
+// Returns the hash as a Uint256.
+//
+// Note: the interface of this function may change in the future.
+// Note: Each input word is verified to be in the range [0, 2 ** 32) by the opcode runner.
+func blake2s{range_check_ptr}(data: felt*, n_bytes: felt) -> (res: Uint256) {
+    let (blake2s_ptr) = alloc();
+    let (output) = blake2s_as_words{blake2s_ptr=blake2s_ptr}(data=data, n_bytes=n_bytes);
+    let res_low = output[3] * 2 ** 96 + output[2] * 2 ** 64 + output[1] * 2 ** 32 + output[0];
+    let res_high = output[7] * 2 ** 96 + output[6] * 2 ** 64 + output[5] * 2 ** 32 + output[4];
+    return (res=Uint256(low=res_low, high=res_high));
+}
+
+// Computes blake2s of 'input', and returns the hash in big endian representation.
+// See blake2s().
+// Note that the input is still treated as little endian.
+func blake2s_bigend{bitwise_ptr: BitwiseBuiltin*, range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt
+) -> (res: Uint256) {
+    let (num) = blake2s(data=data, n_bytes=n_bytes);
+
+    // Reverse byte endianness of 128-bit words.
+    tempvar value = num.high;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000;
+    tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000;
+    tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar high = value / 2 ** (8 + 16 + 32 + 64);
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    tempvar value = num.low;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000;
+    tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000;
+    tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar low = value / 2 ** (8 + 16 + 32 + 64);
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    return (res=Uint256(low=high, high=low));
+}
+
+// Same as blake2s, but outputs a pointer to 8 32-bit little endian words instead.
+func blake2s_as_words{range_check_ptr, blake2s_ptr: felt*}(data: felt*, n_bytes: felt) -> (
+    output: felt*
+) {
+    // Set the initial state to IV (IV[0] is modified).
+    assert blake2s_ptr[0] = 0x6B08E647;  // IV[0] ^ 0x01010020 (config: no key, 32 bytes output).
+    assert blake2s_ptr[1] = 0xBB67AE85;
+    assert blake2s_ptr[2] = 0x3C6EF372;
+    assert blake2s_ptr[3] = 0xA54FF53A;
+    assert blake2s_ptr[4] = 0x510E527F;
+    assert blake2s_ptr[5] = 0x9B05688C;
+    assert blake2s_ptr[6] = 0x1F83D9AB;
+    assert blake2s_ptr[7] = 0x5BE0CD19;
+    static_assert STATE_SIZE_FELTS == 8;
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+
+    let (output) = blake2s_inner(data=data, n_bytes=n_bytes, counter=0);
+    return (output=output);
+}
+
+// Inner loop for blake2s. blake2s_ptr points to after the initial state of the previous instance.
+func blake2s_inner{range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt, counter: felt
+) -> (output: felt*) {
+    alloc_locals;
+    let is_last_block = is_le(n_bytes, INPUT_BLOCK_BYTES);
+    if (is_last_block != 0) {
+        return blake2s_last_block(data=data, n_bytes=n_bytes, counter=counter);
+    }
+
+    // Run the blake2s opcode runner, store its output in blake2s_ptr;
+    let state_ptr = blake2s_ptr - STATE_SIZE_FELTS;
+    run_blake2s_opcode(is_last_block=0, dst=counter + INPUT_BLOCK_BYTES, op0=state_ptr, op1=data);
+
+    return blake2s_inner(
+        data=data + INPUT_BLOCK_FELTS,
+        n_bytes=n_bytes - INPUT_BLOCK_BYTES,
+        counter=counter + INPUT_BLOCK_BYTES,
+    );
+}
+
+func blake2s_last_block{range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt, counter: felt
+) -> (output: felt*) {
+    alloc_locals;
+    let state_ptr = blake2s_ptr - STATE_SIZE_FELTS;
+    let (n_felts, _) = unsigned_div_rem(n_bytes + 3, 4);
+    memset(data + n_felts, 0, INPUT_BLOCK_FELTS - n_felts);
+    let blake2s_ptr = blake2s_ptr + INPUT_BLOCK_FELTS;
+
+    // Run the blake2s opcode runner on the same inputs and store its output.
+    run_blake2s_opcode(is_last_block=1, dst=counter + n_bytes, op0=state_ptr, op1=data);
+    return (output=cast(blake2s_ptr - STATE_SIZE_FELTS, felt*));
+}
+
+// These functions serialize data to a data array to be used with blake2s().
+// They use the property that each data word is verified by blake2s() to be in range [0, 2 ** 32).
+
+// Serializes a uint256 number in a blake2s compatible way (little-endian).
+func blake2s_add_uint256{data: felt*}(num: Uint256) {
+    let high = num.high;
+    let low = num.low;
+    %{
+        B = 32
+        MASK = 2 ** 32 - 1
+        segments.write_arg(ids.data, [(ids.low >> (B * i)) & MASK for i in range(4)])
+        segments.write_arg(ids.data + 4, [(ids.high >> (B * i)) & MASK for i in range(4)])
+    %}
+    assert data[3] * 2 ** 96 + data[2] * 2 ** 64 + data[1] * 2 ** 32 + data[0] = low;
+    assert data[7] * 2 ** 96 + data[6] * 2 ** 64 + data[5] * 2 ** 32 + data[4] = high;
+    let data = data + 8;
+    return ();
+}
+
+// Serializes a uint256 number in a blake2s compatible way (big-endian).
+func blake2s_add_uint256_bigend{bitwise_ptr: BitwiseBuiltin*, data: felt*}(num: Uint256) {
+    // Reverse byte endianness of 32-bit chunks.
+    tempvar value = num.high;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    tempvar high = value / 2 ** (8 + 16);
+
+    tempvar value = num.low;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar low = value / 2 ** (8 + 16);
+
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    %{
+        B = 32
+        MASK = 2 ** 32 - 1
+        segments.write_arg(ids.data, [(ids.high >> (B * (3 - i))) & MASK for i in range(4)])
+        segments.write_arg(ids.data + 4, [(ids.low >> (B * (3 - i))) & MASK for i in range(4)])
+    %}
+
+    assert data[0] * 2 ** 96 + data[1] * 2 ** 64 + data[2] * 2 ** 32 + data[3] = high;
+    assert data[4] * 2 ** 96 + data[5] * 2 ** 64 + data[6] * 2 ** 32 + data[7] = low;
+    let data = data + 8;
+    return ();
+}
+
+// Serializes a field element in a blake2s compatible way.
+func blake2s_add_felt{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, data: felt*}(
+    num: felt, bigend: felt
+) {
+    let (high, low) = split_felt(num);
+    if (bigend != 0) {
+        blake2s_add_uint256_bigend(Uint256(low=low, high=high));
+        return ();
+    } else {
+        blake2s_add_uint256(Uint256(low=low, high=high));
+        return ();
+    }
+}
+
+// Serializes multiple field elements in a blake2s compatible way.
+// Note: This function does not serialize the number of elements. If desired, this is the caller's
+// responsibility.
+func blake2s_add_felts{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, data: felt*}(
+    n_elements: felt, elements: felt*, bigend: felt
+) -> () {
+    if (n_elements == 0) {
+        return ();
+    }
+    blake2s_add_felt(num=elements[0], bigend=bigend);
+    return blake2s_add_felts(n_elements=n_elements - 1, elements=&elements[1], bigend=bigend);
+}
+
+// Computes the blake2s hash for multiple field elements.
+func blake2s_felts{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, blake2s_ptr: felt*}(
+    n_elements: felt, elements: felt*, bigend: felt
+) -> (res: Uint256) {
+    alloc_locals;
+    let (data) = alloc();
+    let data_start = data;
+    with data {
+        blake2s_add_felts(n_elements=n_elements, elements=elements, bigend=bigend);
+    }
+    let (res) = blake2s(data=data_start, n_bytes=n_elements * 32);
+    return (res=res);
+}
+
+// Taken from https://github.com/starkware-libs/stwo-cairo/blob/main/stwo_cairo_prover/test_data/test_prove_verify_all_opcode_components/all_opcode_components.cairo
+// Forces the runner to execute the Blake2s or Blake2sLastBlock opcode with the given operands.
+// op0 is a pointer to an array of 8 felts as u32 integers of the state.
+// op1 is a pointer to an array of 16 felts as u32 integers of the message.
+// dst is a felt representing a u32 of the counter.
+// ap contains a pointer to an array of 8 felts as u32 integers of the output state.
+// Those values are stored within addresses fp-5, fp-4 and fp-3 respectively.
+// An instruction encoding is built from offsets -5, -4, -3 and flags which are all 0 except for
+// those denoting uses of fp as the base for operand addresses and flag_opcode_blake (16th flag).
+// The instruction is then written to [pc] and the runner is forced to execute Blake2s.
+// Writes the output to the pointer in blake2s_ptr.
+func run_blake2s_opcode{blake2s_ptr: felt*}(
+    is_last_block: felt, dst: felt, op0: felt*, op1: felt*
+) {
+    alloc_locals;
+
+    // Set the offsets for the operands.
+    let offset0 = (2 ** 15) - 5;
+    let offset1 = (2 ** 15) - 4;
+    let offset2 = (2 ** 15) - 3;
+    static_assert dst == [fp - 5];
+    static_assert op0 == [fp - 4];
+    static_assert op1 == [fp - 3];
+
+    // Set the flags for the instruction.
+    let flag_dst_base_fp = 1;
+    let flag_op0_base_fp = 1;
+    let flag_op1_imm = 0;
+    let flag_op1_base_fp = 1;
+    let flag_op1_base_ap = 0;
+    let flag_res_add = 0;
+    let flag_res_mul = 0;
+    let flag_PC_update_jump = 0;
+    let flag_PC_update_jump_rel = 0;
+    let flag_PC_update_jnz = 0;
+    let flag_ap_update_add = 0;
+    let flag_ap_update_add_1 = 0;
+    let flag_opcode_call = 0;
+    let flag_opcode_ret = 0;
+    let flag_opcode_assert_eq = 0;
+
+    let flag_num = flag_dst_base_fp + flag_op0_base_fp * (2 ** 1) + flag_op1_imm * (2 ** 2) +
+        flag_op1_base_fp * (2 ** 3);
+    let blake2s_opcode_extension_num = 1;
+    let blake2s_last_block_opcode_extension_num = 2;
+    let blake2s_instruction_num = offset0 + offset1 * (2 ** 16) + offset2 * (2 ** 32) + flag_num * (
+        2 ** 48
+    ) + blake2s_opcode_extension_num * (2 ** 63);
+    let blake2s_last_block_instruction_num = offset0 + offset1 * (2 ** 16) + offset2 * (2 ** 32) +
+        flag_num * (2 ** 48) + blake2s_last_block_opcode_extension_num * (2 ** 63);
+    static_assert blake2s_instruction_num == 9226608988349300731;
+    static_assert blake2s_last_block_instruction_num == 18449981025204076539;
+
+    // Write the instruction to [pc] and point [ap] to the designated output.
+    assert [ap] = cast(blake2s_ptr, felt);
+
+    jmp last_block if is_last_block != 0;
+    dw 9226608988349300731;
+    ap += 1;
+    let blake2s_ptr = cast([fp - 7], felt*) + STATE_SIZE_FELTS;
+    return ();
+
+    last_block:
+    dw 18449981025204076539;
+    ap += 1;
+    let blake2s_ptr = cast([fp - 7], felt*) + STATE_SIZE_FELTS;
+    return ();
+}


Reviews:
user: login: ClementWalter
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T16:06:15Z
user: login: ClementWalter
body: use blake2s opcode
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-22T16:50:40Z
user: login: Eikix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T07:50:44Z
user: login: Eikix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T07:51:17Z
user: login: Eikix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T07:52:18Z
user: login: Eikix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T07:57:09Z
user: login: Eikix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T07:58:27Z
user: login: Eikix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T07:58:55Z
user: login: Eikix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T07:59:34Z
user: login: Eikix
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T08:00:23Z
user: login: enitrat
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T08:44:04Z
user: login: enitrat
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T08:45:07Z
user: login: enitrat
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T08:45:32Z
user: login: enitrat
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T08:46:32Z
user: login: ClementWalter
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T09:37:00Z
user: login: ClementWalter
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T09:47:13Z
user: login: ClementWalter
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T09:52:21Z
user: login: ClementWalter
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T09:53:29Z
user: login: ClementWalter
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T09:53:51Z
user: login: ClementWalter
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T09:54:35Z
user: login: ClementWalter
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T10:47:49Z
user: login: ClementWalter
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-04-24T10:49:07Z

Review comments:
diff_hunk: @@ -118,6 +118,7 @@ from ethereum.cancun.state import (
     TransientStorageStruct,
     empty_transient_storage,
     process_withdrawal,
+    state_root,
path: cairo/ethereum/cancun/fork.cairo
user: login: ClementWalter
body: ```suggestion
```
created_at: 2025-04-22T16:06:15Z
author_association: MEMBER
diff_hunk: @@ -28,3 +30,43 @@ func keccak256{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: Keccak
     tempvar hash = Hash32(value=value);
     return hash;
 }
+
+// @notice Computes the blake2s hash of a bytes object.
+// @dev `finalize_blake2s` must absolutely be called at the end of the program.
+func blake2s_bytes{range_check_ptr, blake2s_ptr: felt*}(buffer: Bytes) -> Hash32 {
+    alloc_locals;
+    let n_bytes = buffer.value.len;
+    let (dst: felt*) = alloc();
+    bytes_to_bytes4_little_endian(dst, n_bytes, buffer.value.data);
+    let (result) = blake2s(dst, n_bytes);
+    tempvar value = new Uint256(low=result.low, high=result.high);
+    tempvar hash = Hash32(value=value);
+    return hash;
+}
+
+// @notice Computes the hash of a bytes object using the given hash function.
+// @dev To avoid re-binding the arguments in the correct order, the `hash_function_name` must be the
path: cairo/ethereum/crypto/hash.cairo
user: login: Eikix
body: is this comment conveying correct information? the hash function name is not an implicit argument
created_at: 2025-04-23T07:50:44Z
author_association: MEMBER
diff_hunk: @@ -28,3 +30,43 @@ func keccak256{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: Keccak
     tempvar hash = Hash32(value=value);
     return hash;
 }
+
+// @notice Computes the blake2s hash of a bytes object.
+// @dev `finalize_blake2s` must absolutely be called at the end of the program.
+func blake2s_bytes{range_check_ptr, blake2s_ptr: felt*}(buffer: Bytes) -> Hash32 {
+    alloc_locals;
+    let n_bytes = buffer.value.len;
+    let (dst: felt*) = alloc();
+    bytes_to_bytes4_little_endian(dst, n_bytes, buffer.value.data);
+    let (result) = blake2s(dst, n_bytes);
+    tempvar value = new Uint256(low=result.low, high=result.high);
+    tempvar hash = Hash32(value=value);
+    return hash;
+}
+
+// @notice Computes the hash of a bytes object using the given hash function.
+// @dev To avoid re-binding the arguments in the correct order, the `hash_function_name` must be the
+// first implicit argument.
+// @dev This function takes as implicit arguments all possible arguments for the hash_function_names used.
+func hash_with{
+    range_check_ptr, bitwise_ptr: BitwiseBuiltin*, keccak_ptr: KeccakBuiltin*, blake2s_ptr: felt*
+}(buffer: Bytes, hash_function_name: felt) -> Hash32 {
+    alloc_locals;
+    let n_bytes = buffer.value.len;
+
+    if (hash_function_name == 'blake2s') {
+        let (dst: felt*) = alloc();
path: cairo/ethereum/crypto/hash.cairo
user: login: Eikix
body: you can use `blake2s_bytes` here I think
created_at: 2025-04-23T07:51:17Z
author_association: MEMBER
diff_hunk: @@ -251,6 +251,103 @@ func uint256_to_bytes32{range_check_ptr}(dst: felt*, n: Uint256) {
     return ();
 }
 
+// @notice Converts an array of bytes to an array of bytes4, little endian
+// @dev The function is sound because the number of steps is limited to 2^50 by the verifier.
+//      Consequently, `bytes4` cannot wrap around P. No range_check is needed in the main loop.
+//      Only in the final step, depending on the size of the remainder, is the total length of the
+//      output array checked.
+// @param dst The destination array.
+// @param bytes_len The number of bytes in the input array.
+// @param bytes The input array.
+func bytes_to_bytes4_little_endian{range_check_ptr}(dst: felt*, bytes_len: felt, bytes: felt*) -> (
+    ) {
+    alloc_locals;
+
+    if (bytes_len == 0) {
+        return ();
+    }
+
+    tempvar less_than_4;
+    %{ bytes_len_less_than_4 %}
+    tempvar bytes4 = dst;
+    tempvar bytes = bytes;
+
+    static_assert bytes4 == [ap - 2];
+    static_assert bytes == [ap - 1];
+
+    jmp skip_full_word_loop if less_than_4 != 0;
+
+    // Main loop done a random number of times
path: cairo/legacy/utils/bytes.cairo
user: login: Eikix
body: why random?
created_at: 2025-04-23T07:52:18Z
author_association: MEMBER
diff_hunk: @@ -891,6 +891,10 @@ def _gen_arg(
         return struct_ptr
 
     if arg_type in (int, bool, U64, Uint, Bytes0, Bytes4, Bytes8, Bytes20):
+        # Case short string: arg type is int but actual type is str
+        if type(arg) is str:
+            arg = int.from_bytes(arg.encode(), "big")
path: cairo/tests/utils/args_gen.py
user: login: Eikix
body: should we fail is arg > Prime? since we assume it's a short string
created_at: 2025-04-23T07:57:09Z
author_association: MEMBER
diff_hunk: @@ -125,18 +128,31 @@ def build_entrypoint(
         f"{entrypoint}.Return", TypeDefinition
     ).cairo_type
 
-    return_data_types = [
-        *(arg["cairo_type"] for arg in _implicit_args.values()),
-        # Filter for the empty tuple return type
-        *(
-            [explicit_return_data]
-            if not (
-                isinstance(explicit_return_data, TypeTuple)
-                and len(explicit_return_data.members) == 0
-            )
-            else []
-        ),
-    ]
+    # Construct the full list of return data types with their names and includes flags
+    return_data_types = []
path: python/cairo-addons/src/cairo_addons/testing/runner.py
user: login: Eikix
body: why did we need this?
created_at: 2025-04-23T07:58:27Z
author_association: MEMBER
diff_hunk: @@ -325,7 +339,8 @@ def _run(entrypoint, *args, **kwargs):
         #   and performs security checks
         # ============================================================================
         runner.end_run(disable_trace_padding=False)
-        cumulative_retdata_offsets = serde.get_offsets(return_data_types)
+        cairo_types = [item["type"] for item in return_data_types]
path: python/cairo-addons/src/cairo_addons/testing/runner.py
user: login: Eikix
body: why do we need this? is it a boyscout commit?
created_at: 2025-04-23T07:58:55Z
author_association: MEMBER
diff_hunk: @@ -432,13 +447,21 @@ def _run(entrypoint, *args, **kwargs):
         # - Rationale: Convert Cairo return values to Python types, handle exceptions,
         #   and format the final output for the caller.
         # ============================================================================
-        unfiltered_output = [
-            serde.serialize(return_data_type, runner.vm.run_context.ap, offset)
-            for offset, return_data_type in zip(
-                cumulative_retdata_offsets, return_data_types
-            )
-        ]
-        function_output = serde.filter_no_error_flag(unfiltered_output)
+        function_output = []
+
+        # Simplified filtering based on the include flag
+        for i, (return_item, offset) in enumerate(
path: python/cairo-addons/src/cairo_addons/testing/runner.py
user: login: Eikix
body: it doesn't look like we use `i`
created_at: 2025-04-23T07:59:34Z
author_association: MEMBER
diff_hunk: @@ -546,11 +568,19 @@ def _run(entrypoint, *args, verify_squashed_dicts: bool = False, **kwargs):
             if gen_arg_builder is not None
             else lambda _python_type, _value: runner.segments.gen_arg(_value)
         )
-        for i, (arg_name, python_type) in enumerate(
-            [(k, v["python_type"]) for k, v in {**_implicit_args, **_args}.items()]
-        ):
+
+        i = 0
path: python/cairo-addons/src/cairo_addons/testing/runner.py
user: login: Eikix
body: same question here
created_at: 2025-04-23T08:00:23Z
author_association: MEMBER
diff_hunk: @@ -251,6 +251,103 @@ func uint256_to_bytes32{range_check_ptr}(dst: felt*, n: Uint256) {
     return ();
 }
 
+// @notice Converts an array of bytes to an array of bytes4, little endian
+// @dev The function is sound because the number of steps is limited to 2^50 by the verifier.
+//      Consequently, `bytes4` cannot wrap around P. No range_check is needed in the main loop.
+//      Only in the final step, depending on the size of the remainder, is the total length of the
+//      output array checked.
+// @param dst The destination array.
+// @param bytes_len The number of bytes in the input array.
+// @param bytes The input array.
+func bytes_to_bytes4_little_endian{range_check_ptr}(dst: felt*, bytes_len: felt, bytes: felt*) -> (
+    ) {
+    alloc_locals;
+
+    if (bytes_len == 0) {
+        return ();
+    }
+
+    tempvar less_than_4;
+    %{ bytes_len_less_than_4 %}
+    tempvar bytes4 = dst;
+    tempvar bytes = bytes;
+
+    static_assert bytes4 == [ap - 2];
+    static_assert bytes == [ap - 1];
+
+    jmp skip_full_word_loop if less_than_4 != 0;
+
+    // Main loop done a random number of times
path: cairo/legacy/utils/bytes.cairo
user: login: enitrat
body: see `bytes_to_bytes8_little_endian`
created_at: 2025-04-23T08:44:04Z
author_association: MEMBER
diff_hunk: @@ -125,18 +128,31 @@ def build_entrypoint(
         f"{entrypoint}.Return", TypeDefinition
     ).cairo_type
 
-    return_data_types = [
-        *(arg["cairo_type"] for arg in _implicit_args.values()),
-        # Filter for the empty tuple return type
-        *(
-            [explicit_return_data]
-            if not (
-                isinstance(explicit_return_data, TypeTuple)
-                and len(explicit_return_data.members) == 0
-            )
-            else []
-        ),
-    ]
+    # Construct the full list of return data types with their names and includes flags
+    return_data_types = []
path: python/cairo-addons/src/cairo_addons/testing/runner.py
user: login: enitrat
body: skip returning the blake2s ptr
created_at: 2025-04-23T08:45:07Z
author_association: MEMBER
diff_hunk: @@ -325,7 +339,8 @@ def _run(entrypoint, *args, **kwargs):
         #   and performs security checks
         # ============================================================================
         runner.end_run(disable_trace_padding=False)
-        cumulative_retdata_offsets = serde.get_offsets(return_data_types)
+        cairo_types = [item["type"] for item in return_data_types]
path: python/cairo-addons/src/cairo_addons/testing/runner.py
user: login: enitrat
body: skip returning blake2s_ptr
created_at: 2025-04-23T08:45:32Z
author_association: MEMBER
diff_hunk: @@ -546,11 +568,19 @@ def _run(entrypoint, *args, verify_squashed_dicts: bool = False, **kwargs):
             if gen_arg_builder is not None
             else lambda _python_type, _value: runner.segments.gen_arg(_value)
         )
-        for i, (arg_name, python_type) in enumerate(
-            [(k, v["python_type"]) for k, v in {**_implicit_args, **_args}.items()]
-        ):
+
+        i = 0
path: python/cairo-addons/src/cairo_addons/testing/runner.py
user: login: enitrat
body: properly serialize `felt*` (like blake2s_ptr) with no associated args in runner
created_at: 2025-04-23T08:46:32Z
author_association: MEMBER
diff_hunk: @@ -60,7 +61,9 @@ func main{
         chain.value.blocks.value.len - 1
     ].value.header;
     let pre_state_root = parent_header.value.state_root;
-    state_transition{chain=chain}(block);
+    let (local blake2s_ptr_start: felt*) = alloc();
+    let blake2s_ptr = blake2s_ptr_start;
+    state_transition{chain=chain, blake2s_ptr=blake2s_ptr}(block);
path: cairo/ethereum/cancun/main.cairo
user: login: ClementWalter
body: undo ?
created_at: 2025-04-23T09:37:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,321 @@
+// Module adapted from starkware.cairo.common.cairo_blake2s.blake2s to use the newly introduced
+// `blake2s` opcode.
+
+// This module provides a set of functions to compute the blake2s hash function.
+//
+// This module is similar to the keccak.cairo module. See more info there.
+
+from starkware.cairo.common.alloc import alloc
+from starkware.cairo.common.cairo_blake2s.packed_blake2s import N_PACKED_INSTANCES, blake2s_compress
+from starkware.cairo.common.cairo_builtins import BitwiseBuiltin
+from starkware.cairo.common.math import assert_nn_le, split_felt, unsigned_div_rem
+from starkware.cairo.common.math_cmp import is_le
+from starkware.cairo.common.memcpy import memcpy
+from starkware.cairo.common.memset import memset
+from starkware.cairo.common.pow import pow
+from starkware.cairo.common.registers import get_fp_and_pc, get_label_location
+from starkware.cairo.common.uint256 import Uint256
+
+const INPUT_BLOCK_FELTS = 16;
+const INPUT_BLOCK_BYTES = 64;
+const STATE_SIZE_FELTS = 8;
+const INPUT_SIZE_FELTS = INPUT_BLOCK_FELTS + STATE_SIZE_FELTS;
+// Each instance consists of 8 words for the input state, 16 words of message,
+// 2 words for t0 and f0, and 8 words for the output state.
+const INSTANCE_SIZE = STATE_SIZE_FELTS + INPUT_BLOCK_FELTS + 2 + STATE_SIZE_FELTS;
+
+// Computes blake2s of 'input'.
+// To use this function, split the input into words of 32 bits (little endian).
+// For example, to compute blake2s('Hello world'), use:
+//   input = [1819043144, 1870078063, 6581362]
+// where:
+//   1819043144 == int.from_bytes(b'Hell', 'little')
+//   1870078063 == int.from_bytes(b'o wo', 'little')
+//   6581362 == int.from_bytes(b'rld', 'little')
+//
+// Returns the hash as a Uint256.
+//
+// Note: Unlike the blake2s implementation from starkware, this one does not require calling
+// any finalize function, as it's using a specific opcode proven by STWO.
+// Note: the interface of this function may change in the future.
+// Note: Each input word is verified to be in the range [0, 2 ** 32) by this function.
+func blake2s{range_check_ptr}(data: felt*, n_bytes: felt) -> (res: Uint256) {
+    let (blake2s_ptr) = alloc();
+    let (output) = blake2s_as_words{blake2s_ptr=blake2s_ptr}(data=data, n_bytes=n_bytes);
+    let res_low = output[3] * 2 ** 96 + output[2] * 2 ** 64 + output[1] * 2 ** 32 + output[0];
+    let res_high = output[7] * 2 ** 96 + output[6] * 2 ** 64 + output[5] * 2 ** 32 + output[4];
+    return (res=Uint256(low=res_low, high=res_high));
+}
+
+// Computes blake2s of 'input', and returns the hash in big endian representation.
+// See blake2s().
+// Note that the input is still treated as little endian.
+func blake2s_bigend{bitwise_ptr: BitwiseBuiltin*, range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt
+) -> (res: Uint256) {
+    let (num) = blake2s(data=data, n_bytes=n_bytes);
+
+    // Reverse byte endianness of 128-bit words.
+    tempvar value = num.high;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000;
+    tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000;
+    tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar high = value / 2 ** (8 + 16 + 32 + 64);
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    tempvar value = num.low;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000;
+    tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000;
+    tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar low = value / 2 ** (8 + 16 + 32 + 64);
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    return (res=Uint256(low=high, high=low));
+}
+
+// Same as blake2s, but outputs a pointer to 8 32-bit little endian words instead.
+func blake2s_as_words{range_check_ptr, blake2s_ptr: felt*}(data: felt*, n_bytes: felt) -> (
+    output: felt*
+) {
+    // Set the initial state to IV (IV[0] is modified).
+    assert blake2s_ptr[0] = 0x6B08E647;  // IV[0] ^ 0x01010020 (config: no key, 32 bytes output).
+    assert blake2s_ptr[1] = 0xBB67AE85;
+    assert blake2s_ptr[2] = 0x3C6EF372;
+    assert blake2s_ptr[3] = 0xA54FF53A;
+    assert blake2s_ptr[4] = 0x510E527F;
+    assert blake2s_ptr[5] = 0x9B05688C;
+    assert blake2s_ptr[6] = 0x1F83D9AB;
+    assert blake2s_ptr[7] = 0x5BE0CD19;
+    static_assert STATE_SIZE_FELTS == 8;
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+
+    let (output) = blake2s_inner(data=data, n_bytes=n_bytes, counter=0);
+    return (output=output);
+}
+
+// Inner loop for blake2s. blake2s_ptr points to the middle of an instance: after the initial state,
+// before the message.
+func blake2s_inner{range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt, counter: felt
+) -> (output: felt*) {
+    alloc_locals;
+    let is_last_block = is_le(n_bytes, INPUT_BLOCK_BYTES);
+    if (is_last_block != 0) {
+        return blake2s_last_block(data=data, n_bytes=n_bytes, counter=counter);
+    }
+
+    let state_ptr = blake2s_ptr - STATE_SIZE_FELTS;
+    let message_ptr = blake2s_ptr;
+    memcpy(blake2s_ptr, data, INPUT_BLOCK_FELTS);
+
+    // Run the blake2s opcode runner on the same inputs and store its output.
+    let vm_output = run_blake2s_opcode(
+        is_last_block=0, dst=counter, op0=state_ptr, op1=message_ptr
+    );
+
+    // Write the current output to the input state for the next instance.
+    memcpy(blake2s_ptr, vm_output, STATE_SIZE_FELTS);
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+    return blake2s_inner(
+        data=data + INPUT_BLOCK_FELTS,
+        n_bytes=n_bytes - INPUT_BLOCK_BYTES,
+        counter=counter + INPUT_BLOCK_BYTES,
+    );
+}
+
+func blake2s_last_block{range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt, counter: felt
+) -> (output: felt*) {
+    alloc_locals;
+    let state_ptr = blake2s_ptr - STATE_SIZE_FELTS;
+    let message_ptr = blake2s_ptr;
+    let (n_felts, _) = unsigned_div_rem(n_bytes + 3, 4);
+    memcpy(blake2s_ptr, data, n_felts);
+    memset(blake2s_ptr + n_felts, 0, INPUT_BLOCK_FELTS - n_felts);
+    let blake2s_ptr = blake2s_ptr + INPUT_BLOCK_FELTS;
+
+    // Run the blake2s opcode runner on the same inputs and store its output.
+    let vm_output = run_blake2s_opcode(
+        is_last_block=1, dst=counter + n_bytes, op0=state_ptr, op1=message_ptr
+    );
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+
+    return (output=vm_output);
+}
+
+// These functions serialize data to a data array to be used with blake2s().
+// They use the property that each data word is verified by blake2s() to be in range [0, 2 ** 32).
+
+// Serializes a uint256 number in a blake2s compatible way (little-endian).
+func blake2s_add_uint256{data: felt*}(num: Uint256) {
+    let high = num.high;
+    let low = num.low;
+    %{
+        B = 32
+        MASK = 2 ** 32 - 1
+        segments.write_arg(ids.data, [(ids.low >> (B * i)) & MASK for i in range(4)])
+        segments.write_arg(ids.data + 4, [(ids.high >> (B * i)) & MASK for i in range(4)])
+    %}
+    assert data[3] * 2 ** 96 + data[2] * 2 ** 64 + data[1] * 2 ** 32 + data[0] = low;
+    assert data[7] * 2 ** 96 + data[6] * 2 ** 64 + data[5] * 2 ** 32 + data[4] = high;
+    let data = data + 8;
+    return ();
+}
+
+// Serializes a uint256 number in a blake2s compatible way (big-endian).
+func blake2s_add_uint256_bigend{bitwise_ptr: BitwiseBuiltin*, data: felt*}(num: Uint256) {
+    // Reverse byte endianness of 32-bit chunks.
+    tempvar value = num.high;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    tempvar high = value / 2 ** (8 + 16);
+
+    tempvar value = num.low;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar low = value / 2 ** (8 + 16);
+
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    %{
+        B = 32
+        MASK = 2 ** 32 - 1
+        segments.write_arg(ids.data, [(ids.high >> (B * (3 - i))) & MASK for i in range(4)])
+        segments.write_arg(ids.data + 4, [(ids.low >> (B * (3 - i))) & MASK for i in range(4)])
+    %}
+
+    assert data[0] * 2 ** 96 + data[1] * 2 ** 64 + data[2] * 2 ** 32 + data[3] = high;
+    assert data[4] * 2 ** 96 + data[5] * 2 ** 64 + data[6] * 2 ** 32 + data[7] = low;
+    let data = data + 8;
+    return ();
+}
+
+// Serializes a field element in a blake2s compatible way.
+func blake2s_add_felt{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, data: felt*}(
+    num: felt, bigend: felt
+) {
+    let (high, low) = split_felt(num);
+    if (bigend != 0) {
+        blake2s_add_uint256_bigend(Uint256(low=low, high=high));
+        return ();
+    } else {
+        blake2s_add_uint256(Uint256(low=low, high=high));
+        return ();
+    }
+}
+
+// Serializes multiple field elements in a blake2s compatible way.
+// Note: This function does not serialize the number of elements. If desired, this is the caller's
+// responsibility.
+func blake2s_add_felts{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, data: felt*}(
+    n_elements: felt, elements: felt*, bigend: felt
+) -> () {
+    if (n_elements == 0) {
+        return ();
+    }
+    blake2s_add_felt(num=elements[0], bigend=bigend);
+    return blake2s_add_felts(n_elements=n_elements - 1, elements=&elements[1], bigend=bigend);
+}
+
+// Computes the blake2s hash for multiple field elements.
+func blake2s_felts{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, blake2s_ptr: felt*}(
+    n_elements: felt, elements: felt*, bigend: felt
+) -> (res: Uint256) {
+    alloc_locals;
+    let (data) = alloc();
+    let data_start = data;
+    with data {
+        blake2s_add_felts(n_elements=n_elements, elements=elements, bigend=bigend);
+    }
+    let (res) = blake2s(data=data_start, n_bytes=n_elements * 32);
+    return (res=res);
+}
+
+// Taken from https://github.com/starkware-libs/stwo-cairo/blob/main/stwo_cairo_prover/test_data/test_prove_verify_all_opcode_components/all_opcode_components.cairo
+// Forces the runner to execute the Blake2s or Blake2sLastBlock opcode with the given operands.
+// op0 is a pointer to an array of 8 felts as u32 integers of the state.
+// op1 is a pointer to an array of 16 felts as u32 integers of the message.
+// dst is a felt representing a u32 of the counter.
+// ap contains a pointer to an array of 8 felts as u32 integers of the output state.
+// Those values are stored within addresses fp-5, fp-4 and fp-3 respectively.
+// An instruction encoding is built from offsets -5, -4, -3 and flags which are all 0 except for
+// those denoting uses of fp as the base for operand addresses and flag_opcode_blake (16th flag).
+// The instruction is then written to [pc] and the runner is forced to execute Blake2s.
+func run_blake2s_opcode(is_last_block: felt, dst: felt, op0: felt*, op1: felt*) -> felt* {
path: python/cairo-core/src/cairo_core/hash/blake2s.cairo
user: login: ClementWalter
body: I don't think that we need to copy this all test function. We can just use the compiled `dw` directly
created_at: 2025-04-23T09:47:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,321 @@
+// Module adapted from starkware.cairo.common.cairo_blake2s.blake2s to use the newly introduced
+// `blake2s` opcode.
+
+// This module provides a set of functions to compute the blake2s hash function.
+//
+// This module is similar to the keccak.cairo module. See more info there.
+
+from starkware.cairo.common.alloc import alloc
+from starkware.cairo.common.cairo_blake2s.packed_blake2s import N_PACKED_INSTANCES, blake2s_compress
+from starkware.cairo.common.cairo_builtins import BitwiseBuiltin
+from starkware.cairo.common.math import assert_nn_le, split_felt, unsigned_div_rem
+from starkware.cairo.common.math_cmp import is_le
+from starkware.cairo.common.memcpy import memcpy
+from starkware.cairo.common.memset import memset
+from starkware.cairo.common.pow import pow
+from starkware.cairo.common.registers import get_fp_and_pc, get_label_location
+from starkware.cairo.common.uint256 import Uint256
+
+const INPUT_BLOCK_FELTS = 16;
+const INPUT_BLOCK_BYTES = 64;
+const STATE_SIZE_FELTS = 8;
+const INPUT_SIZE_FELTS = INPUT_BLOCK_FELTS + STATE_SIZE_FELTS;
+// Each instance consists of 8 words for the input state, 16 words of message,
+// 2 words for t0 and f0, and 8 words for the output state.
+const INSTANCE_SIZE = STATE_SIZE_FELTS + INPUT_BLOCK_FELTS + 2 + STATE_SIZE_FELTS;
+
+// Computes blake2s of 'input'.
+// To use this function, split the input into words of 32 bits (little endian).
+// For example, to compute blake2s('Hello world'), use:
+//   input = [1819043144, 1870078063, 6581362]
+// where:
+//   1819043144 == int.from_bytes(b'Hell', 'little')
+//   1870078063 == int.from_bytes(b'o wo', 'little')
+//   6581362 == int.from_bytes(b'rld', 'little')
+//
+// Returns the hash as a Uint256.
+//
+// Note: Unlike the blake2s implementation from starkware, this one does not require calling
+// any finalize function, as it's using a specific opcode proven by STWO.
+// Note: the interface of this function may change in the future.
+// Note: Each input word is verified to be in the range [0, 2 ** 32) by this function.
+func blake2s{range_check_ptr}(data: felt*, n_bytes: felt) -> (res: Uint256) {
+    let (blake2s_ptr) = alloc();
+    let (output) = blake2s_as_words{blake2s_ptr=blake2s_ptr}(data=data, n_bytes=n_bytes);
+    let res_low = output[3] * 2 ** 96 + output[2] * 2 ** 64 + output[1] * 2 ** 32 + output[0];
+    let res_high = output[7] * 2 ** 96 + output[6] * 2 ** 64 + output[5] * 2 ** 32 + output[4];
+    return (res=Uint256(low=res_low, high=res_high));
+}
+
+// Computes blake2s of 'input', and returns the hash in big endian representation.
+// See blake2s().
+// Note that the input is still treated as little endian.
+func blake2s_bigend{bitwise_ptr: BitwiseBuiltin*, range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt
+) -> (res: Uint256) {
+    let (num) = blake2s(data=data, n_bytes=n_bytes);
+
+    // Reverse byte endianness of 128-bit words.
+    tempvar value = num.high;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000;
+    tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000;
+    tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar high = value / 2 ** (8 + 16 + 32 + 64);
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    tempvar value = num.low;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000;
+    tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000;
+    tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar low = value / 2 ** (8 + 16 + 32 + 64);
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    return (res=Uint256(low=high, high=low));
+}
+
+// Same as blake2s, but outputs a pointer to 8 32-bit little endian words instead.
+func blake2s_as_words{range_check_ptr, blake2s_ptr: felt*}(data: felt*, n_bytes: felt) -> (
+    output: felt*
+) {
+    // Set the initial state to IV (IV[0] is modified).
+    assert blake2s_ptr[0] = 0x6B08E647;  // IV[0] ^ 0x01010020 (config: no key, 32 bytes output).
+    assert blake2s_ptr[1] = 0xBB67AE85;
+    assert blake2s_ptr[2] = 0x3C6EF372;
+    assert blake2s_ptr[3] = 0xA54FF53A;
+    assert blake2s_ptr[4] = 0x510E527F;
+    assert blake2s_ptr[5] = 0x9B05688C;
+    assert blake2s_ptr[6] = 0x1F83D9AB;
+    assert blake2s_ptr[7] = 0x5BE0CD19;
+    static_assert STATE_SIZE_FELTS == 8;
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+
+    let (output) = blake2s_inner(data=data, n_bytes=n_bytes, counter=0);
+    return (output=output);
+}
+
+// Inner loop for blake2s. blake2s_ptr points to the middle of an instance: after the initial state,
+// before the message.
+func blake2s_inner{range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt, counter: felt
+) -> (output: felt*) {
+    alloc_locals;
+    let is_last_block = is_le(n_bytes, INPUT_BLOCK_BYTES);
+    if (is_last_block != 0) {
+        return blake2s_last_block(data=data, n_bytes=n_bytes, counter=counter);
+    }
+
+    let state_ptr = blake2s_ptr - STATE_SIZE_FELTS;
+    let message_ptr = blake2s_ptr;
+    memcpy(blake2s_ptr, data, INPUT_BLOCK_FELTS);
+
+    // Run the blake2s opcode runner on the same inputs and store its output.
+    let vm_output = run_blake2s_opcode(
+        is_last_block=0, dst=counter, op0=state_ptr, op1=message_ptr
+    );
+
+    // Write the current output to the input state for the next instance.
+    memcpy(blake2s_ptr, vm_output, STATE_SIZE_FELTS);
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+    return blake2s_inner(
+        data=data + INPUT_BLOCK_FELTS,
+        n_bytes=n_bytes - INPUT_BLOCK_BYTES,
+        counter=counter + INPUT_BLOCK_BYTES,
+    );
+}
+
+func blake2s_last_block{range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt, counter: felt
+) -> (output: felt*) {
+    alloc_locals;
+    let state_ptr = blake2s_ptr - STATE_SIZE_FELTS;
+    let message_ptr = blake2s_ptr;
+    let (n_felts, _) = unsigned_div_rem(n_bytes + 3, 4);
+    memcpy(blake2s_ptr, data, n_felts);
+    memset(blake2s_ptr + n_felts, 0, INPUT_BLOCK_FELTS - n_felts);
+    let blake2s_ptr = blake2s_ptr + INPUT_BLOCK_FELTS;
+
+    // Run the blake2s opcode runner on the same inputs and store its output.
+    let vm_output = run_blake2s_opcode(
+        is_last_block=1, dst=counter + n_bytes, op0=state_ptr, op1=message_ptr
+    );
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+
+    return (output=vm_output);
+}
+
+// These functions serialize data to a data array to be used with blake2s().
+// They use the property that each data word is verified by blake2s() to be in range [0, 2 ** 32).
+
+// Serializes a uint256 number in a blake2s compatible way (little-endian).
+func blake2s_add_uint256{data: felt*}(num: Uint256) {
+    let high = num.high;
+    let low = num.low;
+    %{
+        B = 32
+        MASK = 2 ** 32 - 1
+        segments.write_arg(ids.data, [(ids.low >> (B * i)) & MASK for i in range(4)])
+        segments.write_arg(ids.data + 4, [(ids.high >> (B * i)) & MASK for i in range(4)])
+    %}
+    assert data[3] * 2 ** 96 + data[2] * 2 ** 64 + data[1] * 2 ** 32 + data[0] = low;
+    assert data[7] * 2 ** 96 + data[6] * 2 ** 64 + data[5] * 2 ** 32 + data[4] = high;
+    let data = data + 8;
+    return ();
+}
+
+// Serializes a uint256 number in a blake2s compatible way (big-endian).
+func blake2s_add_uint256_bigend{bitwise_ptr: BitwiseBuiltin*, data: felt*}(num: Uint256) {
path: python/cairo-core/src/cairo_core/hash/blake2s.cairo
user: login: ClementWalter
body: so it's like uint256 to bytes4 big ? I don't see any "blake2s" thing in this function
created_at: 2025-04-23T09:52:21Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,321 @@
+// Module adapted from starkware.cairo.common.cairo_blake2s.blake2s to use the newly introduced
+// `blake2s` opcode.
+
+// This module provides a set of functions to compute the blake2s hash function.
+//
+// This module is similar to the keccak.cairo module. See more info there.
+
+from starkware.cairo.common.alloc import alloc
+from starkware.cairo.common.cairo_blake2s.packed_blake2s import N_PACKED_INSTANCES, blake2s_compress
+from starkware.cairo.common.cairo_builtins import BitwiseBuiltin
+from starkware.cairo.common.math import assert_nn_le, split_felt, unsigned_div_rem
+from starkware.cairo.common.math_cmp import is_le
+from starkware.cairo.common.memcpy import memcpy
+from starkware.cairo.common.memset import memset
+from starkware.cairo.common.pow import pow
+from starkware.cairo.common.registers import get_fp_and_pc, get_label_location
+from starkware.cairo.common.uint256 import Uint256
+
+const INPUT_BLOCK_FELTS = 16;
+const INPUT_BLOCK_BYTES = 64;
+const STATE_SIZE_FELTS = 8;
+const INPUT_SIZE_FELTS = INPUT_BLOCK_FELTS + STATE_SIZE_FELTS;
+// Each instance consists of 8 words for the input state, 16 words of message,
+// 2 words for t0 and f0, and 8 words for the output state.
+const INSTANCE_SIZE = STATE_SIZE_FELTS + INPUT_BLOCK_FELTS + 2 + STATE_SIZE_FELTS;
+
+// Computes blake2s of 'input'.
+// To use this function, split the input into words of 32 bits (little endian).
+// For example, to compute blake2s('Hello world'), use:
+//   input = [1819043144, 1870078063, 6581362]
+// where:
+//   1819043144 == int.from_bytes(b'Hell', 'little')
+//   1870078063 == int.from_bytes(b'o wo', 'little')
+//   6581362 == int.from_bytes(b'rld', 'little')
+//
+// Returns the hash as a Uint256.
+//
+// Note: Unlike the blake2s implementation from starkware, this one does not require calling
+// any finalize function, as it's using a specific opcode proven by STWO.
+// Note: the interface of this function may change in the future.
+// Note: Each input word is verified to be in the range [0, 2 ** 32) by this function.
+func blake2s{range_check_ptr}(data: felt*, n_bytes: felt) -> (res: Uint256) {
+    let (blake2s_ptr) = alloc();
+    let (output) = blake2s_as_words{blake2s_ptr=blake2s_ptr}(data=data, n_bytes=n_bytes);
+    let res_low = output[3] * 2 ** 96 + output[2] * 2 ** 64 + output[1] * 2 ** 32 + output[0];
+    let res_high = output[7] * 2 ** 96 + output[6] * 2 ** 64 + output[5] * 2 ** 32 + output[4];
+    return (res=Uint256(low=res_low, high=res_high));
+}
+
+// Computes blake2s of 'input', and returns the hash in big endian representation.
+// See blake2s().
+// Note that the input is still treated as little endian.
+func blake2s_bigend{bitwise_ptr: BitwiseBuiltin*, range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt
+) -> (res: Uint256) {
+    let (num) = blake2s(data=data, n_bytes=n_bytes);
+
+    // Reverse byte endianness of 128-bit words.
+    tempvar value = num.high;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000;
+    tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000;
+    tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar high = value / 2 ** (8 + 16 + 32 + 64);
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    tempvar value = num.low;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000;
+    tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000;
+    tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar low = value / 2 ** (8 + 16 + 32 + 64);
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    return (res=Uint256(low=high, high=low));
+}
+
+// Same as blake2s, but outputs a pointer to 8 32-bit little endian words instead.
+func blake2s_as_words{range_check_ptr, blake2s_ptr: felt*}(data: felt*, n_bytes: felt) -> (
+    output: felt*
+) {
+    // Set the initial state to IV (IV[0] is modified).
+    assert blake2s_ptr[0] = 0x6B08E647;  // IV[0] ^ 0x01010020 (config: no key, 32 bytes output).
+    assert blake2s_ptr[1] = 0xBB67AE85;
+    assert blake2s_ptr[2] = 0x3C6EF372;
+    assert blake2s_ptr[3] = 0xA54FF53A;
+    assert blake2s_ptr[4] = 0x510E527F;
+    assert blake2s_ptr[5] = 0x9B05688C;
+    assert blake2s_ptr[6] = 0x1F83D9AB;
+    assert blake2s_ptr[7] = 0x5BE0CD19;
+    static_assert STATE_SIZE_FELTS == 8;
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+
+    let (output) = blake2s_inner(data=data, n_bytes=n_bytes, counter=0);
+    return (output=output);
+}
+
+// Inner loop for blake2s. blake2s_ptr points to the middle of an instance: after the initial state,
+// before the message.
+func blake2s_inner{range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt, counter: felt
+) -> (output: felt*) {
+    alloc_locals;
+    let is_last_block = is_le(n_bytes, INPUT_BLOCK_BYTES);
+    if (is_last_block != 0) {
+        return blake2s_last_block(data=data, n_bytes=n_bytes, counter=counter);
+    }
+
+    let state_ptr = blake2s_ptr - STATE_SIZE_FELTS;
+    let message_ptr = blake2s_ptr;
+    memcpy(blake2s_ptr, data, INPUT_BLOCK_FELTS);
+
+    // Run the blake2s opcode runner on the same inputs and store its output.
+    let vm_output = run_blake2s_opcode(
+        is_last_block=0, dst=counter, op0=state_ptr, op1=message_ptr
+    );
+
+    // Write the current output to the input state for the next instance.
+    memcpy(blake2s_ptr, vm_output, STATE_SIZE_FELTS);
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+    return blake2s_inner(
+        data=data + INPUT_BLOCK_FELTS,
+        n_bytes=n_bytes - INPUT_BLOCK_BYTES,
+        counter=counter + INPUT_BLOCK_BYTES,
+    );
+}
+
+func blake2s_last_block{range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt, counter: felt
+) -> (output: felt*) {
+    alloc_locals;
+    let state_ptr = blake2s_ptr - STATE_SIZE_FELTS;
+    let message_ptr = blake2s_ptr;
+    let (n_felts, _) = unsigned_div_rem(n_bytes + 3, 4);
+    memcpy(blake2s_ptr, data, n_felts);
+    memset(blake2s_ptr + n_felts, 0, INPUT_BLOCK_FELTS - n_felts);
+    let blake2s_ptr = blake2s_ptr + INPUT_BLOCK_FELTS;
+
+    // Run the blake2s opcode runner on the same inputs and store its output.
+    let vm_output = run_blake2s_opcode(
+        is_last_block=1, dst=counter + n_bytes, op0=state_ptr, op1=message_ptr
+    );
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+
+    return (output=vm_output);
+}
+
+// These functions serialize data to a data array to be used with blake2s().
+// They use the property that each data word is verified by blake2s() to be in range [0, 2 ** 32).
+
+// Serializes a uint256 number in a blake2s compatible way (little-endian).
+func blake2s_add_uint256{data: felt*}(num: Uint256) {
path: python/cairo-core/src/cairo_core/hash/blake2s.cairo
user: login: ClementWalter
body: same here, not sure about the "blake2s" relevance
created_at: 2025-04-23T09:53:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,321 @@
+// Module adapted from starkware.cairo.common.cairo_blake2s.blake2s to use the newly introduced
+// `blake2s` opcode.
+
+// This module provides a set of functions to compute the blake2s hash function.
+//
+// This module is similar to the keccak.cairo module. See more info there.
+
+from starkware.cairo.common.alloc import alloc
+from starkware.cairo.common.cairo_blake2s.packed_blake2s import N_PACKED_INSTANCES, blake2s_compress
+from starkware.cairo.common.cairo_builtins import BitwiseBuiltin
+from starkware.cairo.common.math import assert_nn_le, split_felt, unsigned_div_rem
+from starkware.cairo.common.math_cmp import is_le
+from starkware.cairo.common.memcpy import memcpy
+from starkware.cairo.common.memset import memset
+from starkware.cairo.common.pow import pow
+from starkware.cairo.common.registers import get_fp_and_pc, get_label_location
+from starkware.cairo.common.uint256 import Uint256
+
+const INPUT_BLOCK_FELTS = 16;
+const INPUT_BLOCK_BYTES = 64;
+const STATE_SIZE_FELTS = 8;
+const INPUT_SIZE_FELTS = INPUT_BLOCK_FELTS + STATE_SIZE_FELTS;
+// Each instance consists of 8 words for the input state, 16 words of message,
+// 2 words for t0 and f0, and 8 words for the output state.
+const INSTANCE_SIZE = STATE_SIZE_FELTS + INPUT_BLOCK_FELTS + 2 + STATE_SIZE_FELTS;
+
+// Computes blake2s of 'input'.
+// To use this function, split the input into words of 32 bits (little endian).
+// For example, to compute blake2s('Hello world'), use:
+//   input = [1819043144, 1870078063, 6581362]
+// where:
+//   1819043144 == int.from_bytes(b'Hell', 'little')
+//   1870078063 == int.from_bytes(b'o wo', 'little')
+//   6581362 == int.from_bytes(b'rld', 'little')
+//
+// Returns the hash as a Uint256.
+//
+// Note: Unlike the blake2s implementation from starkware, this one does not require calling
+// any finalize function, as it's using a specific opcode proven by STWO.
+// Note: the interface of this function may change in the future.
+// Note: Each input word is verified to be in the range [0, 2 ** 32) by this function.
+func blake2s{range_check_ptr}(data: felt*, n_bytes: felt) -> (res: Uint256) {
+    let (blake2s_ptr) = alloc();
+    let (output) = blake2s_as_words{blake2s_ptr=blake2s_ptr}(data=data, n_bytes=n_bytes);
+    let res_low = output[3] * 2 ** 96 + output[2] * 2 ** 64 + output[1] * 2 ** 32 + output[0];
+    let res_high = output[7] * 2 ** 96 + output[6] * 2 ** 64 + output[5] * 2 ** 32 + output[4];
+    return (res=Uint256(low=res_low, high=res_high));
+}
+
+// Computes blake2s of 'input', and returns the hash in big endian representation.
+// See blake2s().
+// Note that the input is still treated as little endian.
+func blake2s_bigend{bitwise_ptr: BitwiseBuiltin*, range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt
+) -> (res: Uint256) {
+    let (num) = blake2s(data=data, n_bytes=n_bytes);
+
+    // Reverse byte endianness of 128-bit words.
+    tempvar value = num.high;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000;
+    tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000;
+    tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar high = value / 2 ** (8 + 16 + 32 + 64);
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    tempvar value = num.low;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ffffffff00000000ffffffff000000;
+    tempvar value = value + (2 ** 64 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffffffffffffffff00000000000000;
+    tempvar value = value + (2 ** 128 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar low = value / 2 ** (8 + 16 + 32 + 64);
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    return (res=Uint256(low=high, high=low));
+}
+
+// Same as blake2s, but outputs a pointer to 8 32-bit little endian words instead.
+func blake2s_as_words{range_check_ptr, blake2s_ptr: felt*}(data: felt*, n_bytes: felt) -> (
+    output: felt*
+) {
+    // Set the initial state to IV (IV[0] is modified).
+    assert blake2s_ptr[0] = 0x6B08E647;  // IV[0] ^ 0x01010020 (config: no key, 32 bytes output).
+    assert blake2s_ptr[1] = 0xBB67AE85;
+    assert blake2s_ptr[2] = 0x3C6EF372;
+    assert blake2s_ptr[3] = 0xA54FF53A;
+    assert blake2s_ptr[4] = 0x510E527F;
+    assert blake2s_ptr[5] = 0x9B05688C;
+    assert blake2s_ptr[6] = 0x1F83D9AB;
+    assert blake2s_ptr[7] = 0x5BE0CD19;
+    static_assert STATE_SIZE_FELTS == 8;
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+
+    let (output) = blake2s_inner(data=data, n_bytes=n_bytes, counter=0);
+    return (output=output);
+}
+
+// Inner loop for blake2s. blake2s_ptr points to the middle of an instance: after the initial state,
+// before the message.
+func blake2s_inner{range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt, counter: felt
+) -> (output: felt*) {
+    alloc_locals;
+    let is_last_block = is_le(n_bytes, INPUT_BLOCK_BYTES);
+    if (is_last_block != 0) {
+        return blake2s_last_block(data=data, n_bytes=n_bytes, counter=counter);
+    }
+
+    let state_ptr = blake2s_ptr - STATE_SIZE_FELTS;
+    let message_ptr = blake2s_ptr;
+    memcpy(blake2s_ptr, data, INPUT_BLOCK_FELTS);
+
+    // Run the blake2s opcode runner on the same inputs and store its output.
+    let vm_output = run_blake2s_opcode(
+        is_last_block=0, dst=counter, op0=state_ptr, op1=message_ptr
+    );
+
+    // Write the current output to the input state for the next instance.
+    memcpy(blake2s_ptr, vm_output, STATE_SIZE_FELTS);
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+    return blake2s_inner(
+        data=data + INPUT_BLOCK_FELTS,
+        n_bytes=n_bytes - INPUT_BLOCK_BYTES,
+        counter=counter + INPUT_BLOCK_BYTES,
+    );
+}
+
+func blake2s_last_block{range_check_ptr, blake2s_ptr: felt*}(
+    data: felt*, n_bytes: felt, counter: felt
+) -> (output: felt*) {
+    alloc_locals;
+    let state_ptr = blake2s_ptr - STATE_SIZE_FELTS;
+    let message_ptr = blake2s_ptr;
+    let (n_felts, _) = unsigned_div_rem(n_bytes + 3, 4);
+    memcpy(blake2s_ptr, data, n_felts);
+    memset(blake2s_ptr + n_felts, 0, INPUT_BLOCK_FELTS - n_felts);
+    let blake2s_ptr = blake2s_ptr + INPUT_BLOCK_FELTS;
+
+    // Run the blake2s opcode runner on the same inputs and store its output.
+    let vm_output = run_blake2s_opcode(
+        is_last_block=1, dst=counter + n_bytes, op0=state_ptr, op1=message_ptr
+    );
+    let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS;
+
+    return (output=vm_output);
+}
+
+// These functions serialize data to a data array to be used with blake2s().
+// They use the property that each data word is verified by blake2s() to be in range [0, 2 ** 32).
+
+// Serializes a uint256 number in a blake2s compatible way (little-endian).
+func blake2s_add_uint256{data: felt*}(num: Uint256) {
+    let high = num.high;
+    let low = num.low;
+    %{
+        B = 32
+        MASK = 2 ** 32 - 1
+        segments.write_arg(ids.data, [(ids.low >> (B * i)) & MASK for i in range(4)])
+        segments.write_arg(ids.data + 4, [(ids.high >> (B * i)) & MASK for i in range(4)])
+    %}
+    assert data[3] * 2 ** 96 + data[2] * 2 ** 64 + data[1] * 2 ** 32 + data[0] = low;
+    assert data[7] * 2 ** 96 + data[6] * 2 ** 64 + data[5] * 2 ** 32 + data[4] = high;
+    let data = data + 8;
+    return ();
+}
+
+// Serializes a uint256 number in a blake2s compatible way (big-endian).
+func blake2s_add_uint256_bigend{bitwise_ptr: BitwiseBuiltin*, data: felt*}(num: Uint256) {
+    // Reverse byte endianness of 32-bit chunks.
+    tempvar value = num.high;
+    assert bitwise_ptr[0].x = value;
+    assert bitwise_ptr[0].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[0].x_and_y;
+    assert bitwise_ptr[1].x = value;
+    assert bitwise_ptr[1].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[1].x_and_y;
+    tempvar high = value / 2 ** (8 + 16);
+
+    tempvar value = num.low;
+    assert bitwise_ptr[2].x = value;
+    assert bitwise_ptr[2].y = 0x00ff00ff00ff00ff00ff00ff00ff00ff;
+    tempvar value = value + (2 ** 16 - 1) * bitwise_ptr[2].x_and_y;
+    assert bitwise_ptr[3].x = value;
+    assert bitwise_ptr[3].y = 0x00ffff0000ffff0000ffff0000ffff00;
+    tempvar value = value + (2 ** 32 - 1) * bitwise_ptr[3].x_and_y;
+    tempvar low = value / 2 ** (8 + 16);
+
+    let bitwise_ptr = bitwise_ptr + 4 * BitwiseBuiltin.SIZE;
+
+    %{
+        B = 32
+        MASK = 2 ** 32 - 1
+        segments.write_arg(ids.data, [(ids.high >> (B * (3 - i))) & MASK for i in range(4)])
+        segments.write_arg(ids.data + 4, [(ids.low >> (B * (3 - i))) & MASK for i in range(4)])
+    %}
+
+    assert data[0] * 2 ** 96 + data[1] * 2 ** 64 + data[2] * 2 ** 32 + data[3] = high;
+    assert data[4] * 2 ** 96 + data[5] * 2 ** 64 + data[6] * 2 ** 32 + data[7] = low;
+    let data = data + 8;
+    return ();
+}
+
+// Serializes a field element in a blake2s compatible way.
+func blake2s_add_felt{range_check_ptr, bitwise_ptr: BitwiseBuiltin*, data: felt*}(
path: python/cairo-core/src/cairo_core/hash/blake2s.cairo
user: login: ClementWalter
body: again, like felt to bytes4 little ?
created_at: 2025-04-23T09:53:51Z
author_association: MEMBER
path: python/cairo-core/src/cairo_core/hash/blake2s.cairo
user: login: ClementWalter
body: can we isolate the felt/uint256 to bytes4 little/lib to blake2s things
created_at: 2025-04-23T09:54:35Z
author_association: MEMBER
diff_hunk: @@ -11,6 +11,7 @@ from starkware.cairo.common.cairo_builtins import (
     SignatureBuiltin,
     EcOpBuiltin,
 )
+from starkware.cairo.common.alloc import alloc
path: cairo/ethereum/cancun/main.cairo
user: login: ClementWalter
body: ```suggestion
```
created_at: 2025-04-24T10:47:49Z
author_association: MEMBER

PR comments:
user: login: codecov[bot]
created_at: 2025-04-22T16:02:11Z
body: ## [Codecov](https://app.codecov.io/gh/kkrt-labs/keth/pull/1390?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs) Report
Attention: Patch coverage is `75.78692%` with `100 lines` in your changes missing coverage. Please review.
> Project coverage is 81.99%. Comparing base [(`77fb1e2`)](https://app.codecov.io/gh/kkrt-labs/keth/commit/77fb1e2b364c84c827cbaebb0638fbb043ba49e5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs) to head [(`e983585`)](https://app.codecov.io/gh/kkrt-labs/keth/commit/e9835854ee264f71de21d032853eee88624b694d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/kkrt-labs/keth/pull/1390?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs) | Patch % | Lines |
|---|---|---|
| [cairo/ethereum/cancun/state.cairo](https://app.codecov.io/gh/kkrt-labs/keth/pull/1390?src=pr&el=tree&filepath=cairo%2Fethereum%2Fcancun%2Fstate.cairo&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs#diff-Y2Fpcm8vZXRoZXJldW0vY2FuY3VuL3N0YXRlLmNhaXJv) | 50.00% | [99 Missing :warning: ](https://app.codecov.io/gh/kkrt-labs/keth/pull/1390?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs) |
| [cairo/ethereum/cancun/trie.cairo](https://app.codecov.io/gh/kkrt-labs/keth/pull/1390?src=pr&el=tree&filepath=cairo%2Fethereum%2Fcancun%2Ftrie.cairo&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs#diff-Y2Fpcm8vZXRoZXJldW0vY2FuY3VuL3RyaWUuY2Fpcm8=) | 99.00% | [1 Missing :warning: ](https://app.codecov.io/gh/kkrt-labs/keth/pull/1390?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs) |

:x: Your patch status has failed because the patch coverage (75.78%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the [target](https://docs.codecov.com/docs/commit-status#target) coverage.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1390      +/-   ##
==========================================
+ Coverage   80.58%   81.99%   +1.40%     
==========================================
  Files          74       74              
  Lines       20927    21208     +281     
==========================================
+ Hits        16864    17389     +525     
+ Misses       4063     3819     -244     
```

| [Flag](https://app.codecov.io/gh/kkrt-labs/keth/pull/1390/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs) | Coverage Δ | |
|---|---|---|
| [ci-ef-tests](https://app.codecov.io/gh/kkrt-labs/keth/pull/1390/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs) | `78.70% <37.98%> (?)` | |
| [ci-unit](https://app.codecov.io/gh/kkrt-labs/keth/pull/1390/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs) | `76.28% <75.78%> (-4.30%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/kkrt-labs/keth/pull/1390?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kkrt-labs).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: enitrat
created_at: 2025-04-23T15:26:11Z
body: Resolved all convos related to blake2s function utils, mirrored from SW
author_association: MEMBER