Skip to content

Commit

Permalink
Merkle Proofs of KZG commitments (prysmaticlabs#13159)
Browse files Browse the repository at this point in the history
* Merkle Proofs of KZG commitments

* fix mock

* Implement Merkle proof spectests

* Check Proof construction in spectests

* fix Merkle proof generator

* Add unit test

* add ssz package unit tests

* add benchmark

* fix typo in comment

* ProposerSlashing was repeated

* Terence's review

* move to consensus_blocks

* use existing error
  • Loading branch information
potuz authored Nov 6, 2023
1 parent f663f60 commit afaeff9
Show file tree
Hide file tree
Showing 13 changed files with 485 additions and 123 deletions.
1 change: 1 addition & 0 deletions config/fieldparams/mainnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
MaxWithdrawalsPerPayload = 16 // MaxWithdrawalsPerPayloadLength defines the maximum number of withdrawals that can be included in a payload.
MaxBlobsPerBlock = 6 // MaxBlobsPerBlock defines the maximum number of blobs with respect to consensus rule can be included in a block.
MaxBlobCommitmentsPerBlock = 4096 // MaxBlobCommitmentsPerBlock defines the theoretical limit of blobs can be included in a block.
LogMaxBlobCommitments = 12 // Log_2 of MaxBlobCommitmentsPerBlock
BlobLength = 131072 // BlobLength defines the byte length of a blob.
BlobSize = 131072 // defined to match blob.size in bazel ssz codegen
)
1 change: 1 addition & 0 deletions config/fieldparams/minimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
MaxWithdrawalsPerPayload = 4 // MaxWithdrawalsPerPayloadLength defines the maximum number of withdrawals that can be included in a payload.
MaxBlobsPerBlock = 6 // MaxBlobsPerBlock defines the maximum number of blobs with respect to consensus rule can be included in a block.
MaxBlobCommitmentsPerBlock = 16 // MaxBlobCommitmentsPerBlock defines the theoretical limit of blobs can be included in a block.
LogMaxBlobCommitments = 4 // Log_2 of MaxBlobCommitmentsPerBlock
BlobLength = 4 // BlobLength defines the byte length of a blob.
BlobSize = 128 // defined to match blob.size in bazel ssz codegen
)
7 changes: 7 additions & 0 deletions consensus-types/blocks/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ go_library(
"execution.go",
"factory.go",
"getters.go",
"kzg.go",
"proto.go",
"roblob.go",
"roblock.go",
Expand All @@ -16,9 +17,11 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//config/fieldparams:go_default_library",
"//config/params:go_default_library",
"//consensus-types:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//container/trie:go_default_library",
"//encoding/bytesutil:go_default_library",
"//encoding/ssz:go_default_library",
"//math:go_default_library",
Expand All @@ -28,6 +31,7 @@ go_library(
"//runtime/version:go_default_library",
"@com_github_pkg_errors//:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
"@com_github_prysmaticlabs_gohashtree//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@org_golang_google_protobuf//proto:go_default_library",
],
Expand All @@ -39,6 +43,7 @@ go_test(
"execution_test.go",
"factory_test.go",
"getters_test.go",
"kzg_test.go",
"proto_test.go",
"roblob_test.go",
"roblock_test.go",
Expand All @@ -49,6 +54,7 @@ go_test(
"//consensus-types:go_default_library",
"//consensus-types/interfaces:go_default_library",
"//consensus-types/primitives:go_default_library",
"//container/trie:go_default_library",
"//encoding/bytesutil:go_default_library",
"//proto/engine/v1:go_default_library",
"//proto/prysm/v1alpha1:go_default_library",
Expand All @@ -58,5 +64,6 @@ go_test(
"//testing/require:go_default_library",
"@com_github_prysmaticlabs_fastssz//:go_default_library",
"@com_github_prysmaticlabs_go_bitfield//:go_default_library",
"@com_github_prysmaticlabs_gohashtree//:go_default_library",
],
)
5 changes: 5 additions & 0 deletions consensus-types/blocks/getters.go
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,11 @@ func (b *BeaconBlockBody) BlobKzgCommitments() ([][]byte, error) {
}
}

// Version returns the version of the beacon block body
func (b *BeaconBlockBody) Version() int {
return b.version
}

// HashTreeRoot returns the ssz root of the block body.
func (b *BeaconBlockBody) HashTreeRoot() ([field_params.RootLength]byte, error) {
pb, err := b.Proto()
Expand Down
192 changes: 192 additions & 0 deletions consensus-types/blocks/kzg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package blocks

import (
"github.com/pkg/errors"
"github.com/prysmaticlabs/gohashtree"
field_params "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
"github.com/prysmaticlabs/prysm/v4/config/params"
"github.com/prysmaticlabs/prysm/v4/consensus-types/interfaces"
"github.com/prysmaticlabs/prysm/v4/container/trie"
"github.com/prysmaticlabs/prysm/v4/encoding/ssz"
"github.com/prysmaticlabs/prysm/v4/runtime/version"
)

const (
bodyLength = 12 // The number of elements in the BeaconBlockBody Container
logBodyLength = 4 // The log 2 of bodyLength
kzgPosition = 11 // The index of the KZG commitment list in the Body
)

var (
errInvalidIndex = errors.New("index out of bounds")
)

// MerkleProofKZGCommitment constructs a Merkle proof of inclusion of the KZG
// commitment of index `index` into the Beacon Block with the given `body`
func MerkleProofKZGCommitment(body interfaces.ReadOnlyBeaconBlockBody, index int) ([][]byte, error) {
bodyVersion := body.Version()
if bodyVersion < version.Deneb {
return nil, errUnsupportedBeaconBlockBody
}
commitments, err := body.BlobKzgCommitments()
if err != nil {
return nil, err
}
proof, err := bodyProof(commitments, index)
if err != nil {
return nil, err
}
membersRoots, err := topLevelRoots(body)
if err != nil {
return nil, err
}
sparse, err := trie.GenerateTrieFromItems(membersRoots, logBodyLength)
if err != nil {
return nil, err
}
topProof, err := sparse.MerkleProof(kzgPosition)
if err != nil {
return nil, err
}
// sparse.MerkleProof always includes the length of the slice this is
// why we remove the last element that is not needed in topProof
proof = append(proof, topProof[:len(topProof)-1]...)
return proof, nil
}

// leavesFromCommitments hashes each commitment to construct a slice of roots
func leavesFromCommitments(commitments [][]byte) [][]byte {
leaves := make([][]byte, len(commitments))
for i, kzg := range commitments {
chunk := make([][32]byte, 2)
copy(chunk[0][:], kzg)
copy(chunk[1][:], kzg[field_params.RootLength:])
gohashtree.HashChunks(chunk, chunk)
leaves[i] = chunk[0][:]
}
return leaves
}

// bodyProof returns the Merkle proof of the subtree up to the root of the KZG
// commitment list.
func bodyProof(commitments [][]byte, index int) ([][]byte, error) {
if index < 0 || index >= len(commitments) {
return nil, errInvalidIndex
}
leaves := leavesFromCommitments(commitments)
sparse, err := trie.GenerateTrieFromItems(leaves, field_params.LogMaxBlobCommitments)
if err != nil {
return nil, err
}
proof, err := sparse.MerkleProof(index)
if err != nil {
return nil, err
}
return proof, err
}

// topLevelRoots computes the slice with the roots of each element in the
// BeaconBlockBody. Notice that the KZG commitments root is not needed for the
// proof computation thus it's omitted
func topLevelRoots(body interfaces.ReadOnlyBeaconBlockBody) ([][]byte, error) {
layer := make([][]byte, bodyLength)
for i := range layer {
layer[i] = make([]byte, 32)
}

// Randao Reveal
randao := body.RandaoReveal()
root, err := ssz.MerkleizeByteSliceSSZ(randao[:])
if err != nil {
return nil, err
}
copy(layer[0], root[:])

// eth1_data
eth1 := body.Eth1Data()
root, err = eth1.HashTreeRoot()
if err != nil {
return nil, err
}
copy(layer[1], root[:])

// graffiti
root = body.Graffiti()
copy(layer[2], root[:])

// Proposer slashings
ps := body.ProposerSlashings()
root, err = ssz.MerkleizeListSSZ(ps, params.BeaconConfig().MaxProposerSlashings)
if err != nil {
return nil, err
}
copy(layer[3], root[:])

// Attester slashings
as := body.AttesterSlashings()
root, err = ssz.MerkleizeListSSZ(as, params.BeaconConfig().MaxAttesterSlashings)
if err != nil {
return nil, err
}
copy(layer[4], root[:])

// Attestations
att := body.Attestations()
root, err = ssz.MerkleizeListSSZ(att, params.BeaconConfig().MaxAttestations)
if err != nil {
return nil, err
}
copy(layer[5], root[:])

// Deposits
dep := body.Deposits()
root, err = ssz.MerkleizeListSSZ(dep, params.BeaconConfig().MaxDeposits)
if err != nil {
return nil, err
}
copy(layer[6], root[:])

// Voluntary Exits
ve := body.VoluntaryExits()
root, err = ssz.MerkleizeListSSZ(ve, params.BeaconConfig().MaxVoluntaryExits)
if err != nil {
return nil, err
}
copy(layer[7], root[:])

// Sync Aggregate
sa, err := body.SyncAggregate()
if err != nil {
return nil, err
}
root, err = sa.HashTreeRoot()
if err != nil {
return nil, err
}
copy(layer[8], root[:])

// Execution Payload
ep, err := body.Execution()
if err != nil {
return nil, err
}
root, err = ep.HashTreeRoot()
if err != nil {
return nil, err
}
copy(layer[9], root[:])

// BLS Changes
bls, err := body.BLSToExecutionChanges()
if err != nil {
return nil, err
}
root, err = ssz.MerkleizeListSSZ(bls, params.BeaconConfig().MaxBlsToExecutionChanges)
if err != nil {
return nil, err
}
copy(layer[10], root[:])

// KZG commitments is not needed
return layer, nil
}
Loading

0 comments on commit afaeff9

Please sign in to comment.