Skip to content

Commit 275d66a

Browse files
LouisTsai-CsieSamWilsn
authored andcommitted
feat: implement eip-7951
1 parent 4708376 commit 275d66a

File tree

7 files changed

+204
-1
lines changed

7 files changed

+204
-1
lines changed

setup.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,13 @@ package_dir =
129129

130130
python_requires = >=3.11
131131
install_requires =
132-
pycryptodome>=3,<4
132+
pycryptodome>=3.22.0,<4
133133
coincurve>=20,<21
134134
typing_extensions>=4.4
135135
py-ecc>=8.0.0b2,<9
136136
ethereum-types>=0.2.1,<0.3
137137
ethereum-rlp>=0.1.4,<0.2
138+
cryptography>=45.0.1,<46
138139

139140
[options.package_data]
140141
ethereum =

src/ethereum/crypto/elliptic_curve.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
"""
55

66
import coincurve
7+
from Crypto.Util.asn1 import DerSequence
8+
from cryptography.hazmat.backends import default_backend
9+
from cryptography.hazmat.primitives import hashes
10+
from cryptography.hazmat.primitives.asymmetric import ec
11+
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
712
from ethereum_types.bytes import Bytes
813
from ethereum_types.numeric import U256
914

@@ -71,3 +76,91 @@ def secp256k1_recover(r: U256, s: U256, v: U256, msg_hash: Hash32) -> Bytes:
7176

7277
public_key = public_key.format(compressed=False)[1:]
7378
return public_key
79+
80+
81+
SECP256R1N = U256(
82+
0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551
83+
)
84+
SECP256R1P = U256(
85+
0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF
86+
)
87+
SECP256R1A = U256(
88+
0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC
89+
)
90+
SECP256R1B = U256(
91+
0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B
92+
)
93+
94+
95+
def secp256r1_verify(
96+
r: U256, s: U256, x: U256, y: U256, msg_hash: Hash32
97+
) -> None:
98+
"""
99+
Verifies a P-256 signature.
100+
Parameters
101+
----------
102+
r :
103+
the `r` component of the signature
104+
s :
105+
the `s` component of the signature
106+
x:
107+
the `x` coordinate of the public key
108+
y:
109+
the `y` coordinate of the public key
110+
msg_hash :
111+
Hash of the message being recovered.
112+
Returns
113+
-------
114+
result : `ethereum.base_types.Bytes`
115+
return 1 if the signature is valid, empty bytes otherwise
116+
"""
117+
# Convert U256 to regular integers for DerSequence
118+
r_int = int(r)
119+
s_int = int(s)
120+
x_int = int(x)
121+
y_int = int(y)
122+
123+
sig = DerSequence([r_int, s_int]).encode()
124+
125+
pubnum = ec.EllipticCurvePublicNumbers(x_int, y_int, ec.SECP256R1())
126+
pubkey = pubnum.public_key(default_backend())
127+
pubkey.verify(sig, msg_hash, ec.ECDSA(Prehashed(hashes.SHA256())))
128+
129+
return
130+
131+
132+
def is_on_curve_secp256r1(x: U256, y: U256) -> bool:
133+
"""
134+
Checks if a point is on the secp256r1 curve.
135+
136+
The point (x, y) must satisfy the curve equation:
137+
y^2 ≡ x^3 + a*x + b (mod p)
138+
139+
Parameters
140+
----------
141+
x : U256
142+
The x-coordinate of the point
143+
y : U256
144+
The y-coordinate of the point
145+
146+
Returns
147+
-------
148+
bool
149+
True if the point is on the curve, False otherwise
150+
"""
151+
# Convert U256 to int for calculations
152+
x_int = int(x)
153+
y_int = int(y)
154+
p_int = int(SECP256R1P)
155+
a_int = int(SECP256R1A)
156+
b_int = int(SECP256R1B)
157+
158+
# Calculate y^2 mod p
159+
y_squared = (y_int * y_int) % p_int
160+
161+
# Calculate x^3 + ax + b mod p
162+
x_cubed = (x_int * x_int * x_int) % p_int
163+
ax = (a_int * x_int) % p_int
164+
right_side = (x_cubed + ax + b_int) % p_int
165+
166+
return y_squared == right_side

src/ethereum/osaka/vm/gas.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
GAS_SELF_DESTRUCT = Uint(5000)
5454
GAS_SELF_DESTRUCT_NEW_ACCOUNT = Uint(25000)
5555
GAS_ECRECOVER = Uint(3000)
56+
GAS_P256VERIFY = Uint(3450)
5657
GAS_SHA256 = Uint(60)
5758
GAS_SHA256_WORD = Uint(12)
5859
GAS_RIPEMD160 = Uint(600)

src/ethereum/osaka/vm/precompiled_contracts/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"BLS12_PAIRING_ADDRESS",
3434
"BLS12_MAP_FP_TO_G1_ADDRESS",
3535
"BLS12_MAP_FP2_TO_G2_ADDRESS",
36+
"P256VERIFY_ADDRESS",
3637
)
3738

3839
ECRECOVER_ADDRESS = hex_to_address("0x01")
@@ -52,3 +53,4 @@
5253
BLS12_PAIRING_ADDRESS = hex_to_address("0x0f")
5354
BLS12_MAP_FP_TO_G1_ADDRESS = hex_to_address("0x10")
5455
BLS12_MAP_FP2_TO_G2_ADDRESS = hex_to_address("0x11")
56+
P256VERIFY_ADDRESS = hex_to_address("0x100")

src/ethereum/osaka/vm/precompiled_contracts/mapping.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
ECRECOVER_ADDRESS,
3030
IDENTITY_ADDRESS,
3131
MODEXP_ADDRESS,
32+
P256VERIFY_ADDRESS,
3233
POINT_EVALUATION_ADDRESS,
3334
RIPEMD160_ADDRESS,
3435
SHA256_ADDRESS,
@@ -49,6 +50,7 @@
4950
from .ecrecover import ecrecover
5051
from .identity import identity
5152
from .modexp import modexp
53+
from .p256verify import p256verify
5254
from .point_evaluation import point_evaluation
5355
from .ripemd160 import ripemd160
5456
from .sha256 import sha256
@@ -71,4 +73,5 @@
7173
BLS12_PAIRING_ADDRESS: bls12_pairing,
7274
BLS12_MAP_FP_TO_G1_ADDRESS: bls12_map_fp_to_g1,
7375
BLS12_MAP_FP2_TO_G2_ADDRESS: bls12_map_fp2_to_g2,
76+
P256VERIFY_ADDRESS: p256verify,
7477
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""
2+
Ethereum Virtual Machine (EVM) P256VERIFY PRECOMPILED CONTRACT
3+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
.. contents:: Table of Contents
5+
:backlinks: none
6+
:local:
7+
Introduction
8+
------------
9+
Implementation of the P256VERIFY precompiled contract.
10+
"""
11+
from ethereum_types.numeric import U256
12+
13+
from ethereum.crypto.elliptic_curve import (
14+
SECP256R1N,
15+
SECP256R1P,
16+
is_on_curve_secp256r1,
17+
secp256r1_verify,
18+
)
19+
from ethereum.crypto.hash import Hash32
20+
from ethereum.utils.byte import left_pad_zero_bytes
21+
22+
from ...vm import Evm
23+
from ...vm.gas import GAS_P256VERIFY, charge_gas
24+
from ...vm.memory import buffer_read
25+
26+
27+
def p256verify(evm: Evm) -> None:
28+
"""
29+
Verifies a P-256 signature.
30+
Parameters
31+
----------
32+
evm :
33+
The current EVM frame.
34+
"""
35+
data = evm.message.data
36+
37+
# GAS
38+
charge_gas(evm, GAS_P256VERIFY)
39+
40+
if len(data) != 160:
41+
return
42+
43+
# OPERATION
44+
message_hash_bytes = buffer_read(data, U256(0), U256(32))
45+
message_hash = Hash32(message_hash_bytes)
46+
r = U256.from_be_bytes(buffer_read(data, U256(32), U256(32)))
47+
s = U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))
48+
public_key_x = U256.from_be_bytes(
49+
buffer_read(data, U256(96), U256(32))
50+
) # qx
51+
public_key_y = U256.from_be_bytes(
52+
buffer_read(data, U256(128), U256(32))
53+
) # qy
54+
55+
# Signature component bounds:
56+
# Both r and s MUST satisfy 0 < r < n and 0 < s < n
57+
if r <= U256(0) or r >= SECP256R1N:
58+
return
59+
if s <= U256(0) or s >= SECP256R1N:
60+
return
61+
62+
# Public key bounds:
63+
# Both qx and qy MUST satisfy 0 ≤ qx < p and 0 ≤ qy < p
64+
if public_key_x < U256(0) or public_key_x >= SECP256R1P:
65+
return
66+
if public_key_y < U256(0) or public_key_y >= SECP256R1P:
67+
return
68+
69+
# Point validity: The point (qx, qy) MUST satisfy the curve equation
70+
# qy^2 ≡ qx^3 + a*qx + b (mod p)
71+
if not is_on_curve_secp256r1(public_key_x, public_key_y):
72+
return
73+
74+
# Point should not be at infinity (represented as (0, 0))
75+
if public_key_x == U256(0) and public_key_y == U256(0):
76+
return
77+
78+
success_return_value = left_pad_zero_bytes(b"\x01", 32)
79+
80+
try:
81+
secp256r1_verify(r, s, public_key_x, public_key_y, message_hash)
82+
except Exception:
83+
return
84+
85+
evm.output = success_return_value

whitelist.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,4 +476,22 @@ blockchain
476476
listdir
477477
precompiles
478478

479+
asn1
480+
backends
481+
Der
482+
ECDSA
483+
Prehashed
484+
pubnum
485+
P256VERIFY
486+
p256verify
487+
qx
488+
qy
489+
SECP256R1
490+
SECP256R1A
491+
SECP256R1B
492+
SECP256R1N
493+
SECP256R1P
494+
secp256r1
495+
sig
496+
479497
CLZ

0 commit comments

Comments
 (0)