diff --git a/consensus/polybft/checkpoint_manager.go b/consensus/polybft/checkpoint_manager.go index 7477f1cffe..86ee6c98c5 100644 --- a/consensus/polybft/checkpoint_manager.go +++ b/consensus/polybft/checkpoint_manager.go @@ -11,9 +11,9 @@ import ( "github.com/0xPolygon/polygon-edge/consensus/polybft/validator" "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/helper/hex" - "github.com/0xPolygon/polygon-edge/merkle-tree" "github.com/0xPolygon/polygon-edge/txrelayer" "github.com/0xPolygon/polygon-edge/types" + merkle "github.com/Ethernal-Tech/merkle-tree" hclog "github.com/hashicorp/go-hclog" "github.com/umbracle/ethgo" bolt "go.etcd.io/bbolt" @@ -310,7 +310,7 @@ func (c *checkpointManager) BuildEventRoot(epoch uint64) (types.Hash, error) { return types.ZeroHash, err } - return tree.Hash(), nil + return types.Hash(tree.Hash()), nil } // GenerateExitProof generates proof of exit event @@ -404,7 +404,7 @@ func (c *checkpointManager) GenerateExitProof(exitID uint64) (types.Proof, error exitEventHex := hex.EncodeToString(exitEventEncoded) return types.Proof{ - Data: proof, + Data: types.FromMerkleToTypesHash(proof), Metadata: map[string]interface{}{ "LeafIndex": leafIndex, "ExitEvent": exitEventHex, diff --git a/consensus/polybft/checkpoint_manager_test.go b/consensus/polybft/checkpoint_manager_test.go index e3e238c1f2..08fa7f7f2a 100644 --- a/consensus/polybft/checkpoint_manager_test.go +++ b/consensus/polybft/checkpoint_manager_test.go @@ -14,7 +14,7 @@ import ( "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/helper/common" - "github.com/0xPolygon/polygon-edge/merkle-tree" + merkle "github.com/Ethernal-Tech/merkle-tree" hclog "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -342,7 +342,7 @@ func TestCheckpointManager_BuildEventRoot(t *testing.T) { hash, err := checkpointManager.BuildEventRoot(1) require.NoError(t, err) - require.Equal(t, tree.Hash(), hash) + require.Equal(t, tree.Hash(), merkle.Hash(hash)) }) t.Run("Get exit event root hash - no events", func(t *testing.T) { @@ -409,7 +409,7 @@ func TestCheckpointManager_GenerateExitProof(t *testing.T) { t.Run("Generate and validate exit proof", func(t *testing.T) { t.Parallel() // verify generated proof on desired tree - require.NoError(t, merkle.VerifyProof(correctBlockToGetExit, encodedEvents[1], proof.Data, tree.Hash())) + require.NoError(t, merkle.VerifyProof(correctBlockToGetExit, encodedEvents[1], types.FromTypesToMerkleHash(proof.Data), tree.Hash())) }) t.Run("Generate and validate exit proof - invalid proof", func(t *testing.T) { @@ -422,7 +422,7 @@ func TestCheckpointManager_GenerateExitProof(t *testing.T) { // verify generated proof on desired tree require.ErrorContains(t, merkle.VerifyProof(correctBlockToGetExit, - encodedEvents[1], invalidProof, tree.Hash()), "not a member of merkle tree") + encodedEvents[1], types.FromTypesToMerkleHash(invalidProof), tree.Hash()), "not a member of merkle tree") }) t.Run("Generate exit proof - no event", func(t *testing.T) { diff --git a/consensus/polybft/sc_integration_test.go b/consensus/polybft/sc_integration_test.go index b91757abe1..0ef3c10409 100644 --- a/consensus/polybft/sc_integration_test.go +++ b/consensus/polybft/sc_integration_test.go @@ -194,7 +194,7 @@ func TestIntegration_PerformExit(t *testing.T) { EpochNumber: epochNumber, CurrentValidatorsHash: accSetHash, NextValidatorsHash: accSetHash, - EventRoot: eventRoot, + EventRoot: types.Hash(eventRoot), } checkpointHash, err := checkpointData.Hash( @@ -249,7 +249,7 @@ func TestIntegration_PerformExit(t *testing.T) { BlockNumber: new(big.Int).SetUint64(blockNumber), LeafIndex: new(big.Int).SetUint64(leafIndex), UnhashedLeaf: proofExitEvent, - Proof: proof, + Proof: types.FromMerkleToTypesHash(proof), }).EncodeAbi() require.NoError(t, err) diff --git a/consensus/polybft/state_store_state_sync_test.go b/consensus/polybft/state_store_state_sync_test.go index 415d3993f0..1c63eeb385 100644 --- a/consensus/polybft/state_store_state_sync_test.go +++ b/consensus/polybft/state_store_state_sync_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" - "github.com/0xPolygon/polygon-edge/merkle-tree" "github.com/0xPolygon/polygon-edge/types" + merkle "github.com/Ethernal-Tech/merkle-tree" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.etcd.io/bbolt" @@ -253,7 +253,7 @@ func createTestCommitmentMessage(t *testing.T, fromIndex uint64) *CommitmentMess require.NoError(t, err) msg := &contractsapi.StateSyncCommitment{ - Root: tree.Hash(), + Root: types.Hash(tree.Hash()), StartID: big.NewInt(int64(fromIndex)), EndID: big.NewInt(int64(fromIndex + maxCommitmentSize - 1)), } diff --git a/consensus/polybft/state_sync_commitment.go b/consensus/polybft/state_sync_commitment.go index 6f109d834f..1d9e50cbba 100644 --- a/consensus/polybft/state_sync_commitment.go +++ b/consensus/polybft/state_sync_commitment.go @@ -7,9 +7,9 @@ import ( "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" "github.com/0xPolygon/polygon-edge/crypto" - "github.com/0xPolygon/polygon-edge/merkle-tree" "github.com/0xPolygon/polygon-edge/state/runtime/precompiled" "github.com/0xPolygon/polygon-edge/types" + merkle "github.com/Ethernal-Tech/merkle-tree" ) const ( @@ -37,7 +37,7 @@ func NewPendingCommitment(epoch uint64, stateSyncEvents []*contractsapi.StateSyn StateSyncCommitment: &contractsapi.StateSyncCommitment{ StartID: stateSyncEvents[0].ID, EndID: stateSyncEvents[len(stateSyncEvents)-1].ID, - Root: tree.Hash(), + Root: types.Hash(tree.Hash()), }, }, nil } @@ -90,7 +90,7 @@ func (cm *CommitmentMessageSigned) VerifyStateSyncProof(proof []types.Hash, } return merkle.VerifyProof(stateSync.ID.Uint64()-cm.Message.StartID.Uint64(), - hash, proof, cm.Message.Root) + hash, types.FromTypesToMerkleHash(proof), merkle.Hash(cm.Message.Root)) } // ContainsStateSync checks if commitment contains given state sync event diff --git a/consensus/polybft/state_sync_commitment_test.go b/consensus/polybft/state_sync_commitment_test.go index 61018f7517..28be6ad89f 100644 --- a/consensus/polybft/state_sync_commitment_test.go +++ b/consensus/polybft/state_sync_commitment_test.go @@ -25,10 +25,10 @@ func TestCommitmentMessage_Hash(t *testing.T) { trie2, err := createMerkleTree(stateSyncEvents[0 : len(stateSyncEvents)-1]) require.NoError(t, err) - commitmentMessage1 := newTestCommitmentSigned(t, trie1.Hash(), 2, 8) - commitmentMessage2 := newTestCommitmentSigned(t, trie1.Hash(), 2, 8) - commitmentMessage3 := newTestCommitmentSigned(t, trie1.Hash(), 6, 10) - commitmentMessage4 := newTestCommitmentSigned(t, trie2.Hash(), 2, 8) + commitmentMessage1 := newTestCommitmentSigned(t, types.Hash(trie1.Hash()), 2, 8) + commitmentMessage2 := newTestCommitmentSigned(t, types.Hash(trie1.Hash()), 2, 8) + commitmentMessage3 := newTestCommitmentSigned(t, types.Hash(trie1.Hash()), 6, 10) + commitmentMessage4 := newTestCommitmentSigned(t, types.Hash(trie2.Hash()), 2, 8) hash1, err := commitmentMessage1.Hash() require.NoError(t, err) @@ -85,7 +85,7 @@ func TestCommitmentMessage_VerifyProof(t *testing.T) { require.NoError(t, err) execute := &contractsapi.ExecuteStateReceiverFn{ - Proof: proof, + Proof: types.FromMerkleToTypesHash(proof), Obj: (*contractsapi.StateSync)(stateSync), } @@ -98,7 +98,7 @@ func TestCommitmentMessage_VerifyProof(t *testing.T) { require.Equal(t, stateSync.Sender, executionStateSync.Obj.Sender) require.Equal(t, stateSync.Receiver, executionStateSync.Obj.Receiver) require.Equal(t, stateSync.Data, executionStateSync.Obj.Data) - require.Equal(t, proof, executionStateSync.Proof) + require.Equal(t, proof, types.FromTypesToMerkleHash(executionStateSync.Proof)) err = commitmentSigned.VerifyStateSyncProof(executionStateSync.Proof, (*contractsapi.StateSyncedEvent)(executionStateSync.Obj)) @@ -136,11 +136,11 @@ func TestCommitmentMessage_VerifyProof_StateSyncHashNotEqualToProof(t *testing.T Message: &contractsapi.StateSyncCommitment{ StartID: big.NewInt(fromIndex), EndID: big.NewInt(toIndex), - Root: tree.Hash(), + Root: types.Hash(tree.Hash()), }, } - assert.ErrorContains(t, commitment.VerifyStateSyncProof(proof, stateSyncs[4]), "not a member of merkle tree") + assert.ErrorContains(t, commitment.VerifyStateSyncProof(types.FromMerkleToTypesHash(proof), stateSyncs[4]), "not a member of merkle tree") } func newTestCommitmentSigned(t *testing.T, root types.Hash, startID, endID int64) *CommitmentMessageSigned { diff --git a/consensus/polybft/state_sync_manager.go b/consensus/polybft/state_sync_manager.go index 441d8002a3..8acc90a561 100644 --- a/consensus/polybft/state_sync_manager.go +++ b/consensus/polybft/state_sync_manager.go @@ -473,7 +473,7 @@ func (s *stateSyncManager) buildProofs(commitmentMsg *contractsapi.StateSyncComm } stateSyncProofs[i] = &StateSyncProof{ - Proof: p, + Proof: types.FromMerkleToTypesHash(p), StateSync: event, } } diff --git a/consensus/polybft/state_sync_manager_test.go b/consensus/polybft/state_sync_manager_test.go index bb782f6b72..41a17f3906 100644 --- a/consensus/polybft/state_sync_manager_test.go +++ b/consensus/polybft/state_sync_manager_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + merkle "github.com/Ethernal-Tech/merkle-tree" "github.com/hashicorp/go-hclog" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" @@ -18,7 +19,6 @@ import ( "github.com/0xPolygon/polygon-edge/consensus/polybft/validator" "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/helper/common" - "github.com/0xPolygon/polygon-edge/merkle-tree" "github.com/0xPolygon/polygon-edge/types" ) @@ -237,7 +237,7 @@ func TestStateSyncManager_BuildCommitment(t *testing.T) { { MerkleTree: tree, StateSyncCommitment: &contractsapi.StateSyncCommitment{ - Root: tree.Hash(), + Root: types.Hash(tree.Hash()), StartID: big.NewInt(0), EndID: big.NewInt(1), }, @@ -540,7 +540,7 @@ func TestStateSyncManager_GetProofs_NoProof_BuildProofs(t *testing.T) { Message: &contractsapi.StateSyncCommitment{ StartID: big.NewInt(fromIndex), EndID: big.NewInt(maxCommitmentSize), - Root: tree.Hash(), + Root: types.Hash(tree.Hash()), }, } diff --git a/go.mod b/go.mod index c353b969ba..b70d27c800 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( cloud.google.com/go/secretmanager v1.11.4 github.com/0xPolygon/go-ibft v0.4.1-0.20230717081138-628065cf23b6 github.com/Ethernal-Tech/blockchain-event-tracker v0.0.0-20231202204931-b886edca635a + github.com/Ethernal-Tech/merkle-tree v0.0.0-20231213143318-4db9da419e04 github.com/armon/go-metrics v0.4.1 github.com/aws/aws-sdk-go v1.46.1 github.com/btcsuite/btcd v0.22.1 diff --git a/go.sum b/go.sum index 4be33ca7b8..77ecf49714 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/DataDog/sketches-go v1.4.2 h1:gppNudE9d19cQ98RYABOetxIhpTCl4m7CnbRZjv github.com/DataDog/sketches-go v1.4.2/go.mod h1:xJIXldczJyyjnbDop7ZZcLxJdV3+7Kra7H1KMgpgkLk= github.com/Ethernal-Tech/blockchain-event-tracker v0.0.0-20231202204931-b886edca635a h1:IujnjiVu6UcVYhUIAaCN1ZI122VZzTO80epIbF6pDbk= github.com/Ethernal-Tech/blockchain-event-tracker v0.0.0-20231202204931-b886edca635a/go.mod h1:IgWSrKhiPnqV6M68wQt8MyWNeWwA+3sYZQP6Y6STR2M= +github.com/Ethernal-Tech/merkle-tree v0.0.0-20231213143318-4db9da419e04 h1:DGIOHe3qAeU+mrRLNJ83mJWyH1t5SqN8XvTrwBUAXK4= +github.com/Ethernal-Tech/merkle-tree v0.0.0-20231213143318-4db9da419e04/go.mod h1:vwaNJ2xOVILZoOcD6CxswTg8XOCDvIhcRIp8xanmS3s= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= diff --git a/merkle-tree/README.md b/merkle-tree/README.md deleted file mode 100644 index 7bed961e29..0000000000 --- a/merkle-tree/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Merkle Tree Implementation in Go - -This repository contains an implementation of a Merkle tree in Go, along with benchmarking results. - -## Overview - -A Merkle tree is a hash-based data structure that allows efficient and secure verification of the contents of large data sets. It is commonly used in peer-to-peer networks, blockchain systems, and other distributed systems. The tree is constructed by recursively hashing pairs of data elements until a single hash value is obtained, which is called the root hash. - -The main advantages of using a Merkle tree are its ability to efficiently verify data integrity, its compact representation of large data sets, and its support for partial data verification. - -## Implementation - -The Merkle tree implementation in this repository is relatively simple and follows the basic structure of a binary tree. Each leaf node in the tree represents a data element, and each non-leaf node is the hash of its two child nodes. The root hash is the hash of the two child nodes of the root node. - -The implementation includes functions for constructing a Merkle tree from a set of data elements, computing the root hash of the tree, generating and verifying the proof of membership for a piece of data. - -## Benchmarking - -To benchmark the performance of the Merkle tree implementation, we used the Go testing package and the built-in benchmarking tools. We tested the implementation with different sizes of data sets, ranging from 10 elements to 1,000,000 elements. - -The benchmark results show that the Merkle tree implementation has a linear time complexity with respect to the size of the data set. The construction of the tree takes O(n log n) time, where n is the number of data elements. The verification of a single data element takes O(log n) time. - -Here are the benchmarking results for constructing a Merkle tree from a set of data elements: - -| TestName | Number of leaves in tree | Results | -|----------|--------------------------|---------| -| Benchmark_MerkleTreeCreation_10-8 | 10 | 73446 17496 ns/op 14272 B/op 119 allocs/op | -| Benchmark_MerkleTreeCreation_100-8 | 100 | 7204 148145 ns/op 132744 B/op 1043 allocs/op | -| Benchmark_MerkleTreeCreation_1K-8 | 1000 | 715 1485988 ns/op 1305667 B/op 10064 allocs/op | -| Benchmark_MerkleTreeCreation_10K-8 | 10,000 | 74 15883932 ns/op 13130629 B/op 100141 allocs/op | -| Benchmark_MerkleTreeCreation_100K-8 | 100,000 | 7 147831662 ns/op 132476874 B/op 1000217 allocs/op | -| Benchmark_MerkleTreeCreation_1M-8 | 1,000,000 | 1 1538599360 ns/op 1329522960 B/op 10000325 allocs/op | - -Here are the benchmarking results for generating a proof of membership for a piece of data in a Merkle tree: - -| TestName | Number of leaves in tree | Results | -|----------|--------------------------|---------| -| Benchmark_GenerateProof_10-8 | 10 | 6827118 156.8 ns/op 224 B/op 3 allocs/op | -| Benchmark_GenerateProof_100-8 | 100 | 4569232 251.5 ns/op 480 B/op 4 allocs/op | -| Benchmark_GenerateProof_1K-8 | 1000 | 2740286 443.0 ns/op 992 B/op 5 allocs/op | -| Benchmark_GenerateProof_10K-8 | 10,000 | 2008087 521.0 ns/op 992 B/op 5 allocs/op | -| Benchmark_GenerateProof_100K-8 | 100,000 | 1252176 840.1 ns/op 2016 B/op 6 allocs/op | -| Benchmark_GenerateProof_1M-8 | 1,000,000 | 1550371 793.2 ns/op 2016 B/op 6 allocs/op | - -Here are the benchmarking results for verifying a proof of membership for a piece of data in a Merkle tree: - -| TestName | Number of leaves in tree | Results | -|----------|--------------------------|---------| -| Benchmark_VerifyProof_10-8 | 10 | 299299 3485 ns/op 3136 B/op 20 allocs/op | -| Benchmark_VerifyProof_100-8 | 100 | 216274 5620 ns/op 4768 B/op 32 allocs/op | -| Benchmark_VerifyProof_1K-8 | 1000 | 151994 7340 ns/op 6400 B/op 44 allocs/op | -| Benchmark_VerifyProof_10K-8 | 10,000 | 113112 10024 ns/op 8576 B/op 60 allocs/op | -| Benchmark_VerifyProof_100K-8 | 100,000 | 97400 12088 ns/op 10208 B/op 72 allocs/op | -| Benchmark_VerifyProof_1M-8 | 1,000,000 | 95178 13368 ns/op 11840 B/op 84 allocs/op | - -As you can see, the construction of the Merkle tree takes longer than verifying a single data element. However, both operations have a reasonable time complexity, even for large data sets. - -## Conclusion - -This Merkle tree implementation in Go provides a simple and efficient way to verify the integrity of large data sets. The implementation is easy to understand and customize, and the benchmarking results show that it performs well for data sets of different sizes. diff --git a/merkle-tree/merkle_tree.go b/merkle-tree/merkle_tree.go deleted file mode 100644 index d39ac98803..0000000000 --- a/merkle-tree/merkle_tree.go +++ /dev/null @@ -1,226 +0,0 @@ -package merkle - -import ( - "bytes" - "encoding/hex" - "errors" - "fmt" - "hash" - "math" - - "github.com/0xPolygon/polygon-edge/crypto" - "github.com/0xPolygon/polygon-edge/types" -) - -// A Merkle tree example: -// -// ROOT -// / \ -// h3 h7 -// / \ / \ -// h1 h2 h5 h6 -// / \ / \ / \ / \ -// L0 L1 L2 L3 L4 L5 L6 L7 -// -// Each leaf node (L0 - L7) contains a data value, and each intermediate node -// (h1 - h7) contains the hash of its two child nodes. The root node (ROOT) contains -// the hash of the entire tree. To prove the inclusion of a leaf node in the tree, -// a Merkle proof would consist of the leaf node, along with the hash values of its -// sibling nodes on the path from the leaf to the root. -// -// So the proof for leaf node L0 will look like this: -// H(Hash(L1), Hash(H2), Hash(H7)) - -var ( - errLeafNotFound = errors.New("leaf not found") -) - -// MerkleNode represents a single node in merkle tree -type MerkleNode struct { - left *MerkleNode - right *MerkleNode - parent *MerkleNode - data []byte - hash types.Hash -} - -// newMerkleNode creates a new merkle node -func newMerkleNode(left, right *MerkleNode, data []byte, hasher hash.Hash) *MerkleNode { - node := &MerkleNode{left: left, right: right, data: data} - - var dataToHash []byte - if left == nil && right == nil { - // it's a leaf node - dataToHash = data - } else { - // it's an inner node - dataToHash = append(left.hash.Bytes(), right.hash.Bytes()...) - } - - hasher.Reset() - hasher.Write(dataToHash) - node.hash = types.BytesToHash(hasher.Sum(nil)) - - return node -} - -// MerkleTree is the structure for the Merkle tree. -type MerkleTree struct { - // hasher is a pointer to the hashing struct (e.g., Keccak256) - hasher hash.Hash - // rootNode is the root node of the tree - rootNode *MerkleNode - // leaf nodes is the list of leaf nodes of the tree (the lowest level of the tree) - leafNodes []*MerkleNode -} - -// NewMerkleTree creates a new Merkle tree from the provided data and using the default hashing (Keccak256). -func NewMerkleTree(data [][]byte) (*MerkleTree, error) { - return NewMerkleTreeWithHashing(data, crypto.NewKeccakState()) -} - -// NewMerkleTreeWithHashing creates a new Merkle tree from the provided data and hash type -func NewMerkleTreeWithHashing(data [][]byte, hasher hash.Hash) (*MerkleTree, error) { - if len(data) == 0 { - return nil, errors.New("tree must contain at least one leaf") - } - - leafNodes := make([]*MerkleNode, len(data)) - for i, d := range data { - leafNodes[i] = newMerkleNode(nil, nil, d, hasher) - } - - nodes := leafNodes - for len(nodes) > 1 { - var newLevel []*MerkleNode - - for i := 0; i < len(nodes); i += 2 { - left := nodes[i] - right := nodes[i] // if the tree has uneven number of nodes, this will duplicate the last one - - if i+1 < len(nodes) { - right = nodes[i+1] // if it has even numbers, then this will point to the last node - } - - parent := newMerkleNode(left, right, nil, hasher) - left.parent = parent - right.parent = parent - - newLevel = append(newLevel, parent) - } - - nodes = newLevel - } - - return &MerkleTree{ - hasher: hasher, - rootNode: nodes[0], - leafNodes: leafNodes, - }, nil -} - -// LeafIndex returns the index of given leaf if found in tree -func (t *MerkleTree) LeafIndex(leaf []byte) (uint64, error) { - for i, leafNode := range t.leafNodes { - if bytes.Equal(leafNode.data, leaf) { - return uint64(i), nil - } - } - - return 0, errLeafNotFound -} - -// Hash is the Merkle Tree root hash -func (t *MerkleTree) Hash() types.Hash { - return t.rootNode.hash -} - -// String implements the stringer interface -func (t *MerkleTree) String() string { - return hex.EncodeToString(t.Hash().Bytes()) -} - -// Depth returns the depth of merkle tree -func (t *MerkleTree) Depth() int { - return int(math.Ceil(math.Log2(float64(len(t.leafNodes))))) -} - -// GenerateProof generates the proof of membership for a piece of data in the Merkle tree. -func (t *MerkleTree) GenerateProof(leaf []byte) ([]types.Hash, error) { - proof := []types.Hash{} - - leafNode := t.findLeafNode(leaf) - if leafNode == nil { - return nil, fmt.Errorf("given data not in merkle tree") - } - - node := leafNode - for !bytes.Equal(node.hash.Bytes(), t.rootNode.hash.Bytes()) { - if bytes.Equal(node.parent.left.hash.Bytes(), node.hash.Bytes()) { - proof = append(proof, node.parent.right.hash) - } else { - proof = append(proof, node.parent.left.hash) - } - - node = node.parent - } - - return proof, nil -} - -// findLeafNode finds the given leaf node that corresponds to given leaf data -func (t *MerkleTree) findLeafNode(leaf []byte) *MerkleNode { - for _, leafNode := range t.leafNodes { - if bytes.Equal(leafNode.data, leaf) { - return leafNode - } - } - - return nil -} - -// VerifyProof verifies a Merkle tree proof of membership for provided data using the default hash type (Keccak256) -func VerifyProof(index uint64, leaf []byte, proof []types.Hash, root types.Hash) error { - return VerifyProofUsing(index, leaf, proof, root, crypto.NewKeccakState()) -} - -// VerifyProofUsing verifies a Merkle tree proof of membership for provided data using the provided hash type -func VerifyProofUsing(index uint64, leaf []byte, proof []types.Hash, root types.Hash, hasher hash.Hash) error { - if len(leaf) == 0 { - return fmt.Errorf("empty leaf") - } - - if int(index) >= int(math.Pow(2, float64(len(proof)))) { - return fmt.Errorf("invalid leaf index %v", index) - } - - computedHash := getProofHash(index, leaf, proof, hasher) - if !bytes.Equal(root.Bytes(), computedHash) { - return fmt.Errorf("leaf with index %v, not a member of merkle tree. Merkle root hash: %v", index, root) - } - - return nil -} - -// getProofHash uses the leaf and proof to recalculate the root hash of a tree that contains given leaf -func getProofHash(index uint64, leaf []byte, proof []types.Hash, hasher hash.Hash) []byte { - hasher.Write(leaf) - computedHash := hasher.Sum(nil) - - for i := 0; i < len(proof); i++ { - hasher.Reset() - - if index%2 == 0 { - hasher.Write(computedHash) - hasher.Write(proof[i].Bytes()) - } else { - hasher.Write(proof[i].Bytes()) - hasher.Write(computedHash) - } - - computedHash = hasher.Sum(nil) - index /= 2 - } - - return computedHash -} diff --git a/merkle-tree/merkle_tree_benchmark_test.go b/merkle-tree/merkle_tree_benchmark_test.go deleted file mode 100644 index dd505f2780..0000000000 --- a/merkle-tree/merkle_tree_benchmark_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package merkle - -import ( - "testing" - - "github.com/0xPolygon/polygon-edge/helper/common" - "github.com/stretchr/testify/require" -) - -func Benchmark_MerkleTreeCreation_10(b *testing.B) { - benchmarkMerkleTreeCreation(b, 10) -} - -func Benchmark_MerkleTreeCreation_100(b *testing.B) { - benchmarkMerkleTreeCreation(b, 100) -} - -func Benchmark_MerkleTreeCreation_1K(b *testing.B) { - benchmarkMerkleTreeCreation(b, 1000) -} - -func Benchmark_MerkleTreeCreation_10K(b *testing.B) { - benchmarkMerkleTreeCreation(b, 10_000) -} - -func Benchmark_MerkleTreeCreation_100K(b *testing.B) { - benchmarkMerkleTreeCreation(b, 100_000) -} - -func Benchmark_MerkleTreeCreation_1M(b *testing.B) { - benchmarkMerkleTreeCreation(b, 1_000_000) -} - -func Benchmark_GenerateProof_10(b *testing.B) { - benchmarkGenerateProof(b, 10) -} - -func Benchmark_GenerateProof_100(b *testing.B) { - benchmarkGenerateProof(b, 100) -} - -func Benchmark_GenerateProof_1K(b *testing.B) { - benchmarkGenerateProof(b, 1000) -} - -func Benchmark_GenerateProof_10K(b *testing.B) { - benchmarkGenerateProof(b, 10_000) -} - -func Benchmark_GenerateProof_100K(b *testing.B) { - benchmarkGenerateProof(b, 100_000) -} - -func Benchmark_GenerateProof_1M(b *testing.B) { - benchmarkGenerateProof(b, 1_000_000) -} - -func Benchmark_VerifyProof_10(b *testing.B) { - benchmarkVerifyProof(b, 10) -} - -func Benchmark_VerifyProof_100(b *testing.B) { - benchmarkVerifyProof(b, 100) -} - -func Benchmark_VerifyProof_1K(b *testing.B) { - benchmarkVerifyProof(b, 1000) -} - -func Benchmark_VerifyProof_10K(b *testing.B) { - benchmarkVerifyProof(b, 10_000) -} - -func Benchmark_VerifyProof_100K(b *testing.B) { - benchmarkVerifyProof(b, 100_000) -} - -func Benchmark_VerifyProof_1M(b *testing.B) { - benchmarkVerifyProof(b, 1_000_000) -} - -func benchmarkVerifyProof(b *testing.B, numOfLeaves uint64) { - b.Helper() - - const ( - leafIndex = 0 - ) - - data := make([][]byte, numOfLeaves) - for i := uint64(0); i < numOfLeaves; i++ { - data[i] = common.EncodeUint64ToBytes(i) - } - - tree, err := NewMerkleTree(data) - require.NoError(b, err) - - proof, err := tree.GenerateProof(data[leafIndex]) - require.NoError(b, err) - - rootHash := tree.Hash() - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - VerifyProof(leafIndex, data[leafIndex], proof, rootHash) //nolint:errcheck - } -} - -func benchmarkMerkleTreeCreation(b *testing.B, numOfLeaves uint64) { - b.Helper() - - data := make([][]byte, numOfLeaves) - for i := uint64(0); i < numOfLeaves; i++ { - data[i] = common.EncodeUint64ToBytes(i) - } - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - NewMerkleTree(data) //nolint:errcheck - } -} - -func benchmarkGenerateProof(b *testing.B, numOfLeaves uint64) { - b.Helper() - - data := make([][]byte, numOfLeaves) - for i := uint64(0); i < numOfLeaves; i++ { - data[i] = common.EncodeUint64ToBytes(i) - } - - tree, err := NewMerkleTree(data) - require.NoError(b, err) - - b.ResetTimer() - - for i := 0; i < b.N; i++ { - tree.GenerateProof(data[0]) //nolint:errcheck - } -} diff --git a/merkle-tree/merkle_tree_test.go b/merkle-tree/merkle_tree_test.go deleted file mode 100644 index 2f342a9277..0000000000 --- a/merkle-tree/merkle_tree_test.go +++ /dev/null @@ -1,92 +0,0 @@ -package merkle - -import ( - "math" - "testing" - - "github.com/0xPolygon/polygon-edge/helper/common" - "github.com/0xPolygon/polygon-edge/types" - "github.com/stretchr/testify/require" -) - -func TestMerkleTree_VerifyProofs(t *testing.T) { - t.Parallel() - - const dataLen = 515 - - verifyProof := func(numOfItems uint64) { - data := make([][]byte, numOfItems) - for i := uint64(0); i < numOfItems; i++ { - data[i] = common.EncodeUint64ToBytes(i) - } - - tree, err := NewMerkleTree(data) - require.NoError(t, err) - - merkleRootHash := tree.Hash() - treeDepth := tree.Depth() - - for i := uint64(0); i < numOfItems; i++ { - leafIndex, err := tree.LeafIndex(data[i]) - require.NoError(t, err) - - proof, err := tree.GenerateProof(data[i]) - require.NoError(t, err) - - // valid proof - require.NoError(t, VerifyProof(leafIndex, data[i], proof, merkleRootHash)) - // proof length should always equal to the depth of the tree - require.Equal(t, treeDepth, len(proof)) - - // invalid leaf - require.ErrorContains(t, VerifyProof(leafIndex, []byte{}, proof, merkleRootHash), "empty leaf") // invalid leaf - // invalid index - require.ErrorContains(t, VerifyProof(uint64(math.Pow(2, float64(len(proof))+1)), - data[i], proof, merkleRootHash), "invalid leaf index") - // invalid proof - not a member of merkle tree - proof[0][0]++ - require.ErrorContains(t, VerifyProof(leafIndex, data[i], proof, merkleRootHash), "not a member of merkle tree") - - // invalid leaf data on generating proof - dataCopy := make([]byte, len(data[i])) - copy(dataCopy, data[i]) - dataCopy[0] = dataCopy[0] + 1 - _, err = tree.GenerateProof(dataCopy) - require.ErrorContains(t, err, "data not in merkle tree") - } - } - - // verify proofs for trees of different sizes - for i := uint64(2); i <= dataLen; i++ { - verifyProof(i) - } -} - -func TestMerkleTree_VerifyProof_TreeWithOneNode(t *testing.T) { - t.Parallel() - - leafData := []byte{1} - treeData := [][]byte{leafData} - - tree, err := NewMerkleTree(treeData) - require.NoError(t, err) - - proof, err := tree.GenerateProof(leafData) - require.NoError(t, err) - require.Empty(t, proof) // since tree contains one node, there is no proof, it's proof is rootHash == hashOfLeaf - - index, err := tree.LeafIndex(leafData) - require.NoError(t, err) - require.Equal(t, uint64(0), index) // should be 0 since tree only has one node - require.NoError(t, VerifyProof(index, leafData, proof, tree.Hash())) - - // invalid proof - invalidProof := []types.Hash{types.BytesToHash([]byte{0, 1, 2, 3, 4})} - require.ErrorContains(t, VerifyProof(index, leafData, invalidProof, tree.Hash()), "not a member of merkle tree") - - // invalid index - require.ErrorContains(t, VerifyProof(11, leafData, proof, tree.Hash()), "invalid leaf index") - - // empty leaf - require.ErrorContains(t, VerifyProof(11, []byte{}, proof, tree.Hash()), "empty leaf") -} diff --git a/types/types.go b/types/types.go index d3e53a1d36..f863dd7c07 100644 --- a/types/types.go +++ b/types/types.go @@ -9,6 +9,7 @@ import ( "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/helper/keccak" + "github.com/Ethernal-Tech/merkle-tree" ) const ( @@ -147,6 +148,26 @@ func StringToBytes(str string) []byte { return b } +// FromTypesToMerkleHash fills array of merkle.Hash from array types.Hash +func FromTypesToMerkleHash(hashes []Hash) []merkle.Hash { + merkleHashes := make([]merkle.Hash, 0, len(hashes)) + for _, hash := range hashes { + merkleHashes = append(merkleHashes, merkle.Hash(hash)) + } + + return merkleHashes +} + +// FromMerkleToTypesHash fills array of types.Hash from array merkle.Hash +func FromMerkleToTypesHash(merkleHashes []merkle.Hash) []Hash { + hashes := make([]Hash, 0, len(merkleHashes)) + for _, merkleHash := range merkleHashes { + hashes = append(hashes, Hash(merkleHash)) + } + + return hashes +} + // IsValidAddress checks if provided string is a valid Ethereum address func IsValidAddress(address string) error { // remove 0x prefix if it exists