Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions crypto/kzg4844/kzg4844.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,28 @@ var (
blobT = reflect.TypeFor[Blob]()
commitmentT = reflect.TypeFor[Commitment]()
proofT = reflect.TypeFor[Proof]()
cellT = reflect.TypeFor[Cell]()
)

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

Expand Down Expand Up @@ -179,3 +197,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
}
130 changes: 130 additions & 0 deletions crypto/kzg4844/kzg4844_ckzg_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,133 @@ func ckzgComputeCellProofs(blob *Blob) ([]Proof, error) {
}
return p, nil
}

// ckzgVerifyCellProofBatch verifies that the blob data corresponds to the provided commitment.
func ckzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs []Proof) error {
ckzgIniter.Do(ckzgInit)
var (
proofs = make([]ckzg4844.Bytes48, len(cellProofs))
commits = make([]ckzg4844.Bytes48, 0, len(cellProofs))
cellIndices = make([]uint64, 0, len(cellProofs))
cells = make([]ckzg4844.Cell, 0, len(cellProofs))
)
// Copy over the cell proofs
for i, proof := range cellProofs {
proofs[i] = (ckzg4844.Bytes48)(proof)
}
// Blow up the commitments to be the same length as the proofs
for _, commitment := range commitments {
for range gokzg4844.CellsPerExtBlob {
commits = append(commits, (ckzg4844.Bytes48)(commitment))
}
}
// Compute the cells and cell indices
for i := range blobs {
cellsI, err := ckzg4844.ComputeCells((*ckzg4844.Blob)(&blobs[i]))
if err != nil {
return err
}
cells = append(cells, cellsI[:]...)
for idx := range len(cellsI) {
cellIndices = append(cellIndices, uint64(idx))
}
}

valid, err := ckzg4844.VerifyCellKZGProofBatch(commits, cellIndices, cells, proofs)
if err != nil {
return err
}
if !valid {
return errors.New("invalid proof")
}
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
}
12 changes: 12 additions & 0 deletions crypto/kzg4844/kzg4844_ckzg_nocgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,15 @@ func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
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")
}
116 changes: 116 additions & 0 deletions crypto/kzg4844/kzg4844_gokzg.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,119 @@ func gokzgComputeCellProofs(blob *Blob) ([]Proof, error) {
}
return p, nil
}

// gokzgVerifyCellProofBatch verifies that the blob data corresponds to the provided commitment.
func gokzgVerifyCellProofBatch(blobs []Blob, commitments []Commitment, cellProofs []Proof) error {
gokzgIniter.Do(gokzgInit)

var (
proofs = make([]gokzg4844.KZGProof, len(cellProofs))
commits = make([]gokzg4844.KZGCommitment, 0, len(cellProofs))
cellIndices = make([]uint64, 0, len(cellProofs))
cells = make([]*gokzg4844.Cell, 0, len(cellProofs))
)
// Copy over the cell proofs
for i, proof := range cellProofs {
proofs[i] = gokzg4844.KZGProof(proof)
}
// Blow up the commitments to be the same length as the proofs
for _, commitment := range commitments {
for range gokzg4844.CellsPerExtBlob {
commits = append(commits, gokzg4844.KZGCommitment(commitment))
}
}
// Compute the cell and cell indices
for i := range blobs {
cellsI, err := context.ComputeCells((*gokzg4844.Blob)(&blobs[i]), 2)
if err != nil {
return err
}
cells = append(cells, cellsI[:]...)
for idx := range len(cellsI) {
cellIndices = append(cellIndices, uint64(idx))
}
}
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
}
Loading