crypto/kzg4844: add more tests

This commit is contained in:
healthykim 2026-04-21 20:45:12 +02:00
parent 04a6c861d8
commit 5eb2e5e265

View file

@ -18,6 +18,8 @@ package kzg4844
import (
"crypto/rand"
mrand "math/rand"
"slices"
"testing"
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
@ -254,6 +256,51 @@ func benchmarkComputeCellProofs(b *testing.B, ckzg bool) {
}
}
// randCellIndices picks n random unique indices from [0, CellsPerBlob) in sorted order.
func randCellIndices(rng *mrand.Rand, n int) []uint64 {
perm := rng.Perm(CellsPerBlob)
indices := make([]uint64, n)
for i := 0; i < n; i++ {
indices[i] = uint64(perm[i])
}
slices.Sort(indices)
return indices
}
// randBlobAndProofs generates random blobs and precomputes their cells, proofs, and commitments.
type randBlobAndProofs struct {
blobs []Blob
commitments []Commitment
cells []Cell // flat: blobs[i] cells at [i*CellsPerBlob : (i+1)*CellsPerBlob]
proofs []Proof
}
func newBlobs(t *testing.T, blobCount int) *randBlobAndProofs {
d := &randBlobAndProofs{
blobs: make([]Blob, blobCount),
commitments: make([]Commitment, blobCount),
}
for i := range blobCount {
d.blobs[i] = *randBlob()
commitment, err := BlobToCommitment(&d.blobs[i])
if err != nil {
t.Fatalf("failed to compute commitment: %v", err)
}
d.commitments[i] = commitment
proofs, err := ComputeCellProofs(&d.blobs[i])
if err != nil {
t.Fatalf("failed to compute cell proofs: %v", err)
}
d.proofs = append(d.proofs, proofs...)
}
cells, err := ComputeCells(d.blobs)
if err != nil {
t.Fatalf("failed to compute cells: %v", err)
}
d.cells = cells
return d
}
func TestCKZGVerifyPartialCells(t *testing.T) { testVerifyPartialCells(t, true) }
func TestGoKZGVerifyPartialCells(t *testing.T) { testVerifyPartialCells(t, false) }
@ -264,48 +311,101 @@ func testVerifyPartialCells(t *testing.T, ckzg bool) {
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
useCKZG.Store(ckzg)
const blobCount = 3
var blobs []*Blob
var commitments []Commitment
for range blobCount {
blob := randBlob()
commitment, err := BlobToCommitment(blob)
if err != nil {
t.Fatalf("failed to commit blob: %v", err)
}
blobs = append(blobs, blob)
commitments = append(commitments, commitment)
}
var (
partialCells []Cell
partialProofs []Proof
commits []Commitment
indices []uint64
const (
iterations = 50
blobCount = 3
cellsCount = 8
)
// Precompute blobs once, vary only cell indices per iteration
d := newBlobs(t, blobCount)
for bi, blob := range blobs {
proofs, err := ComputeCellProofs(blob)
if err != nil {
t.Fatalf("failed to compute cell proofs: %v", err)
for iter := range iterations {
rng := mrand.New(mrand.NewSource(int64(iter)))
indices := randCellIndices(rng, cellsCount)
var partialCells []Cell
var partialProofs []Proof
for i := range blobCount {
for _, idx := range indices {
partialCells = append(partialCells, d.cells[i*CellsPerBlob+int(idx)])
partialProofs = append(partialProofs, d.proofs[i*CellProofsPerBlob+int(idx)])
}
}
cells, err := ComputeCells([]Blob{*blob})
if err != nil {
t.Fatalf("failed to compute cells: %v", err)
}
commits = append(commits, commitments[bi])
// sample 0, 31, 63, 95 cells
step := len(cells) / 4
indices = []uint64{0, uint64(step - 1), uint64(2*step - 1), uint64(3*step - 1)}
for _, idx := range indices {
partialCells = append(partialCells, cells[idx])
partialProofs = append(partialProofs, proofs[idx])
if err := VerifyCells(partialCells, d.commitments, partialProofs, indices); err != nil {
t.Fatalf("iter %d: failed to verify partial cells: %v", iter, err)
}
}
if err := VerifyCells(partialCells, commits, partialProofs, indices); err != nil {
t.Fatalf("failed to verify partial cell proofs: %v", err)
}
func TestCKZGVerifyCellsWithCorruptedCells(t *testing.T) {
testVerifyCellsWithCorruptedCells(t, true)
}
func TestGoKZGVerifyCellsWithCorruptedCells(t *testing.T) {
testVerifyCellsWithCorruptedCells(t, false)
}
func testVerifyCellsWithCorruptedCells(t *testing.T, ckzg bool) {
if ckzg && !ckzgAvailable {
t.Skip("CKZG unavailable in this test build")
}
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
useCKZG.Store(ckzg)
const blobCount = 3
d := newBlobs(t, blobCount)
indices := []uint64{0, 15, 63, 64, 95, 100, 120, 127}
var partialCells []Cell
var partialProofs []Proof
for i := range blobCount {
for _, idx := range indices {
partialCells = append(partialCells, d.cells[i*CellsPerBlob+int(idx)])
partialProofs = append(partialProofs, d.proofs[i*CellProofsPerBlob+int(idx)])
}
}
// Corrupt the first cell
corruptedCells := make([]Cell, len(partialCells))
copy(corruptedCells, partialCells)
corruptedCells[0][0] ^= 0xff
if err := VerifyCells(corruptedCells, d.commitments, partialProofs, indices); err == nil {
t.Fatal("expected verification failure with corrupted cell")
}
}
func TestCKZGVerifyCellsWithCorruptedProofs(t *testing.T) {
testVerifyCellsWithCorruptedProofs(t, true)
}
func TestGoKZGVerifyCellsWithCorruptedProofs(t *testing.T) {
testVerifyCellsWithCorruptedProofs(t, false)
}
func testVerifyCellsWithCorruptedProofs(t *testing.T, ckzg bool) {
if ckzg && !ckzgAvailable {
t.Skip("CKZG unavailable in this test build")
}
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
useCKZG.Store(ckzg)
const blobCount = 3
d := newBlobs(t, blobCount)
indices := []uint64{0, 15, 63, 64, 95, 100, 120, 127}
var partialCells []Cell
var partialProofs []Proof
for i := range blobCount {
for _, idx := range indices {
partialCells = append(partialCells, d.cells[i*CellsPerBlob+int(idx)])
partialProofs = append(partialProofs, d.proofs[i*CellProofsPerBlob+int(idx)])
}
}
// Swap first and last proof
wrongProofs := make([]Proof, len(partialProofs))
copy(wrongProofs, partialProofs)
wrongProofs[0], wrongProofs[len(wrongProofs)-1] = wrongProofs[len(wrongProofs)-1], wrongProofs[0]
if err := VerifyCells(partialCells, d.commitments, wrongProofs, indices); err == nil {
t.Fatal("expected verification failure with swapped proofs")
}
}
@ -319,53 +419,64 @@ func testRecoverBlob(t *testing.T, ckzg bool) {
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
useCKZG.Store(ckzg)
blobs := []Blob{}
blobs = append(blobs, *randBlob())
blobs = append(blobs, *randBlob())
blobs = append(blobs, *randBlob())
// Precompute blobs once, vary only cell indices per iteration
d := newBlobs(t, 3)
cells, err := ComputeCells(blobs)
if err != nil {
t.Fatalf("failed to compute cells: %v", err)
}
proofs := make([]Proof, 0)
commitments := make([]Commitment, len(blobs))
for i, blob := range blobs {
proof, err := ComputeCellProofs(&blob)
if err != nil {
t.Fatalf("failed to compute proof: %v", err)
for iter := range 50 {
rng := mrand.New(mrand.NewSource(int64(iter)))
numCells := DataPerBlob + rng.Intn(CellsPerBlob-DataPerBlob+1)
indices := randCellIndices(rng, numCells)
var partialCells []Cell
for bi := range 3 {
for _, idx := range indices {
partialCells = append(partialCells, d.cells[bi*CellsPerBlob+int(idx)])
}
}
proofs = append(proofs, proof...)
commitment, err := BlobToCommitment(&blob)
recovered, err := RecoverBlobs(partialCells, indices)
if err != nil {
t.Fatalf("failed to compute commitment: %v", err)
t.Fatalf("iter %d: failed to recover blob with %d cells: %v", iter, numCells, err)
}
if err := VerifyCellProofs(recovered, d.commitments, d.proofs); err != nil {
t.Fatalf("iter %d: recovered blobs failed verification: %v", iter, err)
}
for i := range d.blobs {
if recovered[i] != d.blobs[i] {
t.Fatalf("iter %d: recovered blob %d does not match original", iter, i)
}
}
commitments[i] = commitment
}
var (
partialCells []Cell
indices []uint64
)
for ci := 64; ci < 128; ci++ {
indices = append(indices, uint64(ci))
}
for i := 0; i < len(cells); i += 128 {
start := i + 64
end := i + 128
partialCells = append(partialCells, cells[start:end]...)
}
recoverBlobs, err := RecoverBlobs(partialCells, indices)
if err != nil {
t.Fatalf("failed to recover blob: %v", err)
}
if err := VerifyCellProofs(recoverBlobs, commitments, proofs); err != nil {
t.Fatalf("failed to verify recovered blob: %v", err)
}
}
func TestCKZGRecoverBlobWithInsufficientCells(t *testing.T) {
testRecoverBlobWithInsufficientCells(t, true)
}
func TestGoKZGRecoverBlobWithInsufficientCells(t *testing.T) {
testRecoverBlobWithInsufficientCells(t, false)
}
func testRecoverBlobWithInsufficientCells(t *testing.T, ckzg bool) {
if ckzg && !ckzgAvailable {
t.Skip("CKZG unavailable in this test build")
}
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
useCKZG.Store(ckzg)
const blobCount = 3
d := newBlobs(t, blobCount)
// Use DataPerBlob-1 cells (one short of minimum required)
indices := make([]uint64, DataPerBlob-1)
for i := range indices {
indices[i] = uint64(i)
}
var partialCells []Cell
for bi := range blobCount {
for _, idx := range indices {
partialCells = append(partialCells, d.cells[bi*CellsPerBlob+int(idx)])
}
}
if _, err := RecoverBlobs(partialCells, indices); err == nil {
t.Fatalf("expected error with only %d cells, got none", len(indices))
}
}