Skip to content

Commit 8a990e6

Browse files
Amxxcairoethernestognw
authored
Avoid storing hashing function pointers in storage make MerkleTree structure upgrade-safe (#5080)
Co-authored-by: cairo <[email protected]> Co-authored-by: ernestognw <[email protected]>
1 parent 53b5d84 commit 8a990e6

File tree

2 files changed

+58
-11
lines changed

2 files changed

+58
-11
lines changed

contracts/utils/structs/MerkleTree.sol

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {Panic} from "../Panic.sol";
1717
*
1818
* * Depth: The number of levels in the tree, it also defines the maximum number of leaves as 2**depth.
1919
* * Zero value: The value that represents an empty leaf. Used to avoid regular zero values to be part of the tree.
20-
* * Hashing function: A cryptographic hash function used to produce internal nodes.
20+
* * Hashing function: A cryptographic hash function used to produce internal nodes. Defaults to {Hashes-commutativeKeccak256}.
2121
*
2222
* _Available since v5.1._
2323
*/
@@ -27,9 +27,6 @@ library MerkleTree {
2727
*
2828
* The `sides` and `zero` arrays are set to have a length equal to the depth of the tree during setup.
2929
*
30-
* The hashing function used during initialization to compute the `zeros` values (value of a node at a given depth
31-
* for which the subtree is full of zero leaves). This function is kept in the structure for handling insertions.
32-
*
3330
* Struct members have an underscore prefix indicating that they are "private" and should not be read or written to
3431
* directly. Use the functions provided below instead. Modifying the struct manually may violate assumptions and
3532
* lead to unexpected behavior.
@@ -44,7 +41,6 @@ library MerkleTree {
4441
uint256 _nextLeafIndex;
4542
bytes32[] _sides;
4643
bytes32[] _zeros;
47-
function(bytes32, bytes32) view returns (bytes32) _fnHash;
4844
}
4945

5046
/**
@@ -53,6 +49,9 @@ library MerkleTree {
5349
*
5450
* Calling this function on MerkleTree that was already setup and used will reset it to a blank state.
5551
*
52+
* Once a tree is setup, any push to it must use the same hashing function. This means that values
53+
* should be pushed to it using the default {xref-MerkleTree-push-struct-MerkleTree-Bytes32PushTree-bytes32-}[push] function.
54+
*
5655
* IMPORTANT: The zero value should be carefully chosen since it will be stored in the tree representing
5756
* empty leaves. It should be a value that is not expected to be part of the tree.
5857
*/
@@ -61,7 +60,10 @@ library MerkleTree {
6160
}
6261

6362
/**
64-
* @dev Same as {setup}, but allows to specify a custom hashing function.
63+
* @dev Same as {xref-MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-}[setup], but allows to specify a custom hashing function.
64+
*
65+
* Once a tree is setup, any push to it must use the same hashing function. This means that values
66+
* should be pushed to it using the custom push function, which should be the same one as used during the setup.
6567
*
6668
* IMPORTANT: Providing a custom hashing function is a security-sensitive operation since it may
6769
* compromise the soundness of the tree. Consider using functions from {Hashes}.
@@ -85,7 +87,6 @@ library MerkleTree {
8587

8688
// Set the first root
8789
self._nextLeafIndex = 0;
88-
self._fnHash = fnHash;
8990

9091
return currentZero;
9192
}
@@ -96,11 +97,32 @@ library MerkleTree {
9697
*
9798
* Hashing the leaf before calling this function is recommended as a protection against
9899
* second pre-image attacks.
100+
*
101+
* This variant uses {Hashes-commutativeKeccak256} to hash internal nodes. It should only be used on merkle trees
102+
* that were setup using the same (default) hashing function (i.e. by calling
103+
* {xref-MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-}[the default setup] function).
99104
*/
100105
function push(Bytes32PushTree storage self, bytes32 leaf) internal returns (uint256 index, bytes32 newRoot) {
106+
return push(self, leaf, Hashes.commutativeKeccak256);
107+
}
108+
109+
/**
110+
* @dev Insert a new leaf in the tree, and compute the new root. Returns the position of the inserted leaf in the
111+
* tree, and the resulting root.
112+
*
113+
* Hashing the leaf before calling this function is recommended as a protection against
114+
* second pre-image attacks.
115+
*
116+
* This variant uses a custom hashing function to hash internal nodes. It should only be called with the same
117+
* function as the one used during the initial setup of the merkle tree.
118+
*/
119+
function push(
120+
Bytes32PushTree storage self,
121+
bytes32 leaf,
122+
function(bytes32, bytes32) view returns (bytes32) fnHash
123+
) internal returns (uint256 index, bytes32 newRoot) {
101124
// Cache read
102125
uint256 levels = self._zeros.length;
103-
function(bytes32, bytes32) view returns (bytes32) fnHash = self._fnHash;
104126

105127
// Get leaf index
106128
index = self._nextLeafIndex++;
@@ -123,7 +145,7 @@ library MerkleTree {
123145
}
124146

125147
// Compute the current node hash by using the hash function
126-
// with either the its sibling (side) or the zero value for that level.
148+
// with either its sibling (side) or the zero value for that level.
127149
currentLevelHash = fnHash(
128150
isLeft ? currentLevelHash : Arrays.unsafeAccess(self._sides, i).value,
129151
isLeft ? Arrays.unsafeAccess(self._zeros, i).value : currentLevelHash

docs/modules/ROOT/pages/utilities.adoc

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,10 @@ Building an on-chain Merkle Tree allow developers to keep track of the history o
157157

158158
The Merkle Tree does not keep track of the roots purposely, so that developers can choose their tracking mechanism. Setting up and using an Merkle Tree in Solidity is as simple as follows:
159159

160+
NOTE: Functions are exposed without access control for demonstration purposes
161+
160162
[source,solidity]
161163
----
162-
// NOTE: Functions are exposed without access control for demonstration purposes
163-
164164
using MerkleTree for MerkleTree.Bytes32PushTree;
165165
MerkleTree.Bytes32PushTree private _tree;
166166
@@ -174,6 +174,31 @@ function push(bytes32 leaf) public /* onlyOwner */ {
174174
}
175175
----
176176

177+
The library also supports custom hashing functions, which can be passed as an extra parameter to the xref:api:utils.adoc#MerkleTree-push-struct-MerkleTree-Bytes32PushTree-bytes32-[`push`] and xref:api:utils.adoc#MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-[`setup`] functions.
178+
179+
Using custom hashing functions is a sensitive operation. After setup, it requires to keep using the same hashing function for every new valued pushed to the tree to avoid corrupting the tree. For this reason, it's a good practice to keep your hashing function static in your implementation contract as follows:
180+
181+
[source,solidity]
182+
----
183+
using MerkleTree for MerkleTree.Bytes32PushTree;
184+
MerkleTree.Bytes32PushTree private _tree;
185+
186+
function setup(uint8 _depth, bytes32 _zero) public /* onlyOwner */ {
187+
root = _tree.setup(_depth, _zero, _hashFn);
188+
}
189+
190+
function push(bytes32 leaf) public /* onlyOwner */ {
191+
(uint256 leafIndex, bytes32 currentRoot) = _tree.push(leaf, _hashFn);
192+
// Store the new root.
193+
}
194+
195+
function _hashFn(bytes32 a, bytes32 b) internal view returns(bytes32) {
196+
// Custom hash function implementation
197+
// Kept as an internal implementation detail to
198+
// guarantee the same function is always used
199+
}
200+
----
201+
177202
[[misc]]
178203
== Misc
179204

0 commit comments

Comments
 (0)