diff --git a/listings/ch02-advanced-concepts/hash_solidity_compatible/.gitignore b/listings/ch02-advanced-concepts/hash_solidity_compatible/.gitignore new file mode 100644 index 00000000..1de56593 --- /dev/null +++ b/listings/ch02-advanced-concepts/hash_solidity_compatible/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/listings/ch02-advanced-concepts/hash_solidity_compatible/Scarb.lock b/listings/ch02-advanced-concepts/hash_solidity_compatible/Scarb.lock new file mode 100644 index 00000000..195fb297 --- /dev/null +++ b/listings/ch02-advanced-concepts/hash_solidity_compatible/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "hash_solidity_compatible" +version = "0.1.0" diff --git a/listings/ch02-advanced-concepts/hash_solidity_compatible/Scarb.toml b/listings/ch02-advanced-concepts/hash_solidity_compatible/Scarb.toml new file mode 100644 index 00000000..35d7a5fc --- /dev/null +++ b/listings/ch02-advanced-concepts/hash_solidity_compatible/Scarb.toml @@ -0,0 +1,8 @@ +[package] +name = "hash_solidity_compatible" +version = "0.1.0" + +[dependencies] +starknet = ">=2.3.0" + +[[target.starknet-contract]] \ No newline at end of file diff --git a/listings/ch02-advanced-concepts/hash_solidity_compatible/src/contract.cairo b/listings/ch02-advanced-concepts/hash_solidity_compatible/src/contract.cairo new file mode 100644 index 00000000..5f6f8d24 --- /dev/null +++ b/listings/ch02-advanced-concepts/hash_solidity_compatible/src/contract.cairo @@ -0,0 +1,34 @@ +#[starknet::interface] +trait ISolidityHashExample { + fn hash_data(ref self: TContractState, input_data: Span) -> u256; +} + + +#[starknet::contract] +mod SolidityHashExample { + use keccak::{keccak_u256s_be_inputs}; + use array::Span; + + #[storage] + struct Storage {} + + #[abi(embed_v0)] + impl SolidityHashExample of super::ISolidityHashExample { + fn hash_data(ref self: ContractState, input_data: Span) -> u256 { + let hashed = keccak_u256s_be_inputs(input_data); + + // Split the hashed value into two 128-bit segments + let low: u128 = hashed.low; + let high: u128 = hashed.high; + + // Reverse each 128-bit segment + let reversed_low = integer::u128_byte_reverse(low); + let reversed_high = integer::u128_byte_reverse(high); + + // Reverse merge the reversed segments back into a u256 value + let compatible_hash = u256 { low: reversed_high, high: reversed_low }; + + compatible_hash + } + } +} diff --git a/listings/ch02-advanced-concepts/hash_solidity_compatible/src/lib.cairo b/listings/ch02-advanced-concepts/hash_solidity_compatible/src/lib.cairo new file mode 100644 index 00000000..11ada17a --- /dev/null +++ b/listings/ch02-advanced-concepts/hash_solidity_compatible/src/lib.cairo @@ -0,0 +1,4 @@ +mod contract; + +#[cfg(test)] +mod tests; diff --git a/listings/ch02-advanced-concepts/hash_solidity_compatible/src/tests.cairo b/listings/ch02-advanced-concepts/hash_solidity_compatible/src/tests.cairo new file mode 100644 index 00000000..d1d1726b --- /dev/null +++ b/listings/ch02-advanced-concepts/hash_solidity_compatible/src/tests.cairo @@ -0,0 +1,30 @@ +mod tests { + use hash_solidity_compatible::{contract::{SolidityHashExample, ISolidityHashExample}}; + use debug::PrintTrait; + + use starknet::{ + ContractAddress, get_contract_address, contract_address_const, call_contract_syscall, + testing::{set_contract_address} + }; + + fn setup() -> SolidityHashExample::ContractState { + let mut state = SolidityHashExample::contract_state_for_testing(); + let contract_address = contract_address_const::<0x1>(); + set_contract_address(contract_address); + state + } + + #[test] + #[available_gas(2000000000)] + fn get_same_hash_solidity() { + let mut state = setup(); + let mut array: Array = ArrayTrait::new(); + array.append(1); + + let hash_expected: u256 = + 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6; + let hash_received: u256 = state.hash_data(array.span()); + + assert(hash_received == hash_expected, 'hash_received != hash_expected'); + } +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 85a76907..e47987d4 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -42,5 +42,6 @@ Summary - [Writing to any storage slot](./ch02/write_to_any_slot.md) - [Storing Arrays](./ch02/storing_arrays.md) - [Struct as mapping key](./ch02/struct-mapping-key.md) + - [Hash Solidity Compatible](./ch02/hash-solidity-compatible.md) - [Optimisations](./ch02/optimisations/optimisations.md) - [Storage Optimisations](./ch02/optimisations/store_using_packing.md) diff --git a/src/ch02/hash-solidity-compatible.md b/src/ch02/hash-solidity-compatible.md new file mode 100644 index 00000000..68f3a2ec --- /dev/null +++ b/src/ch02/hash-solidity-compatible.md @@ -0,0 +1,11 @@ +# Hash Solidity Compatible + +This contract demonstrates Keccak hashing in Cairo to match Solidity's keccak256. While both use Keccak, their endianness differs: Cairo is little-endian, Solidity big-endian. The contract achieves compatibility by hashing in big-endian using `keccak_u256s_be_inputs`, and reversing the bytes of the result with `u128_byte_reverse`. + +For example: + +```rust +{{#include ../../listings/ch02-advanced-concepts/hash_solidity_compatible/src/contract.cairo}} +``` + +Play with the contract in [Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch02-advanced-concepts/hash_solidity_compatible/src/contract.cairo).