fuzz: Add fuzz tests for sparse merkle trie (#10662)

* Add fuzz tests for sparse merkle trie and change HTR signature to return an error

* fix capitalization of error message
This commit is contained in:
Preston Van Loon
2022-05-09 11:51:48 -05:00
committed by GitHub
parent 7d9d8454b1
commit 97663548a1
24 changed files with 418 additions and 90 deletions

View File

@@ -19,7 +19,11 @@ go_library(
go_test(
name = "go_default_test",
size = "small",
srcs = ["sparse_merkle_test.go"],
srcs = [
"sparse_merkle_test.go",
"sparse_merkle_trie_fuzz_test.go",
],
data = glob(["testdata/**"]),
deps = [
":go_default_library",
"//config/fieldparams:go_default_library",
@@ -31,5 +35,6 @@ go_test(
"//testing/assert:go_default_library",
"//testing/require:go_default_library",
"@com_github_ethereum_go_ethereum//accounts/abi/bind:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
],
)

View File

@@ -81,7 +81,11 @@ func (m *SparseMerkleTrie) Items() [][]byte {
// HashTreeRoot of the Merkle trie as defined in the deposit contract.
// Spec Definition:
// sha256(concat(node, self.to_little_endian_64(self.deposit_count), slice(zero_bytes32, start=0, len=24)))
func (m *SparseMerkleTrie) HashTreeRoot() [32]byte {
func (m *SparseMerkleTrie) HashTreeRoot() ([32]byte, error) {
if len(m.branches) == 0 || len(m.branches[len(m.branches)-1]) == 0 {
return [32]byte{}, errors.New("invalid branches provided to compute root")
}
enc := [32]byte{}
depositCount := uint64(len(m.originalItems))
if len(m.originalItems) == 1 && bytes.Equal(m.originalItems[0], ZeroHashes[0][:]) {
@@ -89,7 +93,7 @@ func (m *SparseMerkleTrie) HashTreeRoot() [32]byte {
depositCount = 0
}
binary.LittleEndian.PutUint64(enc[:], depositCount)
return hash.Hash(append(m.branches[len(m.branches)-1][0], enc[:]...))
return hash.Hash(append(m.branches[len(m.branches)-1][0], enc[:]...)), nil
}
// Insert an item into the trie.
@@ -97,6 +101,12 @@ func (m *SparseMerkleTrie) Insert(item []byte, index int) error {
if index < 0 {
return fmt.Errorf("negative index provided: %d", index)
}
if len(m.branches) == 0 {
return errors.New("invalid trie: no branches")
}
if m.depth > uint(len(m.branches)) {
return errors.New("invalid trie: depth is greater than number of branches")
}
for index >= len(m.branches[0]) {
m.branches[0] = append(m.branches[0], ZeroHashes[0][:])
}
@@ -143,11 +153,17 @@ func (m *SparseMerkleTrie) MerkleProof(index int) ([][]byte, error) {
if index < 0 {
return nil, fmt.Errorf("merkle index is negative: %d", index)
}
merkleIndex := uint(index)
if len(m.branches) == 0 {
return nil, errors.New("invalid trie: no branches")
}
leaves := m.branches[0]
if index >= len(leaves) {
return nil, fmt.Errorf("merkle index out of range in trie, max range: %d, received: %d", len(leaves), index)
}
if m.depth > uint(len(m.branches)) {
return nil, errors.New("invalid trie: depth is greater than number of branches")
}
merkleIndex := uint(index)
proof := make([][]byte, m.depth+1)
for i := uint(0); i < m.depth; i++ {
subIndex := (merkleIndex / (1 << i)) ^ 1
@@ -185,6 +201,9 @@ func VerifyMerkleProofWithDepth(root, item []byte, merkleIndex uint64, proof [][
if uint64(len(proof)) != depth+1 {
return false
}
if depth >= 64 {
return false // PowerOf2 would overflow.
}
node := bytesutil.ToBytes32(item)
for i := uint64(0); i <= depth; i++ {
if (merkleIndex / math.PowerOf2(i) % 2) != 0 {
@@ -200,15 +219,10 @@ func VerifyMerkleProofWithDepth(root, item []byte, merkleIndex uint64, proof [][
// VerifyMerkleProof given a trie root, a leaf, the generalized merkle index
// of the leaf in the trie, and the proof itself.
func VerifyMerkleProof(root, item []byte, merkleIndex uint64, proof [][]byte) bool {
node := bytesutil.ToBytes32(item)
for i := 0; i < len(proof); i++ {
if (merkleIndex / math.PowerOf2(uint64(i)) % 2) != 0 {
node = hash.Hash(append(proof[i], node[:]...))
} else {
node = hash.Hash(append(node[:], proof[i]...))
}
if len(proof) == 0 {
return false
}
return bytes.Equal(root, node[:])
return VerifyMerkleProofWithDepth(root, item, merkleIndex, proof, uint64(len(proof)-1))
}
// Copy performs a deep copy of the trie.

View File

@@ -79,7 +79,9 @@ func TestMerkleTrieRoot_EmptyTrie(t *testing.T) {
depRoot, err := testAccount.Contract.GetDepositRoot(&bind.CallOpts{})
require.NoError(t, err)
require.DeepEqual(t, depRoot, trie.HashTreeRoot())
root, err := trie.HashTreeRoot()
require.NoError(t, err)
require.DeepEqual(t, depRoot, root)
}
func TestGenerateTrieFromItems_NoItemsProvided(t *testing.T) {
@@ -104,7 +106,8 @@ func TestMerkleTrie_VerifyMerkleProofWithDepth(t *testing.T) {
proof, err := m.MerkleProof(0)
require.NoError(t, err)
require.Equal(t, int(params.BeaconConfig().DepositContractTreeDepth)+1, len(proof))
root := m.HashTreeRoot()
root, err := m.HashTreeRoot()
require.NoError(t, err)
if ok := trie.VerifyMerkleProofWithDepth(root[:], items[0], 0, proof, params.BeaconConfig().DepositContractTreeDepth); !ok {
t.Error("First Merkle proof did not verify")
}
@@ -130,7 +133,8 @@ func TestMerkleTrie_VerifyMerkleProof(t *testing.T) {
proof, err := m.MerkleProof(0)
require.NoError(t, err)
require.Equal(t, int(params.BeaconConfig().DepositContractTreeDepth)+1, len(proof))
root := m.HashTreeRoot()
root, err := m.HashTreeRoot()
require.NoError(t, err)
if ok := trie.VerifyMerkleProof(root[:], items[0], 0, proof); !ok {
t.Error("First Merkle proof did not verify")
}
@@ -170,14 +174,16 @@ func TestMerkleTrie_VerifyMerkleProof_TrieUpdated(t *testing.T) {
require.NoError(t, err)
proof, err := m.MerkleProof(0)
require.NoError(t, err)
root := m.HashTreeRoot()
root, err := m.HashTreeRoot()
require.NoError(t, err)
require.Equal(t, true, trie.VerifyMerkleProofWithDepth(root[:], items[0], 0, proof, depth))
// Now we update the trie.
assert.NoError(t, m.Insert([]byte{5}, 3))
proof, err = m.MerkleProof(3)
require.NoError(t, err)
root = m.HashTreeRoot()
root, err = m.HashTreeRoot()
require.NoError(t, err)
if ok := trie.VerifyMerkleProofWithDepth(root[:], []byte{5}, 3, proof, depth); !ok {
t.Error("Second Merkle proof did not verify")
}
@@ -200,10 +206,13 @@ func TestRoundtripProto_OK(t *testing.T) {
require.NoError(t, err)
protoTrie := m.ToProto()
depositRoot := m.HashTreeRoot()
depositRoot, err := m.HashTreeRoot()
require.NoError(t, err)
newTrie := trie.CreateTrieFromProto(protoTrie)
require.DeepEqual(t, depositRoot, newTrie.HashTreeRoot())
root, err := newTrie.HashTreeRoot()
require.NoError(t, err)
require.DeepEqual(t, depositRoot, root)
}
@@ -221,8 +230,11 @@ func TestCopy_OK(t *testing.T) {
if copiedTrie == source {
t.Errorf("Original trie returned.")
}
copyHash := copiedTrie.HashTreeRoot()
require.DeepEqual(t, copyHash, copiedTrie.HashTreeRoot())
a, err := copiedTrie.HashTreeRoot()
require.NoError(t, err)
b, err := source.HashTreeRoot()
require.NoError(t, err)
require.DeepEqual(t, a, b)
}
func BenchmarkGenerateTrieFromItems(b *testing.B) {
@@ -296,7 +308,8 @@ func BenchmarkVerifyMerkleProofWithDepth(b *testing.B) {
proof, err := m.MerkleProof(2)
require.NoError(b, err)
root := m.HashTreeRoot()
root, err := m.HashTreeRoot()
require.NoError(b, err)
b.StartTimer()
for i := 0; i < b.N; i++ {
if ok := trie.VerifyMerkleProofWithDepth(root[:], items[2], 2, proof, params.BeaconConfig().DepositContractTreeDepth); !ok {

View File

@@ -0,0 +1,148 @@
package trie_test
import (
"testing"
"github.com/golang/protobuf/proto"
"github.com/prysmaticlabs/prysm/config/params"
"github.com/prysmaticlabs/prysm/container/trie"
"github.com/prysmaticlabs/prysm/crypto/hash"
ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
"github.com/prysmaticlabs/prysm/testing/require"
)
func FuzzSparseMerkleTrie_HashTreeRoot(f *testing.F) {
h := hash.Hash([]byte("hi"))
pb := &ethpb.SparseMerkleTrie{
Layers: []*ethpb.TrieLayer{
{
Layer: [][]byte{h[:]},
},
{
Layer: [][]byte{h[:]},
},
{
Layer: [][]byte{},
},
},
Depth: 4,
}
b, err := proto.Marshal(pb)
require.NoError(f, err)
f.Add(b)
f.Fuzz(func(t *testing.T, b []byte) {
pb := &ethpb.SparseMerkleTrie{}
if err := proto.Unmarshal(b, pb); err != nil {
return
}
_, err := trie.CreateTrieFromProto(pb).HashTreeRoot()
if err != nil {
return
}
})
}
func FuzzSparseMerkleTrie_MerkleProof(f *testing.F) {
h := hash.Hash([]byte("hi"))
pb := &ethpb.SparseMerkleTrie{
Layers: []*ethpb.TrieLayer{
{
Layer: [][]byte{h[:]},
},
{
Layer: [][]byte{h[:]},
},
{
Layer: [][]byte{},
},
},
Depth: 4,
}
b, err := proto.Marshal(pb)
require.NoError(f, err)
f.Add(b, 0)
f.Fuzz(func(t *testing.T, b []byte, i int) {
pb := &ethpb.SparseMerkleTrie{}
if err := proto.Unmarshal(b, pb); err != nil {
return
}
_, err := trie.CreateTrieFromProto(pb).MerkleProof(i)
if err != nil {
return
}
})
}
func FuzzSparseMerkleTrie_Insert(f *testing.F) {
h := hash.Hash([]byte("hi"))
pb := &ethpb.SparseMerkleTrie{
Layers: []*ethpb.TrieLayer{
{
Layer: [][]byte{h[:]},
},
{
Layer: [][]byte{h[:]},
},
{
Layer: [][]byte{},
},
},
Depth: 4,
}
b, err := proto.Marshal(pb)
require.NoError(f, err)
f.Add(b, []byte{}, 0)
f.Fuzz(func(t *testing.T, b, item []byte, i int) {
pb := &ethpb.SparseMerkleTrie{}
if err := proto.Unmarshal(b, pb); err != nil {
return
}
if err := trie.CreateTrieFromProto(pb).Insert(item, i); err != nil {
return
}
})
}
func FuzzSparseMerkleTrie_VerifyMerkleProofWithDepth(f *testing.F) {
splitProofs := func(proofRaw []byte) [][]byte {
var proofs [][]byte
for i := 0; i < len(proofRaw); i += 32 {
end := i + 32
if end >= len(proofRaw) {
end = len(proofRaw) - 1
}
proofs = append(proofs, proofRaw[i:end])
}
return proofs
}
items := [][]byte{
[]byte("A"),
[]byte("B"),
[]byte("C"),
[]byte("D"),
[]byte("E"),
[]byte("F"),
[]byte("G"),
[]byte("H"),
}
m, err := trie.GenerateTrieFromItems(items, params.BeaconConfig().DepositContractTreeDepth)
require.NoError(f, err)
proof, err := m.MerkleProof(0)
require.NoError(f, err)
require.Equal(f, int(params.BeaconConfig().DepositContractTreeDepth)+1, len(proof))
root, err := m.HashTreeRoot()
require.NoError(f, err)
var proofRaw []byte
for _, p := range proof {
proofRaw = append(proofRaw, p...)
}
f.Add(root[:], items[0], uint64(0), proofRaw, params.BeaconConfig().DepositContractTreeDepth)
f.Fuzz(func(t *testing.T, root, item []byte, merkleIndex uint64, proofRaw []byte, depth uint64) {
trie.VerifyMerkleProofWithDepth(root, item, merkleIndex, splitProofs(proofRaw), depth)
})
}

View File

@@ -0,0 +1,4 @@
go test fuzz v1
[]byte("")
[]byte("0")
int(41)

View File

@@ -0,0 +1,3 @@
go test fuzz v1
[]byte("")
int(63)

View File

@@ -0,0 +1,6 @@
go test fuzz v1
[]byte("0")
[]byte("000000000")
uint64(0)
[]byte("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))00000000000000000000000000000000000000000000000000000000000000")
uint64(71)