mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-08 23:18:15 -05:00
Merkle Proofs of KZG commitments (#13159)
* 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
This commit is contained in:
@@ -6,6 +6,7 @@ go_library(
|
||||
"execution.go",
|
||||
"factory.go",
|
||||
"getters.go",
|
||||
"kzg.go",
|
||||
"proto.go",
|
||||
"roblob.go",
|
||||
"roblock.go",
|
||||
@@ -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",
|
||||
@@ -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",
|
||||
],
|
||||
@@ -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",
|
||||
@@ -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",
|
||||
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
|
||||
192
consensus-types/blocks/kzg.go
Normal file
192
consensus-types/blocks/kzg.go
Normal file
@@ -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
|
||||
}
|
||||
130
consensus-types/blocks/kzg_test.go
Normal file
130
consensus-types/blocks/kzg_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package blocks
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/prysmaticlabs/gohashtree"
|
||||
fieldparams "github.com/prysmaticlabs/prysm/v4/config/fieldparams"
|
||||
"github.com/prysmaticlabs/prysm/v4/container/trie"
|
||||
enginev1 "github.com/prysmaticlabs/prysm/v4/proto/engine/v1"
|
||||
ethpb "github.com/prysmaticlabs/prysm/v4/proto/prysm/v1alpha1"
|
||||
"github.com/prysmaticlabs/prysm/v4/testing/require"
|
||||
)
|
||||
|
||||
func Test_MerkleProofKZGCommitment_Altair(t *testing.T) {
|
||||
kzgs := make([][]byte, 3)
|
||||
kzgs[0] = make([]byte, 48)
|
||||
_, err := rand.Read(kzgs[0])
|
||||
require.NoError(t, err)
|
||||
kzgs[1] = make([]byte, 48)
|
||||
_, err = rand.Read(kzgs[1])
|
||||
require.NoError(t, err)
|
||||
kzgs[2] = make([]byte, 48)
|
||||
_, err = rand.Read(kzgs[2])
|
||||
require.NoError(t, err)
|
||||
pbBody := ðpb.BeaconBlockBodyAltair{}
|
||||
|
||||
body, err := NewBeaconBlockBody(pbBody)
|
||||
require.NoError(t, err)
|
||||
_, err = MerkleProofKZGCommitment(body, 0)
|
||||
require.ErrorIs(t, errUnsupportedBeaconBlockBody, err)
|
||||
}
|
||||
|
||||
func Test_MerkleProofKZGCommitment(t *testing.T) {
|
||||
kzgs := make([][]byte, 3)
|
||||
kzgs[0] = make([]byte, 48)
|
||||
_, err := rand.Read(kzgs[0])
|
||||
require.NoError(t, err)
|
||||
kzgs[1] = make([]byte, 48)
|
||||
_, err = rand.Read(kzgs[1])
|
||||
require.NoError(t, err)
|
||||
kzgs[2] = make([]byte, 48)
|
||||
_, err = rand.Read(kzgs[2])
|
||||
require.NoError(t, err)
|
||||
pbBody := ðpb.BeaconBlockBodyDeneb{
|
||||
SyncAggregate: ðpb.SyncAggregate{
|
||||
SyncCommitteeBits: make([]byte, fieldparams.SyncAggregateSyncCommitteeBytesLength),
|
||||
SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
ExecutionPayload: &enginev1.ExecutionPayloadDeneb{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
||||
LogsBloom: make([]byte, 256),
|
||||
PrevRandao: make([]byte, fieldparams.RootLength),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
Transactions: make([][]byte, 0),
|
||||
ExtraData: make([]byte, 0),
|
||||
},
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
},
|
||||
BlobKzgCommitments: kzgs,
|
||||
}
|
||||
|
||||
body, err := NewBeaconBlockBody(pbBody)
|
||||
require.NoError(t, err)
|
||||
index := 1
|
||||
_, err = MerkleProofKZGCommitment(body, 10)
|
||||
require.ErrorIs(t, errInvalidIndex, err)
|
||||
proof, err := MerkleProofKZGCommitment(body, index)
|
||||
require.NoError(t, err)
|
||||
|
||||
chunk := make([][32]byte, 2)
|
||||
copy(chunk[0][:], kzgs[index])
|
||||
copy(chunk[1][:], kzgs[index][32:])
|
||||
gohashtree.HashChunks(chunk, chunk)
|
||||
root, err := body.HashTreeRoot()
|
||||
require.NoError(t, err)
|
||||
kzgOffset := 54 * fieldparams.MaxBlobCommitmentsPerBlock
|
||||
require.Equal(t, true, trie.VerifyMerkleProof(root[:], chunk[0][:], uint64(index+kzgOffset), proof))
|
||||
}
|
||||
|
||||
func Benchmark_MerkleProofKZGCommitment(b *testing.B) {
|
||||
kzgs := make([][]byte, 3)
|
||||
kzgs[0] = make([]byte, 48)
|
||||
_, err := rand.Read(kzgs[0])
|
||||
require.NoError(b, err)
|
||||
kzgs[1] = make([]byte, 48)
|
||||
_, err = rand.Read(kzgs[1])
|
||||
require.NoError(b, err)
|
||||
kzgs[2] = make([]byte, 48)
|
||||
_, err = rand.Read(kzgs[2])
|
||||
require.NoError(b, err)
|
||||
pbBody := ðpb.BeaconBlockBodyDeneb{
|
||||
SyncAggregate: ðpb.SyncAggregate{
|
||||
SyncCommitteeBits: make([]byte, fieldparams.SyncAggregateSyncCommitteeBytesLength),
|
||||
SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength),
|
||||
},
|
||||
ExecutionPayload: &enginev1.ExecutionPayloadDeneb{
|
||||
ParentHash: make([]byte, fieldparams.RootLength),
|
||||
FeeRecipient: make([]byte, 20),
|
||||
StateRoot: make([]byte, fieldparams.RootLength),
|
||||
ReceiptsRoot: make([]byte, fieldparams.RootLength),
|
||||
LogsBloom: make([]byte, 256),
|
||||
PrevRandao: make([]byte, fieldparams.RootLength),
|
||||
BaseFeePerGas: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
Transactions: make([][]byte, 0),
|
||||
ExtraData: make([]byte, 0),
|
||||
},
|
||||
Eth1Data: ðpb.Eth1Data{
|
||||
DepositRoot: make([]byte, fieldparams.RootLength),
|
||||
BlockHash: make([]byte, fieldparams.RootLength),
|
||||
},
|
||||
BlobKzgCommitments: kzgs,
|
||||
}
|
||||
|
||||
body, err := NewBeaconBlockBody(pbBody)
|
||||
require.NoError(b, err)
|
||||
index := 1
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := MerkleProofKZGCommitment(body, index)
|
||||
require.NoError(b, err)
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ type ReadOnlyBeaconBlock interface {
|
||||
// ReadOnlyBeaconBlockBody describes the method set employed by an object
|
||||
// that is a beacon block body.
|
||||
type ReadOnlyBeaconBlockBody interface {
|
||||
Version() int
|
||||
RandaoReveal() [field_params.BLSSignatureLength]byte
|
||||
Eth1Data() *ethpb.Eth1Data
|
||||
Graffiti() [field_params.RootLength]byte
|
||||
|
||||
@@ -316,6 +316,10 @@ func (b *BeaconBlockBody) Attestations() []*eth.Attestation {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (b *BeaconBlockBody) Version() int {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
var _ interfaces.ReadOnlySignedBeaconBlock = &SignedBeaconBlock{}
|
||||
var _ interfaces.ReadOnlyBeaconBlock = &BeaconBlock{}
|
||||
var _ interfaces.ReadOnlyBeaconBlockBody = &BeaconBlockBody{}
|
||||
|
||||
Reference in New Issue
Block a user