diff --git a/consensus-types/blocks/kzg.go b/consensus-types/blocks/kzg.go index 720c0ada3a96..c77ddd242dc7 100644 --- a/consensus-types/blocks/kzg.go +++ b/consensus-types/blocks/kzg.go @@ -15,12 +15,39 @@ 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 + KZGOffset = 54 * field_params.MaxBlobCommitmentsPerBlock ) var ( - errInvalidIndex = errors.New("index out of bounds") + errInvalidIndex = errors.New("index out of bounds") + errInvalidBodyRoot = errors.New("invalid Beacon Block Body root") + errInvalidInclusionProof = errors.New("invalid KZG commitment inclusion proof") ) +// VerifyKZGIncusionProof verifies the Merkle proof in a Blob sidecar against +// the beacon block body root. +func VerifyKZGInclusionProof(blob ROBlob) error { + if blob.SignedBlockHeader == nil { + return errNilBlockHeader + } + if blob.SignedBlockHeader.Header == nil { + return errNilBlockHeader + } + root := blob.SignedBlockHeader.Header.BodyRoot + if len(root) != field_params.RootLength { + return errInvalidBodyRoot + } + chunks := make([][32]byte, 2) + copy(chunks[0][:], blob.KzgCommitment) + copy(chunks[1][:], blob.KzgCommitment[field_params.RootLength:]) + gohashtree.HashChunks(chunks, chunks) + verified := trie.VerifyMerkleProof(root, chunks[0][:], blob.Index+KZGOffset, blob.CommitmentInclusionProof) + if !verified { + return errInvalidInclusionProof + } + return nil +} + // 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) { diff --git a/consensus-types/blocks/kzg_test.go b/consensus-types/blocks/kzg_test.go index 28b09ce5e89d..672560e86da4 100644 --- a/consensus-types/blocks/kzg_test.go +++ b/consensus-types/blocks/kzg_test.go @@ -128,3 +128,67 @@ func Benchmark_MerkleProofKZGCommitment(b *testing.B) { require.NoError(b, err) } } + +func Test_VerifyKZGInclusionProof(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) + root, err := body.HashTreeRoot() + require.NoError(t, err) + index := 1 + proof, err := MerkleProofKZGCommitment(body, index) + require.NoError(t, err) + + header := ðpb.BeaconBlockHeader{ + BodyRoot: root[:], + ParentRoot: make([]byte, 32), + StateRoot: make([]byte, 32), + } + signedHeader := ðpb.SignedBeaconBlockHeader{ + Header: header, + } + sidecar := ðpb.BlobSidecar{ + Index: uint64(index), + KzgCommitment: kzgs[index], + CommitmentInclusionProof: proof, + SignedBlockHeader: signedHeader, + } + blob, err := NewROBlob(sidecar) + require.NoError(t, err) + require.NoError(t, VerifyKZGInclusionProof(blob)) + proof[2] = make([]byte, 32) + require.ErrorIs(t, errInvalidInclusionProof, VerifyKZGInclusionProof(blob)) +} diff --git a/testing/spectest/shared/common/merkle_proof/single_merkle_proof.go b/testing/spectest/shared/common/merkle_proof/single_merkle_proof.go index b8faf2c91db1..bdf1f3f5434e 100644 --- a/testing/spectest/shared/common/merkle_proof/single_merkle_proof.go +++ b/testing/spectest/shared/common/merkle_proof/single_merkle_proof.go @@ -18,8 +18,6 @@ import ( "github.com/prysmaticlabs/prysm/v4/testing/util" ) -const kzgOffset = 54 * field_params.MaxBlobCommitmentsPerBlock - // SingleMerkleProof is the format used to read spectest Merkle Proof test data. type SingleMerkleProof struct { Leaf string `json:"leaf"` @@ -83,10 +81,10 @@ func runSingleMerkleProofTests(t *testing.T, config, forkOrPhase string, unmarsh if err != nil { return } - if index < kzgOffset || index > kzgOffset+field_params.MaxBlobsPerBlock { + if index < consensus_blocks.KZGOffset || index > consensus_blocks.KZGOffset+field_params.MaxBlobsPerBlock { return } - localProof, err := consensus_blocks.MerkleProofKZGCommitment(body, int(index-kzgOffset)) + localProof, err := consensus_blocks.MerkleProofKZGCommitment(body, int(index-consensus_blocks.KZGOffset)) require.NoError(t, err) require.Equal(t, len(branch), len(localProof)) for i, root := range localProof {