diff --git a/crypto/kzg4844/kzg4844.go b/crypto/kzg4844/kzg4844.go index 3ccc204838..26ea6ff53b 100644 --- a/crypto/kzg4844/kzg4844.go +++ b/crypto/kzg4844/kzg4844.go @@ -34,10 +34,21 @@ var ( blobT = reflect.TypeFor[Blob]() commitmentT = reflect.TypeFor[Commitment]() proofT = reflect.TypeFor[Proof]() + cellT = reflect.TypeFor[Cell]() ) const CellProofsPerBlob = 128 +type Cell [2048]byte + +func (c *Cell) UnmarshalJSON(input []byte) error { + return hexutil.UnmarshalFixedJSON(cellT, input, c[:]) +} + +func (c *Cell) MarshalText() ([]byte, error) { + return hexutil.Bytes(c[:]).MarshalText() +} + // Blob represents a 4844 data blob. type Blob [131072]byte @@ -86,6 +97,10 @@ type Claim [32]byte // useCKZG controls whether the cryptography should use the Go or C backend. var useCKZG atomic.Bool +func init() { + UseCKZG(true) +} + // UseCKZG can be called to switch the default Go implementation of KZG to the C // library if for some reason the user wishes to do so (e.g. consensus bug in one // or the other). @@ -189,3 +204,27 @@ func CalcBlobHashV1(hasher hash.Hash, commit *Commitment) (vh [32]byte) { func IsValidVersionedHash(h []byte) bool { return len(h) == 32 && h[0] == 0x01 } + +// VerifyCellProof verifies a batch of proofs corresponding to the cells and commitments. +// Expects length of proofs, cells and cellIndices, flattened +func VerifyCells(cells []Cell, commitments []Commitment, proofs []Proof, cellIndices []uint64) error { + if useCKZG.Load() { + return ckzgVerifyCells(cells, commitments, proofs, cellIndices) + } + return gokzgVerifyCells(cells, commitments, proofs, cellIndices) +} + +func ComputeCells(blobs []Blob) ([]Cell, error) { + if useCKZG.Load() { + return ckzgComputeCells(blobs) + } + return gokzgComputeCells(blobs) +} + +func RecoverBlob(cells []Cell, cellIndices []uint64) (Blob, error) { + if useCKZG.Load() { + return ckzgRecoverBlob(cells, cellIndices) + } + return gokzgRecoverBlob(cells, cellIndices) + +} diff --git a/crypto/kzg4844/kzg4844_ckzg_cgo.go b/crypto/kzg4844/kzg4844_ckzg_cgo.go index 46509674b6..e539692de8 100644 --- a/crypto/kzg4844/kzg4844_ckzg_cgo.go +++ b/crypto/kzg4844/kzg4844_ckzg_cgo.go @@ -190,3 +190,74 @@ func ckzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs } return nil } + +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)) + kzgcells = make([]ckzg4844.Cell, 0, len(cellProofs)) + ) + // Copy over the cell proofs and cells + 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) + // 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, (ckzg4844.Bytes48)(commitment)) + } + } + valid, err := ckzg4844.VerifyCellKZGProofBatch(commits, cellIndices, kzgcells, proofs) + if err != nil { + return err + } + if !valid { + return errors.New("invalid proof") + } + return nil +} + +func ckzgComputeCells(blobs []Blob) ([]Cell, error) { + ckzgIniter.Do(ckzgInit) + var cells = make([]Cell, 0, ckzg4844.CellsPerExtBlob*len(blobs)) + + for i := range blobs { + cellsI, err := ckzg4844.ComputeCells((*ckzg4844.Blob)(&blobs[i])) + if err != nil { + return nil, err + } + for _, c := range cellsI { + cells = append(cells, Cell(c)) + } + } + return cells, nil +} + +func ckzgRecoverBlob(cells []Cell, cellIndices []uint64) (Blob, error) { + ckzgIniter.Do(ckzgInit) + + var kzgcells = make([]ckzg4844.Cell, 0, len(cells)) + + for _, cell := range cells { + kzgcells = append(kzgcells, ckzg4844.Cell(cell)) + } + + extCells, _, err := ckzg4844.RecoverCellsAndKZGProofs(cellIndices, kzgcells) + if err != nil { + return Blob{}, err + } + + var result Blob + offset := 0 + for _, cell := range extCells[:64] { + copy(result[offset:], cell[:]) + offset += len(cell) + } + return result, nil +} diff --git a/crypto/kzg4844/kzg4844_ckzg_nocgo.go b/crypto/kzg4844/kzg4844_ckzg_nocgo.go index 7c552e9a18..965817adcb 100644 --- a/crypto/kzg4844/kzg4844_ckzg_nocgo.go +++ b/crypto/kzg4844/kzg4844_ckzg_nocgo.go @@ -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 ckzgRecoverBlob(cells []Cell, cellIndices []uint64) (Blob, error) { + panic("unsupported platform") +} diff --git a/crypto/kzg4844/kzg4844_gokzg.go b/crypto/kzg4844/kzg4844_gokzg.go index e9676ff1b8..22471ebcf4 100644 --- a/crypto/kzg4844/kzg4844_gokzg.go +++ b/crypto/kzg4844/kzg4844_gokzg.go @@ -18,6 +18,7 @@ package kzg4844 import ( "encoding/json" + "errors" "sync" gokzg4844 "github.com/crate-crypto/go-eth-kzg" @@ -115,7 +116,7 @@ func gokzgComputeCellProofs(blob *Blob) ([]Proof, error) { return p, nil } -// gokzgVerifyCellProofBatch verifies that the blob data corresponds to the provided commitment. +// gokzgVerifyCellProofs verifies that the blob data corresponds to the provided commitment. func gokzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs []Proof) error { gokzgIniter.Do(gokzgInit) @@ -148,3 +149,74 @@ func gokzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProof } return context.VerifyCellKZGProofBatch(commits, cellIndices, cells[:], proofs) } + +// gokzgVerifyCell 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)) + 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) + } + if len(cellProofs)%len(commitments) != 0 { + return errors.New("wrong cell proofs and commitments length") + } + 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)) + } + } + return context.VerifyCellKZGProofBatch(commits, cellIndices, kzgcells, proofs) +} + +// gokzgComputeCells computes cells from blobs. +func gokzgComputeCells(blobs []Blob) ([]Cell, error) { + gokzgIniter.Do(gokzgInit) + var cells = make([]Cell, 0, gokzg4844.CellsPerExtBlob*len(blobs)) + + // Compute the cell and cell indices + for i := range blobs { + cellsI, err := context.ComputeCells((*gokzg4844.Blob)(&blobs[i]), 2) + if err != nil { + return nil, err + } + for _, c := range cellsI { + if c != nil { + cells = append(cells, Cell(*c)) + } + } + } + return cells, nil +} + +func gokzgRecoverBlob(cells []Cell, cellIndices []uint64) (Blob, error) { + gokzgIniter.Do(gokzgInit) + + var kzgcells = make([]*gokzg4844.Cell, 0, len(cells)) + + for _, cell := range cells { + gc := gokzg4844.Cell(cell) + kzgcells = append(kzgcells, &gc) + } + + extCells, _, err := context.RecoverCellsAndComputeKZGProofs(cellIndices, kzgcells, 2) // todo + if err != nil { + return Blob{}, err + } + + var result Blob + offset := 0 + for _, cell := range extCells[:64] { + copy(result[offset:], cell[:]) + offset += len(cell) + } + return result, nil +} diff --git a/crypto/kzg4844/kzg4844_test.go b/crypto/kzg4844/kzg4844_test.go index 7e73efd850..f0fde35c6e 100644 --- a/crypto/kzg4844/kzg4844_test.go +++ b/crypto/kzg4844/kzg4844_test.go @@ -230,3 +230,104 @@ func testKZGCells(t *testing.T, ckzg bool) { t.Fatalf("failed to verify KZG proof at point: %v", err) } } +func TestCKZGVerifyPartialCells(t *testing.T) { testVerifyPartialCells(t, true) } +func TestGoKZGVerifyPartialCells(t *testing.T) { testVerifyPartialCells(t, false) } + +func testVerifyPartialCells(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 + 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 + ) + + for bi, blob := range blobs { + proofs, err := ComputeCellProofs(blob) + if err != nil { + t.Fatalf("failed to compute cell proofs: %v", err) + } + cells, err := ComputeCells([]Blob{*blob}) + if err != nil { + t.Fatalf("failed to compute cells: %v", err) + } + + // sample 0, 31, 63, 95 cells + step := len(cells) / 4 + + sampleIdx := []int{0, step - 1, 2*step - 1, 3*step - 1} + for _, idx := range sampleIdx { + partialCells = append(partialCells, cells[idx]) + partialProofs = append(partialProofs, proofs[idx]) + commits = append(commits, commitments[bi]) + indices = append(indices, uint64(idx)) + } + } + + if err := VerifyCells(partialCells, commits, partialProofs, indices); err != nil { + t.Fatalf("failed to verify partial cell proofs: %v", err) + } +} + +func TestCKZGRecoverBlob(t *testing.T) { testRecoverBlob(t, true) } +func TestGoKZGRecoverBlob(t *testing.T) { testRecoverBlob(t, false) } + +func testRecoverBlob(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) + + var blob = randBlob() + cells, err := ComputeCells([]Blob{*blob}) + if err != nil { + t.Fatalf("failed to compute cells: %v", err) + } + proof, err := ComputeCellProofs(blob) + if err != nil { + t.Fatalf("failed to compute proof: %v", err) + } + commitment, err := BlobToCommitment(blob) + if err != nil { + t.Fatalf("failed to compute commitment: %v", err) + } + + var ( + partialCells []Cell + indices []uint64 + ) + + for ci, cell := range cells { + partialCells = append(partialCells, cell) + indices = append(indices, uint64(ci)) + } + + recoverBlob, err := RecoverBlob(partialCells, indices) + + if err != nil { + t.Fatalf("failed to recover blob: %v", err) + } + + if err := VerifyCellProofs([]Blob{recoverBlob}, []Commitment{commitment}, proof); err != nil { + t.Fatalf("failed to verify recovered blob: %v", err) + } +}