Skip to content

secp256r1 precompile #3382

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jun 23, 2025
5 changes: 3 additions & 2 deletions PrecompileTests.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ PrecompileTests
===
## PrecompileTests
```diff
+ P256Verify.json OK
+ blake2F.json OK
+ blsG1Add.json OK
+ blsG1MultiExp.json OK
Expand All @@ -24,7 +25,7 @@ PrecompileTests
+ ripemd160.json OK
+ sha256.json OK
```
OK: 21/21 Fail: 0/21 Skip: 0/21
OK: 22/22 Fail: 0/22 Skip: 0/22
## eest
```diff
+ add_G1_bls.json OK
Expand All @@ -49,4 +50,4 @@ OK: 21/21 Fail: 0/21 Skip: 0/21
OK: 18/18 Fail: 0/18 Skip: 0/18

---TOTAL---
OK: 39/39 Fail: 0/39 Skip: 0/39
OK: 40/40 Fail: 0/40 Skip: 0/40
1 change: 1 addition & 0 deletions execution_chain/evm/interpreter/gas_costs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -858,3 +858,4 @@ const
Bls12381PairingPerPairGas* = GasInt 32600
Bls12381MapG1Gas* = GasInt 5500
Bls12381MapG2Gas* = GasInt 23800
GasP256VerifyGas* = GasInt 3450
171 changes: 129 additions & 42 deletions execution_chain/evm/precompiles.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,56 +17,111 @@ import
chronicles,
nimcrypto/[ripemd, sha2, utils],
bncurve/[fields, groups],
stew/assign2,
stew/[assign2, endians2, byteutils],
libp2p/crypto/ecnist,
../common/evmforks,
../core/eip4844,
./modexp,
./evm_errors,
./computation,
./secp256r1verify,
eth/common/[base, addresses]

type
PrecompileAddresses* = enum
Precompiles* = enum
# Frontier to Spurious Dragron
paEcRecover = 0x01
paSha256 = 0x02
paRipeMd160 = 0x03
paIdentity = 0x04
paEcRecover
paSha256
paRipeMd160
paIdentity
# Byzantium and Constantinople
paModExp = 0x05
paEcAdd = 0x06
paEcMul = 0x07
paPairing = 0x08
paModExp
paEcAdd
paEcMul
paPairing
# Istanbul
paBlake2bf = 0x09
paBlake2bf
# Cancun
paPointEvaluation = 0x0a
paPointEvaluation
# Prague (EIP-2537)
paBlsG1Add = 0x0b
paBlsG1MultiExp = 0x0c
paBlsG2Add = 0x0d
paBlsG2MultiExp = 0x0e
paBlsPairing = 0x0f
paBlsMapG1 = 0x10
paBlsMapG2 = 0x11
paBlsG1Add
paBlsG1MultiExp
paBlsG2Add
paBlsG2MultiExp
paBlsPairing
paBlsMapG1
paBlsMapG2
# Osaka
paP256Verify

SigRes = object
msgHash: array[32, byte]
sig: Signature

const
# Frontier to Spurious Dragron
paEcRecoverAddress = address"0x0000000000000000000000000000000000000001"
paSha256Address = address"0x0000000000000000000000000000000000000002"
paRipeMd160Address = address"0x0000000000000000000000000000000000000003"
paIdentityAddress = address"0x0000000000000000000000000000000000000004"

# Byzantium and Constantinople
paModExpAddress = address"0x0000000000000000000000000000000000000005"
paEcAddAddress = address"0x0000000000000000000000000000000000000006"
paEcMulAddress = address"0x0000000000000000000000000000000000000007"
paPairingAddress = address"0x0000000000000000000000000000000000000008"

# Istanbul
paBlake2bfAddress = address"0x0000000000000000000000000000000000000009"

# Cancun
paPointEvaluationAddress = address"0x000000000000000000000000000000000000000a"

# Prague (EIP-2537)
paBlsG1AddAddress = address"0x000000000000000000000000000000000000000b"
paBlsG1MultiExpAddress = address"0x000000000000000000000000000000000000000c"
paBlsG2AddAddress = address"0x000000000000000000000000000000000000000d"
paBlsG2MultiExpAddress = address"0x000000000000000000000000000000000000000e"
paBlsPairingAddress = address"0x000000000000000000000000000000000000000f"
paBlsMapG1Address = address"0x0000000000000000000000000000000000000010"
paBlsMapG2Address = address"0x0000000000000000000000000000000000000011"

# Osaka
paP256VerifyAddress = address"0x0000000000000000000000000000000000000100"

precompileAddrs*: array[Precompiles, Address] = [
paEcRecoverAddress, # paEcRecover
paSha256Address, # paSha256
paRipeMd160Address, # paRipeMd160
paIdentityAddress, # paIdentity
paModExpAddress, # paModExp
paEcAddAddress, # paEcAdd
paEcMulAddress, # paEcMul
paPairingAddress, # paPairing
paBlake2bfAddress, # paBlake2bf
paPointEvaluationAddress, # paPointEvaluation
paBlsG1AddAddress, # paBlsG1Add
paBlsG1MultiExpAddress, # paBlsG1MultiExp
paBlsG2AddAddress, # paBlsG2Add
paBlsG2MultiExpAddress, # paBlsG2MultiExp
paBlsPairingAddress, # paBlsPairing
paBlsMapG1Address, # paBlsMapG1
paBlsMapG2Address, # paBlsMapG2
paP256VerifyAddress # paP256Verify
]


# ------------------------------------------------------------------------------
# Private functions
# ------------------------------------------------------------------------------

func getMaxPrecompileAddr(fork: EVMFork): PrecompileAddresses =
func getMaxPrecompile(fork: EVMFork): Precompiles =
if fork < FkByzantium: paIdentity
elif fork < FkIstanbul: paPairing
elif fork < FkCancun: paBlake2bf
elif fork < FkPrague: paPointEvaluation
else: PrecompileAddresses.high

func validPrecompileAddr(addrByte, maxPrecompileAddr: byte): bool =
(addrByte in PrecompileAddresses.low.byte .. maxPrecompileAddr)
elif fork < FkOsaka: paBlsMapG2
else: Precompiles.high

func getSignature(c: Computation): EvmResult[SigRes] =
# input is Hash, V, R, S
Expand Down Expand Up @@ -694,36 +749,67 @@ proc pointEvaluation(c: Computation): EvmResultVoid =
c.output = @PointEvaluationResult
ok()

proc p256verify(c: Computation): EvmResultVoid =

template failed() =
c.output.setLen(0)
return ok()

? c.gasMeter.consumeGas(GasP256VerifyGas, reason="P256VERIFY Precompile")

if c.msg.data.len != 160:
failed()

var inputPubKey: array[65, byte]

# Validations
if isInfinityByte(c.msg.data.toOpenArray(96, 159)):
failed()

# Check scalar and field bounds (r, s ∈ (0, n), qx, qy ∈ [0, p))
var sig: EcSignature
if not sig.initRaw(c.msg.data.toOpenArray(32, 95)):
failed()

var pubkey: EcPublicKey
inputPubKey[0] = 4.byte
assign(inputPubKey.toOpenArray(1, 64), c.msg.data.toOpenArray(96, 159))

if not pubkey.initRaw(inputPubKey):
failed()

let isValid = sig.verifyRaw(c.msg.data.toOpenArray(0, 31), pubkey)

if isValid:
c.output.setLen(32)
c.output[^1] = 1.byte # return 0x...01
else:
c.output.setLen(0)

ok()

# ------------------------------------------------------------------------------
# Public functions
# ------------------------------------------------------------------------------

iterator activePrecompiles*(fork: EVMFork): Address =
var res: Address
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
for c in PrecompileAddresses.low..maxPrecompileAddr:
if validPrecompileAddr(c.byte, maxPrecompileAddr.byte):
res.data[^1] = c.byte
yield res
let maxPrecompile = getMaxPrecompile(fork)
for c in Precompiles.low..maxPrecompile:
yield precompileAddrs[c]

func activePrecompilesList*(fork: EVMFork): seq[Address] =
for address in activePrecompiles(fork):
result.add address

proc getPrecompile*(fork: EVMFork, b: byte): Opt[PrecompileAddresses] =
let maxPrecompileAddr = getMaxPrecompileAddr(fork)
if validPrecompileAddr(b, maxPrecompileAddr.byte):
Opt.some(PrecompileAddresses(b))
else:
Opt.none(PrecompileAddresses)
proc getPrecompile*(fork: EVMFork, codeAddress: Address): Opt[Precompiles] =
let maxPrecompile = getMaxPrecompile(fork)
for c in Precompiles.low..maxPrecompile:
if precompileAddrs[c] == codeAddress:
return Opt.some(c)

proc getPrecompile*(fork: EVMFork, codeAddress: Address): Opt[PrecompileAddresses] =
for i in 0..18:
if codeAddress.data[i] != 0:
return Opt.none(PrecompileAddresses)
getPrecompile(fork, codeAddress.data[19])
Opt.none(Precompiles)

proc execPrecompile*(c: Computation, precompile: PrecompileAddresses) =
proc execPrecompile*(c: Computation, precompile: Precompiles) =
let fork = c.fork
let res = case precompile
of paEcRecover: ecRecover(c)
Expand All @@ -743,6 +829,7 @@ proc execPrecompile*(c: Computation, precompile: PrecompileAddresses) =
of paBlsPairing: blsPairing(c)
of paBlsMapG1: blsMapG1(c)
of paBlsMapG2: blsMapG2(c)
of paP256Verify: p256verify(c)

if res.isErr:
if res.error.code == EvmErrorCode.OutOfGas:
Expand Down
44 changes: 44 additions & 0 deletions execution_chain/evm/secp256r1verify.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Nimbus
# Copyright (c) 2018-2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.

import
libp2p/crypto/ecnist,
bearssl/[ec, hash]

proc isInfinityByte*(data: openArray[byte]): bool =
## Check if all values in ``data`` are zero.
for b in data:
if b != 0:
return false
return true

proc verifyRaw*[T: byte | char](
sig: EcSignature, message: openArray[T], pubkey: ecnist.EcPublicKey
): bool {.inline.} =
## Verify ECDSA signature ``sig`` using public key ``pubkey`` and data
## ``message``.
##
## Return ``true`` if message verification succeeded, ``false`` if
## verification failed.
doAssert((not isNil(sig)) and (not isNil(pubkey)))
var hc: HashCompatContext
var hash: array[32, byte]
var impl = ecGetDefault()
if pubkey.key.curve in EcSupportedCurvesCint:
let res = ecdsaI31VrfyRaw(
impl,
addr message[0],
uint(len(message)),
unsafeAddr pubkey.key,
addr sig.buffer[0],
uint(len(sig.buffer)),
)
# Clear context with initial value
result = (res == 1)
Loading
Loading