Skip to main content

ethrex_levm/opcode_handlers/
keccak.rs

1//! # Keccak256 operations
2//!
3//! Includes the following opcodes:
4//!   - `KECCAK256`
5
6use crate::{
7    errors::{OpcodeResult, VMError},
8    gas_cost,
9    memory::calculate_memory_size,
10    opcode_handlers::OpcodeHandler,
11    utils::size_offset_to_usize,
12    vm::VM,
13};
14use ethrex_common::{U256, utils::u256_from_big_endian};
15
16/// `keccak256("")` as a `U256`. Returned directly for zero-length input so we
17/// skip the permutation entirely; the result is a well-known constant
18/// (matches what other clients do).
19const EMPTY_KECCAK_U256: U256 = U256([
20    0x7bfad8045d85a470,
21    0xe500b653ca82273b,
22    0x927e7db2dcc703c0,
23    0xc5d2460186f7233c,
24]);
25
26pub struct OpKeccak256Handler;
27impl OpcodeHandler for OpKeccak256Handler {
28    #[inline(always)]
29    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
30        let [offset, len] = *vm.current_call_frame.stack.pop()?;
31        let (len, offset) = size_offset_to_usize(len, offset)?;
32
33        vm.current_call_frame
34            .increase_consumed_gas(gas_cost::keccak256(
35                calculate_memory_size(offset, len)?,
36                vm.current_call_frame.memory.len(),
37                len,
38            )?)?;
39
40        // Hash the memory range in place — `with_range` lends a borrow to keccak256
41        // instead of allocating a throwaway `Bytes` copy (KECCAK256 fires ~15x/tx).
42        // Bind `crypto` first so the closure doesn't capture `vm` while `memory` is
43        // borrowed mutably.
44        let hash = if len == 0 {
45            EMPTY_KECCAK_U256
46        } else {
47            let crypto = vm.crypto;
48            u256_from_big_endian(&vm.current_call_frame.memory.with_range(
49                offset,
50                len,
51                |bytes| crypto.keccak256(bytes),
52            )?)
53        };
54        vm.current_call_frame.stack.push(hash)?;
55
56        Ok(OpcodeResult::Continue)
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use ethrex_common::constants::EMPTY_KECCAK_HASH;
64    use ethrex_crypto::{Crypto, NativeCrypto};
65
66    #[test]
67    fn empty_keccak_const_matches_hash() {
68        let expected = u256_from_big_endian(&NativeCrypto.keccak256(&[]));
69        assert_eq!(EMPTY_KECCAK_U256, expected);
70    }
71
72    #[test]
73    fn empty_keccak_const_matches_common_constant() {
74        // Guards against drift between this const and `EMPTY_KECCAK_HASH`
75        // in `ethrex_common::constants`.
76        assert_eq!(
77            EMPTY_KECCAK_U256,
78            u256_from_big_endian(EMPTY_KECCAK_HASH.as_bytes())
79        );
80    }
81}