Files
precompile/zk/commitment_test.go
T

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)
}
})
}