Skip to content

Commit 22612e3

Browse files
committed
zkevm: add worst case for keccak
Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
1 parent 1c2e150 commit 22612e3

File tree

2 files changed

+109
-1
lines changed

2 files changed

+109
-1
lines changed

tests/zkevm/test_worst_bytecode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
abstract: Tests for zkEVMs
33
Tests for zkEVMs.
44
5-
Tests for zkEVMs worst-cases scenarios.
5+
Tests for zkEVMs worst-cases bytecode scenarios.
66
"""
77

88
import pytest

tests/zkevm/test_worst_compute.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""
2+
abstract: Tests zkEVMs worst-case compute scenarios.
3+
Tests zkEVMs worst-case compute scenarios.
4+
5+
Tests running worst-case compute opcodes and precompile scenarios for zkEVMs.
6+
"""
7+
8+
import math
9+
10+
import pytest
11+
12+
from ethereum_test_forks import Fork
13+
from ethereum_test_tools import Alloc, Block, BlockchainTestFiller, Environment, Transaction
14+
from ethereum_test_tools.vm.opcode import Opcodes as Op
15+
16+
REFERENCE_SPEC_GIT_PATH = "TODO"
17+
REFERENCE_SPEC_VERSION = "TODO"
18+
19+
MAX_CODE_SIZE = 24 * 1024
20+
KECCAK_RATE = 136
21+
22+
23+
@pytest.mark.zkevm
24+
@pytest.mark.valid_from("Cancun")
25+
@pytest.mark.parametrize(
26+
"gas_limit",
27+
[
28+
36_000_000,
29+
60_000_000,
30+
100_000_000,
31+
300_000_000,
32+
],
33+
)
34+
def test_worst_keccak(
35+
blockchain_test: BlockchainTestFiller,
36+
pre: Alloc,
37+
fork: Fork,
38+
gas_limit: int,
39+
):
40+
"""Test running a block with as many KECCAK256 permutations as possible."""
41+
env = Environment(gas_limit=gas_limit)
42+
43+
# Intrisic gas cost is paid once.
44+
tx_intrinsic_gas = 21_000
45+
available_gas = gas_limit - tx_intrinsic_gas
46+
47+
gsc = fork.gas_costs()
48+
mem_exp_gas_calculator = fork.memory_expansion_gas_calculator()
49+
50+
# Discover the optimal input size to maximize keccak-permutations, not keccak calls.
51+
# The complication of the discovery arises from the non-linear gas cost of memory expansion.
52+
max_keccak_perm_per_block = 0
53+
optimal_input_length = 0
54+
for i in range(1, 1_000_000, 32):
55+
iteration_gas_cost = (
56+
2 * gsc.G_VERY_LOW # PUSHN + PUSH1
57+
+ gsc.G_KECCAK_256 # KECCAK256 static cost
58+
+ math.ceil(i / 32) * gsc.G_KECCAK_256_WORD # KECCAK256 dynamic cost
59+
+ gsc.G_BASE # POP
60+
)
61+
available_gas_after_expansion = max(0, available_gas - mem_exp_gas_calculator(new_bytes=i))
62+
num_keccak_calls = available_gas_after_expansion // iteration_gas_cost
63+
num_keccak_permutations = num_keccak_calls * math.ceil(i / KECCAK_RATE)
64+
65+
if num_keccak_permutations > max_keccak_perm_per_block:
66+
max_keccak_perm_per_block = num_keccak_permutations
67+
optimal_input_length = i
68+
69+
# max_iters_loop contains how many keccak calls can be done per loop.
70+
# The loop is as big as possible bounded by the maximum code size.
71+
#
72+
# The loop structure is: JUMPDEST + [attack iteration] + PUSH0 + JUMP
73+
#
74+
# Now calculate available gas for [attack iteration]:
75+
# Numerator = MAX_CODE_SIZE-3. The -3 is for the JUMPDEST, PUSH0 and JUMP.
76+
# Denominator = (PUSHN + PUSH1 + KECCAK256 + POP) + PUSH1_DATA + PUSHN_DATA
77+
# TODO: the testing framework uses PUSH1(0) instead of PUSH0 which is suboptimal for the attack,
78+
# whenever this is fixed adjust accordingly.
79+
max_iters_loop = (MAX_CODE_SIZE - 3) // (4 + 1 + (optimal_input_length.bit_length() + 7) // 8)
80+
code = (
81+
Op.JUMPDEST
82+
+ sum([Op.SHA3(0, optimal_input_length) + Op.POP] * max_iters_loop)
83+
+ Op.PUSH0
84+
+ Op.JUMP
85+
)
86+
if len(code) > MAX_CODE_SIZE:
87+
# Must never happen, but keep it as a sanity check.
88+
raise ValueError(
89+
f"Code size {len(code)} exceeds maximum code size {MAX_CODE_SIZE} {bytes(sum([Op.SHA3(0, optimal_input_length) + Op.POP] * 1))}"
90+
)
91+
92+
code_address = pre.deploy_contract(code=bytes(code))
93+
94+
tx = Transaction(
95+
to=code_address,
96+
gas_limit=gas_limit,
97+
gas_price=10,
98+
sender=pre.fund_eoa(),
99+
data=[],
100+
value=0,
101+
)
102+
103+
blockchain_test(
104+
env=env,
105+
pre=pre,
106+
post={},
107+
blocks=[Block(txs=[tx])],
108+
)

0 commit comments

Comments
 (0)