mirror of
https://github.com/luxfi/precompile.git
synced 2026-07-27 03:33:45 +00:00
D1: verifyTEESignature now parses the receipt's X.509 chain, verifies it to an embedded Intel SGX Root CA at the report's timestamp, and verifies the report signature by the leaf key (ECDSA raw/ASN.1, Ed25519, RSA). Fails closed; never returns true by default. B2: fflonkVerify now performs two real BLS12-381 KZG pairing openings bound by Fiat-Shamir so every commitment, opening proof, claim and bound evaluation is load-bearing; off-curve/wrong-subgroup G1 points rejected via gnark-crypto. isValidCompressedPoint now decodes the sign bit and checks x<p and that x^3+5 is a QR mod the Pallas field (on-curve == in-subgroup for cofactor 1). Tests: KAT happy paths + tampered/wrong-signer/truncated/expired/empty-root and soundness negatives; halo2 fixtures emit real on-curve points.
1801 lines
50 KiB
Go
1801 lines
50 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||
// See the file LICENSE for licensing terms.
|
||
|
||
package zk
|
||
|
||
import (
|
||
"encoding/hex"
|
||
"math/big"
|
||
"testing"
|
||
|
||
"github.com/luxfi/crypto/bn256"
|
||
"github.com/luxfi/geth/common"
|
||
)
|
||
|
||
// TestNewZKVerifier tests verifier creation
|
||
func TestNewZKVerifier(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
if zv == nil {
|
||
t.Fatal("Expected non-nil ZKVerifier")
|
||
}
|
||
|
||
if zv.VerifyingKeys == nil {
|
||
t.Error("Expected VerifyingKeys map to be initialized")
|
||
}
|
||
if zv.Nullifiers == nil {
|
||
t.Error("Expected Nullifiers map to be initialized")
|
||
}
|
||
if zv.Commitments == nil {
|
||
t.Error("Expected Commitments map to be initialized")
|
||
}
|
||
if zv.Rollups == nil {
|
||
t.Error("Expected Rollups map to be initialized")
|
||
}
|
||
if zv.Pools == nil {
|
||
t.Error("Expected Pools map to be initialized")
|
||
}
|
||
}
|
||
|
||
// TestRegisterVerifyingKey tests verifying key registration
|
||
func TestRegisterVerifyingKey(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
alpha := []byte("alpha_point")
|
||
beta := []byte("beta_point")
|
||
gamma := []byte("gamma_point")
|
||
delta := []byte("delta_point")
|
||
ic := [][]byte{[]byte("ic0"), []byte("ic1"), []byte("ic2")}
|
||
|
||
keyID, err := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16,
|
||
CircuitTransfer,
|
||
alpha, beta, gamma, delta, ic,
|
||
)
|
||
|
||
if err != nil {
|
||
t.Fatalf("RegisterVerifyingKey failed: %v", err)
|
||
}
|
||
|
||
if keyID == [32]byte{} {
|
||
t.Error("Expected non-zero key ID")
|
||
}
|
||
|
||
// Verify key was stored
|
||
vk := zv.VerifyingKeys[keyID]
|
||
if vk == nil {
|
||
t.Fatal("Verifying key not stored")
|
||
}
|
||
if vk.ProofSystem != ProofSystemGroth16 {
|
||
t.Errorf("Expected Groth16, got %v", vk.ProofSystem)
|
||
}
|
||
if vk.CircuitType != CircuitTransfer {
|
||
t.Errorf("Expected Transfer circuit, got %v", vk.CircuitType)
|
||
}
|
||
if vk.Owner != owner {
|
||
t.Error("Owner mismatch")
|
||
}
|
||
if len(vk.IC) != 3 {
|
||
t.Errorf("Expected 3 IC points, got %d", len(vk.IC))
|
||
}
|
||
}
|
||
|
||
// TestVerifyGroth16 tests Groth16 proof verification
|
||
func TestVerifyGroth16(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
// Register verifying key with 3 IC points (2 public inputs + 1)
|
||
ic := [][]byte{[]byte("ic0"), []byte("ic1"), []byte("ic2")}
|
||
keyID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16,
|
||
CircuitTransfer,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
ic,
|
||
)
|
||
|
||
// Create proof
|
||
proofA := make([]byte, 64)
|
||
proofB := make([]byte, 128)
|
||
proofC := make([]byte, 64)
|
||
publicInputs := []*big.Int{big.NewInt(100), big.NewInt(200)}
|
||
|
||
result, err := zv.VerifyGroth16(keyID, proofA, proofB, proofC, publicInputs)
|
||
if err != nil {
|
||
t.Fatalf("VerifyGroth16 failed: %v", err)
|
||
}
|
||
|
||
if result == nil {
|
||
t.Fatal("Expected non-nil result")
|
||
}
|
||
if result.ProofSystem != ProofSystemGroth16 {
|
||
t.Errorf("Expected Groth16, got %v", result.ProofSystem)
|
||
}
|
||
if result.GasUsed != GasGroth16Verify {
|
||
t.Errorf("Expected gas %d, got %d", GasGroth16Verify, result.GasUsed)
|
||
}
|
||
if len(result.PublicInputs) != 2 {
|
||
t.Errorf("Expected 2 public inputs, got %d", len(result.PublicInputs))
|
||
}
|
||
|
||
// Verify stats updated
|
||
if zv.TotalVerifications != 1 {
|
||
t.Errorf("Expected 1 verification, got %d", zv.TotalVerifications)
|
||
}
|
||
}
|
||
|
||
// TestVerifyGroth16InvalidKey tests error for non-existent key
|
||
func TestVerifyGroth16InvalidKey(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
|
||
nonExistent := [32]byte{0xFF}
|
||
_, err := zv.VerifyGroth16(nonExistent, nil, nil, nil, nil)
|
||
if err != ErrInvalidVerifyingKey {
|
||
t.Errorf("Expected ErrInvalidVerifyingKey, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyGroth16WrongProofSystem tests proof system validation
|
||
func TestVerifyGroth16WrongProofSystem(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
// Register as PLONK
|
||
keyID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemPlonk,
|
||
CircuitTransfer,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
|
||
// Try to verify as Groth16
|
||
_, err := zv.VerifyGroth16(keyID, nil, nil, nil, nil)
|
||
if err != ErrProofSystemMismatch {
|
||
t.Errorf("Expected ErrProofSystemMismatch, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyGroth16InvalidPublicInputs tests public input count validation
|
||
func TestVerifyGroth16InvalidPublicInputs(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
// Register with 3 IC points (expects 2 public inputs)
|
||
keyID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16,
|
||
CircuitTransfer,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0"), []byte("ic1"), []byte("ic2")},
|
||
)
|
||
|
||
// Provide wrong number of public inputs
|
||
publicInputs := []*big.Int{big.NewInt(1)} // Only 1, should be 2
|
||
|
||
_, err := zv.VerifyGroth16(keyID, []byte("a"), []byte("b"), []byte("c"), publicInputs)
|
||
if err != ErrInvalidPublicInputs {
|
||
t.Errorf("Expected ErrInvalidPublicInputs, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyPlonk tests PLONK proof verification
|
||
func TestVerifyPlonk(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
keyID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemPlonk,
|
||
CircuitRollupBatch,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
|
||
proof := make([]byte, 512)
|
||
publicInputs := []*big.Int{big.NewInt(100)}
|
||
|
||
result, err := zv.VerifyPlonk(keyID, proof, publicInputs)
|
||
if err != nil {
|
||
t.Fatalf("VerifyPlonk failed: %v", err)
|
||
}
|
||
|
||
if result == nil {
|
||
t.Fatal("Expected non-nil result")
|
||
}
|
||
if result.ProofSystem != ProofSystemPlonk {
|
||
t.Errorf("Expected PLONK, got %v", result.ProofSystem)
|
||
}
|
||
if result.GasUsed != GasPlonkVerify {
|
||
t.Errorf("Expected gas %d, got %d", GasPlonkVerify, result.GasUsed)
|
||
}
|
||
}
|
||
|
||
// TestCheckNullifier tests nullifier checking
|
||
func TestCheckNullifier(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
|
||
nullifierHash := [32]byte{0x01, 0x02, 0x03}
|
||
|
||
// Initially not spent
|
||
spent, err := zv.CheckNullifier(nullifierHash)
|
||
if err != nil {
|
||
t.Fatalf("CheckNullifier failed: %v", err)
|
||
}
|
||
if spent {
|
||
t.Error("Expected nullifier to be unspent")
|
||
}
|
||
|
||
// Spend it
|
||
txHash := common.HexToHash("0xABCDEF")
|
||
err = zv.SpendNullifier(nullifierHash, txHash, 100)
|
||
if err != nil {
|
||
t.Fatalf("SpendNullifier failed: %v", err)
|
||
}
|
||
|
||
// Now should be spent
|
||
spent, err = zv.CheckNullifier(nullifierHash)
|
||
if err != nil {
|
||
t.Fatalf("CheckNullifier failed: %v", err)
|
||
}
|
||
if !spent {
|
||
t.Error("Expected nullifier to be spent")
|
||
}
|
||
}
|
||
|
||
// TestSpendNullifierAlreadySpent tests double-spend prevention
|
||
func TestSpendNullifierAlreadySpent(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
|
||
nullifierHash := [32]byte{0x01, 0x02, 0x03}
|
||
txHash := common.HexToHash("0xABCDEF")
|
||
|
||
// First spend
|
||
err := zv.SpendNullifier(nullifierHash, txHash, 100)
|
||
if err != nil {
|
||
t.Fatalf("First spend failed: %v", err)
|
||
}
|
||
|
||
// Second spend should fail
|
||
err = zv.SpendNullifier(nullifierHash, txHash, 101)
|
||
if err != ErrNullifierSpent {
|
||
t.Errorf("Expected ErrNullifierSpent, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestCreateConfidentialPool tests pool creation
|
||
func TestCreateConfidentialPool(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||
|
||
poolID, err := zv.CreateConfidentialPool(owner, token, 32)
|
||
if err != nil {
|
||
t.Fatalf("CreateConfidentialPool failed: %v", err)
|
||
}
|
||
|
||
if poolID == [32]byte{} {
|
||
t.Error("Expected non-zero pool ID")
|
||
}
|
||
|
||
// Verify pool was created
|
||
pool := zv.Pools[poolID]
|
||
if pool == nil {
|
||
t.Fatal("Pool not stored")
|
||
}
|
||
if pool.Token != token {
|
||
t.Error("Token mismatch")
|
||
}
|
||
if pool.MerkleDepth != 32 {
|
||
t.Errorf("Expected depth 32, got %d", pool.MerkleDepth)
|
||
}
|
||
if !pool.Enabled {
|
||
t.Error("Expected pool to be enabled")
|
||
}
|
||
}
|
||
|
||
// TestAddCommitment tests adding commitment to pool
|
||
func TestAddCommitment(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||
|
||
poolID, _ := zv.CreateConfidentialPool(owner, token, 32)
|
||
|
||
commitment := &Commitment{
|
||
Value: []byte("commitment_hash"),
|
||
Amount: big.NewInt(1e18),
|
||
Blinding: []byte("blinding_factor"),
|
||
}
|
||
|
||
commitID, err := zv.AddCommitment(poolID, commitment)
|
||
if err != nil {
|
||
t.Fatalf("AddCommitment failed: %v", err)
|
||
}
|
||
|
||
if commitID == [32]byte{} {
|
||
t.Error("Expected non-zero commitment ID")
|
||
}
|
||
|
||
// Verify pool state updated
|
||
pool := zv.Pools[poolID]
|
||
if pool.TotalDeposits.Cmp(big.NewInt(1e18)) != 0 {
|
||
t.Error("Total deposits not updated")
|
||
}
|
||
}
|
||
|
||
// TestAddCommitmentPoolNotFound tests error for non-existent pool
|
||
func TestAddCommitmentPoolNotFound(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
|
||
nonExistent := [32]byte{0xFF}
|
||
_, err := zv.AddCommitment(nonExistent, &Commitment{})
|
||
if err != ErrPoolNotFound {
|
||
t.Errorf("Expected ErrPoolNotFound, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestAddCommitmentPoolDisabled tests error for disabled pool
|
||
func TestAddCommitmentPoolDisabled(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||
|
||
poolID, _ := zv.CreateConfidentialPool(owner, token, 32)
|
||
|
||
// Disable pool
|
||
zv.Pools[poolID].Enabled = false
|
||
|
||
_, err := zv.AddCommitment(poolID, &Commitment{Amount: big.NewInt(1)})
|
||
if err != ErrPoolDisabled {
|
||
t.Errorf("Expected ErrPoolDisabled, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyCommitmentInclusion tests merkle proof verification
|
||
func TestVerifyCommitmentInclusion(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
token := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||
|
||
poolID, _ := zv.CreateConfidentialPool(owner, token, 32)
|
||
|
||
// Test with empty proof (function still works for basic check)
|
||
valid, err := zv.VerifyCommitmentInclusion(poolID, [32]byte{0x01}, [][]byte{}, 0)
|
||
if err != nil {
|
||
t.Fatalf("VerifyCommitmentInclusion failed: %v", err)
|
||
}
|
||
|
||
_ = valid // Returns true for empty root (valid by convention)
|
||
}
|
||
|
||
// TestRegisterRollup tests rollup registration
|
||
func TestRegisterRollup(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
sequencer := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||
|
||
// First register a verifying key
|
||
vkID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16,
|
||
CircuitRollupBatch,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
|
||
rollupID, err := zv.RegisterRollup(
|
||
owner,
|
||
vkID,
|
||
ProofSystemGroth16,
|
||
1000, // max tx per batch
|
||
60, // batch interval
|
||
sequencer,
|
||
)
|
||
|
||
if err != nil {
|
||
t.Fatalf("RegisterRollup failed: %v", err)
|
||
}
|
||
|
||
if rollupID == [32]byte{} {
|
||
t.Error("Expected non-zero rollup ID")
|
||
}
|
||
|
||
// Verify rollup config
|
||
config := zv.Rollups[rollupID]
|
||
if config == nil {
|
||
t.Fatal("Rollup config not stored")
|
||
}
|
||
if config.Owner != owner {
|
||
t.Error("Owner mismatch")
|
||
}
|
||
if config.Sequencer != sequencer {
|
||
t.Error("Sequencer mismatch")
|
||
}
|
||
if config.MaxTxPerBatch != 1000 {
|
||
t.Errorf("Expected max tx 1000, got %d", config.MaxTxPerBatch)
|
||
}
|
||
if !config.Enabled {
|
||
t.Error("Expected rollup to be enabled")
|
||
}
|
||
|
||
// Verify state initialized
|
||
state := zv.RollupStates[rollupID]
|
||
if state == nil {
|
||
t.Fatal("Rollup state not initialized")
|
||
}
|
||
}
|
||
|
||
// TestRegisterRollupInvalidKey tests error for non-existent verifying key
|
||
func TestRegisterRollupInvalidKey(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
nonExistent := [32]byte{0xFF}
|
||
_, err := zv.RegisterRollup(owner, nonExistent, ProofSystemGroth16, 1000, 60, owner)
|
||
if err != ErrInvalidVerifyingKey {
|
||
t.Errorf("Expected ErrInvalidVerifyingKey, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyRollupBatch tests batch verification
|
||
// Note: With real cryptographic verification, invalid proof data is rejected.
|
||
// This test verifies the error handling for invalid proofs.
|
||
func TestVerifyRollupBatch(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
// Setup rollup with test VK (valid format but won't verify real proofs)
|
||
vkID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16,
|
||
CircuitRollupBatch,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0"), []byte("ic1"), []byte("ic2"), []byte("ic3"), []byte("ic4")},
|
||
)
|
||
rollupID, _ := zv.RegisterRollup(owner, vkID, ProofSystemGroth16, 1000, 60, owner)
|
||
|
||
// Create batch with test proof data (invalid curve points)
|
||
batch := &RollupBatch{
|
||
BatchID: [32]byte{0x01},
|
||
RollupID: rollupID,
|
||
PrevStateRoot: [32]byte{}, // Matches initial state
|
||
NewStateRoot: [32]byte{0x02},
|
||
Transactions: 500,
|
||
L1BatchNum: 1,
|
||
Proposer: owner,
|
||
Proof: &Proof{
|
||
A: make([]byte, 64),
|
||
B: make([]byte, 128),
|
||
C: make([]byte, 64),
|
||
},
|
||
}
|
||
|
||
// With real crypto verification, invalid proof data should fail
|
||
err := zv.VerifyRollupBatch(rollupID, batch)
|
||
if err != ErrInvalidProof {
|
||
t.Errorf("Expected ErrInvalidProof for invalid proof data, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyRollupBatchStateUpdate tests that valid state updates work
|
||
// when cryptographic verification is bypassed (integration test pattern)
|
||
func TestVerifyRollupBatchStateUpdate(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
// Setup rollup
|
||
vkID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16,
|
||
CircuitRollupBatch,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0"), []byte("ic1"), []byte("ic2"), []byte("ic3"), []byte("ic4")},
|
||
)
|
||
rollupID, _ := zv.RegisterRollup(owner, vkID, ProofSystemGroth16, 1000, 60, owner)
|
||
|
||
// Verify initial state
|
||
state, err := zv.GetRollupState(rollupID)
|
||
if err != nil {
|
||
t.Fatalf("GetRollupState failed: %v", err)
|
||
}
|
||
if state.TotalBatches != 0 {
|
||
t.Errorf("Expected 0 initial batches, got %d", state.TotalBatches)
|
||
}
|
||
if state.LastStateRoot != ([32]byte{}) {
|
||
t.Error("Expected empty initial state root")
|
||
}
|
||
}
|
||
|
||
// TestVerifyRollupBatchNotFound tests error for non-existent rollup
|
||
func TestVerifyRollupBatchNotFound(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
|
||
nonExistent := [32]byte{0xFF}
|
||
err := zv.VerifyRollupBatch(nonExistent, &RollupBatch{})
|
||
if err != ErrRollupNotFound {
|
||
t.Errorf("Expected ErrRollupNotFound, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyRollupBatchUnauthorized tests proposer authorization
|
||
func TestVerifyRollupBatchUnauthorized(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
sequencer := common.HexToAddress("0xABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD")
|
||
unauthorized := common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||
|
||
vkID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16,
|
||
CircuitRollupBatch,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
rollupID, _ := zv.RegisterRollup(owner, vkID, ProofSystemGroth16, 1000, 60, sequencer)
|
||
|
||
batch := &RollupBatch{
|
||
RollupID: rollupID,
|
||
Proposer: unauthorized,
|
||
}
|
||
|
||
err := zv.VerifyRollupBatch(rollupID, batch)
|
||
if err != ErrUnauthorizedProposer {
|
||
t.Errorf("Expected ErrUnauthorizedProposer, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyRollupBatchTooLarge tests batch size validation
|
||
func TestVerifyRollupBatchTooLarge(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
vkID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16,
|
||
CircuitRollupBatch,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
rollupID, _ := zv.RegisterRollup(owner, vkID, ProofSystemGroth16, 100, 60, owner)
|
||
|
||
batch := &RollupBatch{
|
||
RollupID: rollupID,
|
||
Transactions: 500, // Exceeds max 100
|
||
Proposer: owner,
|
||
}
|
||
|
||
err := zv.VerifyRollupBatch(rollupID, batch)
|
||
if err != ErrBatchTooLarge {
|
||
t.Errorf("Expected ErrBatchTooLarge, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyRollupBatchInvalidStateRoot tests state root validation
|
||
func TestVerifyRollupBatchInvalidStateRoot(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
vkID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16,
|
||
CircuitRollupBatch,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
rollupID, _ := zv.RegisterRollup(owner, vkID, ProofSystemGroth16, 1000, 60, owner)
|
||
|
||
batch := &RollupBatch{
|
||
RollupID: rollupID,
|
||
PrevStateRoot: [32]byte{0xFF}, // Doesn't match current state (empty)
|
||
Transactions: 10,
|
||
Proposer: owner,
|
||
}
|
||
|
||
err := zv.VerifyRollupBatch(rollupID, batch)
|
||
if err != ErrInvalidStateRoot {
|
||
t.Errorf("Expected ErrInvalidStateRoot, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestGetRollupState tests state retrieval
|
||
func TestGetRollupState(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
vkID, _ := zv.RegisterVerifyingKey(
|
||
owner, ProofSystemGroth16, CircuitRollupBatch,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
rollupID, _ := zv.RegisterRollup(owner, vkID, ProofSystemGroth16, 1000, 60, owner)
|
||
|
||
state, err := zv.GetRollupState(rollupID)
|
||
if err != nil {
|
||
t.Fatalf("GetRollupState failed: %v", err)
|
||
}
|
||
|
||
if state == nil {
|
||
t.Fatal("Expected non-nil state")
|
||
}
|
||
if state.TotalBatches != 0 {
|
||
t.Errorf("Expected 0 batches, got %d", state.TotalBatches)
|
||
}
|
||
}
|
||
|
||
// TestGetRollupStateNotFound tests error for non-existent rollup
|
||
func TestGetRollupStateNotFound(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
|
||
nonExistent := [32]byte{0xFF}
|
||
_, err := zv.GetRollupState(nonExistent)
|
||
if err != ErrRollupNotFound {
|
||
t.Errorf("Expected ErrRollupNotFound, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyKZGNotInitialized tests KZG verification without setup
|
||
func TestVerifyKZGNotInitialized(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
|
||
_, err := zv.VerifyKZG([]byte("commit"), big.NewInt(1), big.NewInt(2), []byte("proof"))
|
||
if err == nil {
|
||
t.Error("Expected error for uninitialized KZG setup")
|
||
}
|
||
}
|
||
|
||
// TestVerifyRangeProof tests range proof verification
|
||
func TestVerifyRangeProof(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
|
||
commitment := make([]byte, 32)
|
||
rangeProof := make([]byte, 128)
|
||
|
||
valid, err := zv.VerifyRangeProof(commitment, rangeProof, 64)
|
||
if err != nil {
|
||
t.Fatalf("VerifyRangeProof failed: %v", err)
|
||
}
|
||
|
||
_ = valid // Returns true for range proof structural validation
|
||
}
|
||
|
||
// TestVerificationStatistics tests stats tracking
|
||
func TestVerificationStatistics(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
// Register keys (use different values to avoid keyID collision)
|
||
grothKeyID, err := zv.RegisterVerifyingKey(
|
||
owner, ProofSystemGroth16, CircuitTransfer,
|
||
[]byte("groth16_alpha"), []byte("groth16_beta"), []byte("groth16_gamma"), []byte("groth16_delta"),
|
||
[][]byte{[]byte("ic0"), []byte("ic1")},
|
||
)
|
||
if err != nil {
|
||
t.Fatalf("Failed to register Groth16 key: %v", err)
|
||
}
|
||
|
||
plonkKeyID, err := zv.RegisterVerifyingKey(
|
||
owner, ProofSystemPlonk, CircuitTransfer,
|
||
[]byte("plonk_alpha"), []byte("plonk_beta"), []byte("plonk_gamma"), []byte("plonk_delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
if err != nil {
|
||
t.Fatalf("Failed to register PLONK key: %v", err)
|
||
}
|
||
|
||
// Perform verifications - each should increment TotalVerifications
|
||
_, err = zv.VerifyGroth16(grothKeyID, []byte("a"), []byte("b"), []byte("c"), []*big.Int{big.NewInt(1)})
|
||
if err != nil {
|
||
t.Logf("VerifyGroth16 #1 error: %v", err)
|
||
}
|
||
|
||
_, err = zv.VerifyGroth16(grothKeyID, []byte("a"), []byte("b"), []byte("c"), []*big.Int{big.NewInt(2)})
|
||
if err != nil {
|
||
t.Logf("VerifyGroth16 #2 error: %v", err)
|
||
}
|
||
|
||
_, err = zv.VerifyPlonk(plonkKeyID, []byte("proof"), []*big.Int{})
|
||
if err != nil {
|
||
t.Logf("VerifyPlonk error: %v", err)
|
||
}
|
||
|
||
if zv.TotalVerifications != 3 {
|
||
t.Errorf("Expected 3 verifications, got %d", zv.TotalVerifications)
|
||
}
|
||
}
|
||
|
||
// Benchmark tests
|
||
|
||
func BenchmarkVerifyGroth16(b *testing.B) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
keyID, _ := zv.RegisterVerifyingKey(
|
||
owner, ProofSystemGroth16, CircuitTransfer,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0"), []byte("ic1")},
|
||
)
|
||
|
||
proofA := make([]byte, 64)
|
||
proofB := make([]byte, 128)
|
||
proofC := make([]byte, 64)
|
||
publicInputs := []*big.Int{big.NewInt(100)}
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
_, _ = zv.VerifyGroth16(keyID, proofA, proofB, proofC, publicInputs)
|
||
}
|
||
}
|
||
|
||
func BenchmarkVerifyPlonk(b *testing.B) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
keyID, _ := zv.RegisterVerifyingKey(
|
||
owner, ProofSystemPlonk, CircuitTransfer,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
|
||
proof := make([]byte, 512)
|
||
publicInputs := []*big.Int{big.NewInt(100)}
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
_, _ = zv.VerifyPlonk(keyID, proof, publicInputs)
|
||
}
|
||
}
|
||
|
||
func BenchmarkSpendNullifier(b *testing.B) {
|
||
zv := NewZKVerifier()
|
||
txHash := common.HexToHash("0xABCDEF")
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
nullifier := [32]byte{byte(i >> 24), byte(i >> 16), byte(i >> 8), byte(i)}
|
||
_ = zv.SpendNullifier(nullifier, txHash, uint64(i))
|
||
}
|
||
}
|
||
|
||
func BenchmarkCheckNullifier(b *testing.B) {
|
||
zv := NewZKVerifier()
|
||
|
||
// Pre-populate some nullifiers
|
||
for i := range 10000 {
|
||
nullifier := [32]byte{byte(i >> 24), byte(i >> 16), byte(i >> 8), byte(i)}
|
||
zv.Nullifiers[nullifier] = &Nullifier{}
|
||
}
|
||
|
||
nullifier := [32]byte{0x00, 0x00, 0x27, 0x10} // 10000
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
_, _ = zv.CheckNullifier(nullifier)
|
||
}
|
||
}
|
||
|
||
// TestBN256PairingCheck tests that the BN256 pairing operations work correctly
|
||
// using real curve points from the BN254 curve.
|
||
func TestBN256PairingCheck(t *testing.T) {
|
||
// Test that identity pairing check passes
|
||
// e(0, 0) = 1 (empty product is identity)
|
||
emptyG1 := []*bn256.G1{}
|
||
emptyG2 := []*bn256.G2{}
|
||
|
||
if !bn256.PairingCheck(emptyG1, emptyG2) {
|
||
t.Error("Empty pairing check should return true")
|
||
}
|
||
}
|
||
|
||
// TestGroth16WithRealCurvePoints tests Groth16 verification with properly
|
||
// formatted curve points to verify parsing and verification logic.
|
||
func TestGroth16WithRealCurvePoints(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
// Create G1 point at infinity (all zeros in x,y)
|
||
g1Infinity := make([]byte, 64)
|
||
|
||
// Create G2 point at infinity (all zeros in x,y components)
|
||
g2Infinity := make([]byte, 128)
|
||
|
||
// Register a key with points at infinity (valid curve points)
|
||
keyID, err := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16,
|
||
CircuitTransfer,
|
||
g1Infinity, // alpha (G1)
|
||
g2Infinity, // beta (G2)
|
||
g2Infinity, // gamma (G2)
|
||
g2Infinity, // delta (G2)
|
||
[][]byte{g1Infinity, g1Infinity}, // IC points (G1)
|
||
)
|
||
if err != nil {
|
||
t.Fatalf("RegisterVerifyingKey failed: %v", err)
|
||
}
|
||
|
||
// Try to verify with infinity points
|
||
proofA := g1Infinity
|
||
proofB := g2Infinity
|
||
proofC := g1Infinity
|
||
publicInputs := []*big.Int{big.NewInt(0)}
|
||
|
||
result, err := zv.VerifyGroth16(keyID, proofA, proofB, proofC, publicInputs)
|
||
if err != nil {
|
||
t.Fatalf("VerifyGroth16 returned error: %v", err)
|
||
}
|
||
|
||
// Note: Mathematically, identity points (infinity) satisfy the pairing equation
|
||
// because e(O, Q) = 1 for the identity O in G1, and ∏ 1 = 1.
|
||
// This is correct crypto behavior - the test verifies that parsing works
|
||
// and the verification completes without errors.
|
||
// Real proofs with proper curve points would need to satisfy e(A,B) = e(α,β)·e(vk_x,γ)·e(C,δ)
|
||
if result.ProofSystem != ProofSystemGroth16 {
|
||
t.Errorf("Expected ProofSystemGroth16, got %v", result.ProofSystem)
|
||
}
|
||
}
|
||
|
||
// TestKZGPointEvaluationInvalidSizes tests that KZG verification rejects invalid input sizes
|
||
func TestKZGPointEvaluationInvalidSizes(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
|
||
// Setup with a KZGSetup to pass the nil check
|
||
zv.KZGSetup = &KZGSetup{
|
||
MaxDegree: 4096,
|
||
}
|
||
|
||
testCases := []struct {
|
||
name string
|
||
commitment []byte
|
||
proof []byte
|
||
wantValid bool
|
||
}{
|
||
{"commitment too short", make([]byte, 32), make([]byte, 48), false},
|
||
{"proof too short", make([]byte, 48), make([]byte, 32), false},
|
||
{"both too short", make([]byte, 32), make([]byte, 32), false},
|
||
{"correct sizes but invalid data", make([]byte, 48), make([]byte, 48), false},
|
||
}
|
||
|
||
for _, tc := range testCases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
valid, _ := zv.VerifyKZG(tc.commitment, big.NewInt(1), big.NewInt(2), tc.proof)
|
||
if valid != tc.wantValid {
|
||
t.Errorf("Expected valid=%v, got %v", tc.wantValid, valid)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestCurvePointParsing tests that curve points can be correctly parsed
|
||
func TestCurvePointParsing(t *testing.T) {
|
||
// BN254 G1 generator point coordinates (from Ethereum precompile test vectors)
|
||
// G1 = (1, 2)
|
||
g1GenX := "0000000000000000000000000000000000000000000000000000000000000001"
|
||
g1GenY := "0000000000000000000000000000000000000000000000000000000000000002"
|
||
|
||
g1Bytes, _ := hex.DecodeString(g1GenX + g1GenY)
|
||
|
||
var g1 bn256.G1
|
||
n, err := g1.Unmarshal(g1Bytes)
|
||
if err != nil {
|
||
t.Fatalf("Failed to unmarshal G1 generator: %v", err)
|
||
}
|
||
if n != 64 {
|
||
t.Errorf("Expected to consume 64 bytes, consumed %d", n)
|
||
}
|
||
|
||
// Verify serialization roundtrip
|
||
serialized := g1.Marshal()
|
||
if len(serialized) != 64 {
|
||
t.Errorf("Expected 64 bytes, got %d", len(serialized))
|
||
}
|
||
}
|
||
|
||
// TestPLONKProofSizeValidation tests that PLONK verification rejects undersized proofs
|
||
func TestPLONKProofSizeValidation(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
// Create minimal valid-format VK
|
||
g1Point := make([]byte, 64)
|
||
g2Point := make([]byte, 128)
|
||
|
||
// PLONK VK needs 9 IC entries (8 G1 selector points + 1 G2 X2 point)
|
||
ic := make([][]byte, 9)
|
||
for i := range 8 {
|
||
ic[i] = g1Point
|
||
}
|
||
ic[8] = g2Point
|
||
|
||
keyID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemPlonk,
|
||
CircuitTransfer,
|
||
g1Point,
|
||
g2Point,
|
||
g2Point,
|
||
g2Point,
|
||
ic,
|
||
)
|
||
|
||
// Test with undersized proof
|
||
shortProof := make([]byte, 100) // Less than required 768 bytes
|
||
result, err := zv.VerifyPlonk(keyID, shortProof, []*big.Int{})
|
||
if err != nil {
|
||
t.Fatalf("VerifyPlonk returned error: %v", err)
|
||
}
|
||
if result.Valid {
|
||
t.Error("Expected undersized proof to fail verification")
|
||
}
|
||
}
|
||
|
||
// =============================================================================
|
||
// Halo2 Verifier Tests
|
||
// =============================================================================
|
||
|
||
// TestParseHalo2Proof tests Halo2 proof parsing
|
||
func TestParseHalo2Proof(t *testing.T) {
|
||
// Build a valid Halo2 proof structure
|
||
proof := buildTestHalo2Proof(2, 3, 2, 8) // 2 inputs, 3 advice, 2 instance, 8 rounds
|
||
|
||
parsed, err := parseHalo2Proof(proof)
|
||
if err != nil {
|
||
t.Fatalf("parseHalo2Proof failed: %v", err)
|
||
}
|
||
|
||
if len(parsed.publicInputs) != 2 {
|
||
t.Errorf("Expected 2 public inputs, got %d", len(parsed.publicInputs))
|
||
}
|
||
if len(parsed.adviceCommitments) != 3 {
|
||
t.Errorf("Expected 3 advice commitments, got %d", len(parsed.adviceCommitments))
|
||
}
|
||
if len(parsed.instanceCommitments) != 2 {
|
||
t.Errorf("Expected 2 instance commitments, got %d", len(parsed.instanceCommitments))
|
||
}
|
||
if parsed.numRounds != 8 {
|
||
t.Errorf("Expected 8 rounds, got %d", parsed.numRounds)
|
||
}
|
||
if len(parsed.lPoints) != 8 {
|
||
t.Errorf("Expected 8 L points, got %d", len(parsed.lPoints))
|
||
}
|
||
if len(parsed.rPoints) != 8 {
|
||
t.Errorf("Expected 8 R points, got %d", len(parsed.rPoints))
|
||
}
|
||
}
|
||
|
||
// TestParseHalo2ProofTooShort tests error handling for truncated proofs
|
||
func TestParseHalo2ProofTooShort(t *testing.T) {
|
||
// Test minimum length check
|
||
shortData := make([]byte, 35) // Less than 36 bytes minimum
|
||
_, err := parseHalo2Proof(shortData)
|
||
if err == nil {
|
||
t.Error("Expected error for data shorter than 36 bytes")
|
||
}
|
||
|
||
// Test truncated public inputs
|
||
truncated := make([]byte, 40)
|
||
// vkID: 32 bytes, numInputs: 4 (value = 10), then only 4 bytes remaining
|
||
copy(truncated[32:36], []byte{0, 0, 0, 10}) // 10 inputs would need 320 bytes
|
||
_, err = parseHalo2Proof(truncated)
|
||
if err == nil {
|
||
t.Error("Expected error for truncated public inputs")
|
||
}
|
||
}
|
||
|
||
// TestParseHalo2ProofInvalidRounds tests error for invalid round count
|
||
func TestParseHalo2ProofInvalidRounds(t *testing.T) {
|
||
// Build proof with 0 rounds (invalid)
|
||
proof := buildTestHalo2ProofWithRounds(0)
|
||
_, err := parseHalo2Proof(proof)
|
||
if err == nil {
|
||
t.Error("Expected error for 0 rounds")
|
||
}
|
||
|
||
// Build proof with 33 rounds (invalid - max is 32)
|
||
proof = buildTestHalo2ProofWithRounds(33)
|
||
_, err = parseHalo2Proof(proof)
|
||
if err == nil {
|
||
t.Error("Expected error for 33 rounds")
|
||
}
|
||
}
|
||
|
||
// TestVerifyHalo2Structural tests structural validation
|
||
func TestVerifyHalo2Structural(t *testing.T) {
|
||
// Build valid proof structure
|
||
proofData := buildTestHalo2Proof(1, 2, 1, 8)
|
||
proof, err := parseHalo2Proof(proofData)
|
||
if err != nil {
|
||
t.Fatalf("parseHalo2Proof failed: %v", err)
|
||
}
|
||
|
||
// Structural validation should pass but return error (no VK)
|
||
valid, err := verifyHalo2Structural(proof)
|
||
if valid {
|
||
t.Error("Expected structural validation to return false without VK")
|
||
}
|
||
if err == nil {
|
||
t.Error("Expected error message about needing verification key")
|
||
}
|
||
if err != nil && err.Error() != "halo2: structural validation passed - full verification requires registered verification key" {
|
||
t.Errorf("Unexpected error: %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyHalo2StructuralInvalidCommitment tests rejection of invalid commitments
|
||
func TestVerifyHalo2StructuralInvalidCommitment(t *testing.T) {
|
||
// Build proof with zero commitment (invalid)
|
||
proofData := buildTestHalo2Proof(1, 1, 1, 8)
|
||
proof, _ := parseHalo2Proof(proofData)
|
||
|
||
// Zero out an advice commitment
|
||
proof.adviceCommitments[0] = make([]byte, 32) // All zeros = invalid
|
||
|
||
valid, err := verifyHalo2Structural(proof)
|
||
if valid {
|
||
t.Error("Expected invalid commitment to fail")
|
||
}
|
||
if err == nil || err.Error() != "halo2: invalid advice commitment at index 0" {
|
||
t.Errorf("Expected specific error about invalid advice commitment, got: %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyHalo2StructuralInvalidLPoint tests rejection of invalid L points
|
||
func TestVerifyHalo2StructuralInvalidLPoint(t *testing.T) {
|
||
proofData := buildTestHalo2Proof(1, 1, 1, 8)
|
||
proof, _ := parseHalo2Proof(proofData)
|
||
|
||
// Zero out an L point
|
||
proof.lPoints[3] = make([]byte, 32) // All zeros = invalid
|
||
|
||
valid, err := verifyHalo2Structural(proof)
|
||
if valid {
|
||
t.Error("Expected invalid L point to fail")
|
||
}
|
||
if err == nil || err.Error() != "halo2: invalid L point at round 3" {
|
||
t.Errorf("Expected specific error about invalid L point, got: %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyHalo2Full tests full verification with registered key
|
||
func TestVerifyHalo2Full(t *testing.T) {
|
||
proofData := buildTestHalo2Proof(1, 2, 1, 8)
|
||
proof, err := parseHalo2Proof(proofData)
|
||
if err != nil {
|
||
t.Fatalf("parseHalo2Proof failed: %v", err)
|
||
}
|
||
|
||
// Create a verification key for Halo2
|
||
vk := &VerifyingKey{
|
||
KeyID: proof.vkID,
|
||
ProofSystem: ProofSystemHalo2,
|
||
CircuitType: CircuitTransfer,
|
||
IC: make([][]byte, 3), // Enough for 1 public input + 2
|
||
}
|
||
for i := range vk.IC {
|
||
vk.IC[i] = make([]byte, 32)
|
||
vk.IC[i][0] = byte(i + 1) // Non-zero
|
||
}
|
||
|
||
// Full verification should complete
|
||
valid, err := verifyHalo2Full(proof, vk)
|
||
if err != nil {
|
||
t.Fatalf("verifyHalo2Full failed: %v", err)
|
||
}
|
||
// Structural validation passes; actual curve point verification
|
||
// is performed by the on-chain BN254/Pallas precompile calls.
|
||
_ = valid
|
||
}
|
||
|
||
// TestComputeHalo2Challenges tests Fiat-Shamir challenge generation
|
||
func TestComputeHalo2Challenges(t *testing.T) {
|
||
proofData := buildTestHalo2Proof(2, 3, 2, 8)
|
||
proof, _ := parseHalo2Proof(proofData)
|
||
|
||
challenges, err := computeHalo2Challenges(proof)
|
||
if err != nil {
|
||
t.Fatalf("computeHalo2Challenges failed: %v", err)
|
||
}
|
||
|
||
if len(challenges) != 8 {
|
||
t.Errorf("Expected 8 challenges, got %d", len(challenges))
|
||
}
|
||
|
||
// Verify challenges are non-zero and 32 bytes
|
||
for i, ch := range challenges {
|
||
if len(ch) != 32 {
|
||
t.Errorf("Challenge %d is %d bytes, expected 32", i, len(ch))
|
||
}
|
||
if isZeroBytes(ch) {
|
||
t.Errorf("Challenge %d is zero", i)
|
||
}
|
||
}
|
||
|
||
// Verify challenges are deterministic
|
||
challenges2, _ := computeHalo2Challenges(proof)
|
||
for i := range challenges {
|
||
for j := range challenges[i] {
|
||
if challenges[i][j] != challenges2[i][j] {
|
||
t.Errorf("Challenges are not deterministic at round %d", i)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestModInverse tests modular inverse computation
|
||
func TestModInverse(t *testing.T) {
|
||
// Test with a simple value
|
||
a := make([]byte, 32)
|
||
a[31] = 7 // a = 7
|
||
|
||
inv, err := modInverse(a)
|
||
if err != nil {
|
||
t.Fatalf("modInverse failed: %v", err)
|
||
}
|
||
|
||
if len(inv) != 32 {
|
||
t.Errorf("Expected 32 bytes, got %d", len(inv))
|
||
}
|
||
|
||
// Test zero returns error
|
||
zero := make([]byte, 32)
|
||
_, err = modInverse(zero)
|
||
if err == nil {
|
||
t.Error("Expected error for zero inverse")
|
||
}
|
||
}
|
||
|
||
// TestIsValidCompressedPoint tests real Pallas compressed-point validation.
|
||
func TestIsValidCompressedPoint(t *testing.T) {
|
||
// A genuine on-curve Pallas point: the standard generator (x = -1 ≡ p-1,
|
||
// y = 2), since (-1)^3 + 5 = 4 = 2^2. Encoded as 32-byte big-endian x with
|
||
// the sign bit clear (p-1 < 2^255).
|
||
x := new(big.Int).Sub(pallasFieldModulus, big.NewInt(1))
|
||
valid := make([]byte, 32)
|
||
copy(valid[32-len(x.Bytes()):], x.Bytes())
|
||
if !isValidCompressedPoint(valid) {
|
||
t.Error("Expected on-curve Pallas point to pass")
|
||
}
|
||
|
||
// Invalid: wrong size
|
||
if isValidCompressedPoint(make([]byte, 31)) {
|
||
t.Error("Expected wrong size to fail")
|
||
}
|
||
|
||
// Invalid: all zeros (identity)
|
||
if isValidCompressedPoint(make([]byte, 32)) {
|
||
t.Error("Expected zero point to fail")
|
||
}
|
||
|
||
// Invalid: non-canonical x >= field modulus (all 0xFF, sign bit cleared
|
||
// => x = 2^255 - 1 > p).
|
||
overflow := make([]byte, 32)
|
||
for i := range overflow {
|
||
overflow[i] = 0xFF
|
||
}
|
||
overflow[0] &^= 0x80
|
||
if isValidCompressedPoint(overflow) {
|
||
t.Error("Expected non-canonical x >= p to fail")
|
||
}
|
||
|
||
// Invalid: off-curve x (x^3 + 5 is a quadratic non-residue mod p).
|
||
offX := big.NewInt(2)
|
||
for {
|
||
rhs := new(big.Int).Mul(offX, offX)
|
||
rhs.Mul(rhs, offX)
|
||
rhs.Add(rhs, big.NewInt(5))
|
||
rhs.Mod(rhs, pallasFieldModulus)
|
||
if !isQuadraticResidue(rhs, pallasFieldModulus) {
|
||
break // found an x with no curve point
|
||
}
|
||
offX.Add(offX, big.NewInt(1))
|
||
}
|
||
off := make([]byte, 32)
|
||
copy(off[32-len(offX.Bytes()):], offX.Bytes())
|
||
if isValidCompressedPoint(off) {
|
||
t.Errorf("Expected off-curve point (x=%s) to fail", offX)
|
||
}
|
||
}
|
||
|
||
// TestHalo2Transcript tests transcript operations
|
||
func TestHalo2Transcript(t *testing.T) {
|
||
tr := newHalo2Transcript()
|
||
|
||
// Add some data
|
||
tr.appendScalar([]byte{1, 2, 3})
|
||
tr.appendPoint([]byte{4, 5, 6})
|
||
|
||
// Get challenge
|
||
ch1 := tr.challenge()
|
||
if len(ch1) != 32 {
|
||
t.Errorf("Expected 32-byte challenge, got %d", len(ch1))
|
||
}
|
||
|
||
// Get another challenge (should be different)
|
||
ch2 := tr.challenge()
|
||
if len(ch2) != 32 {
|
||
t.Errorf("Expected 32-byte challenge, got %d", len(ch2))
|
||
}
|
||
|
||
// Challenges should differ
|
||
same := true
|
||
for i := range ch1 {
|
||
if ch1[i] != ch2[i] {
|
||
same = false
|
||
break
|
||
}
|
||
}
|
||
if same {
|
||
t.Error("Consecutive challenges should be different")
|
||
}
|
||
}
|
||
|
||
// TestVerifyHalo2Precompile tests the precompile interface
|
||
func TestVerifyHalo2Precompile(t *testing.T) {
|
||
p := &zkVerifyPrecompile{
|
||
verifier: NewZKVerifier(),
|
||
}
|
||
|
||
// Build valid proof data
|
||
proofData := buildTestHalo2Proof(1, 2, 1, 8)
|
||
|
||
// Should parse successfully but fail verification (no VK registered)
|
||
valid, err := p.verifyHalo2(proofData)
|
||
if valid {
|
||
t.Error("Expected false without registered VK")
|
||
}
|
||
if err == nil {
|
||
t.Error("Expected error without registered VK")
|
||
}
|
||
|
||
// Test with invalid (too short) data
|
||
_, err = p.verifyHalo2(make([]byte, 30))
|
||
if err == nil {
|
||
t.Error("Expected error for short data")
|
||
}
|
||
}
|
||
|
||
// Helper function to build test Halo2 proof data
|
||
// realCompressedPallasPoint returns a valid on-curve compressed Pallas point,
|
||
// deterministically derived from seed (distinct seeds give distinct points).
|
||
// Used so test proof fixtures present points that pass isValidCompressedPoint.
|
||
func realCompressedPallasPoint(seed byte) []byte {
|
||
x := new(big.Int).SetBytes([]byte{seed, 0xA5, 0x5A, 0x01})
|
||
for {
|
||
rhs := new(big.Int).Mul(x, x)
|
||
rhs.Mul(rhs, x)
|
||
rhs.Add(rhs, big.NewInt(5))
|
||
rhs.Mod(rhs, pallasFieldModulus)
|
||
if isQuadraticResidue(rhs, pallasFieldModulus) {
|
||
break
|
||
}
|
||
x.Add(x, big.NewInt(1))
|
||
}
|
||
out := make([]byte, 32)
|
||
xb := x.Bytes()
|
||
copy(out[32-len(xb):], xb)
|
||
return out
|
||
}
|
||
|
||
func buildTestHalo2Proof(numInputs, numAdvice, numInstance int, numRounds uint32) []byte {
|
||
// Calculate total size
|
||
size := 32 + // vkID
|
||
4 + numInputs*32 + // public inputs
|
||
4 + numAdvice*32 + // advice commitments
|
||
4 + numInstance*32 + // instance commitments
|
||
4 + int(numRounds)*32 + // L points
|
||
int(numRounds)*32 + // R points
|
||
32 + // a_scalar
|
||
32 + // evalPoint
|
||
32 // claimedEval
|
||
|
||
data := make([]byte, size)
|
||
offset := 0
|
||
|
||
// vkID (32 bytes with non-zero content)
|
||
for i := range 32 {
|
||
data[offset+i] = byte(i + 1)
|
||
}
|
||
offset += 32
|
||
|
||
// numInputs (4 bytes)
|
||
data[offset] = byte(numInputs >> 24)
|
||
data[offset+1] = byte(numInputs >> 16)
|
||
data[offset+2] = byte(numInputs >> 8)
|
||
data[offset+3] = byte(numInputs)
|
||
offset += 4
|
||
|
||
// public inputs (numInputs * 32 bytes)
|
||
for i := range numInputs {
|
||
data[offset+31] = byte(i + 1) // Non-zero value
|
||
offset += 32
|
||
}
|
||
|
||
// numAdvice (4 bytes)
|
||
data[offset] = byte(numAdvice >> 24)
|
||
data[offset+1] = byte(numAdvice >> 16)
|
||
data[offset+2] = byte(numAdvice >> 8)
|
||
data[offset+3] = byte(numAdvice)
|
||
offset += 4
|
||
|
||
// advice commitments (numAdvice * 32 bytes) - real on-curve points
|
||
for i := range numAdvice {
|
||
copy(data[offset:offset+32], realCompressedPallasPoint(byte(i+0x10)))
|
||
offset += 32
|
||
}
|
||
|
||
// numInstance (4 bytes)
|
||
data[offset] = byte(numInstance >> 24)
|
||
data[offset+1] = byte(numInstance >> 16)
|
||
data[offset+2] = byte(numInstance >> 8)
|
||
data[offset+3] = byte(numInstance)
|
||
offset += 4
|
||
|
||
// instance commitments (numInstance * 32 bytes) - real on-curve points
|
||
for i := range numInstance {
|
||
copy(data[offset:offset+32], realCompressedPallasPoint(byte(i+0x20)))
|
||
offset += 32
|
||
}
|
||
|
||
// numRounds (4 bytes)
|
||
data[offset] = byte(numRounds >> 24)
|
||
data[offset+1] = byte(numRounds >> 16)
|
||
data[offset+2] = byte(numRounds >> 8)
|
||
data[offset+3] = byte(numRounds)
|
||
offset += 4
|
||
|
||
// L points (numRounds * 32 bytes) - real on-curve points
|
||
for i := range numRounds {
|
||
copy(data[offset:offset+32], realCompressedPallasPoint(byte(i+0x30)))
|
||
offset += 32
|
||
}
|
||
|
||
// R points (numRounds * 32 bytes) - real on-curve points
|
||
for i := range numRounds {
|
||
copy(data[offset:offset+32], realCompressedPallasPoint(byte(i+0x40)))
|
||
offset += 32
|
||
}
|
||
|
||
// a_scalar (32 bytes) - non-zero
|
||
data[offset] = 0x50
|
||
offset += 32
|
||
|
||
// evalPoint (32 bytes) - non-zero
|
||
data[offset] = 0x60
|
||
offset += 32
|
||
|
||
// claimedEval (32 bytes) - non-zero
|
||
data[offset] = 0x70
|
||
|
||
return data
|
||
}
|
||
|
||
// Helper to build proof with specific round count for edge case testing
|
||
func buildTestHalo2ProofWithRounds(numRounds uint32) []byte {
|
||
// Minimal proof: 0 inputs, 0 advice, 0 instance
|
||
size := 32 + 4 + 4 + 4 + 4 // vkID + numInputs + numAdvice + numInstance + numRounds
|
||
if numRounds > 0 && numRounds <= 32 {
|
||
size += int(numRounds)*32 + int(numRounds)*32 + 32 + 32 + 32
|
||
}
|
||
|
||
data := make([]byte, size)
|
||
offset := 0
|
||
|
||
// vkID
|
||
data[0] = 1 // Non-zero
|
||
offset = 32
|
||
|
||
// numInputs = 0
|
||
offset += 4
|
||
|
||
// numAdvice = 0
|
||
offset += 4
|
||
|
||
// numInstance = 0
|
||
offset += 4
|
||
|
||
// numRounds
|
||
data[offset] = byte(numRounds >> 24)
|
||
data[offset+1] = byte(numRounds >> 16)
|
||
data[offset+2] = byte(numRounds >> 8)
|
||
data[offset+3] = byte(numRounds)
|
||
|
||
return data
|
||
}
|
||
|
||
// Benchmark for Halo2 proof parsing
|
||
func BenchmarkParseHalo2Proof(b *testing.B) {
|
||
proof := buildTestHalo2Proof(4, 10, 2, 8)
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
_, _ = parseHalo2Proof(proof)
|
||
}
|
||
}
|
||
|
||
// Benchmark for Halo2 challenge computation
|
||
func BenchmarkComputeHalo2Challenges(b *testing.B) {
|
||
proofData := buildTestHalo2Proof(4, 10, 2, 8)
|
||
proof, _ := parseHalo2Proof(proofData)
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
_, _ = computeHalo2Challenges(proof)
|
||
}
|
||
}
|
||
|
||
// Benchmark for Halo2 structural validation
|
||
func BenchmarkVerifyHalo2Structural(b *testing.B) {
|
||
proofData := buildTestHalo2Proof(4, 10, 2, 8)
|
||
proof, _ := parseHalo2Proof(proofData)
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
_, _ = verifyHalo2Structural(proof)
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// fflonk verification tests
|
||
// ============================================================================
|
||
|
||
// TestParseFflonkProof tests fflonk proof parsing
|
||
func TestParseFflonkProof(t *testing.T) {
|
||
// Build valid fflonk proof: 4*48 + 8*32 = 448 bytes minimum
|
||
proof := buildTestFflonkProof(8)
|
||
|
||
fp, err := parseFflonkProof(proof)
|
||
if err != nil {
|
||
t.Fatalf("parseFflonkProof failed: %v", err)
|
||
}
|
||
|
||
if len(fp.C1) != 48 {
|
||
t.Errorf("Expected C1 length 48, got %d", len(fp.C1))
|
||
}
|
||
if len(fp.C2) != 48 {
|
||
t.Errorf("Expected C2 length 48, got %d", len(fp.C2))
|
||
}
|
||
if len(fp.W1) != 48 {
|
||
t.Errorf("Expected W1 length 48, got %d", len(fp.W1))
|
||
}
|
||
if len(fp.W2) != 48 {
|
||
t.Errorf("Expected W2 length 48, got %d", len(fp.W2))
|
||
}
|
||
if len(fp.Evaluations) != 8 {
|
||
t.Errorf("Expected 8 evaluations, got %d", len(fp.Evaluations))
|
||
}
|
||
}
|
||
|
||
// TestParseFflonkProofTooShort tests error for short proof
|
||
func TestParseFflonkProofTooShort(t *testing.T) {
|
||
proof := make([]byte, 100) // Too short
|
||
|
||
_, err := parseFflonkProof(proof)
|
||
if err == nil {
|
||
t.Error("Expected error for short proof")
|
||
}
|
||
}
|
||
|
||
// TestValidateBLS12381G1Point tests BLS12-381 G1 point validation
|
||
func TestValidateBLS12381G1Point(t *testing.T) {
|
||
// Test point at infinity (all zeros)
|
||
infinity := make([]byte, 48)
|
||
err := validateBLS12381G1Point(infinity)
|
||
if err != nil {
|
||
t.Errorf("Expected point at infinity to be valid: %v", err)
|
||
}
|
||
|
||
// Test valid compressed point format (bit 7 set)
|
||
valid := make([]byte, 48)
|
||
valid[0] = 0x80 // Compression flag set
|
||
valid[1] = 0x01 // Some x-coordinate
|
||
err = validateBLS12381G1Point(valid)
|
||
if err != nil {
|
||
t.Errorf("Expected valid compressed point: %v", err)
|
||
}
|
||
|
||
// Test uncompressed point format (error: bit 7 not set)
|
||
uncompressed := make([]byte, 48)
|
||
uncompressed[0] = 0x00
|
||
uncompressed[1] = 0x01
|
||
err = validateBLS12381G1Point(uncompressed)
|
||
if err == nil {
|
||
t.Error("Expected error for uncompressed point")
|
||
}
|
||
|
||
// Test wrong length
|
||
wrongLen := make([]byte, 32)
|
||
err = validateBLS12381G1Point(wrongLen)
|
||
if err == nil {
|
||
t.Error("Expected error for wrong length")
|
||
}
|
||
|
||
// Test infinity flag set but non-zero bytes
|
||
badInfinity := make([]byte, 48)
|
||
badInfinity[0] = 0xC0 // Compression + infinity flags
|
||
badInfinity[1] = 0x01 // But has data
|
||
err = validateBLS12381G1Point(badInfinity)
|
||
if err == nil {
|
||
t.Error("Expected error for invalid infinity point")
|
||
}
|
||
}
|
||
|
||
// TestValidateBLS12381Scalar tests scalar field validation
|
||
func TestValidateBLS12381Scalar(t *testing.T) {
|
||
// Test valid scalar (small value)
|
||
valid := big.NewInt(12345)
|
||
err := validateBLS12381Scalar(valid)
|
||
if err != nil {
|
||
t.Errorf("Expected valid scalar: %v", err)
|
||
}
|
||
|
||
// Test zero (valid)
|
||
zero := big.NewInt(0)
|
||
err = validateBLS12381Scalar(zero)
|
||
if err != nil {
|
||
t.Errorf("Expected zero to be valid: %v", err)
|
||
}
|
||
|
||
// Test negative (invalid)
|
||
negative := big.NewInt(-1)
|
||
err = validateBLS12381Scalar(negative)
|
||
if err == nil {
|
||
t.Error("Expected error for negative scalar")
|
||
}
|
||
|
||
// Test nil (invalid)
|
||
err = validateBLS12381Scalar(nil)
|
||
if err == nil {
|
||
t.Error("Expected error for nil scalar")
|
||
}
|
||
|
||
// Test value >= field order (invalid)
|
||
tooLarge := new(big.Int)
|
||
tooLarge.SetString("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16)
|
||
err = validateBLS12381Scalar(tooLarge)
|
||
if err == nil {
|
||
t.Error("Expected error for scalar >= field order")
|
||
}
|
||
}
|
||
|
||
// TestComputeFflonkChallenge tests Fiat-Shamir challenge computation
|
||
func TestComputeFflonkChallenge(t *testing.T) {
|
||
proof := buildTestFflonkProof(8)
|
||
inputs := []*big.Int{big.NewInt(100), big.NewInt(200)}
|
||
|
||
// Compute challenge
|
||
ch1 := computeFflonkChallenge(proof, inputs, []byte("test"))
|
||
|
||
// Verify it's non-zero
|
||
if ch1.Sign() == 0 {
|
||
t.Error("Expected non-zero challenge")
|
||
}
|
||
|
||
// Verify determinism
|
||
ch2 := computeFflonkChallenge(proof, inputs, []byte("test"))
|
||
if ch1.Cmp(ch2) != 0 {
|
||
t.Error("Challenge computation not deterministic")
|
||
}
|
||
|
||
// Different domain should produce different challenge
|
||
ch3 := computeFflonkChallenge(proof, inputs, []byte("other"))
|
||
if ch1.Cmp(ch3) == 0 {
|
||
t.Error("Different domains should produce different challenges")
|
||
}
|
||
}
|
||
|
||
// TestVerifyFflonkProof tests fflonk proof verification
|
||
func TestVerifyFflonkProof(t *testing.T) {
|
||
proof := buildTestFflonkProof(8)
|
||
inputs := []*big.Int{big.NewInt(100)}
|
||
|
||
// Verify proof structure (will fail KZG check with test data)
|
||
valid, err := verifyFflonkProof(proof, inputs)
|
||
|
||
// Test data won't pass KZG verification, but parsing should succeed
|
||
// The function should return false (invalid proof) not an error
|
||
if err != nil {
|
||
// If error, it should be about invalid point format, not parsing
|
||
t.Logf("verifyFflonkProof returned error: %v", err)
|
||
}
|
||
|
||
// With random test data, proof should be invalid
|
||
if valid {
|
||
t.Error("Random test data should not verify as valid")
|
||
}
|
||
}
|
||
|
||
// TestVerifyFflonkPrecompile tests the precompile interface
|
||
func TestVerifyFflonkPrecompile(t *testing.T) {
|
||
p := &zkVerifyPrecompile{
|
||
verifier: NewZKVerifier(),
|
||
}
|
||
|
||
// Build valid proof data: vkID(32) + numInputs(4) + inputs(n*32) + proof(448+)
|
||
proof := buildTestFflonkProof(8)
|
||
data := make([]byte, 36+32+len(proof))
|
||
|
||
// vkID (32 bytes)
|
||
for i := range 32 {
|
||
data[i] = byte(i + 1)
|
||
}
|
||
|
||
// numInputs = 1 (4 bytes big-endian)
|
||
data[35] = 1
|
||
|
||
// public input (32 bytes)
|
||
data[67] = 0x42
|
||
|
||
// proof data
|
||
copy(data[68:], proof)
|
||
|
||
// Should parse successfully but fail verification (test data)
|
||
valid, err := p.verifyFflonk(data)
|
||
if err != nil {
|
||
t.Logf("verifyFflonk returned error: %v", err)
|
||
}
|
||
if valid {
|
||
t.Error("Random test data should not verify as valid")
|
||
}
|
||
|
||
// Test with too short data
|
||
_, err = p.verifyFflonk(make([]byte, 30))
|
||
if err == nil {
|
||
t.Error("Expected error for short data")
|
||
}
|
||
}
|
||
|
||
// TestVerifyFflonkWithVerifier tests fflonk verification with registered VK
|
||
func TestVerifyFflonkWithVerifier(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
// Register fflonk verification key
|
||
keyID, err := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemFflonk,
|
||
CircuitTransfer,
|
||
[]byte("fflonk_alpha"),
|
||
[]byte("fflonk_beta"),
|
||
[]byte("fflonk_gamma"),
|
||
[]byte("fflonk_delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
if err != nil {
|
||
t.Fatalf("Failed to register fflonk key: %v", err)
|
||
}
|
||
|
||
// Create proof data
|
||
proof := buildTestFflonkProof(8)
|
||
inputs := []*big.Int{big.NewInt(100)}
|
||
|
||
// Verify - should process but fail with test data
|
||
result, err := zv.VerifyFflonk(keyID, proof, inputs)
|
||
if err != nil {
|
||
t.Logf("VerifyFflonk returned error: %v", err)
|
||
}
|
||
if result != nil && result.Valid {
|
||
t.Error("Random test data should not verify as valid")
|
||
}
|
||
}
|
||
|
||
// TestVerifyFflonkInvalidKey tests error for non-existent key
|
||
func TestVerifyFflonkInvalidKey(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
|
||
nonExistent := [32]byte{0xFF}
|
||
proof := buildTestFflonkProof(8)
|
||
inputs := []*big.Int{big.NewInt(100)}
|
||
|
||
_, err := zv.VerifyFflonk(nonExistent, proof, inputs)
|
||
if err != ErrInvalidVerifyingKey {
|
||
t.Errorf("Expected ErrInvalidVerifyingKey, got %v", err)
|
||
}
|
||
}
|
||
|
||
// TestVerifyFflonkWrongProofSystem tests error for wrong proof system
|
||
func TestVerifyFflonkWrongProofSystem(t *testing.T) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
// Register key as Groth16, not fflonk
|
||
keyID, _ := zv.RegisterVerifyingKey(
|
||
owner,
|
||
ProofSystemGroth16, // Wrong proof system
|
||
CircuitTransfer,
|
||
[]byte("alpha"),
|
||
[]byte("beta"),
|
||
[]byte("gamma"),
|
||
[]byte("delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
|
||
proof := buildTestFflonkProof(8)
|
||
inputs := []*big.Int{big.NewInt(100)}
|
||
|
||
_, err := zv.VerifyFflonk(keyID, proof, inputs)
|
||
if err != ErrProofSystemMismatch {
|
||
t.Errorf("Expected ErrProofSystemMismatch, got %v", err)
|
||
}
|
||
}
|
||
|
||
// buildTestFflonkProof builds a test fflonk proof with the specified number of evaluations
|
||
func buildTestFflonkProof(numEvals int) []byte {
|
||
// Proof format: C1(48) + C2(48) + W1(48) + W2(48) + evals(n*32)
|
||
size := 4*48 + numEvals*32
|
||
proof := make([]byte, size)
|
||
|
||
offset := 0
|
||
|
||
// C1: valid compressed BLS12-381 G1 point format
|
||
proof[offset] = 0x80 // Compression flag
|
||
proof[offset+1] = 0x01
|
||
offset += 48
|
||
|
||
// C2: valid compressed BLS12-381 G1 point format
|
||
proof[offset] = 0x80
|
||
proof[offset+1] = 0x02
|
||
offset += 48
|
||
|
||
// W1: valid compressed BLS12-381 G1 point format
|
||
proof[offset] = 0x80
|
||
proof[offset+1] = 0x03
|
||
offset += 48
|
||
|
||
// W2: valid compressed BLS12-381 G1 point format
|
||
proof[offset] = 0x80
|
||
proof[offset+1] = 0x04
|
||
offset += 48
|
||
|
||
// Evaluations: field elements (small values that pass validation)
|
||
for i := range numEvals {
|
||
proof[offset+31] = byte(i + 1) // Small non-zero value
|
||
offset += 32
|
||
}
|
||
|
||
return proof
|
||
}
|
||
|
||
// BenchmarkParseFflonkProof benchmarks fflonk proof parsing
|
||
func BenchmarkParseFflonkProof(b *testing.B) {
|
||
proof := buildTestFflonkProof(8)
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
_, _ = parseFflonkProof(proof)
|
||
}
|
||
}
|
||
|
||
// BenchmarkComputeFflonkChallenge benchmarks challenge computation
|
||
func BenchmarkComputeFflonkChallenge(b *testing.B) {
|
||
proof := buildTestFflonkProof(8)
|
||
inputs := []*big.Int{big.NewInt(100), big.NewInt(200)}
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
_ = computeFflonkChallenge(proof, inputs, []byte("xi"))
|
||
}
|
||
}
|
||
|
||
// BenchmarkVerifyFflonk benchmarks full fflonk verification
|
||
func BenchmarkVerifyFflonk(b *testing.B) {
|
||
zv := NewZKVerifier()
|
||
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
||
|
||
keyID, _ := zv.RegisterVerifyingKey(
|
||
owner, ProofSystemFflonk, CircuitTransfer,
|
||
[]byte("alpha"), []byte("beta"), []byte("gamma"), []byte("delta"),
|
||
[][]byte{[]byte("ic0")},
|
||
)
|
||
|
||
proof := buildTestFflonkProof(8)
|
||
inputs := []*big.Int{big.NewInt(100)}
|
||
|
||
b.ResetTimer()
|
||
for i := 0; i < b.N; i++ {
|
||
_, _ = zv.VerifyFflonk(keyID, proof, inputs)
|
||
}
|
||
}
|