Skip to content

Commit

Permalink
Add KZG point precompile (#489)
Browse files Browse the repository at this point in the history
* add draft for KZG point precompile

* add test case for KZG point precompile

* [precompiles] add verification failure enum status code

* [precompiles] use correct modulus (order of subgroup G1 / Fr not Fp)

* [precompiles] add trusted setup as argument, export KZG elements, cleanup

* [tests] add mini README for source of KZG point precompile test vector

* remove `precompile` suffix

* Apply suggestions from code review

---------

Co-authored-by: Mamy Ratsimbazafy <[email protected]>
  • Loading branch information
Vindaar and mratsim authored Dec 12, 2024
1 parent 585f803 commit 13d5bb7
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 3 deletions.
63 changes: 62 additions & 1 deletion constantine/ethereum_evm_precompiles.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ import
./named/zoo_subgroups,
./math/io/[io_bigints, io_fields],
./math_arbitrary_precision/arithmetic/bigints_views,
./hash_to_curve/hash_to_curve
./hash_to_curve/hash_to_curve,
# For KZG point precompile
./ethereum_eip4844_kzg,
./serialization/codecs_status_codes

# For KZG point precompile
export EthereumKZGContext, TrustedSetupFormat, TrustedSetupStatus, trusted_setup_load, trusted_setup_delete

# ############################################################
#
Expand All @@ -41,6 +47,7 @@ type
cttEVM_IntLargerThanModulus
cttEVM_PointNotOnCurve
cttEVM_PointNotInSubgroup
cttEVM_VerificationFailure

func eth_evm_sha256*(r: var openArray[byte], inputs: openArray[byte]): CttEVMStatus {.libPrefix: prefix_ffi, meter.} =
## SHA256
Expand Down Expand Up @@ -1215,3 +1222,57 @@ func eth_evm_bls12381_map_fp2_to_g2*(r: var openArray[byte], inputs: openarray[b
r.toOpenArray(128, 192-1).marshal(aff.y.c0, bigEndian)
r.toOpenArray(192, 256-1).marshal(aff.y.c1, bigEndian)
return cttEVM_Success

proc kzg_to_versioned_hash(r: var array[32, byte], commitment_bytes: array[48, byte]) =
## Spec: https://eips.ethereum.org/EIPS/eip-4844#helpers
## `return VERSIONED_HASH_VERSION_KZG + sha256(commitment)[1:]`
const VERSIONED_HASH_VERSION_KZG = 0x01.byte
var s {.noinit.}: sha256
s.init()
s.update(commitment_bytes)
s.finish(r)
r[0] = VERSIONED_HASH_VERSION_KZG

func eth_evm_kzg_point_evaluation*(ctx: ptr EthereumKZGContext,
r: var openArray[byte],
input: openArray[byte]): CttEVMStatus {.libPrefix: prefix_ffi, meter.} =
## Verify `p(z) = y` given commitment that corresponds to the polynomial `p(x)` and a KZG proof.
## Also verify that the provided commitment matches the provided versioned_hash.
## Returns `FIELD_ELEMENTS_PER_BLOB` and the BSL12-381 modulus as padded 32 byte big endian values,
## i.e. `r` must be 64 bytes long.
##
## Spec: https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile
# The data is encoded as follows: versioned_hash | z | y | commitment | proof | with z and y being padded 32 byte big endian values
if len(input) != 192:
return cttEVM_InvalidInputSize
if len(r) != 64:
return cttEVM_InvalidOutputSize

var
versioned_hash: array[32, byte]
z: array[32, byte]
y: array[32, byte]
commitment_bytes: array[48, byte]
proof: array[48, byte]
versioned_hash.rawCopy(0, input, 0, 32)
z.rawCopy(0, input, 32, 32)
y.rawCopy(0, input, 64, 32)
commitment_bytes.rawCopy(0, input, 96, 48)
proof.rawCopy(0, input, 144, 48)

# Verify commitment matches versioned_hash
var rhash: array[32, byte]
rhash.kzg_to_versioned_hash(commitment_bytes)
if rhash != versioned_hash:
return cttEVM_VerificationFailure

# Verify KZG proof with z and y in big endian format
if ctx.verify_kzg_proof(commitment_bytes, z, y, proof) != cttEthKzg_Success:
return cttEVM_VerificationFailure

# Reference:
# `return Bytes(U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes32() + U256(BLS_MODULUS).to_be_bytes32())`
r.toOpenArray( 0, 32-1).marshal([FIELD_ELEMENTS_PER_BLOB], WordBitWidth, bigEndian)
r.toOpenArray(32, 64-1).marshal(Fr[BLS12_381].getModulus(), bigEndian)

result = cttEVM_Success
3 changes: 3 additions & 0 deletions tests/protocol_ethereum_evm_precompiles/eip-4844/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The test vector found here for the KZG point precompile (https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile) is taken from Geth:

https://github.com/ethereum/go-ethereum/blob/ce8cec007c42e54f79e53c3769a7279487968c07/core/vm/testdata/precompiles/pointEvaluation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"Input": "01e798154708fe7789429634053cbf9f99b619f9f084048927333fce637f549b564c0a11a0f704f4fc3e8acfe0f8245f0ad1347b378fbf96e206da11a5d3630624d25032e67a7e6a4910df5834b8fe70e6bcfeeac0352434196bdf4b2485d5a18f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7873033e038326e87ed3e1276fd140253fa08e9fc25fb2d9a98527fc22a2c9612fbeafdad446cbc7bcdbdcd780af2c16a",
"Expected": "000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001",
"Name": "pointEvaluation1",
"Gas": 50000,
"NoBenchmark": false
}
]
31 changes: 29 additions & 2 deletions tests/t_ethereum_evm_precompiles.nim
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,35 @@ const
TestVectorsDir* =
currentSourcePath.rsplit(DirSep, 1)[0] / "protocol_ethereum_evm_precompiles"

const
TrustedSetupMainnet =
currentSourcePath.rsplit(DirSep, 1)[0] /
".." / "constantine" /
"commitments_setups" /
"trusted_setup_ethereum_kzg4844_reference.dat"

proc loadVectors(TestType: typedesc, filename: string): TestType =
let content = readFile(TestVectorsDir/filename)
result = content.fromJson(TestType)

template runPrecompileTests(filename: string, funcname: untyped, outsize: int) =
proc trusted_setup*(): ptr EthereumKZGContext =

var ctx: ptr EthereumKZGContext
let tsStatus = ctx.trusted_setup_load(TrustedSetupMainnet, kReferenceCKzg4844)
doAssert tsStatus == tsSuccess, "\n[Trusted Setup Error] " & $tsStatus
echo "Trusted Setup loaded successfully"
return ctx

template runPrecompileTests(filename: string, funcname: untyped, outsize: int, needKzgCtx: static bool = false) =
block:

proc `PrecompileTestrunner _ funcname`() =
let vec = seq[PrecompileTest].loadVectors(filename)
echo "Running ", filename

when needKzgCtx:
var ctx = trusted_setup()

for test in vec:
stdout.write " Testing " & test.Name & " ... "

Expand All @@ -58,7 +77,10 @@ template runPrecompileTests(filename: string, funcname: untyped, outsize: int) =
let outs = if outsize > 0: outsize else: test.Expected.len div 2
var r = newSeq[byte](outs)

let status = funcname(r, inputbytes)
when needKzgCtx:
let status = ctx.funcname(r, inputbytes)
else:
let status = funcname(r, inputbytes)
if status != cttEVM_Success:
doAssert test.ExpectedError.len > 0, "[Test Failure]\n" &
" " & test.Name & "\n" &
Expand All @@ -74,6 +96,9 @@ template runPrecompileTests(filename: string, funcname: untyped, outsize: int) =

stdout.write "Success\n"

when needKzgCtx:
ctx.trusted_setup_delete()

`PrecompileTestrunner _ funcname`()

proc testSha256() =
Expand Down Expand Up @@ -136,3 +161,5 @@ runPrecompileTests("eip-2537/map_fp_to_G1_bls.json", eth_evm_bls12381_map_fp_to_
runPrecompileTests("eip-2537/fail-map_fp_to_G1_bls.json", eth_evm_bls12381_map_fp_to_g1, 128)
runPrecompileTests("eip-2537/map_fp2_to_G2_bls.json", eth_evm_bls12381_map_fp2_to_g2, 256)
runPrecompileTests("eip-2537/fail-map_fp2_to_G2_bls.json", eth_evm_bls12381_map_fp2_to_g2, 256)

runPrecompileTests("eip-4844/pointEvaluation.json", eth_evm_kzg_point_evaluation, 64, true)

0 comments on commit 13d5bb7

Please sign in to comment.