Skip to content

Commit d56f4ef

Browse files
committed
zkevm: add ecrecover attack
Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
1 parent c91549c commit d56f4ef

File tree

1 file changed

+66
-12
lines changed

1 file changed

+66
-12
lines changed

tests/zkevm/test_worst_compute.py

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@
1010
import pytest
1111

1212
from ethereum_test_forks import Fork
13-
from ethereum_test_tools import Alloc, Block, BlockchainTestFiller, Environment, Transaction
13+
from ethereum_test_tools import (Alloc, Block, BlockchainTestFiller, Bytecode,
14+
Environment, Transaction)
1415
from ethereum_test_tools.vm.opcode import Opcodes as Op
1516

1617
REFERENCE_SPEC_GIT_PATH = "TODO"
1718
REFERENCE_SPEC_VERSION = "TODO"
1819

1920
MAX_CODE_SIZE = 24 * 1024
2021
KECCAK_RATE = 136
22+
ECRECOVER_GAS_COST = 3_000
2123

2224

2325
@pytest.mark.valid_from("Cancun")
2426
@pytest.mark.parametrize(
2527
"gas_limit",
2628
[
27-
36_000_000,
29+
Environment().gas_limit,
2830
],
2931
)
3032
def test_worst_keccak(
@@ -144,24 +146,60 @@ def test_worst_modexp(
144146
iter_complexity = exp.bit_length() - 1
145147
gas_cost = math.floor((mul_complexity * iter_complexity) / 3)
146148
attack_block = Op.POP(Op.STATICCALL(gas_cost, 0x5, 0, 32 * 6, 0, 0))
149+
code = code_loop_precompile_call(calldata, attack_block)
147150

148-
# The attack contract is: JUMPDEST + [attack_block]* + PUSH0 + JUMP
149-
jumpdest = Op.JUMPDEST
150-
jump_back = Op.JUMP(len(calldata))
151-
max_iters_loop = (MAX_CODE_SIZE - len(calldata) - len(jumpdest) - len(jump_back)) // len(
152-
attack_block
151+
code_address = pre.deploy_contract(code=code)
152+
153+
tx = Transaction(
154+
to=code_address,
155+
gas_limit=gas_limit,
156+
sender=pre.fund_eoa(),
153157
)
154-
code = calldata + jumpdest + sum([attack_block] * max_iters_loop) + jump_back
155-
if len(code) > MAX_CODE_SIZE:
156-
# Must never happen, but keep it as a sanity check.
157-
raise ValueError(f"Code size {len(code)} exceeds maximum code size {MAX_CODE_SIZE}")
158158

159-
code_address = pre.deploy_contract(code=code)
159+
blockchain_test(
160+
env=env,
161+
pre=pre,
162+
post={},
163+
blocks=[Block(txs=[tx])],
164+
)
165+
166+
167+
@pytest.mark.zkevm
168+
@pytest.mark.valid_from("Cancun")
169+
@pytest.mark.parametrize(
170+
"gas_limit",
171+
[
172+
Environment().gas_limit,
173+
],
174+
)
175+
def test_worst_ecrecover(
176+
blockchain_test: BlockchainTestFiller,
177+
pre: Alloc,
178+
fork: Fork,
179+
gas_limit: int,
180+
):
181+
"""Test running a block with as many ECRECOVER calls as possible."""
182+
env = Environment(gas_limit=gas_limit)
183+
184+
# Calldata
185+
calldata = (
186+
Op.MSTORE(0 * 32, 0x38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E)
187+
+ Op.MSTORE(1 * 32, 27)
188+
+ Op.MSTORE(2 * 32, 0x38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E)
189+
+ Op.MSTORE(3 * 32, 0x789D1DD423D25F0772D2748D60F7E4B81BB14D086EBA8E8E8EFB6DCFF8A4AE02)
190+
)
191+
192+
attack_block = Op.STATICCALL(ECRECOVER_GAS_COST, 0x1, 0, 32 * 4, 0, 0) + Op.POP
193+
code = code_loop_precompile_call(calldata, attack_block)
194+
code_address = pre.deploy_contract(code=bytes(code))
160195

161196
tx = Transaction(
162197
to=code_address,
163198
gas_limit=gas_limit,
199+
gas_price=10,
164200
sender=pre.fund_eoa(),
201+
data=[],
202+
value=0,
165203
)
166204

167205
blockchain_test(
@@ -170,3 +208,19 @@ def test_worst_modexp(
170208
post={},
171209
blocks=[Block(txs=[tx])],
172210
)
211+
212+
213+
def code_loop_precompile_call(calldata: Bytecode, attack_block: Bytecode):
214+
"""Create a code loop that calls a precompile with the given calldata."""
215+
# The attack contract is: JUMPDEST + [attack_block]* + PUSH0 + JUMP
216+
jumpdest = Op.JUMPDEST
217+
jump_back = Op.JUMP(len(calldata))
218+
max_iters_loop = (MAX_CODE_SIZE - len(calldata) - len(jumpdest) - len(jump_back)) // len(
219+
attack_block
220+
)
221+
code = calldata + jumpdest + sum([attack_block] * max_iters_loop) + jump_back
222+
if len(code) > MAX_CODE_SIZE:
223+
# Must never happen, but keep it as a sanity check.
224+
raise ValueError(f"Code size {len(code)} exceeds maximum code size {MAX_CODE_SIZE}")
225+
226+
return code

0 commit comments

Comments
 (0)