mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 01:43:47 +00:00
When the full data domain (cell indices 0..DataPerBlob-1) is present, a blob is the concatenation of its data cells, so it can be reconstructed without any KZG work. Add BlobsFromDataCells, which returns the blobs by concatenation when the data cells are present in canonical order and declines otherwise (so callers fall back to RecoverBlobs). Its accepted inputs are a strict subset of those accepted by RecoverBlobs, so a successful result is byte-identical; being pure byte copying it is independent of the selected KZG backend. Shared primitive: used both to skip KZG recovery when serving blobs and by the cell-recovery path (RecoverCells).
344 lines
12 KiB
Go
344 lines
12 KiB
Go
// Copyright 2023 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
// Package kzg4844 implements the KZG crypto for EIP-4844.
|
|
package kzg4844
|
|
|
|
import (
|
|
"embed"
|
|
"errors"
|
|
"hash"
|
|
"reflect"
|
|
"sync/atomic"
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
)
|
|
|
|
//go:embed trusted_setup.json
|
|
var content embed.FS
|
|
|
|
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
|
|
|
|
// A blob is exactly its DataPerBlob data cells, concatenated; the remaining
|
|
// cells (indices DataPerBlob..CellsPerBlob-1) carry redundancy only. Code
|
|
// reassembling blobs from cells relies on this, so assert it at compile time.
|
|
var _ [len(Blob{})]byte = [DataPerBlob * len(Cell{})]byte{}
|
|
|
|
// UnmarshalJSON parses a blob in hex syntax.
|
|
func (b *Blob) UnmarshalJSON(input []byte) error {
|
|
return hexutil.UnmarshalFixedJSON(blobT, input, b[:])
|
|
}
|
|
|
|
// MarshalText returns the hex representation of b.
|
|
func (b *Blob) MarshalText() ([]byte, error) {
|
|
return hexutil.Bytes(b[:]).MarshalText()
|
|
}
|
|
|
|
// Commitment is a serialized commitment to a polynomial.
|
|
type Commitment [48]byte
|
|
|
|
// UnmarshalJSON parses a commitment in hex syntax.
|
|
func (c *Commitment) UnmarshalJSON(input []byte) error {
|
|
return hexutil.UnmarshalFixedJSON(commitmentT, input, c[:])
|
|
}
|
|
|
|
// MarshalText returns the hex representation of c.
|
|
func (c Commitment) MarshalText() ([]byte, error) {
|
|
return hexutil.Bytes(c[:]).MarshalText()
|
|
}
|
|
|
|
// Proof is a serialized commitment to the quotient polynomial.
|
|
type Proof [48]byte
|
|
|
|
// UnmarshalJSON parses a proof in hex syntax.
|
|
func (p *Proof) UnmarshalJSON(input []byte) error {
|
|
return hexutil.UnmarshalFixedJSON(proofT, input, p[:])
|
|
}
|
|
|
|
// MarshalText returns the hex representation of p.
|
|
func (p Proof) MarshalText() ([]byte, error) {
|
|
return hexutil.Bytes(p[:]).MarshalText()
|
|
}
|
|
|
|
// Point is a BLS field element.
|
|
type Point [32]byte
|
|
|
|
// Claim is a claimed evaluation value in a specific point.
|
|
type Claim [32]byte
|
|
|
|
// useCKZG controls whether the cryptography should use the Go or C backend.
|
|
var useCKZG atomic.Bool
|
|
|
|
// 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).
|
|
func UseCKZG(use bool) error {
|
|
if use && !ckzgAvailable {
|
|
return errors.New("CKZG unavailable on your platform")
|
|
}
|
|
useCKZG.Store(use)
|
|
|
|
// Initializing the library can take 2-4 seconds - and can potentially crash
|
|
// on CKZG and non-ADX CPUs - so might as well do it now and don't wait until
|
|
// a crypto operation is actually needed live.
|
|
if use {
|
|
ckzgIniter.Do(ckzgInit)
|
|
} else {
|
|
gokzgIniter.Do(gokzgInit)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// BlobToCommitment creates a small commitment out of a data blob.
|
|
func BlobToCommitment(blob *Blob) (Commitment, error) {
|
|
if useCKZG.Load() {
|
|
return ckzgBlobToCommitment(blob)
|
|
}
|
|
return gokzgBlobToCommitment(blob)
|
|
}
|
|
|
|
// ComputeProof computes the KZG proof at the given point for the polynomial
|
|
// represented by the blob.
|
|
func ComputeProof(blob *Blob, point Point) (Proof, Claim, error) {
|
|
if useCKZG.Load() {
|
|
return ckzgComputeProof(blob, point)
|
|
}
|
|
return gokzgComputeProof(blob, point)
|
|
}
|
|
|
|
// VerifyProof verifies the KZG proof that the polynomial represented by the blob
|
|
// evaluated at the given point is the claimed value.
|
|
func VerifyProof(commitment Commitment, point Point, claim Claim, proof Proof) error {
|
|
if useCKZG.Load() {
|
|
return ckzgVerifyProof(commitment, point, claim, proof)
|
|
}
|
|
return gokzgVerifyProof(commitment, point, claim, proof)
|
|
}
|
|
|
|
// ComputeBlobProof returns the KZG proof that is used to verify the blob against
|
|
// the commitment.
|
|
//
|
|
// This method does not verify that the commitment is correct with respect to blob.
|
|
func ComputeBlobProof(blob *Blob, commitment Commitment) (Proof, error) {
|
|
if useCKZG.Load() {
|
|
return ckzgComputeBlobProof(blob, commitment)
|
|
}
|
|
return gokzgComputeBlobProof(blob, commitment)
|
|
}
|
|
|
|
// VerifyBlobProof verifies that the blob data corresponds to the provided commitment.
|
|
func VerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
|
|
if useCKZG.Load() {
|
|
return ckzgVerifyBlobProof(blob, commitment, proof)
|
|
}
|
|
return gokzgVerifyBlobProof(blob, commitment, proof)
|
|
}
|
|
|
|
// VerifyCellProofs verifies a batch of proofs corresponding to the blobs and commitments.
|
|
// Expects length of blobs and commitments to be equal.
|
|
// Expects length of proofs be 128 * length of blobs.
|
|
func VerifyCellProofs(blobs []Blob, commitments []Commitment, proofs []Proof) error {
|
|
if useCKZG.Load() {
|
|
return ckzgVerifyCellProofBatch(blobs, commitments, proofs)
|
|
}
|
|
return gokzgVerifyCellProofBatch(blobs, commitments, proofs)
|
|
}
|
|
|
|
// ComputeCellProofs returns the KZG cell proofs that are used to verify the blob against
|
|
// the commitment.
|
|
//
|
|
// This method does not verify that the commitment is correct with respect to blob.
|
|
func ComputeCellProofs(blob *Blob) ([]Proof, error) {
|
|
if useCKZG.Load() {
|
|
return ckzgComputeCellProofs(blob)
|
|
}
|
|
return gokzgComputeCellProofs(blob)
|
|
}
|
|
|
|
// CalcBlobHashV1 calculates the 'versioned blob hash' of a commitment.
|
|
// The given hasher must be a sha256 hash instance, otherwise the result will be invalid!
|
|
func CalcBlobHashV1(hasher hash.Hash, commit *Commitment) (vh [32]byte) {
|
|
if hasher.Size() != 32 {
|
|
panic("wrong hash size")
|
|
}
|
|
hasher.Reset()
|
|
hasher.Write(commit[:])
|
|
hasher.Sum(vh[:0])
|
|
vh[0] = 0x01 // version
|
|
return vh
|
|
}
|
|
|
|
// IsValidVersionedHash checks that h is a structurally-valid versioned blob hash.
|
|
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)
|
|
}
|
|
|
|
// blobsFromDataCells reconstructs blobs by concatenating their data cells (cell
|
|
// indices 0..DataPerBlob-1, by definition the blob contents), with no KZG
|
|
// involvement and no validation of the cell contents whatsoever (see
|
|
// RecoverBlobsUnchecked for the contract). It accepts a strict subset of the
|
|
// inputs RecoverBlobs accepts and returns identical bytes; on ok=false the
|
|
// caller should fall back to RecoverBlobs.
|
|
//
|
|
// For the layout of cells and cellIndices, see RecoverBlobs.
|
|
func blobsFromDataCells(cells []Cell, cellIndices []uint64) ([]Blob, bool) {
|
|
if validateCellIndices(cells, cellIndices) != nil {
|
|
return nil, false
|
|
}
|
|
// The head must be exactly the data cells in canonical order:
|
|
// cellIndices[i] == i for i < DataPerBlob.
|
|
if len(cellIndices) < DataPerBlob {
|
|
return nil, false
|
|
}
|
|
for i := range DataPerBlob {
|
|
if cellIndices[i] != uint64(i) {
|
|
return nil, false
|
|
}
|
|
}
|
|
// The tail is ignored by the concatenation but must still be well-formed:
|
|
// the KZG library that would reject it is never reached on this path.
|
|
for i := DataPerBlob; i < len(cellIndices); i++ {
|
|
if cellIndices[i] <= cellIndices[i-1] || cellIndices[i] >= CellsPerBlob {
|
|
return nil, false
|
|
}
|
|
}
|
|
blobCount := len(cells) / len(cellIndices)
|
|
blobs := make([]Blob, blobCount)
|
|
for b := range blobCount {
|
|
data := cells[b*len(cellIndices):][:DataPerBlob]
|
|
for i := range data {
|
|
copy(blobs[b][i*len(data[i]):], data[i][:])
|
|
}
|
|
}
|
|
return blobs, true
|
|
}
|
|
|
|
// RecoverBlobsUnchecked is RecoverBlobs for callers that have already
|
|
// established the cells' authenticity (e.g. via VerifyCells at ingest): when the
|
|
// data cells are all present, the blobs are returned as their concatenation,
|
|
// skipping the KZG erasure decode. The result is byte-identical to RecoverBlobs.
|
|
//
|
|
// "Unchecked" refers to the cell contents: the concatenation validates nothing,
|
|
// not even field-element canonicalness -- the one check RecoverBlobs performs
|
|
// (neither verifies cell proofs or the commitment). Pass only cells whose
|
|
// authenticity is already assured.
|
|
//
|
|
// For the layout of cells and cellIndices, see RecoverBlobs.
|
|
func RecoverBlobsUnchecked(cells []Cell, cellIndices []uint64) ([]Blob, error) {
|
|
if blobs, ok := blobsFromDataCells(cells, cellIndices); ok {
|
|
return blobs, nil
|
|
}
|
|
return RecoverBlobs(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
|
|
}
|