Skip to content

Commit 60a4cf2

Browse files
authored
feat(tests): tests for ecrecover precompile (#1685)
1 parent 0008dce commit 60a4cf2

File tree

2 files changed

+283
-0
lines changed

2 files changed

+283
-0
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Test fixtures for use by clients are available for each release on the [Github r
2727
- ✨ Expand EIP-6110 modified contract tests, where the extra event log has no topics at all ([#1693](https://github.com/ethereum/execution-specs/pull/1693)).
2828
- ✨ Add a CREATE/2 test cases for when it runs OOG on code deposit ([#1705](https://github.com/ethereum/execution-specs/pull/1705)).
2929
- ✨ Expand cases to test *CALL opcodes causing OOG ([#1703](https://github.com/ethereum/execution-specs/pull/1703)).
30+
- ✨ Add `ecrecover` precompile tests originating form `evmone` unittests ([#1685](https://github.com/ethereum/execution-specs/pull/1685))
3031

3132
## [v5.3.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v5.3.0) - 2025-10-09
3233

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
"""Tests ecrecover precompiled contract."""
2+
3+
import pytest
4+
from execution_testing import (
5+
Account,
6+
Alloc,
7+
Environment,
8+
StateTestFiller,
9+
Transaction,
10+
)
11+
from execution_testing.forks.forks.forks import Byzantium
12+
from execution_testing.forks.helpers import Fork
13+
from execution_testing.vm import Opcodes as Op
14+
15+
16+
@pytest.mark.valid_from("Frontier")
17+
@pytest.mark.parametrize(
18+
"msg_hash, v, r, s, output",
19+
[
20+
pytest.param(
21+
bytes.fromhex(
22+
"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c"
23+
),
24+
bytes.fromhex(
25+
"000000000000000000000000000000000000000000000000000000000000001c"
26+
),
27+
bytes.fromhex(
28+
"73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f"
29+
),
30+
bytes.fromhex(
31+
"eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"
32+
),
33+
bytes.fromhex(
34+
"000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b"
35+
),
36+
id="valid_signature_1",
37+
),
38+
pytest.param(
39+
bytes.fromhex(
40+
"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c"
41+
),
42+
bytes.fromhex(
43+
"000000000000000000000000000000000000000000000000000000000000001b"
44+
),
45+
bytes.fromhex(
46+
"7af9e73057870458f03c143483bc5fcb6f39d01c9b26d28ed9f3fe23714f6628"
47+
),
48+
bytes.fromhex(
49+
"3134a4ba8fafe11b351a720538398a5635e235c0b3258dce19942000731079ec"
50+
),
51+
bytes.fromhex(
52+
"0000000000000000000000009a04aede774152f135315670f562c19c5726df2c"
53+
),
54+
id="valid_signature_2",
55+
),
56+
# z >= Order
57+
pytest.param(
58+
bytes.fromhex(
59+
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"
60+
),
61+
bytes.fromhex(
62+
"000000000000000000000000000000000000000000000000000000000000001b"
63+
),
64+
bytes.fromhex(
65+
"7af9e73057870458f03c143483bc5fcb6f39d01c9b26d28ed9f3fe23714f6628"
66+
),
67+
bytes.fromhex(
68+
"3134a4ba8fafe11b351a720538398a5635e235c0b3258dce19942000731079ec"
69+
),
70+
bytes.fromhex(
71+
"000000000000000000000000b32cf3c8616537a28583fc00d29a3e8c9614cd61"
72+
),
73+
id="z_gte_order",
74+
),
75+
pytest.param(
76+
bytes.fromhex(
77+
"6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9"
78+
),
79+
bytes.fromhex(
80+
"000000000000000000000000000000000000000000000000000000000000001b"
81+
),
82+
bytes.fromhex(
83+
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
84+
),
85+
bytes.fromhex(
86+
"6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9"
87+
),
88+
b"",
89+
id="invalid_signature_1",
90+
),
91+
pytest.param(
92+
bytes.fromhex(
93+
"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c"
94+
),
95+
bytes.fromhex(
96+
"000000000000000000000000000000000000000000000000000000000000001c"
97+
),
98+
bytes.fromhex(
99+
"0000000000000000000000000000000000000000000000000000000000000000"
100+
),
101+
bytes.fromhex(
102+
"eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"
103+
),
104+
b"",
105+
id="invalid_signature_2",
106+
),
107+
pytest.param(
108+
bytes.fromhex(
109+
"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c"
110+
),
111+
bytes.fromhex(
112+
"000000000000000000000000000000000000000000000000000000000000001c"
113+
),
114+
bytes.fromhex(
115+
"73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f"
116+
),
117+
bytes.fromhex(
118+
"0000000000000000000000000000000000000000000000000000000000000000"
119+
),
120+
b"",
121+
id="invalid_signature_3",
122+
),
123+
# r >= Order
124+
pytest.param(
125+
bytes.fromhex(
126+
"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c"
127+
),
128+
bytes.fromhex(
129+
"000000000000000000000000000000000000000000000000000000000000001c"
130+
),
131+
bytes.fromhex(
132+
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"
133+
),
134+
bytes.fromhex(
135+
"eeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549"
136+
),
137+
b"",
138+
id="invalid_signature_3",
139+
),
140+
# s >= Order
141+
pytest.param(
142+
bytes.fromhex(
143+
"18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c"
144+
),
145+
bytes.fromhex(
146+
"000000000000000000000000000000000000000000000000000000000000001c"
147+
),
148+
bytes.fromhex(
149+
"73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f"
150+
),
151+
bytes.fromhex(
152+
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"
153+
),
154+
b"",
155+
id="s_gte_order",
156+
),
157+
# u1 == u2 && R == G
158+
pytest.param(
159+
bytes.fromhex(
160+
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
161+
),
162+
bytes.fromhex(
163+
"000000000000000000000000000000000000000000000000000000000000001b"
164+
),
165+
bytes.fromhex(
166+
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
167+
),
168+
bytes.fromhex(
169+
"3a2db9fe7908dcc36d81824d2338fc3dd5ae2692e4c6790043d7868872b09cd1"
170+
),
171+
bytes.fromhex(
172+
"0000000000000000000000002e4db28b1f03ec8acfc2865e0c08308730e7ddf2"
173+
),
174+
id="u1_eq_u2_R_eq_G",
175+
),
176+
# u1 == -u2 && R == -G
177+
pytest.param(
178+
bytes.fromhex(
179+
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
180+
),
181+
bytes.fromhex(
182+
"000000000000000000000000000000000000000000000000000000000000001c"
183+
),
184+
bytes.fromhex(
185+
"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
186+
),
187+
bytes.fromhex(
188+
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
189+
),
190+
bytes.fromhex(
191+
"0000000000000000000000002e4db28b1f03ec8acfc2865e0c08308730e7ddf2"
192+
),
193+
id="u1_eq_neg_u2_R_eq_neg_G",
194+
),
195+
# 13u1 == u2 && R == -13G
196+
pytest.param(
197+
bytes.fromhex(
198+
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
199+
),
200+
bytes.fromhex(
201+
"000000000000000000000000000000000000000000000000000000000000001b"
202+
),
203+
bytes.fromhex(
204+
"f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8"
205+
),
206+
bytes.fromhex(
207+
"533e9827446324ac92450a05ef04622bc0081f8d5b394e4d7b514ed35c946ee9"
208+
),
209+
b"",
210+
id="13u1_eq_u2_R_eq_neg_13G",
211+
),
212+
# 13u1 == u2 && R == 13G
213+
pytest.param(
214+
bytes.fromhex(
215+
"c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
216+
),
217+
bytes.fromhex(
218+
"000000000000000000000000000000000000000000000000000000000000001c"
219+
),
220+
bytes.fromhex(
221+
"f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8"
222+
),
223+
bytes.fromhex(
224+
"533e9827446324ac92450a05ef04622bc0081f8d5b394e4d7b514ed35c946ee9"
225+
),
226+
bytes.fromhex(
227+
"000000000000000000000000fc4b7e97f115ac81f9a6997254892b45e8159d46"
228+
),
229+
id="13u1_eq_u2_R_eq_13G",
230+
),
231+
],
232+
)
233+
def test_precompiles(
234+
state_test: StateTestFiller,
235+
pre: Alloc,
236+
fork: Fork,
237+
msg_hash: bytes,
238+
v: bytes,
239+
r: bytes,
240+
s: bytes,
241+
output: bytes,
242+
) -> None:
243+
"""
244+
Tests the behavior of `ecrecover` precompiled contract.
245+
"""
246+
env = Environment()
247+
248+
# Memory
249+
hash_offset = 0
250+
v_offset = 32
251+
r_offset = 64
252+
s_offset = 96
253+
ret_offset = 128
254+
255+
account = pre.deploy_contract(
256+
Op.MSTORE(hash_offset, msg_hash)
257+
+ Op.MSTORE(v_offset, v)
258+
+ Op.MSTORE(r_offset, r)
259+
+ Op.MSTORE(s_offset, s)
260+
+ Op.CALL(
261+
gas=50_000,
262+
address="0x01", # ecrecover precompile address
263+
args_offset=hash_offset,
264+
args_size=4 * 32,
265+
ret_offset=ret_offset,
266+
ret_size=32,
267+
)
268+
+ Op.SSTORE(0, Op.MLOAD(ret_offset))
269+
+ Op.STOP,
270+
storage={0: 0xDEADBEEF},
271+
)
272+
273+
tx = Transaction(
274+
to=account,
275+
sender=pre.fund_eoa(),
276+
gas_limit=1_000_000,
277+
protected=fork >= Byzantium,
278+
)
279+
280+
post = {account: Account(storage={0: output})}
281+
282+
state_test(env=env, pre=pre, post=post, tx=tx)

0 commit comments

Comments
 (0)