Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ install_requires =
py-ecc>=8.0.0b2,<9
ethereum-types>=0.2.1,<0.3
ethereum-rlp>=0.1.1,<0.2
pycryptodome>=3.22.0,<4
cryptography>=45.0.1,<46

[options.package_data]
ethereum =
Expand Down
61 changes: 61 additions & 0 deletions src/ethereum/crypto/elliptic_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"""

import coincurve
from Crypto.Util.asn1 import DerSequence
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
from ethereum_types.bytes import Bytes
from ethereum_types.numeric import U256

Expand Down Expand Up @@ -71,3 +76,59 @@ def secp256k1_recover(r: U256, s: U256, v: U256, msg_hash: Hash32) -> Bytes:

public_key = public_key.format(compressed=False)[1:]
return public_key


SECP256R1N = U256(
0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551
)
SECP256R1P = U256(
0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF
)
SECP256R1A = U256(
0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC
)
SECP256R1B = U256(
0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B
)


def secp256r1_verify(
r: U256, s: U256, x: U256, y: U256, msg_hash: Hash32
) -> None:
"""
Verifies a P-256 signature.

Parameters
----------
r :
the `r` component of the signature
s :
the `s` component of the signature
x:
the `x` coordinate of the public key
y:
the `y` coordinate of the public key
msg_hash :
Hash of the message being recovered.

Returns
-------
result : `ethereum.base_types.Bytes`
return 1 if the signature is valid, empty bytes otherwise
"""
# Convert U256 to regular integers for DerSequence
r_int = int(r)
s_int = int(s)
x_int = int(x)
y_int = int(y)

sig = DerSequence([r_int, s_int]).encode()

try:
pubnum = ec.EllipticCurvePublicNumbers(x_int, y_int, ec.SECP256R1())
pubkey = pubnum.public_key(default_backend())
pubkey.verify(sig, msg_hash, ec.ECDSA(Prehashed(hashes.SHA256())))
except Exception as e:
raise Exception from e

return
1 change: 1 addition & 0 deletions src/ethereum/osaka/vm/gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
GAS_SELF_DESTRUCT = Uint(5000)
GAS_SELF_DESTRUCT_NEW_ACCOUNT = Uint(25000)
GAS_ECRECOVER = Uint(3000)
GAS_P256VERIFY = Uint(3450)
GAS_SHA256 = Uint(60)
GAS_SHA256_WORD = Uint(12)
GAS_RIPEMD160 = Uint(600)
Expand Down
2 changes: 2 additions & 0 deletions src/ethereum/osaka/vm/precompiled_contracts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"BLS12_PAIRING_ADDRESS",
"BLS12_MAP_FP_TO_G1_ADDRESS",
"BLS12_MAP_FP2_TO_G2_ADDRESS",
"P256VERIFY_ADDRESS",
)

ECRECOVER_ADDRESS = hex_to_address("0x01")
Expand All @@ -52,3 +53,4 @@
BLS12_PAIRING_ADDRESS = hex_to_address("0x0f")
BLS12_MAP_FP_TO_G1_ADDRESS = hex_to_address("0x10")
BLS12_MAP_FP2_TO_G2_ADDRESS = hex_to_address("0x11")
P256VERIFY_ADDRESS = hex_to_address("0x100")
3 changes: 3 additions & 0 deletions src/ethereum/osaka/vm/precompiled_contracts/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
ECRECOVER_ADDRESS,
IDENTITY_ADDRESS,
MODEXP_ADDRESS,
P256VERIFY_ADDRESS,
POINT_EVALUATION_ADDRESS,
RIPEMD160_ADDRESS,
SHA256_ADDRESS,
Expand All @@ -49,6 +50,7 @@
from .ecrecover import ecrecover
from .identity import identity
from .modexp import modexp
from .p256verify import p256verify
from .point_evaluation import point_evaluation
from .ripemd160 import ripemd160
from .sha256 import sha256
Expand All @@ -71,4 +73,5 @@
BLS12_PAIRING_ADDRESS: bls12_pairing,
BLS12_MAP_FP_TO_G1_ADDRESS: bls12_map_fp_to_g1,
BLS12_MAP_FP2_TO_G2_ADDRESS: bls12_map_fp2_to_g2,
P256VERIFY_ADDRESS: p256verify,
}
101 changes: 101 additions & 0 deletions src/ethereum/osaka/vm/precompiled_contracts/p256verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""
Ethereum Virtual Machine (EVM) P256VERIFY PRECOMPILED CONTRACT
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. contents:: Table of Contents
:backlinks: none
:local:

Introduction
------------

Implementation of the P256VERIFY precompiled contract.
"""
from ethereum_types.numeric import U256

from ethereum.crypto.elliptic_curve import (
SECP256R1A,
SECP256R1B,
SECP256R1N,
SECP256R1P,
secp256r1_verify,
)
from ethereum.crypto.hash import Hash32
from ethereum.utils.byte import left_pad_zero_bytes

from ...vm import Evm
from ...vm.gas import GAS_P256VERIFY, charge_gas
from ...vm.memory import buffer_read


def p256verify(evm: Evm) -> None:
"""
Verifies a P-256 signature.

Parameters
----------
evm :
The current EVM frame.
"""
data = evm.message.data

# GAS
charge_gas(evm, GAS_P256VERIFY)

if len(data) != 160:
return

# OPERATION
message_hash_bytes = buffer_read(data, U256(0), U256(32))
message_hash = Hash32(message_hash_bytes)
r = U256.from_be_bytes(buffer_read(data, U256(32), U256(32)))
s = U256.from_be_bytes(buffer_read(data, U256(64), U256(32)))
qx = U256.from_be_bytes(buffer_read(data, U256(96), U256(32)))
qy = U256.from_be_bytes(buffer_read(data, U256(128), U256(32)))

# Signature component bounds:
# Both r and s MUST satisfy 0 < r < n and 0 < s < n
r_in_bounds = U256(0) < r < SECP256R1N
s_in_bounds = U256(0) < s < SECP256R1N
if not (r_in_bounds and s_in_bounds):
return

# Public key bounds:
# Both qx and qy MUST satisfy 0 ≤ qx < p and 0 ≤ qy < p
qx_in_bounds = U256(0) <= qx < SECP256R1P
qy_in_bounds = U256(0) <= qy < SECP256R1P
if not (qx_in_bounds and qy_in_bounds):
return

# Point validity: The point (qx, qy) MUST satisfy the curve equation
# qy^2 ≡ qx^3 + a*qx + b (mod p)
# Convert U256 to int for calculations
qx_int = int(qx)
qy_int = int(qy)
p_int = int(SECP256R1P)
a_int = int(SECP256R1A)
b_int = int(SECP256R1B)

# Calculate y^2 mod p
y_squared = (qy_int * qy_int) % p_int

# Calculate x^3 + ax + b mod p
x_cubed = (qx_int * qx_int * qx_int) % p_int
ax = (a_int * qx_int) % p_int
right_side = (x_cubed + ax + b_int) % p_int

if y_squared != right_side:
return

# Point should not be at infinity (represented as (0, 0))
if qx == U256(0) and qy == U256(0):
return

success_return_value = left_pad_zero_bytes(b"\x01", 32)

try:
secp256r1_verify(r, s, qx, qy, message_hash)
except Exception:
return

evm.output = success_return_value
20 changes: 19 additions & 1 deletion whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -467,4 +467,22 @@ eoa

blockchain
listdir
precompiles
precompiles

asn1
backends
Der
ECDSA
Prehashed
pubnum
P256VERIFY
p256verify
qx
qy
SECP256R1
SECP256R1A
SECP256R1B
SECP256R1N
SECP256R1P
secp256r1
sig