Skip to content

Commit ddf0c89

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

File tree

1 file changed

+69
-10
lines changed

1 file changed

+69
-10
lines changed

tests/zkevm/test_worst_compute.py

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,22 @@
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 (
14+
Alloc,
15+
Block,
16+
BlockchainTestFiller,
17+
Bytecode,
18+
Environment,
19+
Transaction,
20+
)
1421
from ethereum_test_tools.vm.opcode import Opcodes as Op
1522

1623
REFERENCE_SPEC_GIT_PATH = "TODO"
1724
REFERENCE_SPEC_VERSION = "TODO"
1825

1926
MAX_CODE_SIZE = 24 * 1024
2027
KECCAK_RATE = 136
28+
ECRECOVER_GAS_COST = 3_000
2129

2230

2331
@pytest.mark.zkevm
@@ -60,17 +68,53 @@ def test_worst_modexp(
6068
gas_cost = math.floor((mul_complexity * iter_complexity) / 3)
6169
attack_block = Op.STATICCALL(gas_cost, 0x5, 0, 32 * 6, 0, 0) + Op.POP
6270

63-
# The attack contract is: JUMPDEST + [attack_block]* + PUSH0 + JUMP
64-
jumpdest = Op.JUMPDEST
65-
jump_back = Op.JUMP(len(calldata))
66-
max_iters_loop = (MAX_CODE_SIZE - len(calldata) - len(jumpdest) - len(jump_back)) // len(
67-
attack_block
71+
code = code_loop_precompile_call(calldata, attack_block)
72+
code_address = pre.deploy_contract(code=bytes(code))
73+
74+
tx = Transaction(
75+
to=code_address,
76+
gas_limit=gas_limit,
77+
gas_price=10,
78+
sender=pre.fund_eoa(),
79+
data=[],
80+
value=0,
6881
)
69-
code = calldata + jumpdest + sum([attack_block] * max_iters_loop) + jump_back
70-
if len(code) > MAX_CODE_SIZE:
71-
# Must never happen, but keep it as a sanity check.
72-
raise ValueError(f"Code size {len(code)} exceeds maximum code size {MAX_CODE_SIZE}")
7382

83+
blockchain_test(
84+
env=env,
85+
pre=pre,
86+
post={},
87+
blocks=[Block(txs=[tx])],
88+
)
89+
90+
91+
@pytest.mark.zkevm
92+
@pytest.mark.valid_from("Cancun")
93+
@pytest.mark.parametrize(
94+
"gas_limit",
95+
[
96+
36_000_000,
97+
],
98+
)
99+
def test_worst_ecrecover(
100+
blockchain_test: BlockchainTestFiller,
101+
pre: Alloc,
102+
fork: Fork,
103+
gas_limit: int,
104+
):
105+
"""Test running a block with as many ECRECOVER calls as possible."""
106+
env = Environment(gas_limit=gas_limit)
107+
108+
# Calldata
109+
calldata = (
110+
Op.MSTORE(0 * 32, 0x38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E)
111+
+ Op.MSTORE(1 * 32, 27)
112+
+ Op.MSTORE(2 * 32, 0x38D18ACB67D25C8BB9942764B62F18E17054F66A817BD4295423ADF9ED98873E)
113+
+ Op.MSTORE(3 * 32, 0x789D1DD423D25F0772D2748D60F7E4B81BB14D086EBA8E8E8EFB6DCFF8A4AE02)
114+
)
115+
116+
attack_block = Op.STATICCALL(ECRECOVER_GAS_COST, 0x1, 0, 32 * 4, 0, 0) + Op.POP
117+
code = code_loop_precompile_call(calldata, attack_block)
74118
code_address = pre.deploy_contract(code=bytes(code))
75119

76120
tx = Transaction(
@@ -88,3 +132,18 @@ def test_worst_modexp(
88132
post={},
89133
blocks=[Block(txs=[tx])],
90134
)
135+
136+
137+
def code_loop_precompile_call(calldata: Bytecode, attack_block: Bytecode):
138+
# The attack contract is: JUMPDEST + [attack_block]* + PUSH0 + JUMP
139+
jumpdest = Op.JUMPDEST
140+
jump_back = Op.JUMP(len(calldata))
141+
max_iters_loop = (MAX_CODE_SIZE - len(calldata) - len(jumpdest) - len(jump_back)) // len(
142+
attack_block
143+
)
144+
code = calldata + jumpdest + sum([attack_block] * max_iters_loop) + jump_back
145+
if len(code) > MAX_CODE_SIZE:
146+
# Must never happen, but keep it as a sanity check.
147+
raise ValueError(f"Code size {len(code)} exceeds maximum code size {MAX_CODE_SIZE}")
148+
149+
return code

0 commit comments

Comments
 (0)