mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
411 lines
10 KiB
Go
411 lines
10 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package zk
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestPoseidon2Hash tests basic Poseidon2 hashing
|
|
func TestPoseidon2Hash(t *testing.T) {
|
|
hasher := NewPoseidon2Hasher()
|
|
|
|
// Test single element hash
|
|
var input [32]byte
|
|
input[31] = 42
|
|
|
|
hash1, err := hasher.Hash(input[:])
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, [32]byte{}, hash1)
|
|
|
|
// Same input should produce same hash
|
|
hash2, err := hasher.Hash(input[:])
|
|
require.NoError(t, err)
|
|
require.Equal(t, hash1, hash2)
|
|
|
|
// Different input should produce different hash
|
|
input[31] = 43
|
|
hash3, err := hasher.Hash(input[:])
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, hash1, hash3)
|
|
}
|
|
|
|
// TestPoseidon2HashPair tests Merkle-tree style hashing
|
|
func TestPoseidon2HashPair(t *testing.T) {
|
|
hasher := NewPoseidon2Hasher()
|
|
|
|
var left, right [32]byte
|
|
left[31] = 1
|
|
right[31] = 2
|
|
|
|
hash, err := hasher.HashPair(left, right)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, [32]byte{}, hash)
|
|
|
|
// Order matters
|
|
hash2, err := hasher.HashPair(right, left)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, hash, hash2)
|
|
}
|
|
|
|
// TestPoseidon2MerkleTree tests Merkle tree construction and verification
|
|
func TestPoseidon2MerkleTree(t *testing.T) {
|
|
hasher := NewPoseidon2Hasher()
|
|
|
|
// Create 8 leaves
|
|
leaves := make([][32]byte, 8)
|
|
for i := range leaves {
|
|
leaves[i][31] = byte(i + 1)
|
|
}
|
|
|
|
// Compute root
|
|
root, err := hasher.MerkleRoot(leaves)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, [32]byte{}, root)
|
|
|
|
// Generate and verify proof for each leaf
|
|
for i := range leaves {
|
|
proof, isLeft, err := hasher.MerkleProof(leaves, i)
|
|
require.NoError(t, err)
|
|
|
|
valid, err := hasher.VerifyMerkleProof(leaves[i], proof, isLeft, root)
|
|
require.NoError(t, err)
|
|
require.True(t, valid, "proof should be valid for leaf %d", i)
|
|
}
|
|
}
|
|
|
|
// TestPedersenCommit tests basic Pedersen commitment
|
|
func TestPedersenCommit(t *testing.T) {
|
|
committer := NewPedersenCommitter()
|
|
|
|
var value, blinding [32]byte
|
|
value[31] = 100
|
|
rand.Read(blinding[:])
|
|
|
|
commitment, err := committer.Commit(value, blinding)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, [32]byte{}, commitment)
|
|
|
|
// Verify opening
|
|
valid, err := committer.Verify(commitment, value, blinding)
|
|
require.NoError(t, err)
|
|
require.True(t, valid)
|
|
|
|
// Wrong value should fail
|
|
value[31] = 101
|
|
valid, err = committer.Verify(commitment, value, blinding)
|
|
require.NoError(t, err)
|
|
require.False(t, valid)
|
|
}
|
|
|
|
// TestPedersenHomomorphism tests homomorphic addition
|
|
func TestPedersenHomomorphism(t *testing.T) {
|
|
committer := NewPedersenCommitter()
|
|
|
|
// C1 = 10 * G + r1 * H
|
|
var v1, r1 [32]byte
|
|
v1[31] = 10
|
|
rand.Read(r1[:])
|
|
c1, err := committer.Commit(v1, r1)
|
|
require.NoError(t, err)
|
|
|
|
// C2 = 20 * G + r2 * H
|
|
var v2, r2 [32]byte
|
|
v2[31] = 20
|
|
rand.Read(r2[:])
|
|
c2, err := committer.Commit(v2, r2)
|
|
require.NoError(t, err)
|
|
|
|
// C1 + C2 should equal commitment to (10 + 20) with (r1 + r2)
|
|
sum, err := committer.Add(c1, c2)
|
|
require.NoError(t, err)
|
|
|
|
// This demonstrates homomorphic property
|
|
// In practice you'd also add blinding factors
|
|
require.NotEqual(t, [32]byte{}, sum)
|
|
}
|
|
|
|
// TestNoteCommitments tests note commitment for both schemes
|
|
func TestNoteCommitments(t *testing.T) {
|
|
amount := big.NewInt(1000)
|
|
var assetId [32]byte
|
|
assetId[31] = 1 // ETH
|
|
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
var blinding [32]byte
|
|
rand.Read(blinding[:])
|
|
|
|
// Test Poseidon2 scheme
|
|
poseidonScheme := NewPoseidon2Scheme()
|
|
poseidonNote, err := poseidonScheme.NoteCommitment(amount, assetId, owner, blinding)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, [32]byte{}, poseidonNote)
|
|
require.True(t, poseidonScheme.IsPQSafe())
|
|
|
|
// Test Pedersen scheme
|
|
pedersenScheme := NewPedersenScheme()
|
|
pedersenNote, err := pedersenScheme.NoteCommitment(amount, assetId, owner, blinding)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, [32]byte{}, pedersenNote)
|
|
require.False(t, pedersenScheme.IsPQSafe())
|
|
|
|
// Commitments should be different (different schemes)
|
|
require.NotEqual(t, poseidonNote, pedersenNote)
|
|
}
|
|
|
|
// TestCreateNote tests the Note abstraction
|
|
func TestCreateNote(t *testing.T) {
|
|
var blinding [32]byte
|
|
rand.Read(blinding[:])
|
|
|
|
var assetId [32]byte
|
|
assetId[31] = 1
|
|
|
|
note, err := CreateNote(NoteInput{
|
|
Amount: big.NewInt(500),
|
|
AssetID: assetId,
|
|
Owner: common.HexToAddress("0xabcd"),
|
|
BlindingFactor: blinding,
|
|
SchemeType: SchemePoseidon2,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, note)
|
|
require.Equal(t, SchemePoseidon2, note.SchemeType)
|
|
|
|
// Set leaf index and compute nullifier
|
|
note.LeafIndex = 42
|
|
var nullifierKey [32]byte
|
|
rand.Read(nullifierKey[:])
|
|
|
|
nullifier, err := note.Nullifier(nullifierKey)
|
|
require.NoError(t, err)
|
|
require.NotEqual(t, [32]byte{}, nullifier)
|
|
}
|
|
|
|
// BenchmarkPoseidon2Hash benchmarks Poseidon2 hashing
|
|
func BenchmarkPoseidon2Hash(b *testing.B) {
|
|
hasher := NewPoseidon2Hasher()
|
|
input := make([]byte, 64) // 2 field elements
|
|
rand.Read(input)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = hasher.Hash(input)
|
|
}
|
|
}
|
|
|
|
// BenchmarkPoseidon2Commitment benchmarks Poseidon2 commitment
|
|
func BenchmarkPoseidon2Commitment(b *testing.B) {
|
|
hasher := NewPoseidon2Hasher()
|
|
var value, blinding, salt [32]byte
|
|
rand.Read(value[:])
|
|
rand.Read(blinding[:])
|
|
rand.Read(salt[:])
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = hasher.Commitment(value, blinding, salt)
|
|
}
|
|
}
|
|
|
|
// BenchmarkPedersenCommit benchmarks Pedersen commitment
|
|
func BenchmarkPedersenCommit(b *testing.B) {
|
|
committer := NewPedersenCommitter()
|
|
var value, blinding [32]byte
|
|
rand.Read(value[:])
|
|
rand.Read(blinding[:])
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = committer.Commit(value, blinding)
|
|
}
|
|
}
|
|
|
|
// BenchmarkPedersenVerify benchmarks Pedersen verification
|
|
func BenchmarkPedersenVerify(b *testing.B) {
|
|
committer := NewPedersenCommitter()
|
|
var value, blinding [32]byte
|
|
rand.Read(value[:])
|
|
rand.Read(blinding[:])
|
|
commitment, _ := committer.Commit(value, blinding)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = committer.Verify(commitment, value, blinding)
|
|
}
|
|
}
|
|
|
|
// BenchmarkNoteCommitmentPoseidon2 benchmarks note commitment with Poseidon2
|
|
func BenchmarkNoteCommitmentPoseidon2(b *testing.B) {
|
|
scheme := NewPoseidon2Scheme()
|
|
amount := big.NewInt(1000)
|
|
var assetId [32]byte
|
|
assetId[31] = 1
|
|
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
var blinding [32]byte
|
|
rand.Read(blinding[:])
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = scheme.NoteCommitment(amount, assetId, owner, blinding)
|
|
}
|
|
}
|
|
|
|
// BenchmarkNoteCommitmentPedersen benchmarks note commitment with Pedersen
|
|
func BenchmarkNoteCommitmentPedersen(b *testing.B) {
|
|
scheme := NewPedersenScheme()
|
|
amount := big.NewInt(1000)
|
|
var assetId [32]byte
|
|
assetId[31] = 1
|
|
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
var blinding [32]byte
|
|
rand.Read(blinding[:])
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = scheme.NoteCommitment(amount, assetId, owner, blinding)
|
|
}
|
|
}
|
|
|
|
// BenchmarkMerkleRoot benchmarks Merkle root computation
|
|
func BenchmarkMerkleRoot(b *testing.B) {
|
|
hasher := NewPoseidon2Hasher()
|
|
leaves := make([][32]byte, 1024)
|
|
for i := range leaves {
|
|
rand.Read(leaves[i][:])
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = hasher.MerkleRoot(leaves)
|
|
}
|
|
}
|
|
|
|
// BenchmarkMerkleProof benchmarks Merkle proof generation
|
|
func BenchmarkMerkleProof(b *testing.B) {
|
|
hasher := NewPoseidon2Hasher()
|
|
leaves := make([][32]byte, 1024)
|
|
for i := range leaves {
|
|
rand.Read(leaves[i][:])
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, _, _ = hasher.MerkleProof(leaves, i%1024)
|
|
}
|
|
}
|
|
|
|
// BenchmarkComparison runs comparison benchmarks
|
|
func BenchmarkComparison(b *testing.B) {
|
|
poseidon := NewPoseidon2Hasher()
|
|
pedersen := NewPedersenCommitter()
|
|
|
|
amount := big.NewInt(1000)
|
|
var assetId [32]byte
|
|
assetId[31] = 1
|
|
owner := common.HexToAddress("0x1234567890123456789012345678901234567890")
|
|
var blinding [32]byte
|
|
rand.Read(blinding[:])
|
|
|
|
b.Run("Poseidon2-NoteCommit", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = poseidon.NoteCommitment(amount, assetId, owner, blinding)
|
|
}
|
|
})
|
|
|
|
b.Run("Pedersen-NoteCommit", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_, _ = pedersen.NoteCommitment(amount, assetId, owner, blinding)
|
|
}
|
|
})
|
|
|
|
// Print gas costs
|
|
b.Run("GasCosts", func(b *testing.B) {
|
|
poseidonGas := NewPoseidon2Scheme().RequiredGas()
|
|
pedersenGas := NewPedersenScheme().RequiredGas()
|
|
fmt.Printf("\nGas costs:\n")
|
|
fmt.Printf(" Poseidon2 NoteCommit: %d gas (PQ-safe)\n", poseidonGas)
|
|
fmt.Printf(" Pedersen NoteCommit: %d gas (NOT PQ-safe)\n", pedersenGas)
|
|
fmt.Printf(" Ratio: %.2fx\n", float64(pedersenGas)/float64(poseidonGas))
|
|
})
|
|
}
|
|
|
|
// TestGoldilocksField tests Goldilocks field operations
|
|
func TestGoldilocksField(t *testing.T) {
|
|
f := &GoldilocksField{}
|
|
|
|
// Test basic operations
|
|
a := uint64(12345)
|
|
b := uint64(67890)
|
|
|
|
// Addition
|
|
sum := f.Add(a, b)
|
|
require.Equal(t, a+b, sum)
|
|
|
|
// Multiplication
|
|
product := f.Mul(a, b)
|
|
expected := new(big.Int).Mul(big.NewInt(int64(a)), big.NewInt(int64(b)))
|
|
expected.Mod(expected, GoldilocksModulus)
|
|
require.Equal(t, expected.Uint64(), product)
|
|
|
|
// Inverse
|
|
inv := f.Inv(a)
|
|
check := f.Mul(a, inv)
|
|
require.Equal(t, uint64(1), check, "a * a^(-1) should equal 1")
|
|
}
|
|
|
|
// TestExtensionField tests quadratic extension field
|
|
func TestExtensionField(t *testing.T) {
|
|
// Test multiplication
|
|
x := ExtensionField{A: 3, B: 4}
|
|
y := ExtensionField{A: 5, B: 6}
|
|
|
|
product := ExtMul(x, y)
|
|
require.NotEqual(t, uint64(0), product.A)
|
|
|
|
// Test inverse
|
|
inv := ExtInv(x)
|
|
check := ExtMul(x, inv)
|
|
// Should be close to (1, 0)
|
|
require.Equal(t, uint64(1), check.A)
|
|
require.Equal(t, uint64(0), check.B)
|
|
}
|
|
|
|
// BenchmarkGoldilocksOps benchmarks Goldilocks field operations
|
|
func BenchmarkGoldilocksOps(b *testing.B) {
|
|
f := &GoldilocksField{}
|
|
a := uint64(0x123456789ABCDEF0)
|
|
bb := uint64(0xFEDCBA9876543210)
|
|
|
|
b.Run("Add", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_ = f.Add(a, bb)
|
|
}
|
|
})
|
|
|
|
b.Run("Mul", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_ = f.Mul(a, bb)
|
|
}
|
|
})
|
|
|
|
b.Run("Inv", func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_ = f.Inv(a)
|
|
}
|
|
})
|
|
|
|
b.Run("Exp", func(b *testing.B) {
|
|
exp := uint64(1000)
|
|
for i := 0; i < b.N; i++ {
|
|
_ = f.Exp(a, exp)
|
|
}
|
|
})
|
|
}
|