precompile/kzg4844: retire BLS12-381 KZG blob-opening precompile

KZG point-evaluation verifies via the BLS12-381 pairing
e(C - [y]G1, G2) == e(W, [tau - z]G2). The discrete-log assumption
that anchors BLS12-381's security is broken by Shor's algorithm, so
EIP-4844-style blob commitments give zero soundness against a future
quantum adversary. The Lux EVM is PQ-only.

Strip the implementation (gokzg4844, accel GPU MSM, all CPU opcodes).
The module still registers the address 0xB002 so the precompile
registry numbering stays stable, but every call to the precompile
returns the new contract.ErrClassicalForbiddenInPQ sentinel. The
minimum 21000 gas is deducted on every call to prevent the retired
address being used as a zero-cost probe.

Blob availability on the Lux EVM is now provided by hash-based
commitments (Blake3 / Poseidon2) and STARK-validated state roots.
This commit is contained in:
2026-05-11 12:28:50 -07:00
parent 555a9b68ff
commit 5786c8f57e
4 changed files with 54 additions and 946 deletions
+7
View File
@@ -18,6 +18,13 @@ var (
ErrOutOfGas = errors.New("out of gas")
ErrInvalidInput = errors.New("invalid input")
ErrInvalidOp = errors.New("invalid operation")
// ErrClassicalForbiddenInPQ is returned by precompiles that have been
// retired because their underlying cryptographic assumption (BN254 /
// BLS12-381 pairing, classical SNARK verifier) is broken by Shor's
// algorithm. The precompile address remains reserved so the registry
// keeps numbering stable; the contract refuses to execute.
ErrClassicalForbiddenInPQ = errors.New("classical pairing-based precompile forbidden in post-quantum Lux EVM")
)
// Gas costs for stateful precompiles
+29 -366
View File
@@ -1,143 +1,60 @@
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package kzg4844 implements EIP-4844 KZG polynomial commitment precompile
// for the Lux EVM. Address: 0x0900...0014 (ZK Proofs range)
// Package kzg4844 was the EIP-4844 KZG polynomial commitment precompile.
//
// Note: Ethereum standard KZG point evaluation is at 0x0a (EIP-4844).
// This extended precompile provides additional operations beyond the standard.
// REMOVED: KZG opening proofs are verified with a BLS12-381 pairing check
// (e(C - [y]G1, G2) == e(proof, [tau - z]G2)). The discrete-log assumption
// that anchors the pairing's security is broken by Shor's algorithm on a
// cryptographically relevant quantum computer, so KZG-based blob
// commitments give zero confidentiality and zero soundness against a
// future quantum adversary. The Lux EVM is PQ-only; we therefore retire
// this precompile.
//
// Provides efficient blob verification using Kate-Zaverucha-Goldberg commitments
// with BLS12-381 curve. See LP-3665 for full specification.
// The address 0xB002 is kept reserved (the module still registers) so
// that the precompile registry numbering is stable across releases.
// Any call to this contract returns contract.ErrClassicalForbiddenInPQ.
//
// Blob availability for the Lux EVM is provided by hash-based commitments
// (Blake3 / Poseidon2) and STARK-validated state roots, not KZG.
package kzg4844
import (
"encoding/binary"
"errors"
"fmt"
"github.com/luxfi/accel"
gokzg4844 "github.com/crate-crypto/go-kzg-4844"
"github.com/luxfi/geth/common"
"github.com/luxfi/precompile/contract"
)
var (
// ContractAddress is the address of the KZG4844 precompile (Lux KZG Extensions range 0xB002)
// Note: Standard EIP-4844 point evaluation is at 0x0a
// ContractAddress is the (reserved) address of the retired KZG4844
// precompile. Calls to this address return ErrClassicalForbiddenInPQ.
ContractAddress = common.HexToAddress("0xB002")
// Singleton instance
// KZG4844Precompile is the retired-stub singleton.
KZG4844Precompile = &kzg4844Precompile{}
_ contract.StatefulPrecompiledContract = &kzg4844Precompile{}
// Trusted setup context (initialized once)
kzgContext *gokzg4844.Context
ErrInvalidInput = contract.ErrInvalidInput
ErrInvalidBlob = errors.New("invalid blob data")
ErrInvalidCommitment = errors.New("invalid commitment")
ErrInvalidProof = errors.New("invalid proof")
ErrVerificationFailed = errors.New("verification failed")
ErrContextNotInit = errors.New("KZG context not initialized")
)
// Sizes per EIP-4844 spec
const (
BlobSize = 131072 // 4096 * 32 bytes
CommitmentSize = 48 // G1 point compressed
ProofSize = 48 // G1 point compressed
FieldElementSize = 32 // BLS12-381 scalar field element
BlobElements = 4096 // Number of field elements per blob
)
// Operation selectors
const (
OpBlobToCommitment = 0x01
OpComputeProof = 0x02
OpVerifyProof = 0x03
OpVerifyBlobProof = 0x04
OpBatchVerifyProofs = 0x10
OpComputeChallenge = 0x20
OpEvaluatePolynomial = 0x21
)
// Gas costs aligned with EIP-4844
const (
GasBlobToCommitment = 50000
GasComputeProof = 50000
GasVerifyProof = 50000
GasVerifyBlobProof = 50000
GasBatchVerifyBase = 50000
GasBatchVerifyPerAdd = 10000
GasComputeChallenge = 1000
GasEvaluateBase = 5000
GasEvaluatePerElem = 10
)
func init() {
// Initialize trusted setup from embedded parameters
var err error
kzgContext, err = gokzg4844.NewContext4096Secure()
if err != nil {
// Will fail on operations if not initialized
kzgContext = nil
}
}
// MinCallGas is charged on every call so that the retired precompile
// cannot be probed for free.
const MinCallGas uint64 = 21_000
type kzg4844Precompile struct{}
// Address returns the address of the KZG4844 precompile
// Address returns the reserved address of the retired KZG4844 precompile.
func (p *kzg4844Precompile) Address() common.Address {
return ContractAddress
}
// RequiredGas calculates gas for KZG4844 operations
// RequiredGas always returns MinCallGas; the precompile is retired and
// rejects every call.
func (p *kzg4844Precompile) RequiredGas(input []byte) uint64 {
if len(input) < 1 {
return 0
}
op := input[0]
switch op {
case OpBlobToCommitment:
return GasBlobToCommitment
case OpComputeProof:
return GasComputeProof
case OpVerifyProof:
return GasVerifyProof
case OpVerifyBlobProof:
return GasVerifyBlobProof
case OpBatchVerifyProofs:
if len(input) < 3 {
return GasBatchVerifyBase
}
numProofs := int(binary.BigEndian.Uint16(input[1:3]))
return GasBatchVerifyBase + uint64(numProofs)*GasBatchVerifyPerAdd
case OpComputeChallenge:
return GasComputeChallenge
case OpEvaluatePolynomial:
if len(input) < 5 {
return GasEvaluateBase
}
numCoeffs := int(binary.BigEndian.Uint32(input[1:5]))
return GasEvaluateBase + uint64(numCoeffs)*GasEvaluatePerElem
default:
return 0
}
return MinCallGas
}
// Run executes the KZG4844 precompile
// Run refuses all calls with ErrClassicalForbiddenInPQ. The minimum gas
// is still deducted so that callers cannot use the retired address as a
// zero-cost oracle.
func (p *kzg4844Precompile) Run(
accessibleState contract.AccessibleState,
caller common.Address,
@@ -146,263 +63,9 @@ func (p *kzg4844Precompile) Run(
suppliedGas uint64,
readOnly bool,
) ([]byte, uint64, error) {
gasCost := p.RequiredGas(input)
remainingGas, err := contract.DeductGas(suppliedGas, gasCost)
remainingGas, err := contract.DeductGas(suppliedGas, MinCallGas)
if err != nil {
return nil, 0, err
}
if len(input) < 1 {
return nil, remainingGas, ErrInvalidInput
}
if kzgContext == nil {
return nil, remainingGas, ErrContextNotInit
}
op := input[0]
var result []byte
switch op {
case OpBlobToCommitment:
result, err = p.blobToCommitment(input[1:])
case OpComputeProof:
result, err = p.computeProof(input[1:])
case OpVerifyProof:
result, err = p.verifyProof(input[1:])
case OpVerifyBlobProof:
result, err = p.verifyBlobProof(input[1:])
case OpBatchVerifyProofs:
result, err = p.batchVerifyProofs(input[1:])
case OpComputeChallenge:
result, err = p.computeChallenge(input[1:])
default:
err = fmt.Errorf("unsupported operation: 0x%02x", op)
}
if err != nil {
return nil, remainingGas, err
}
return result, remainingGas, nil
}
// blobToCommitment computes the KZG commitment for a blob
func (p *kzg4844Precompile) blobToCommitment(input []byte) ([]byte, error) {
if len(input) < BlobSize {
return nil, ErrInvalidBlob
}
// Try GPU-accelerated polynomial commitment
if result, gpuUsed := blobToCommitmentGPU(input[:BlobSize]); gpuUsed {
return result, nil
}
// CPU fallback
var blob gokzg4844.Blob
copy(blob[:], input[:BlobSize])
commitment, err := kzgContext.BlobToKZGCommitment(&blob, 0)
if err != nil {
return nil, fmt.Errorf("commitment failed: %w", err)
}
return commitment[:], nil
}
// computeProof computes a KZG proof for a blob at a given point
func (p *kzg4844Precompile) computeProof(input []byte) ([]byte, error) {
if len(input) < BlobSize+FieldElementSize {
return nil, ErrInvalidInput
}
var blob gokzg4844.Blob
copy(blob[:], input[:BlobSize])
// Parse evaluation point
var z gokzg4844.Scalar
copy(z[:], input[BlobSize:BlobSize+FieldElementSize])
// Compute proof
proof, y, err := kzgContext.ComputeKZGProof(&blob, z, 0)
if err != nil {
return nil, fmt.Errorf("proof computation failed: %w", err)
}
// Return proof || y
result := make([]byte, ProofSize+FieldElementSize)
copy(result, proof[:])
copy(result[ProofSize:], y[:])
return result, nil
}
// verifyProof verifies a KZG proof
func (p *kzg4844Precompile) verifyProof(input []byte) ([]byte, error) {
// commitment (48) + z (32) + y (32) + proof (48)
expectedLen := CommitmentSize + FieldElementSize + FieldElementSize + ProofSize
if len(input) < expectedLen {
return nil, ErrInvalidInput
}
var commitment gokzg4844.KZGCommitment
copy(commitment[:], input[:CommitmentSize])
var z, y gokzg4844.Scalar
copy(z[:], input[CommitmentSize:CommitmentSize+FieldElementSize])
copy(y[:], input[CommitmentSize+FieldElementSize:CommitmentSize+2*FieldElementSize])
var proof gokzg4844.KZGProof
copy(proof[:], input[CommitmentSize+2*FieldElementSize:])
err := kzgContext.VerifyKZGProof(commitment, z, y, proof)
if err != nil {
return []byte{0x00}, nil
}
return []byte{0x01}, nil
}
// verifyBlobProof verifies a blob KZG proof (EIP-4844 point_evaluation_precompile)
func (p *kzg4844Precompile) verifyBlobProof(input []byte) ([]byte, error) {
// blob (131072) + commitment (48) + proof (48)
expectedLen := BlobSize + CommitmentSize + ProofSize
if len(input) < expectedLen {
return nil, ErrInvalidInput
}
var blob gokzg4844.Blob
copy(blob[:], input[:BlobSize])
var commitment gokzg4844.KZGCommitment
copy(commitment[:], input[BlobSize:BlobSize+CommitmentSize])
var proof gokzg4844.KZGProof
copy(proof[:], input[BlobSize+CommitmentSize:])
err := kzgContext.VerifyBlobKZGProof(&blob, commitment, proof)
if err != nil {
return []byte{0x00}, nil
}
return []byte{0x01}, nil
}
// batchVerifyProofs verifies multiple KZG proofs efficiently
func (p *kzg4844Precompile) batchVerifyProofs(input []byte) ([]byte, error) {
if len(input) < 2 {
return nil, ErrInvalidInput
}
numProofs := int(binary.BigEndian.Uint16(input[:2]))
if numProofs == 0 {
return []byte{0x01}, nil
}
offset := 2
// Each proof set: commitment (48) + z (32) + y (32) + proof (48) = 160 bytes
proofSetSize := CommitmentSize + FieldElementSize + FieldElementSize + ProofSize
expectedLen := 2 + numProofs*proofSetSize
if len(input) < expectedLen {
return nil, ErrInvalidInput
}
commitments := make([]gokzg4844.KZGCommitment, numProofs)
zs := make([]gokzg4844.Scalar, numProofs)
ys := make([]gokzg4844.Scalar, numProofs)
proofs := make([]gokzg4844.KZGProof, numProofs)
for i := range numProofs {
copy(commitments[i][:], input[offset:offset+CommitmentSize])
offset += CommitmentSize
copy(zs[i][:], input[offset:offset+FieldElementSize])
offset += FieldElementSize
copy(ys[i][:], input[offset:offset+FieldElementSize])
offset += FieldElementSize
copy(proofs[i][:], input[offset:offset+ProofSize])
offset += ProofSize
}
// Verify all proofs individually (batch verification could be optimized)
for i := range numProofs {
err := kzgContext.VerifyKZGProof(commitments[i], zs[i], ys[i], proofs[i])
if err != nil {
return []byte{0x00}, nil
}
}
return []byte{0x01}, nil
}
// computeChallenge computes the Fiat-Shamir challenge for a blob commitment
func (p *kzg4844Precompile) computeChallenge(input []byte) ([]byte, error) {
if len(input) < CommitmentSize {
return nil, ErrInvalidInput
}
// Simple challenge: hash the commitment
// In practice, would include more context
var commitment gokzg4844.KZGCommitment
copy(commitment[:], input[:CommitmentSize])
// Use commitment hash as challenge (simplified)
var challenge gokzg4844.Scalar
copy(challenge[:], commitment[:32])
return challenge[:], nil
}
// blobToCommitmentGPU attempts GPU-accelerated KZG polynomial commitment.
// Returns (commitment, true) if GPU was used, (nil, false) if unavailable.
func blobToCommitmentGPU(blobData []byte) ([]byte, bool) {
if !accel.Available() {
return nil, false
}
sess, err := accel.NewSession()
if err != nil {
return nil, false
}
defer sess.Close()
zk := sess.ZK()
// CommitPoly expects:
// coeffs: [degree+1, field_size] bytes -- blob field elements
// srs: structured reference string (trusted setup)
// commitment: [point_size] bytes
coeffsTensor, err := accel.NewTensorWithData[byte](sess, []int{BlobElements, FieldElementSize}, blobData)
if err != nil {
return nil, false
}
defer coeffsTensor.Close()
// SRS is empty tensor -- GPU backend uses its own embedded trusted setup
srsTensor, err := accel.NewTensor[byte](sess, []int{1})
if err != nil {
return nil, false
}
defer srsTensor.Close()
commitTensor, err := accel.NewTensor[byte](sess, []int{CommitmentSize})
if err != nil {
return nil, false
}
defer commitTensor.Close()
if err := zk.CommitPoly(coeffsTensor.Untyped(), srsTensor.Untyped(), commitTensor.Untyped()); err != nil {
return nil, false
}
result, err := commitTensor.ToSlice()
if err != nil {
return nil, false
}
return result, true
return nil, remainingGas, contract.ErrClassicalForbiddenInPQ
}
+16 -579
View File
@@ -4,594 +4,31 @@
package kzg4844
import (
"crypto/rand"
"encoding/binary"
"testing"
gokzg4844 "github.com/crate-crypto/go-kzg-4844"
"github.com/luxfi/geth/common"
"github.com/luxfi/precompile/contract"
"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())
// TestRetiredAddress confirms the reserved address is stable across releases.
func TestRetiredAddress(t *testing.T) {
require.Equal(t, common.HexToAddress("0xB002"), 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(
// TestForbiddenInPQ confirms every call refuses execution and deducts
// the minimum gas to prevent zero-cost probing.
func TestForbiddenInPQ(t *testing.T) {
for _, input := range [][]byte{
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
}
func TestKZG4844_VerifyProof_Invalid(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 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:]
// Corrupt the proof
proof[0] ^= 0xFF
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{0x00}, result) // Invalid
}
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,
{},
{0x01},
make([]byte, 4096),
} {
_, remainingGas, err := KZG4844Precompile.Run(
nil, common.Address{}, ContractAddress, input, MinCallGas, true,
)
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_ComputeChallenge(t *testing.T) {
commitment := make([]byte, CommitmentSize)
rand.Read(commitment)
input := make([]byte, 1+CommitmentSize)
input[0] = OpComputeChallenge
copy(input[1:], commitment)
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, FieldElementSize, len(result))
}
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},
{"ComputeChallenge", OpComputeChallenge, GasComputeChallenge},
}
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)
require.ErrorIs(t, err, contract.ErrClassicalForbiddenInPQ)
require.Equal(t, uint64(0), remainingGas)
}
}
+2 -1
View File
@@ -11,9 +11,10 @@ import (
"github.com/stretchr/testify/require"
)
// TestGasZero_Rejected asserts the retired precompile refuses to execute
// with zero gas and returns contract.ErrOutOfGas.
func TestGasZero_Rejected(t *testing.T) {
input := make([]byte, 192)
input[0] = OpVerifyProof // 0x03
_, remainingGas, err := KZG4844Precompile.Run(nil, common.Address{}, ContractAddress, input, 0, true)
require.Error(t, err)
require.ErrorIs(t, err, contract.ErrOutOfGas)