mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 01:41:36 +00:00
crypto/kzg4844: add cell-related functions (#34766)
This PR adds three cell-level kzg functions required for the sparse blobpool (eth/72). - VerifyCells: Verifies cells corresponding to proofs. This is used to verify cells received from eth/72 peers. - ComputeCells: Computes cells from blobs. This is needed because user submissions and eth/71 transaction deliveries contain blobs, while eth/72 peers expect cells. - RecoverBlobs: Recovers blobs from partial cells. This is needed to support both eth/71 and eth/72 --------- Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
parent
8e2107dc39
commit
526ad4f6f1
5 changed files with 499 additions and 45 deletions
|
|
@ -34,9 +34,27 @@ var (
|
|||
blobT = reflect.TypeFor[Blob]()
|
||||
commitmentT = reflect.TypeFor[Commitment]()
|
||||
proofT = reflect.TypeFor[Proof]()
|
||||
cellT = reflect.TypeFor[Cell]()
|
||||
)
|
||||
|
||||
const CellProofsPerBlob = 128
|
||||
const (
|
||||
CellProofsPerBlob = 128
|
||||
CellsPerBlob = 128
|
||||
DataPerBlob = 64
|
||||
)
|
||||
|
||||
// Cell represents a single cell in a blob.
|
||||
type Cell [2048]byte
|
||||
|
||||
// UnmarshalJSON parses a cell in hex syntax.
|
||||
func (c *Cell) UnmarshalJSON(input []byte) error {
|
||||
return hexutil.UnmarshalFixedJSON(cellT, input, c[:])
|
||||
}
|
||||
|
||||
// MarshalText returns the hex representation of c.
|
||||
func (c *Cell) MarshalText() ([]byte, error) {
|
||||
return hexutil.Bytes(c[:]).MarshalText()
|
||||
}
|
||||
|
||||
// Blob represents a 4844 data blob.
|
||||
type Blob [131072]byte
|
||||
|
|
@ -189,3 +207,75 @@ func CalcBlobHashV1(hasher hash.Hash, commit *Commitment) (vh [32]byte) {
|
|||
func IsValidVersionedHash(h []byte) bool {
|
||||
return len(h) == 32 && h[0] == 0x01
|
||||
}
|
||||
|
||||
// VerifyCells verifies a batch of proofs corresponding to the cells and blob commitments.
|
||||
//
|
||||
// For this function, it is sufficient to only provide some of the cells.
|
||||
//
|
||||
// The `cellIndices` specify which of the 128 cells of each blob are given.
|
||||
// Indices must be given in ascending order.
|
||||
//
|
||||
// Note the list of indices is shared among all blobs, i.e. for a given list of indices
|
||||
// [1, 2, 13], the cells slice must contain cells [1, 2, 13] of each blob.
|
||||
// Thus, `len(cells)` must be a multiple of `len(cellIndices)`.
|
||||
//
|
||||
// One proof must be given for each cell. As such, `len(proofs)` must equal `len(cells)`.
|
||||
func VerifyCells(cells []Cell, commitments []Commitment, proofs []Proof, cellIndices []uint64) error {
|
||||
// commitments/proofs/cells validation
|
||||
switch {
|
||||
case len(commitments) == 0:
|
||||
return errors.New("no commitments")
|
||||
case len(proofs)%len(commitments) != 0:
|
||||
return errors.New("len(proofs) must be a multiple of len(commitments)")
|
||||
case len(cells) != len(proofs):
|
||||
return errors.New("mismatched len(cellProofs) and len(cells)")
|
||||
}
|
||||
if err := validateCellIndices(cells, cellIndices); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cells)/len(cellIndices) != len(commitments) {
|
||||
return errors.New("invalid number of cells for blob count")
|
||||
}
|
||||
|
||||
if useCKZG.Load() {
|
||||
return ckzgVerifyCells(cells, commitments, proofs, cellIndices)
|
||||
}
|
||||
return gokzgVerifyCells(cells, commitments, proofs, cellIndices)
|
||||
}
|
||||
|
||||
// ComputeCells computes the cells from the given blobs.
|
||||
func ComputeCells(blobs []Blob) ([]Cell, error) {
|
||||
if useCKZG.Load() {
|
||||
return ckzgComputeCells(blobs)
|
||||
}
|
||||
return gokzgComputeCells(blobs)
|
||||
}
|
||||
|
||||
// RecoverBlobs recovers blobs from the given cells and cell indices.
|
||||
// In order to successfully recover, at least DataPerBlob (64) cells must be provided.
|
||||
//
|
||||
// For the layout of cells and cellIndices, please see [VerifyCells].
|
||||
func RecoverBlobs(cells []Cell, cellIndices []uint64) ([]Blob, error) {
|
||||
if err := validateCellIndices(cells, cellIndices); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if useCKZG.Load() {
|
||||
return ckzgRecoverBlobs(cells, cellIndices)
|
||||
}
|
||||
return gokzgRecoverBlobs(cells, cellIndices)
|
||||
}
|
||||
|
||||
func validateCellIndices(cells []Cell, cellIndices []uint64) error {
|
||||
switch {
|
||||
case len(cellIndices) == 0:
|
||||
return errors.New("no cellIndices given")
|
||||
case len(cellIndices) > len(cells):
|
||||
return errors.New("less cells than cellIndices")
|
||||
case len(cellIndices) > CellsPerBlob:
|
||||
return errors.New("too many cellIndices")
|
||||
case len(cells)%len(cellIndices) != 0:
|
||||
return errors.New("len(cells) must be a multiple of len(cellIndices)")
|
||||
}
|
||||
// The library checks the canonical ordering of indices, so we don't have to do it here.
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,3 +190,92 @@ func ckzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ckzgVerifyCells verifies that the cell data corresponds to the provided commitments.
|
||||
func ckzgVerifyCells(cells []Cell, commitments []Commitment, cellProofs []Proof, cellIndices []uint64) error {
|
||||
ckzgIniter.Do(ckzgInit)
|
||||
var (
|
||||
proofs = make([]ckzg4844.Bytes48, len(cellProofs))
|
||||
commits = make([]ckzg4844.Bytes48, 0, len(cellProofs))
|
||||
indices = make([]uint64, 0, len(cellProofs))
|
||||
kzgcells = make([]ckzg4844.Cell, 0, len(cellProofs))
|
||||
)
|
||||
for i := range cellProofs {
|
||||
proofs[i] = (ckzg4844.Bytes48)(cellProofs[i])
|
||||
kzgcells = append(kzgcells, (ckzg4844.Cell)(cells[i]))
|
||||
}
|
||||
if len(cellProofs)%len(commitments) != 0 {
|
||||
return errors.New("wrong cell proofs and commitments length")
|
||||
}
|
||||
cellCounts := len(cellProofs) / len(commitments)
|
||||
for _, commitment := range commitments {
|
||||
for j := 0; j < cellCounts; j++ {
|
||||
commits = append(commits, (ckzg4844.Bytes48)(commitment))
|
||||
}
|
||||
}
|
||||
for j := 0; j < len(commitments); j++ {
|
||||
indices = append(indices, cellIndices...)
|
||||
}
|
||||
|
||||
valid, err := ckzg4844.VerifyCellKZGProofBatch(commits, indices, kzgcells, proofs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !valid {
|
||||
return errors.New("invalid proof")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ckzgComputeCells computes cells from blobs.
|
||||
func ckzgComputeCells(blobs []Blob) ([]Cell, error) {
|
||||
ckzgIniter.Do(ckzgInit)
|
||||
cells := make([]Cell, 0, ckzg4844.CellsPerExtBlob*len(blobs))
|
||||
|
||||
for i := range blobs {
|
||||
cellsI, err := ckzg4844.ComputeCells((*ckzg4844.Blob)(&blobs[i]))
|
||||
if err != nil {
|
||||
return []Cell{}, err
|
||||
}
|
||||
for _, c := range cellsI {
|
||||
cells = append(cells, Cell(c))
|
||||
}
|
||||
}
|
||||
return cells, nil
|
||||
}
|
||||
|
||||
// ckzgRecoverBlobs recovers blobs from cells and cell indices.
|
||||
func ckzgRecoverBlobs(cells []Cell, cellIndices []uint64) ([]Blob, error) {
|
||||
ckzgIniter.Do(ckzgInit)
|
||||
|
||||
if len(cellIndices) == 0 || len(cells)%len(cellIndices) != 0 {
|
||||
return []Blob{}, errors.New("cells with wrong length")
|
||||
}
|
||||
|
||||
blobCount := len(cells) / len(cellIndices)
|
||||
blobs := make([]Blob, 0, blobCount)
|
||||
|
||||
offset := 0
|
||||
for range blobCount {
|
||||
kzgcells := make([]ckzg4844.Cell, 0, len(cellIndices))
|
||||
|
||||
for _, cell := range cells[offset : offset+len(cellIndices)] {
|
||||
kzgcells = append(kzgcells, ckzg4844.Cell(cell))
|
||||
}
|
||||
|
||||
extCells, err := ckzg4844.RecoverCells(cellIndices, kzgcells)
|
||||
if err != nil {
|
||||
return []Blob{}, err
|
||||
}
|
||||
|
||||
var blob Blob
|
||||
for i, cell := range extCells[:DataPerBlob] {
|
||||
copy(blob[i*len(cell):], cell[:])
|
||||
}
|
||||
blobs = append(blobs, blob)
|
||||
|
||||
offset = offset + len(cellIndices)
|
||||
}
|
||||
|
||||
return blobs, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,3 +73,15 @@ func ckzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, proof []Pr
|
|||
func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
|
||||
panic("unsupported platform")
|
||||
}
|
||||
|
||||
func ckzgVerifyCells(cells []Cell, commitments []Commitment, cellProofs []Proof, cellIndices []uint64) error {
|
||||
panic("unsupported platform")
|
||||
}
|
||||
|
||||
func ckzgComputeCells(blobs []Blob) ([]Cell, error) {
|
||||
panic("unsupported platform")
|
||||
}
|
||||
|
||||
func ckzgRecoverBlobs(cells []Cell, cellIndices []uint64) ([]Blob, error) {
|
||||
panic("unsupported platform")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,3 +148,85 @@ func gokzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProof
|
|||
}
|
||||
return context.VerifyCellKZGProofBatch(commits, cellIndices, cells[:], proofs)
|
||||
}
|
||||
|
||||
// gokzgVerifyCells verifies that the cell data corresponds to the provided commitment.
|
||||
func gokzgVerifyCells(cells []Cell, commitments []Commitment, cellProofs []Proof, cellIndices []uint64) error {
|
||||
gokzgIniter.Do(gokzgInit)
|
||||
|
||||
var (
|
||||
proofs = make([]gokzg4844.KZGProof, len(cellProofs))
|
||||
commits = make([]gokzg4844.KZGCommitment, 0, len(cellProofs))
|
||||
indices = make([]uint64, 0, len(cellProofs))
|
||||
kzgcells = make([]*gokzg4844.Cell, 0, len(cellProofs))
|
||||
)
|
||||
// Copy over the cell proofs and cells
|
||||
for i := range cellProofs {
|
||||
proofs[i] = gokzg4844.KZGProof(cellProofs[i])
|
||||
gc := gokzg4844.Cell(cells[i])
|
||||
kzgcells = append(kzgcells, &gc)
|
||||
}
|
||||
cellCounts := len(cellProofs) / len(commitments)
|
||||
// Blow up the commitments to be the same length as the proofs
|
||||
for _, commitment := range commitments {
|
||||
for j := 0; j < cellCounts; j++ {
|
||||
commits = append(commits, gokzg4844.KZGCommitment(commitment))
|
||||
}
|
||||
}
|
||||
for j := 0; j < len(commitments); j++ {
|
||||
indices = append(indices, cellIndices...)
|
||||
}
|
||||
|
||||
return context.VerifyCellKZGProofBatch(commits, indices, kzgcells, proofs)
|
||||
}
|
||||
|
||||
// gokzgComputeCells computes cells from blobs.
|
||||
func gokzgComputeCells(blobs []Blob) ([]Cell, error) {
|
||||
gokzgIniter.Do(gokzgInit)
|
||||
cells := make([]Cell, 0, gokzg4844.CellsPerExtBlob*len(blobs))
|
||||
|
||||
for i := range blobs {
|
||||
cellsI, err := context.ComputeCells((*gokzg4844.Blob)(&blobs[i]), 2)
|
||||
if err != nil {
|
||||
return []Cell{}, err
|
||||
}
|
||||
for _, c := range cellsI {
|
||||
if c != nil {
|
||||
cells = append(cells, Cell(*c))
|
||||
}
|
||||
}
|
||||
}
|
||||
return cells, nil
|
||||
}
|
||||
|
||||
// gokzgRecoverBlobs recovers blobs from cells and cell indices.
|
||||
func gokzgRecoverBlobs(cells []Cell, cellIndices []uint64) ([]Blob, error) {
|
||||
gokzgIniter.Do(gokzgInit)
|
||||
|
||||
blobCount := len(cells) / len(cellIndices)
|
||||
blobs := make([]Blob, 0, blobCount)
|
||||
|
||||
offset := 0
|
||||
for range blobCount {
|
||||
kzgcells := make([]*gokzg4844.Cell, 0, len(cellIndices))
|
||||
|
||||
for _, cell := range cells[offset : offset+len(cellIndices)] {
|
||||
gc := gokzg4844.Cell(cell)
|
||||
kzgcells = append(kzgcells, &gc)
|
||||
}
|
||||
|
||||
extCells, err := context.RecoverCells(cellIndices, kzgcells, 2)
|
||||
if err != nil {
|
||||
return []Blob{}, err
|
||||
}
|
||||
|
||||
var blob Blob
|
||||
for i, cell := range extCells[:DataPerBlob] {
|
||||
copy(blob[i*len(cell):], cell[:])
|
||||
}
|
||||
blobs = append(blobs, blob)
|
||||
|
||||
offset = offset + len(cellIndices)
|
||||
}
|
||||
|
||||
return blobs, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ package kzg4844
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
mrand "math/rand"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/consensys/gnark-crypto/ecc/bls12-381/fr"
|
||||
|
|
@ -45,14 +47,20 @@ func randBlob() *Blob {
|
|||
return &blob
|
||||
}
|
||||
|
||||
func TestCKZGWithPoint(t *testing.T) { testKZGWithPoint(t, true) }
|
||||
func TestGoKZGWithPoint(t *testing.T) { testKZGWithPoint(t, false) }
|
||||
func testKZGWithPoint(t *testing.T, ckzg bool) {
|
||||
func switchBackend(t testing.TB, ckzg bool) (switchBack func()) {
|
||||
t.Helper()
|
||||
if ckzg && !ckzgAvailable {
|
||||
t.Skip("CKZG unavailable in this test build")
|
||||
}
|
||||
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
|
||||
prev := useCKZG.Load()
|
||||
useCKZG.Store(ckzg)
|
||||
return func() { useCKZG.Store(prev) }
|
||||
}
|
||||
|
||||
func TestCKZGWithPoint(t *testing.T) { testKZGWithPoint(t, true) }
|
||||
func TestGoKZGWithPoint(t *testing.T) { testKZGWithPoint(t, false) }
|
||||
func testKZGWithPoint(t *testing.T, ckzg bool) {
|
||||
defer switchBackend(t, ckzg)()
|
||||
|
||||
blob := randBlob()
|
||||
|
||||
|
|
@ -73,11 +81,7 @@ func testKZGWithPoint(t *testing.T, ckzg bool) {
|
|||
func TestCKZGWithBlob(t *testing.T) { testKZGWithBlob(t, true) }
|
||||
func TestGoKZGWithBlob(t *testing.T) { testKZGWithBlob(t, false) }
|
||||
func testKZGWithBlob(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)
|
||||
defer switchBackend(t, ckzg)()
|
||||
|
||||
blob := randBlob()
|
||||
|
||||
|
|
@ -97,11 +101,7 @@ func testKZGWithBlob(t *testing.T, ckzg bool) {
|
|||
func BenchmarkCKZGBlobToCommitment(b *testing.B) { benchmarkBlobToCommitment(b, true) }
|
||||
func BenchmarkGoKZGBlobToCommitment(b *testing.B) { benchmarkBlobToCommitment(b, false) }
|
||||
func benchmarkBlobToCommitment(b *testing.B, ckzg bool) {
|
||||
if ckzg && !ckzgAvailable {
|
||||
b.Skip("CKZG unavailable in this test build")
|
||||
}
|
||||
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
|
||||
useCKZG.Store(ckzg)
|
||||
defer switchBackend(b, ckzg)()
|
||||
|
||||
blob := randBlob()
|
||||
|
||||
|
|
@ -113,11 +113,7 @@ func benchmarkBlobToCommitment(b *testing.B, ckzg bool) {
|
|||
func BenchmarkCKZGComputeProof(b *testing.B) { benchmarkComputeProof(b, true) }
|
||||
func BenchmarkGoKZGComputeProof(b *testing.B) { benchmarkComputeProof(b, false) }
|
||||
func benchmarkComputeProof(b *testing.B, ckzg bool) {
|
||||
if ckzg && !ckzgAvailable {
|
||||
b.Skip("CKZG unavailable in this test build")
|
||||
}
|
||||
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
|
||||
useCKZG.Store(ckzg)
|
||||
defer switchBackend(b, ckzg)()
|
||||
|
||||
var (
|
||||
blob = randBlob()
|
||||
|
|
@ -132,11 +128,7 @@ func benchmarkComputeProof(b *testing.B, ckzg bool) {
|
|||
func BenchmarkCKZGVerifyProof(b *testing.B) { benchmarkVerifyProof(b, true) }
|
||||
func BenchmarkGoKZGVerifyProof(b *testing.B) { benchmarkVerifyProof(b, false) }
|
||||
func benchmarkVerifyProof(b *testing.B, ckzg bool) {
|
||||
if ckzg && !ckzgAvailable {
|
||||
b.Skip("CKZG unavailable in this test build")
|
||||
}
|
||||
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
|
||||
useCKZG.Store(ckzg)
|
||||
defer switchBackend(b, ckzg)()
|
||||
|
||||
var (
|
||||
blob = randBlob()
|
||||
|
|
@ -153,11 +145,7 @@ func benchmarkVerifyProof(b *testing.B, ckzg bool) {
|
|||
func BenchmarkCKZGComputeBlobProof(b *testing.B) { benchmarkComputeBlobProof(b, true) }
|
||||
func BenchmarkGoKZGComputeBlobProof(b *testing.B) { benchmarkComputeBlobProof(b, false) }
|
||||
func benchmarkComputeBlobProof(b *testing.B, ckzg bool) {
|
||||
if ckzg && !ckzgAvailable {
|
||||
b.Skip("CKZG unavailable in this test build")
|
||||
}
|
||||
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
|
||||
useCKZG.Store(ckzg)
|
||||
defer switchBackend(b, ckzg)()
|
||||
|
||||
var (
|
||||
blob = randBlob()
|
||||
|
|
@ -172,11 +160,7 @@ func benchmarkComputeBlobProof(b *testing.B, ckzg bool) {
|
|||
func BenchmarkCKZGVerifyBlobProof(b *testing.B) { benchmarkVerifyBlobProof(b, true) }
|
||||
func BenchmarkGoKZGVerifyBlobProof(b *testing.B) { benchmarkVerifyBlobProof(b, false) }
|
||||
func benchmarkVerifyBlobProof(b *testing.B, ckzg bool) {
|
||||
if ckzg && !ckzgAvailable {
|
||||
b.Skip("CKZG unavailable in this test build")
|
||||
}
|
||||
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
|
||||
useCKZG.Store(ckzg)
|
||||
defer switchBackend(b, ckzg)()
|
||||
|
||||
var (
|
||||
blob = randBlob()
|
||||
|
|
@ -192,11 +176,7 @@ func benchmarkVerifyBlobProof(b *testing.B, ckzg bool) {
|
|||
func TestCKZGCells(t *testing.T) { testKZGCells(t, true) }
|
||||
func TestGoKZGCells(t *testing.T) { testKZGCells(t, false) }
|
||||
func testKZGCells(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)
|
||||
defer switchBackend(t, ckzg)()
|
||||
|
||||
blob1 := randBlob()
|
||||
blob2 := randBlob()
|
||||
|
|
@ -236,11 +216,7 @@ func BenchmarkGOKZGComputeCellProofs(b *testing.B) { benchmarkComputeCellProofs(
|
|||
func BenchmarkCKZGComputeCellProofs(b *testing.B) { benchmarkComputeCellProofs(b, true) }
|
||||
|
||||
func benchmarkComputeCellProofs(b *testing.B, ckzg bool) {
|
||||
if ckzg && !ckzgAvailable {
|
||||
b.Skip("CKZG unavailable in this test build")
|
||||
}
|
||||
defer func(old bool) { useCKZG.Store(old) }(useCKZG.Load())
|
||||
useCKZG.Store(ckzg)
|
||||
defer switchBackend(b, ckzg)()
|
||||
|
||||
blob := randBlob()
|
||||
_, _ = ComputeCellProofs(blob) // for kzg initialization
|
||||
|
|
@ -253,3 +229,208 @@ 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) }
|
||||
|
||||
func testVerifyPartialCells(t *testing.T, ckzg bool) {
|
||||
defer switchBackend(t, ckzg)()
|
||||
|
||||
const (
|
||||
iterations = 50
|
||||
blobCount = 3
|
||||
cellsCount = 8
|
||||
)
|
||||
// Precompute blobs once, vary only cell indices per iteration
|
||||
d := newBlobs(t, blobCount)
|
||||
|
||||
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)])
|
||||
}
|
||||
}
|
||||
if err := VerifyCells(partialCells, d.commitments, partialProofs, indices); err != nil {
|
||||
t.Fatalf("iter %d: failed to verify partial cells: %v", iter, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCKZGVerifyCellsWithCorruptedCells(t *testing.T) {
|
||||
testVerifyCellsWithCorruptedCells(t, true)
|
||||
}
|
||||
func TestGoKZGVerifyCellsWithCorruptedCells(t *testing.T) {
|
||||
testVerifyCellsWithCorruptedCells(t, false)
|
||||
}
|
||||
|
||||
func testVerifyCellsWithCorruptedCells(t *testing.T, ckzg bool) {
|
||||
defer switchBackend(t, 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) {
|
||||
defer switchBackend(t, 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")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCKZGRecoverBlob(t *testing.T) { testRecoverBlob(t, true) }
|
||||
func TestGoKZGRecoverBlob(t *testing.T) { testRecoverBlob(t, false) }
|
||||
|
||||
func testRecoverBlob(t *testing.T, ckzg bool) {
|
||||
defer switchBackend(t, ckzg)()
|
||||
|
||||
// Precompute blobs once, vary only cell indices per iteration
|
||||
d := newBlobs(t, 3)
|
||||
|
||||
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)])
|
||||
}
|
||||
}
|
||||
recovered, err := RecoverBlobs(partialCells, indices)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCKZGRecoverBlobWithInsufficientCells(t *testing.T) {
|
||||
testRecoverBlobWithInsufficientCells(t, true)
|
||||
}
|
||||
func TestGoKZGRecoverBlobWithInsufficientCells(t *testing.T) {
|
||||
testRecoverBlobWithInsufficientCells(t, false)
|
||||
}
|
||||
|
||||
func testRecoverBlobWithInsufficientCells(t *testing.T, ckzg bool) {
|
||||
defer switchBackend(t, 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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue