mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
slhdsa 97.9 · mlkem 97.4 · mldsa 96.8 · xwing 96.2 · bls12381 97.2 · kzg4844 97.8 · v3 98.7. All build, vet and gofmt clean, all green under -count=1, and preserved here rather than lost to a session limit. v3's lane did report before it died: 78.1% to 98.7%, 70 of 70 mutations caught, four of which survived a first pass and needed tests written. The two that matter are rounding: burn rounding principal UP, and fee growth rounding UP, neither of which any behaviour test could see -- a Q128.128 off-by-one is 2^-128 of a token and floors to zero through every payout path, so it took an exact-value assertion. It also found that a full drain of a pool with overlapping positions REVERTS rather than paying out, because deposits round down once per position while payouts round down once per step over combined liquidity, and floor(a+b) >= floor(a)+floor(b). Custody holds; not one unbacked wei leaves. Liveness, not theft. The other six carry no report -- their lanes were killed mid-run. Build, vet, gofmt and the suite are all I can vouch for.
571 lines
14 KiB
Go
571 lines
14 KiB
Go
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package kzg4844
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"testing"
|
|
|
|
gokzg4844 "github.com/crate-crypto/go-kzg-4844"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestKZG4844Precompile_Address(t *testing.T) {
|
|
// KZG4844 in ZK Proofs range: 0x0900...0014
|
|
expectedAddr := common.HexToAddress("0x000000000000000000000000000000000000B002")
|
|
require.Equal(t, expectedAddr, ContractAddress)
|
|
require.Equal(t, expectedAddr, KZG4844Precompile.Address())
|
|
}
|
|
|
|
func TestKZG4844_ContextInitialized(t *testing.T) {
|
|
require.NotNil(t, kzgContext, "KZG context should be initialized")
|
|
}
|
|
|
|
func TestKZG4844_BlobToCommitment(t *testing.T) {
|
|
// Create a valid blob (4096 field elements, each < field modulus)
|
|
blob := createValidBlob(t)
|
|
|
|
input := make([]byte, 1+BlobSize)
|
|
input[0] = OpBlobToCommitment
|
|
copy(input[1:], blob[:])
|
|
|
|
gas := KZG4844Precompile.RequiredGas(input)
|
|
require.Equal(t, uint64(GasBlobToCommitment), gas)
|
|
|
|
commitment, remainingGas, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
input,
|
|
gas,
|
|
false,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, commitment)
|
|
require.Equal(t, uint64(0), remainingGas)
|
|
require.Equal(t, CommitmentSize, len(commitment))
|
|
}
|
|
|
|
func TestKZG4844_ComputeProof(t *testing.T) {
|
|
blob := createValidBlob(t)
|
|
|
|
// Create evaluation point (z)
|
|
z := make([]byte, FieldElementSize)
|
|
z[31] = 0x42 // Simple non-zero point
|
|
|
|
input := make([]byte, 1+BlobSize+FieldElementSize)
|
|
input[0] = OpComputeProof
|
|
copy(input[1:], blob[:])
|
|
copy(input[1+BlobSize:], z)
|
|
|
|
gas := KZG4844Precompile.RequiredGas(input)
|
|
|
|
result, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
input,
|
|
gas,
|
|
false,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Equal(t, ProofSize+FieldElementSize, len(result)) // proof + y
|
|
}
|
|
|
|
func TestKZG4844_VerifyProof(t *testing.T) {
|
|
blob := createValidBlob(t)
|
|
|
|
// First compute commitment
|
|
commitmentInput := make([]byte, 1+BlobSize)
|
|
commitmentInput[0] = OpBlobToCommitment
|
|
copy(commitmentInput[1:], blob[:])
|
|
|
|
commitment, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
commitmentInput,
|
|
GasBlobToCommitment,
|
|
false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// Compute proof
|
|
z := make([]byte, FieldElementSize)
|
|
z[31] = 0x42
|
|
|
|
proofInput := make([]byte, 1+BlobSize+FieldElementSize)
|
|
proofInput[0] = OpComputeProof
|
|
copy(proofInput[1:], blob[:])
|
|
copy(proofInput[1+BlobSize:], z)
|
|
|
|
proofResult, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
proofInput,
|
|
GasComputeProof,
|
|
false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
proof := proofResult[:ProofSize]
|
|
y := proofResult[ProofSize:]
|
|
|
|
// Now verify
|
|
verifyInput := make([]byte, 1+CommitmentSize+FieldElementSize+FieldElementSize+ProofSize)
|
|
verifyInput[0] = OpVerifyProof
|
|
offset := 1
|
|
copy(verifyInput[offset:], commitment)
|
|
offset += CommitmentSize
|
|
copy(verifyInput[offset:], z)
|
|
offset += FieldElementSize
|
|
copy(verifyInput[offset:], y)
|
|
offset += FieldElementSize
|
|
copy(verifyInput[offset:], proof)
|
|
|
|
gas := KZG4844Precompile.RequiredGas(verifyInput)
|
|
|
|
result, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
verifyInput,
|
|
gas,
|
|
false,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, []byte{0x01}, result) // Valid
|
|
}
|
|
|
|
// A well-formed proof that does not open the commitment is answered with a
|
|
// zero; a proof that is not a point encoding at all is a different answer.
|
|
func TestKZG4844_VerifyProof_Invalid(t *testing.T) {
|
|
blob := createValidBlob(t)
|
|
|
|
commit := func() []byte {
|
|
in := append([]byte{OpBlobToCommitment}, blob...)
|
|
out, _, err := KZG4844Precompile.Run(nil, common.Address{}, ContractAddress, in, GasBlobToCommitment, false)
|
|
require.NoError(t, err)
|
|
return out
|
|
}
|
|
open := func(z []byte) (proof, y []byte) {
|
|
in := append(append([]byte{OpComputeProof}, blob...), z...)
|
|
out, _, err := KZG4844Precompile.Run(nil, common.Address{}, ContractAddress, in, GasComputeProof, false)
|
|
require.NoError(t, err)
|
|
return out[:ProofSize], out[ProofSize:]
|
|
}
|
|
verify := func(commitment, z, y, proof []byte) ([]byte, error) {
|
|
in := []byte{OpVerifyProof}
|
|
in = append(append(append(append(in, commitment...), z...), y...), proof...)
|
|
out, _, err := KZG4844Precompile.Run(nil, common.Address{}, ContractAddress, in, GasVerifyProof, false)
|
|
return out, err
|
|
}
|
|
|
|
commitment := commit()
|
|
zA := make([]byte, FieldElementSize)
|
|
zA[31] = 0x42
|
|
zB := make([]byte, FieldElementSize)
|
|
zB[31] = 0x43
|
|
|
|
proofA, yA := open(zA)
|
|
proofB, _ := open(zB)
|
|
|
|
out, err := verify(commitment, zA, yA, proofA)
|
|
require.NoError(t, err)
|
|
require.Equal(t, []byte{0x01}, out, "the honest opening must verify")
|
|
|
|
// The opening for a different point: a real proof, a real point, the
|
|
// wrong answer.
|
|
out, err = verify(commitment, zA, yA, proofB)
|
|
require.NoError(t, err)
|
|
require.Equal(t, []byte{0x00}, out)
|
|
|
|
// A claimed value that is not the one the polynomial takes at z.
|
|
wrongY := bytes.Clone(yA)
|
|
wrongY[31] ^= 0x01
|
|
out, err = verify(commitment, zA, wrongY, proofA)
|
|
require.NoError(t, err)
|
|
require.Equal(t, []byte{0x00}, out)
|
|
|
|
// A proof whose leading byte is not a valid compressed-point header is
|
|
// refused outright rather than reported as a failed opening.
|
|
broken := bytes.Clone(proofA)
|
|
broken[0] ^= 0xFF
|
|
out, err = verify(commitment, zA, yA, broken)
|
|
require.ErrorIs(t, err, ErrInvalidProof)
|
|
require.Nil(t, out)
|
|
|
|
// And so is a commitment that is not a point.
|
|
brokenCommitment := bytes.Clone(commitment)
|
|
brokenCommitment[0] ^= 0xFF
|
|
out, err = verify(brokenCommitment, zA, yA, proofA)
|
|
require.ErrorIs(t, err, ErrInvalidCommitment)
|
|
require.Nil(t, out)
|
|
}
|
|
|
|
func TestKZG4844_VerifyBlobProof(t *testing.T) {
|
|
blob := createValidBlob(t)
|
|
|
|
// Compute commitment
|
|
commitmentInput := make([]byte, 1+BlobSize)
|
|
commitmentInput[0] = OpBlobToCommitment
|
|
copy(commitmentInput[1:], blob[:])
|
|
|
|
commitment, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
commitmentInput,
|
|
GasBlobToCommitment,
|
|
false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// Compute blob proof using go-kzg-4844 directly
|
|
var kzgBlob gokzg4844.Blob
|
|
copy(kzgBlob[:], blob[:])
|
|
|
|
var kzgCommitment gokzg4844.KZGCommitment
|
|
copy(kzgCommitment[:], commitment)
|
|
|
|
proof, err := kzgContext.ComputeBlobKZGProof(&kzgBlob, kzgCommitment, 0)
|
|
require.NoError(t, err)
|
|
|
|
// Verify blob proof
|
|
verifyInput := make([]byte, 1+BlobSize+CommitmentSize+ProofSize)
|
|
verifyInput[0] = OpVerifyBlobProof
|
|
offset := 1
|
|
copy(verifyInput[offset:], blob[:])
|
|
offset += BlobSize
|
|
copy(verifyInput[offset:], commitment)
|
|
offset += CommitmentSize
|
|
copy(verifyInput[offset:], proof[:])
|
|
|
|
gas := KZG4844Precompile.RequiredGas(verifyInput)
|
|
|
|
result, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
verifyInput,
|
|
gas,
|
|
false,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, []byte{0x01}, result) // Valid
|
|
}
|
|
|
|
func TestKZG4844_BatchVerifyProofs(t *testing.T) {
|
|
numProofs := 2
|
|
proofSets := make([][]byte, numProofs)
|
|
|
|
for i := range numProofs {
|
|
blob := createValidBlob(t)
|
|
|
|
// Compute commitment
|
|
commitmentInput := make([]byte, 1+BlobSize)
|
|
commitmentInput[0] = OpBlobToCommitment
|
|
copy(commitmentInput[1:], blob[:])
|
|
|
|
commitment, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
commitmentInput,
|
|
GasBlobToCommitment,
|
|
false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// Compute proof
|
|
z := make([]byte, FieldElementSize)
|
|
z[31] = byte(i + 1)
|
|
|
|
proofInput := make([]byte, 1+BlobSize+FieldElementSize)
|
|
proofInput[0] = OpComputeProof
|
|
copy(proofInput[1:], blob[:])
|
|
copy(proofInput[1+BlobSize:], z)
|
|
|
|
proofResult, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
proofInput,
|
|
GasComputeProof,
|
|
false,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// Build proof set: commitment + z + y + proof
|
|
proofSet := make([]byte, CommitmentSize+FieldElementSize+FieldElementSize+ProofSize)
|
|
copy(proofSet, commitment)
|
|
copy(proofSet[CommitmentSize:], z)
|
|
copy(proofSet[CommitmentSize+FieldElementSize:], proofResult[ProofSize:]) // y
|
|
copy(proofSet[CommitmentSize+2*FieldElementSize:], proofResult[:ProofSize])
|
|
|
|
proofSets[i] = proofSet
|
|
}
|
|
|
|
// Build batch verify input
|
|
input := make([]byte, 3)
|
|
input[0] = OpBatchVerifyProofs
|
|
binary.BigEndian.PutUint16(input[1:], uint16(numProofs))
|
|
|
|
for _, proofSet := range proofSets {
|
|
input = append(input, proofSet...)
|
|
}
|
|
|
|
gas := KZG4844Precompile.RequiredGas(input)
|
|
|
|
result, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
input,
|
|
gas,
|
|
false,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, []byte{0x01}, result) // All valid
|
|
}
|
|
|
|
func TestKZG4844_BatchVerifyProofs_Empty(t *testing.T) {
|
|
input := make([]byte, 3)
|
|
input[0] = OpBatchVerifyProofs
|
|
binary.BigEndian.PutUint16(input[1:], 0) // 0 proofs
|
|
|
|
gas := KZG4844Precompile.RequiredGas(input)
|
|
|
|
result, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
input,
|
|
gas,
|
|
false,
|
|
)
|
|
|
|
require.NoError(t, err)
|
|
require.Equal(t, []byte{0x01}, result) // Empty batch is valid
|
|
}
|
|
|
|
func TestKZG4844_InvalidOperation(t *testing.T) {
|
|
input := []byte{0xFF} // Invalid op
|
|
|
|
gas := KZG4844Precompile.RequiredGas(input)
|
|
require.Equal(t, uint64(0), gas)
|
|
|
|
_, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
input,
|
|
1000000,
|
|
false,
|
|
)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestKZG4844_InputTooShort(t *testing.T) {
|
|
input := []byte{OpBlobToCommitment} // Missing blob
|
|
|
|
_, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
input,
|
|
GasBlobToCommitment,
|
|
false,
|
|
)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestKZG4844_OutOfGas(t *testing.T) {
|
|
blob := createValidBlob(t)
|
|
|
|
input := make([]byte, 1+BlobSize)
|
|
input[0] = OpBlobToCommitment
|
|
copy(input[1:], blob[:])
|
|
|
|
_, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
input,
|
|
100, // Insufficient gas
|
|
false,
|
|
)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "out of gas")
|
|
}
|
|
|
|
func TestKZG4844_EmptyInput(t *testing.T) {
|
|
_, _, err := KZG4844Precompile.Run(
|
|
nil,
|
|
common.Address{},
|
|
ContractAddress,
|
|
[]byte{},
|
|
100000,
|
|
false,
|
|
)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestKZG4844_RequiredGas(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
op byte
|
|
gas uint64
|
|
}{
|
|
{"BlobToCommitment", OpBlobToCommitment, GasBlobToCommitment},
|
|
{"ComputeProof", OpComputeProof, GasComputeProof},
|
|
{"VerifyProof", OpVerifyProof, GasVerifyProof},
|
|
{"VerifyBlobProof", OpVerifyBlobProof, GasVerifyBlobProof},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
input := append([]byte{tt.op}, make([]byte, 200)...)
|
|
gas := KZG4844Precompile.RequiredGas(input)
|
|
require.Equal(t, tt.gas, gas)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKZG4844_BatchVerifyGas(t *testing.T) {
|
|
tests := []struct {
|
|
numProofs int
|
|
expected uint64
|
|
}{
|
|
{1, GasBatchVerifyBase + 1*GasBatchVerifyPerAdd},
|
|
{2, GasBatchVerifyBase + 2*GasBatchVerifyPerAdd},
|
|
{5, GasBatchVerifyBase + 5*GasBatchVerifyPerAdd},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
input := make([]byte, 3)
|
|
input[0] = OpBatchVerifyProofs
|
|
binary.BigEndian.PutUint16(input[1:], uint16(tt.numProofs))
|
|
|
|
gas := KZG4844Precompile.RequiredGas(input)
|
|
require.Equal(t, tt.expected, gas)
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func createValidBlob(t testing.TB) []byte {
|
|
t.Helper()
|
|
|
|
// Create blob with valid field elements (each < BLS12-381 modulus)
|
|
blob := make([]byte, BlobSize)
|
|
|
|
// Fill with small random values that are definitely in field
|
|
for i := range BlobElements {
|
|
offset := i * FieldElementSize
|
|
// Leave first 16 bytes as zero, set small random value in lower bytes
|
|
// This ensures we're well under the field modulus
|
|
randBytes := make([]byte, 16)
|
|
rand.Read(randBytes)
|
|
copy(blob[offset+16:offset+32], randBytes)
|
|
}
|
|
|
|
return blob
|
|
}
|
|
|
|
// Benchmarks
|
|
|
|
func BenchmarkKZG4844_BlobToCommitment(b *testing.B) {
|
|
blob := make([]byte, BlobSize)
|
|
for i := range BlobElements {
|
|
offset := i * FieldElementSize
|
|
blob[offset+31] = byte(i % 256)
|
|
}
|
|
|
|
input := make([]byte, 1+BlobSize)
|
|
input[0] = OpBlobToCommitment
|
|
copy(input[1:], blob)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _, _ = KZG4844Precompile.Run(nil, common.Address{}, ContractAddress, input, GasBlobToCommitment, false)
|
|
}
|
|
}
|
|
|
|
func BenchmarkKZG4844_ComputeProof(b *testing.B) {
|
|
blob := make([]byte, BlobSize)
|
|
for i := range BlobElements {
|
|
offset := i * FieldElementSize
|
|
blob[offset+31] = byte(i % 256)
|
|
}
|
|
|
|
z := make([]byte, FieldElementSize)
|
|
z[31] = 0x42
|
|
|
|
input := make([]byte, 1+BlobSize+FieldElementSize)
|
|
input[0] = OpComputeProof
|
|
copy(input[1:], blob)
|
|
copy(input[1+BlobSize:], z)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _, _ = KZG4844Precompile.Run(nil, common.Address{}, ContractAddress, input, GasComputeProof, false)
|
|
}
|
|
}
|
|
|
|
func BenchmarkKZG4844_VerifyProof(b *testing.B) {
|
|
blob := make([]byte, BlobSize)
|
|
for i := range BlobElements {
|
|
offset := i * FieldElementSize
|
|
blob[offset+31] = byte(i % 256)
|
|
}
|
|
|
|
// Compute commitment
|
|
commitmentInput := make([]byte, 1+BlobSize)
|
|
commitmentInput[0] = OpBlobToCommitment
|
|
copy(commitmentInput[1:], blob)
|
|
|
|
commitment, _, _ := KZG4844Precompile.Run(nil, common.Address{}, ContractAddress, commitmentInput, GasBlobToCommitment, false)
|
|
|
|
// Compute proof
|
|
z := make([]byte, FieldElementSize)
|
|
z[31] = 0x42
|
|
|
|
proofInput := make([]byte, 1+BlobSize+FieldElementSize)
|
|
proofInput[0] = OpComputeProof
|
|
copy(proofInput[1:], blob)
|
|
copy(proofInput[1+BlobSize:], z)
|
|
|
|
proofResult, _, _ := KZG4844Precompile.Run(nil, common.Address{}, ContractAddress, proofInput, GasComputeProof, false)
|
|
|
|
proof := proofResult[:ProofSize]
|
|
y := proofResult[ProofSize:]
|
|
|
|
verifyInput := make([]byte, 1+CommitmentSize+FieldElementSize+FieldElementSize+ProofSize)
|
|
verifyInput[0] = OpVerifyProof
|
|
offset := 1
|
|
copy(verifyInput[offset:], commitment)
|
|
offset += CommitmentSize
|
|
copy(verifyInput[offset:], z)
|
|
offset += FieldElementSize
|
|
copy(verifyInput[offset:], y)
|
|
offset += FieldElementSize
|
|
copy(verifyInput[offset:], proof)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _, _ = KZG4844Precompile.Run(nil, common.Address{}, ContractAddress, verifyInput, GasVerifyProof, false)
|
|
}
|
|
}
|