Skip to content

Cell proof support #31438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
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
32 changes: 32 additions & 0 deletions core/txpool/blobpool/blobpool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,25 @@ func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCa
return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx)
}

func makeMultiBlobTxWithCellProofs(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobCount int, key *ecdsa.PrivateKey) *types.Transaction {
tx := makeMultiBlobTx(nonce, gasTipCap, gasFeeCap, blobFeeCap, blobCount, key)

proofs := make([]kzg4844.Proof, 0)
for i, blob := range tx.BlobTxSidecar().Blobs {
cps, err := kzg4844.ComputeCells(&blob)
if err != nil {
panic(err)
}
tx.BlobTxSidecar().Proofs = append(proofs, cps...)
}
tx.Sidecar.Proofs = make([]kzg4844.Proof, blobCount)
for i := 0; i < blobCount; i++ {
tx.Sidecar.Proofs[i] = testBlobProofs[i]
}

return tx
}

// makeUnsignedTx is a utility method to construct a random blob transaction
// without signing it.
func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx {
Expand Down Expand Up @@ -1482,6 +1501,19 @@ func TestAdd(t *testing.T) {
},
},
},
// Testing blob transaction verification around Osaka fork boundary
{
seeds: map[string]seed{
"alice": {balance: 10000000},
},
adds: []addtx{
{ // New account, no previous txs, nonce 0, but blob fee cap too low
from: "alice",
tx: makeUnsignedTxWithTestBlob(0, 1, 1, 1, 0),
err: nil,
},
},
},
// Tests issue #30518 where a refactor broke internal state invariants,
// causing included transactions not to be properly accounted and thus
// account states going our of sync with the chain.
Expand Down
3 changes: 3 additions & 0 deletions core/txpool/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,7 @@ var (
// input transaction of non-blob type when a blob transaction from this sender
// remains pending (and vice-versa).
ErrAlreadyReserved = errors.New("address already reserved")

// ErrInvalidBlobSidecar is returned if the blob sidecar is invalid.
ErrInvalidBlobSidecar = errors.New("invalid blob sidecar, incorrect proofs")
)
55 changes: 44 additions & 11 deletions core/txpool/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"math/big"

goethkzg "github.com/crate-crypto/go-eth-kzg"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
Expand All @@ -29,6 +30,7 @@ import (
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params/forks"
)

var (
Expand Down Expand Up @@ -153,7 +155,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), maxBlobs)
}
// Ensure commitments, proofs and hashes are valid
if err := validateBlobSidecar(hashes, sidecar); err != nil {
if err := validateBlobSidecar(opts.Config, head.Time, hashes, sidecar); err != nil {
return err
}
}
Expand All @@ -165,23 +167,54 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
return nil
}

func validateBlobSidecar(hashes []common.Hash, sidecar *types.BlobTxSidecar) error {
func validateBlobSidecar(cfg *params.ChainConfig, time uint64, hashes []common.Hash, sidecar *types.BlobTxSidecar) error {
if len(sidecar.Blobs) != len(hashes) {
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
}
if len(sidecar.Proofs) != len(hashes) {
return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sidecar.Proofs), len(hashes))
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", ErrInvalidBlobSidecar, len(sidecar.Blobs), len(hashes))
}
if err := sidecar.ValidateBlobCommitmentHashes(hashes); err != nil {
return err
}
// Blob commitments match with the hashes in the transaction, verify the
// blobs themselves via KZG
for i := range sidecar.Blobs {
if err := kzg4844.VerifyBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil {
return fmt.Errorf("invalid blob %d: %v", i, err)

fork := cfg.LatestFork(time)
if fork >= forks.Osaka {
if len(sidecar.Blobs)*goethkzg.CellsPerExtBlob != len(sidecar.Proofs) {
return fmt.Errorf("invalid number of %d cell proofs compared to %d blobs", ErrInvalidBlobSidecar, len(sidecar.Proofs), len(sidecar.Blobs))
}

// Prepare cells and cell indices for each blob
totalCells := len(sidecar.Blobs) * goethkzg.CellsPerExtBlob
cells := make([]kzg4844.Cell, totalCells)
cellIndices := make([]uint64, totalCells)
for i := range sidecar.Blobs {
// Compute cells for the currentblob
blobCells, err := kzg4844.ComputeCells(&sidecar.Blobs[i])
if err != nil {
return fmt.Errorf("failed to compute cells for blob %d: %v", ErrInvalidBlobSidecar, i, err)
}

cells = append(cells, blobCells...)
for j := uint64(0); j < goethkzg.CellsPerExtBlob; j++ {
cellIndices = append(cellIndices, j)
}
}

if err := kzg4844.VerifyCellKZGProofBatch(sidecar.Commitments, cellIndices, cells, sidecar.Proofs); err != nil {
return fmt.Errorf("failed to verify cell proofs: %v", ErrInvalidBlobSidecar, err)
}
} else { // older folks
if len(sidecar.Proofs) != len(hashes) {
return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", ErrInvalidBlobSidecar, len(sidecar.Proofs), len(hashes))
}

// Blob commitments match with the hashes in the transaction, verify the
// blobs themselves via KZG
for i := range sidecar.Blobs {
if err := kzg4844.VerifyBlobProof(&sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil {
return fmt.Errorf("invalid blob %d: %v", ErrInvalidBlobSidecar, i, err)
}
}
}

return nil
}

Expand Down
27 changes: 27 additions & 0 deletions crypto/kzg4844/kzg4844.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"reflect"
"sync/atomic"

goethkzg "github.com/crate-crypto/go-eth-kzg"
"github.com/ethereum/go-ethereum/common/hexutil"
)

Expand All @@ -39,6 +40,8 @@ var (
// Blob represents a 4844 data blob.
type Blob [131072]byte

type Cell [goethkzg.BytesPerCell]byte

// UnmarshalJSON parses a blob in hex syntax.
func (b *Blob) UnmarshalJSON(input []byte) error {
return hexutil.UnmarshalFixedJSON(blobT, input, b[:])
Expand Down Expand Up @@ -149,6 +152,30 @@ func VerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
return gokzgVerifyBlobProof(blob, commitment, proof)
}

// VerifyCellKZGProofBatch verifies a batch of KZG proofs for a set of cells.
func VerifyCellKZGProofBatch(commitments []Commitment, cellIndicies []uint64, cells []Cell, proofs []Proof) error {
if useCKZG.Load() {
return ckzgVerifyCellKZGProofBatch(commitments, cellIndicies, cells, proofs)
}
return gokzgVerifyCellKZGProofBatch(commitments, cellIndicies, cells, proofs)
}

// ComputeCells computes the cells for a given blob.
func ComputeCells(blob *Blob) ([]Cell, error) {
if useCKZG.Load() {
return ckzgComputeCells(blob)
}
return gokzgComputeCells(blob)
}

// ComputeCellsAndKZGProofs computes the cells and KZG proofs for a given blob.
func ComputeCellsAndKZGProofs(blob *Blob) ([]Cell, []Proof, error) {
if useCKZG.Load() {
return ckzgComputeCellsAndKZGProofs(blob)
}
return gokzgComputeCellsAndKZGProofs(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) {
Expand Down
76 changes: 66 additions & 10 deletions crypto/kzg4844/kzg4844_ckzg_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// 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/>.

//go:build ckzg && !nacl && !js && !wasip1 && cgo && !gofuzz
//xgo:build ckzg && !nacl && !js && !wasip1 && cgo && !gofuzz

package kzg4844

Expand All @@ -23,8 +23,8 @@ import (
"errors"
"sync"

gokzg4844 "github.com/crate-crypto/go-kzg-4844"
ckzg4844 "github.com/ethereum/c-kzg-4844/bindings/go"
goethkzg "github.com/crate-crypto/go-eth-kzg"
ckzg4844 "github.com/ethereum/c-kzg-4844/v2/bindings/go"
"github.com/ethereum/go-ethereum/common/hexutil"
)

Expand All @@ -40,22 +40,34 @@ func ckzgInit() {
if err != nil {
panic(err)
}
params := new(gokzg4844.JSONTrustedSetup)
params := new(goethkzg.JSONTrustedSetup)
if err = json.Unmarshal(config, params); err != nil {
panic(err)
}
if err = gokzg4844.CheckTrustedSetupIsWellFormed(params); err != nil {
if err = goethkzg.CheckTrustedSetupIsWellFormed(params); err != nil {
panic(err)
}
g1s := make([]byte, len(params.SetupG1Lagrange)*(len(params.SetupG1Lagrange[0])-2)/2)
g1MonomialBytes := make([]byte, len(params.SetupG1Monomial)*(len(params.SetupG1Monomial[0])-2)/2)
for i, g1 := range &params.SetupG1Monomial {
copy(g1MonomialBytes[i*(len(g1)-2)/2:], hexutil.MustDecode(g1))
}
g1LagrangeBytes := make([]byte, len(params.SetupG1Lagrange)*(len(params.SetupG1Lagrange[0])-2)/2)
for i, g1 := range params.SetupG1Lagrange {
copy(g1s[i*(len(g1)-2)/2:], hexutil.MustDecode(g1))
copy(g1LagrangeBytes[i*(len(g1)-2)/2:], hexutil.MustDecode(g1))
}
g2s := make([]byte, len(params.SetupG2)*(len(params.SetupG2[0])-2)/2)
g2MonomialBytes := make([]byte, len(params.SetupG2)*(len(params.SetupG2[0])-2)/2)
for i, g2 := range params.SetupG2 {
copy(g2s[i*(len(g2)-2)/2:], hexutil.MustDecode(g2))
copy(g2MonomialBytes[i*(len(g2)-2)/2:], hexutil.MustDecode(g2))
}
if err = ckzg4844.LoadTrustedSetup(g1s, g2s); err != nil {

// c-kzg-4844 uses a global context/setup file, so it needs to be 8 for both the EL and CL (currently CL sets it at 8)
//
// the parameter is mainly for optimization purposes. Heuristically 8 has been shown to be the best tradeoff
// It essentially stores something like 2^8 * len(g1_monomial) points
// So the higher the number, the more memory it uses
// You can set it to 0 to store nothing and it uses a different codepath, but its slow
precompute := uint(8)
if err = ckzg4844.LoadTrustedSetup(g1MonomialBytes, g1LagrangeBytes, g2MonomialBytes, precompute); err != nil {
panic(err)
}
}
Expand Down Expand Up @@ -125,3 +137,47 @@ func ckzgVerifyBlobProof(blob *Blob, commitment Commitment, proof Proof) error {
}
return nil
}

// VerifyCellKZGProofBatch verifies a batch of KZG proofs for a set of cells.
func ckzgVerifyCellKZGProofBatch(commitments []Commitment, cellIndicies []uint64, cells []Cell, proofs []Proof) error {
ckzgIniter.Do(ckzgInit)

comts := make([]ckzg4844.Bytes48, len(commitments))
for i, commitment := range commitments {
comts[i] = (ckzg4844.Bytes48)(commitment)
}

cellInputs := make([]ckzg4844.Cell, len(cells))
for i := range cells {
cellInputs[i] = (ckzg4844.Cell)(cells[i])
}

proofsInput := make([]ckzg4844.Bytes48, len(proofs))
for i, proof := range proofs {
proofsInput[i] = (ckzg4844.Bytes48)(proof)
}

valid, err := ckzg4844.VerifyCellKZGProofBatch(comts, cellIndicies, cellInputs, proofsInput)
if err != nil {
return err
}
if !valid {
return errors.New("invalid proof")
}
return nil
}

func ckzgComputeCells(blob *Blob) ([]Cell, error) {
ckzgIniter.Do(ckzgInit)

result, err := ckzg4844.ComputeCells((*ckzg4844.Blob)(blob))
if err != nil {
return nil, err
}

cells := make([]Cell, len(result))
for i, cell := range result {
cells[i] = (Cell)(cell)
}
return cells, nil
}
Loading