generated from keep-starknet-strange/alexandria
-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement 0x20 - SHA3 Opcode (#281)
* testing herodotus fct * preclean * cleaned code and tests * merge main * forgot to change gas_limit back to 1k * review correction * added helpers * soluce2 + check on memory load * typos * added internal sha3 tests * added helpers tests * wrong error message * used mod internal instead of trait * functions and comments rework * bug correction due to merge * removed unnecessary computation * quick fix review * corrected tests names * docs: added fn docs * made compute_words more verbose * Made helper fcts on u256trait * modified comments * Ensured memory lenght * added memory size verification to tests --------- Co-authored-by: msaug <[email protected]>
- Loading branch information
Showing
6 changed files
with
603 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,152 @@ | ||
//! SHA3. | ||
|
||
// Internal imports | ||
use evm::context::{ExecutionContextTrait, ExecutionContext}; | ||
use evm::context::{ExecutionContext, ExecutionContextTrait, BoxDynamicExecutionContextDestruct}; | ||
use evm::stack::StackTrait; | ||
use evm::memory::MemoryTrait; | ||
use evm::errors::EVMError; | ||
use evm::helpers::U256IntoResultU32; | ||
use keccak::{cairo_keccak, u128_split}; | ||
use utils::helpers::{ArrayExtensionTrait, U256Trait}; | ||
|
||
/// SHA3 operation. | ||
/// Hashes n bytes in memory at a given offset in memory. | ||
/// # Specification: https://www.evm.codes/#20?fork=shanghai | ||
fn exec_sha3(ref context: ExecutionContext) {} | ||
use array::ArrayTrait; | ||
|
||
#[generate_trait] | ||
impl Sha3Impl of Sha3Trait { | ||
/// SHA3 operation : Hashes n bytes in memory at a given offset in memory | ||
/// and push the hash result to the stack. | ||
/// | ||
/// # Inputs | ||
/// * `offset` - The offset in memory where to read the datas | ||
/// * `size` - The amount of bytes to read | ||
/// | ||
/// # Specification: https://www.evm.codes/#20?fork=shanghai | ||
fn exec_sha3(ref self: ExecutionContext) -> Result<(), EVMError> { | ||
let offset: usize = self.stack.pop_usize()?; | ||
let mut size: usize = self.stack.pop_usize()?; | ||
|
||
let mut to_hash: Array<u64> = Default::default(); | ||
|
||
let (nb_words, nb_zeroes) = internal::compute_memory_words_amount( | ||
size, offset, self.memory.bytes_len | ||
); | ||
let mut last_input_offset = internal::fill_array_with_memory_words( | ||
ref self, ref to_hash, offset, nb_words | ||
); | ||
// Fill array to hash with zeroes for bytes out of memory bound | ||
// which is faster than reading them from memory | ||
to_hash.append_n(0, 4 * nb_zeroes); | ||
|
||
// For cases where the size of bytes to hash isn't a multiple of 8, | ||
// prepare the last bytes to hash into last_input instead of appending | ||
// it to to_hash. | ||
let last_input: u64 = if (size % 32 != 0) { | ||
let loaded = self.memory.load(last_input_offset); | ||
internal::prepare_last_input(ref to_hash, loaded, size % 32) | ||
} else { | ||
0 | ||
}; | ||
// Properly set the memory length in case we skipped reading zeroes | ||
self.memory.ensure_length(size + offset); | ||
|
||
let mut hash = cairo_keccak(ref to_hash, last_input, size % 8); | ||
self.stack.push(hash.reverse_endianness()) | ||
} | ||
} | ||
|
||
|
||
mod internal { | ||
use evm::context::{ExecutionContext, ExecutionContextTrait, BoxDynamicExecutionContextDestruct}; | ||
use evm::stack::StackTrait; | ||
use evm::memory::MemoryTrait; | ||
use utils::helpers::U256Trait; | ||
|
||
/// Computes how many words are read from the memory | ||
/// and how many words must be filled with zeroes | ||
/// given a target size, a memory offset and the length of the memory. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `size` - The amount of bytes to hash | ||
/// * `offset` - Offset in memory | ||
/// * `mem_len` - Size of the memory | ||
/// Returns : (nb_words, nb_zeroes) | ||
fn compute_memory_words_amount(size: u32, offset: u32, mem_len: u32) -> (u32, u32) { | ||
// Bytes to hash are less than a word size | ||
if size < 32 { | ||
return (0, 0); | ||
} | ||
// Bytes out of memory bound are zeroes | ||
if offset > mem_len { | ||
return (0, size / 32); | ||
} | ||
// The only word to read from memory is less than 32 bytes | ||
if mem_len - offset < 32 { | ||
return (1, (size / 32) - 1); | ||
} | ||
|
||
let bytes_to_read = cmp::min(mem_len - offset, size); | ||
let nb_words = bytes_to_read / 32; | ||
(nb_words, (size / 32) - nb_words) | ||
} | ||
|
||
/// Fills the `to_hash` array with little endian u64s | ||
/// by splitting words read from the memory and | ||
/// returns the next offset to read from. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `self` - The context in which the memory is read | ||
/// * `to_hash` - A reference to the array to fill | ||
/// * `offset` - Offset in memory to start reading from | ||
/// * `amount` - The amount of words to read from memory | ||
/// Return the new offset | ||
fn fill_array_with_memory_words( | ||
ref self: ExecutionContext, ref to_hash: Array<u64>, mut offset: u32, mut amount: u32 | ||
) -> u32 { | ||
loop { | ||
if amount == 0 { | ||
break; | ||
} | ||
let loaded = self.memory.load(offset); | ||
let ((high_h, low_h), (high_l, low_l)) = loaded.split_into_u64_le(); | ||
to_hash.append(low_h); | ||
to_hash.append(high_h); | ||
to_hash.append(low_l); | ||
to_hash.append(high_l); | ||
|
||
offset += 32; | ||
amount -= 1; | ||
}; | ||
offset | ||
} | ||
|
||
/// Fills the `to_hash` array with the n-1 remaining little endian u64 | ||
/// depending on size from a word and returns | ||
/// the u64 containing the last 8 bytes word to hash. | ||
/// | ||
/// # Arguments | ||
/// | ||
/// * `to_hash` - A reference to the array to fill | ||
/// * `value` - The word to split in u64 words | ||
/// * `size` - The amount of bytes still required to hash | ||
/// Returns the last u64 word that isn't 8 Bytes long. | ||
fn prepare_last_input(ref to_hash: Array<u64>, value: u256, size: u32) -> u64 { | ||
let ((high_h, low_h), (high_l, low_l)) = value.split_into_u64_le(); | ||
if size < 8 { | ||
return low_h; | ||
} else if size < 16 { | ||
to_hash.append(low_h); | ||
return high_h; | ||
} else if size < 24 { | ||
to_hash.append(low_h); | ||
to_hash.append(high_h); | ||
return low_l; | ||
} else { | ||
to_hash.append(low_h); | ||
to_hash.append(high_h); | ||
to_hash.append(low_l); | ||
return high_l; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.