Skip to content

Commit 635b48a

Browse files
authored
Merge pull request #136 from solidstate-network/IMT
Library to handle Merkle trees on-chain
2 parents 0ba41af + cba6980 commit 635b48a

File tree

3 files changed

+450
-0
lines changed

3 files changed

+450
-0
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.0;
4+
5+
library IncrementalMerkleTree {
6+
using IncrementalMerkleTree for Tree;
7+
8+
struct Tree {
9+
bytes32[][] nodes;
10+
}
11+
12+
/**
13+
* @notice query number of elements contained in tree
14+
* @param t Tree struct storage reference
15+
* @return treeSize size of tree
16+
*/
17+
function size(Tree storage t) internal view returns (uint256 treeSize) {
18+
if (t.height() > 0) {
19+
treeSize = t.nodes[0].length;
20+
}
21+
}
22+
23+
/**
24+
* @notice query one-indexed height of tree
25+
* @dev conventional zero-indexed height would require the use of signed integers, so height is one-indexed instead
26+
* @param t Tree struct storage reference
27+
* @return one-indexed height of tree
28+
*/
29+
function height(Tree storage t) internal view returns (uint256) {
30+
return t.nodes.length;
31+
}
32+
33+
/**
34+
* @notice query Merkle root
35+
* @param t Tree struct storage reference
36+
* @return hash root hash
37+
*/
38+
function root(Tree storage t) internal view returns (bytes32 hash) {
39+
uint256 treeHeight = t.height();
40+
41+
if (treeHeight > 0) {
42+
unchecked {
43+
hash = t.nodes[treeHeight - 1][0];
44+
}
45+
}
46+
}
47+
48+
function at(Tree storage t, uint256 index)
49+
internal
50+
view
51+
returns (bytes32 hash)
52+
{
53+
hash = t.nodes[0][index];
54+
}
55+
56+
/**
57+
* @notice add new element to tree
58+
* @param t Tree struct storage reference
59+
* @param hash to add
60+
*/
61+
function push(Tree storage t, bytes32 hash) internal {
62+
unchecked {
63+
uint256 treeHeight = t.height();
64+
uint256 treeSize = t.size();
65+
66+
// add new layer if tree is at capacity
67+
68+
if (treeSize == (1 << treeHeight) >> 1) {
69+
t.nodes.push();
70+
treeHeight++;
71+
}
72+
73+
// add new columns if rows are full
74+
75+
uint256 row;
76+
uint256 col = treeSize;
77+
78+
while (row < treeHeight && t.nodes[row].length <= col) {
79+
t.nodes[row].push();
80+
row++;
81+
col >>= 1;
82+
}
83+
84+
// add hash to tree
85+
86+
t.set(treeSize, hash);
87+
}
88+
}
89+
90+
function pop(Tree storage t) internal {
91+
uint256 treeHeight = t.height();
92+
uint256 treeSize = t.size() - 1;
93+
94+
// remove layer if tree has excess capacity
95+
96+
if (treeSize == (1 << treeHeight) >> 2) {
97+
treeHeight--;
98+
t.nodes.pop();
99+
}
100+
101+
// remove columns if rows are too long
102+
103+
uint256 row;
104+
uint256 col = treeSize;
105+
106+
while (row < treeHeight && t.nodes[row].length > col) {
107+
t.nodes[row].pop();
108+
row++;
109+
col = (col + 1) >> 1;
110+
}
111+
112+
// recalculate hashes
113+
114+
if (treeSize > 0) {
115+
t.set(treeSize - 1, t.at(treeSize - 1));
116+
}
117+
}
118+
119+
/**
120+
* @notice update existing element in tree
121+
* @param t Tree struct storage reference
122+
* @param index index to update
123+
* @param hash new hash to add
124+
*/
125+
function set(
126+
Tree storage t,
127+
uint256 index,
128+
bytes32 hash
129+
) internal {
130+
unchecked {
131+
_set(t.nodes, 0, index, t.height() - 1, hash);
132+
}
133+
}
134+
135+
/**
136+
* @notice update element in tree and recursively recalculate hashes
137+
* @param nodes internal tree structure storage reference
138+
* @param rowIndex index of current row to update
139+
* @param colIndex index of current column to update
140+
* @param rootIndex index of root row
141+
* @param hash hash to store at current position
142+
*/
143+
function _set(
144+
bytes32[][] storage nodes,
145+
uint256 rowIndex,
146+
uint256 colIndex,
147+
uint256 rootIndex,
148+
bytes32 hash
149+
) private {
150+
bytes32[] storage row = nodes[rowIndex];
151+
152+
row[colIndex] = hash;
153+
154+
if (rowIndex == rootIndex) return;
155+
156+
unchecked {
157+
if (colIndex & 1 == 1) {
158+
// sibling is on the left
159+
assembly {
160+
mstore(0x00, row.slot)
161+
let sibling := sload(
162+
add(keccak256(0x00, 0x20), sub(colIndex, 1))
163+
)
164+
mstore(0x00, sibling)
165+
mstore(0x20, hash)
166+
hash := keccak256(0x00, 0x40)
167+
}
168+
} else if (colIndex + 1 < row.length) {
169+
// sibling is on the right (and sibling exists)
170+
assembly {
171+
mstore(0x00, row.slot)
172+
let sibling := sload(
173+
add(keccak256(0x00, 0x20), add(colIndex, 1))
174+
)
175+
mstore(0x00, hash)
176+
mstore(0x20, sibling)
177+
hash := keccak256(0x00, 0x40)
178+
}
179+
}
180+
181+
_set(nodes, rowIndex + 1, colIndex >> 1, rootIndex, hash);
182+
}
183+
}
184+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.0;
4+
5+
import { IncrementalMerkleTree } from './IncrementalMerkleTree.sol';
6+
7+
import 'hardhat/console.sol';
8+
9+
contract IncrementalMerkleTreeMock {
10+
using IncrementalMerkleTree for IncrementalMerkleTree.Tree;
11+
12+
IncrementalMerkleTree.Tree private tree;
13+
14+
function size() external view returns (uint256) {
15+
return tree.size();
16+
}
17+
18+
function height() external view returns (uint256) {
19+
return tree.height();
20+
}
21+
22+
function root() external view returns (bytes32) {
23+
return tree.root();
24+
}
25+
26+
function at(uint256 index) external view returns (bytes32) {
27+
return tree.at(index);
28+
}
29+
30+
function push(bytes32 hash) external {
31+
tree.push(hash);
32+
}
33+
34+
function pop() external {
35+
tree.pop();
36+
}
37+
38+
function set(uint256 index, bytes32 hash) external {
39+
tree.set(index, hash);
40+
}
41+
}

0 commit comments

Comments
 (0)