mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
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:
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user