Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
99cf1ef
propose block changes from peerdas branch
james-prysm Aug 25, 2025
63c3fb1
breaking out broadcast code into its own helper, changing fulu broadc…
james-prysm Aug 25, 2025
6a18d77
Merge branch 'develop' into block-proposal-fulu
james-prysm Aug 25, 2025
ac13ad5
renamed validate blobsidecars to validate blobs, and added check for …
james-prysm Aug 25, 2025
aff6169
gofmt
james-prysm Aug 25, 2025
50ad7df
adding in batch verification for blobs"
james-prysm Aug 25, 2025
87da155
changelog
james-prysm Aug 25, 2025
c53fd25
adding kzg tests, moving new kzg functions to validation.go
james-prysm Aug 26, 2025
8b993a4
linting and other small fixes
james-prysm Aug 26, 2025
a21f153
fixing linting issues and adding some proposer tests
james-prysm Aug 26, 2025
f6a7550
missing dependencies
james-prysm Aug 26, 2025
ceca53d
fixing test
james-prysm Aug 26, 2025
0f471c1
fixing more tests
james-prysm Aug 26, 2025
4434e53
gaz
james-prysm Aug 26, 2025
c72a137
removed return on broadcast data columns
james-prysm Aug 26, 2025
c46ac42
more cleanup and unit test adjustments
james-prysm Aug 26, 2025
adad25f
missed removal of unneeded field
james-prysm Aug 26, 2025
0fd759a
adding data column receiver initialization
james-prysm Aug 26, 2025
07d4389
Merge branch 'develop' into block-proposal-fulu
james-prysm Aug 26, 2025
e3884cf
Update beacon-chain/rpc/eth/beacon/handlers.go
james-prysm Aug 28, 2025
409d8d3
partial review feedback from manu
james-prysm Aug 28, 2025
1174c8a
Merge branch 'develop' into block-proposal-fulu
james-prysm Aug 28, 2025
85dd4e5
gaz
james-prysm Aug 28, 2025
ca27f91
reverting some code to peerdas as I don't believe the broadcast code …
james-prysm Aug 28, 2025
d4dcd5f
missed removal of build dependency
james-prysm Aug 28, 2025
a463332
fixing tests and adding another test based on manu's suggestion
james-prysm Aug 28, 2025
05fb71c
fixing linting
james-prysm Aug 28, 2025
96ad094
Update beacon-chain/rpc/eth/beacon/handlers.go
james-prysm Aug 28, 2025
9d885fd
Update beacon-chain/blockchain/kzg/validation.go
james-prysm Aug 28, 2025
6504c6e
radek's review changes
james-prysm Aug 28, 2025
8c59358
adding missed test
james-prysm Aug 28, 2025
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
1 change: 0 additions & 1 deletion beacon-chain/blockchain/kzg/kzg.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ func VerifyCellKZGProofBatch(commitmentsBytes []Bytes48, cellIndices []uint64, c
for i := range cells {
ckzgCells[i] = ckzg4844.Cell(cells[i])
}

return ckzg4844.VerifyCellKZGProofBatch(commitmentsBytes, cellIndices, ckzgCells, proofsBytes)
}

Expand Down
129 changes: 118 additions & 11 deletions beacon-chain/blockchain/kzg/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,26 @@ package kzg
import (
"github.com/OffchainLabs/prysm/v6/consensus-types/blocks"
GoKZG "github.com/crate-crypto/go-kzg-4844"
ckzg4844 "github.com/ethereum/c-kzg-4844/v2/bindings/go"
"github.com/pkg/errors"
)

func bytesToBlob(blob []byte) *GoKZG.Blob {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Why do the other functions use a named return value and this one uses a variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that one is a pointer the other ones are not

var ret GoKZG.Blob
copy(ret[:], blob)
return &ret
}

func bytesToCommitment(commitment []byte) (ret GoKZG.KZGCommitment) {
copy(ret[:], commitment)
return
}

func bytesToKZGProof(proof []byte) (ret GoKZG.KZGProof) {
copy(ret[:], proof)
return
}

// Verify performs single or batch verification of commitments depending on the number of given BlobSidecars.
func Verify(blobSidecars ...blocks.ROBlob) error {
if len(blobSidecars) == 0 {
Expand All @@ -27,18 +45,107 @@ func Verify(blobSidecars ...blocks.ROBlob) error {
return kzgContext.VerifyBlobKZGProofBatch(blobs, cmts, proofs)
}

func bytesToBlob(blob []byte) *GoKZG.Blob {
var ret GoKZG.Blob
copy(ret[:], blob)
return &ret
}
// VerifyBlobKZGProofBatch verifies KZG proofs for multiple blobs using batch verification.
// This is more efficient than verifying each blob individually when len(blobs) > 1.
// For single blob verification, it uses the optimized single verification path.
func VerifyBlobKZGProofBatch(blobs [][]byte, commitments [][]byte, proofs [][]byte) error {
if len(blobs) != len(commitments) || len(blobs) != len(proofs) {
return errors.New("number of blobs, commitments, and proofs must match")
}

func bytesToCommitment(commitment []byte) (ret GoKZG.KZGCommitment) {
copy(ret[:], commitment)
return
if len(blobs) == 0 {
return nil
}

// Optimize for single blob case - use single verification to avoid batch overhead
if len(blobs) == 1 {
return kzgContext.VerifyBlobKZGProof(
bytesToBlob(blobs[0]),
bytesToCommitment(commitments[0]),
bytesToKZGProof(proofs[0]))
}

// Use batch verification for multiple blobs
ckzgBlobs := make([]ckzg4844.Blob, len(blobs))
ckzgCommitments := make([]ckzg4844.Bytes48, len(commitments))
ckzgProofs := make([]ckzg4844.Bytes48, len(proofs))

for i := range blobs {
copy(ckzgBlobs[i][:], blobs[i])
copy(ckzgCommitments[i][:], commitments[i])
copy(ckzgProofs[i][:], proofs[i])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We never modify any of these objects - is copying necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me try

}

valid, err := ckzg4844.VerifyBlobKZGProofBatch(ckzgBlobs, ckzgCommitments, ckzgProofs)
if err != nil {
return errors.Wrap(err, "batch verification failed")
}
if !valid {
return errors.New("batch KZG proof verification failed")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it could be useful to test this case, because this very case (!valid) is really the reason of all this apparatus.

}

return nil
}

func bytesToKZGProof(proof []byte) (ret GoKZG.KZGProof) {
copy(ret[:], proof)
return
// VerifyCellKZGProofBatchFromBlobData verifies cell KZG proofs in batch format directly from blob data.
// This is more efficient than reconstructing data column sidecars when you have the raw blob data and cell proofs.
// For PeerDAS/Fulu, the execution client provides cell proofs in flattened format via BlobsBundleV2.
// For single blob verification, it optimizes by computing cells once and verifying efficiently.
func VerifyCellKZGProofBatchFromBlobData(blobs [][]byte, commitments [][]byte, cellProofs [][]byte, numberOfColumns uint64) error {
blobCount := uint64(len(blobs))
expectedCellProofs := blobCount * numberOfColumns

if uint64(len(cellProofs)) != expectedCellProofs {
return errors.Errorf("expected %d cell proofs, got %d", expectedCellProofs, len(cellProofs))
}

if len(commitments) != len(blobs) {
return errors.Errorf("number of commitments (%d) must match number of blobs (%d)", len(commitments), len(blobs))
}

if blobCount == 0 {
return nil
}

// Handle multiple blobs - compute cells for all blobs
allCells := make([]Cell, 0, expectedCellProofs)
allCommitments := make([]Bytes48, 0, expectedCellProofs)
allIndices := make([]uint64, 0, expectedCellProofs)
allProofs := make([]Bytes48, 0, expectedCellProofs)

for blobIndex, blobData := range blobs {
// Convert blob to kzg.Blob type
var blob Blob
copy(blob[:], blobData)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we avoid a copy here?


// Compute cells for this blob
cells, err := ComputeCells(&blob)
if err != nil {
return errors.Wrapf(err, "failed to compute cells for blob %d", blobIndex)
}

// Add cells and corresponding data for each column
for columnIndex := uint64(0); columnIndex < numberOfColumns; columnIndex++ {
cellProofIndex := uint64(blobIndex)*numberOfColumns + columnIndex

allCells = append(allCells, cells[columnIndex])
allCommitments = append(allCommitments, Bytes48(commitments[blobIndex]))
allIndices = append(allIndices, columnIndex)

var proof Bytes48
copy(proof[:], cellProofs[cellProofIndex])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we avoid a copy here?

allProofs = append(allProofs, proof)
}
}

// Batch verify all cells
valid, err := VerifyCellKZGProofBatch(allCommitments, allIndices, allCells, allProofs)
if err != nil {
return errors.Wrap(err, "cell batch verification failed")
}
if !valid {
return errors.New("cell KZG proof batch verification failed")
}

return nil
}
Loading
Loading