diff --git a/consensus-types/blocks/kzg.go b/consensus-types/blocks/kzg.go index 67dca3b71..2be40c992 100644 --- a/consensus-types/blocks/kzg.go +++ b/consensus-types/blocks/kzg.go @@ -15,7 +15,8 @@ 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 + kzgRootIndex = 54 // The Merkle index of the KZG commitment list's root in the Body's Merkle tree + KZGOffset = kzgRootIndex * field_params.MaxBlobCommitmentsPerBlock ) var ( @@ -37,9 +38,7 @@ func VerifyKZGInclusionProof(blob ROBlob) error { 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:]) + chunks := makeChunk(blob.KzgCommitment) gohashtree.HashChunks(chunks, chunks) verified := trie.VerifyMerkleProof(root, chunks[0][:], blob.Index+KZGOffset, blob.CommitmentInclusionProof) if !verified { @@ -85,15 +84,21 @@ func MerkleProofKZGCommitment(body interfaces.ReadOnlyBeaconBlockBody, index int 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:]) + chunk := makeChunk(kzg) gohashtree.HashChunks(chunk, chunk) leaves[i] = chunk[0][:] } return leaves } +// makeChunk constructs a chunk from a KZG commitment. +func makeChunk(commitment []byte) [][32]byte { + chunk := make([][32]byte, 2) + copy(chunk[0][:], commitment) + copy(chunk[1][:], commitment[field_params.RootLength:]) + return chunk +} + // 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) { diff --git a/consensus-types/blocks/kzg_test.go b/consensus-types/blocks/kzg_test.go index 26b07cc8a..9febaafef 100644 --- a/consensus-types/blocks/kzg_test.go +++ b/consensus-types/blocks/kzg_test.go @@ -1,7 +1,8 @@ package blocks import ( - "math/rand" + "crypto/rand" + "errors" "testing" "github.com/prysmaticlabs/gohashtree" @@ -74,14 +75,79 @@ func Test_MerkleProofKZGCommitment(t *testing.T) { 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) + // Test the logic of topProof in MerkleProofKZGCommitment. + commitmentsRoot, err := getBlobKzgCommitmentsRoot(kzgs) + require.NoError(t, err) + bodyMembersRoots, err := topLevelRoots(body) + require.NoError(t, err, "Failed to get top level roots") + bodySparse, err := trie.GenerateTrieFromItems( + bodyMembersRoots, + logBodyLength, + ) + require.NoError(t, err, "Failed to generate trie from member roots") + require.Equal(t, bodyLength, bodySparse.NumOfItems()) + topProof, err := bodySparse.MerkleProof(kzgPosition) + require.NoError(t, err, "Failed to generate Merkle proof") + require.DeepEqual(t, + topProof[:len(topProof)-1], + proof[fieldparams.LogMaxBlobCommitments+1:], + ) + 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)) + // Partially verify if the commitments root is in the body root. + // Proof of the commitment length is not needed. + require.Equal(t, true, trie.VerifyMerkleProof(root[:], commitmentsRoot[:], kzgPosition, topProof[:len(topProof)-1])) + + chunk := makeChunk(kzgs[index]) + gohashtree.HashChunks(chunk, chunk) + require.Equal(t, true, trie.VerifyMerkleProof(root[:], chunk[0][:], uint64(index+KZGOffset), proof)) +} + +// This test explains the calculation of the KZG commitment root's Merkle index +// in the Body's Merkle tree based on the index of the KZG commitment list in the Body. +func Test_KZGRootIndex(t *testing.T) { + // Level of the KZG commitment root's parent. + kzgParentRootLevel, err := ceilLog2(kzgPosition) + require.NoError(t, err) + // Merkle index of the KZG commitment root's parent. + // The parent's left child is the KZG commitment root, + // and its right child is the KZG commitment size. + kzgParentRootIndex := kzgPosition + (1 << kzgParentRootLevel) + // The KZG commitment root is the left child of its parent. + // Its Merkle index is the double of its parent's Merkle index. + require.Equal(t, 2*kzgParentRootIndex, kzgRootIndex) +} + +// ceilLog2 returns the smallest integer greater than or equal to +// the base-2 logarithm of x. +func ceilLog2(x uint32) (uint32, error) { + if x == 0 { + return 0, errors.New("log2(0) is undefined") + } + var y uint32 + if (x & (x - 1)) == 0 { + y = 0 + } else { + y = 1 + } + for x > 1 { + x >>= 1 + y += 1 + } + return y, nil +} + +func getBlobKzgCommitmentsRoot(commitments [][]byte) ([32]byte, error) { + commitmentsLeaves := leavesFromCommitments(commitments) + commitmentsSparse, err := trie.GenerateTrieFromItems( + commitmentsLeaves, + fieldparams.LogMaxBlobCommitments, + ) + if err != nil { + return [32]byte{}, err + } + return commitmentsSparse.HashTreeRoot() } func Benchmark_MerkleProofKZGCommitment(b *testing.B) {