-
Notifications
You must be signed in to change notification settings - Fork 57
/
IncrementalBinaryTree.sol
183 lines (154 loc) · 6.36 KB
/
IncrementalBinaryTree.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {PoseidonT3} from "./Hashes.sol";
// Each incremental tree has certain properties and data that will
// be used to add new leaves.
struct IncrementalTreeData {
uint8 depth; // Depth of the tree (levels - 1).
uint256 root; // Root hash of the tree.
uint256 numberOfLeaves; // Number of leaves of the tree.
mapping(uint256 => uint256) zeroes; // Zero hashes used for empty nodes (level -> zero hash).
// The nodes of the subtrees used in the last addition of a leaf (level -> [left node, right node]).
mapping(uint256 => uint256[2]) lastSubtrees; // Caching these values is essential to efficient appends.
}
/// @title Incremental binary Merkle tree.
/// @dev The incremental tree allows to calculate the root hash each time a leaf is added, ensuring
/// the integrity of the tree.
library IncrementalBinaryTree {
uint8 internal constant MAX_DEPTH = 32;
uint256 internal constant SNARK_SCALAR_FIELD =
21888242871839275222246405745257275088548364400416034343698204186575808495617;
/// @dev Initializes a tree.
/// @param self: Tree data.
/// @param depth: Depth of the tree.
/// @param zero: Zero value to be used.
function init(
IncrementalTreeData storage self,
uint8 depth,
uint256 zero
) public {
require(zero < SNARK_SCALAR_FIELD, "IncrementalBinaryTree: leaf must be < SNARK_SCALAR_FIELD");
require(depth > 0 && depth <= MAX_DEPTH, "IncrementalBinaryTree: tree depth must be between 1 and 32");
self.depth = depth;
for (uint8 i = 0; i < depth; i++) {
self.zeroes[i] = zero;
zero = PoseidonT3.poseidon([zero, zero]);
}
self.root = zero;
}
/// @dev Inserts a leaf in the tree.
/// @param self: Tree data.
/// @param leaf: Leaf to be inserted.
function insert(IncrementalTreeData storage self, uint256 leaf) public {
require(leaf < SNARK_SCALAR_FIELD, "IncrementalBinaryTree: leaf must be < SNARK_SCALAR_FIELD");
require(self.numberOfLeaves < 2**self.depth, "IncrementalBinaryTree: tree is full");
uint256 index = self.numberOfLeaves;
uint256 hash = leaf;
for (uint8 i = 0; i < self.depth; i++) {
if (index % 2 == 0) {
self.lastSubtrees[i] = [hash, self.zeroes[i]];
} else {
self.lastSubtrees[i][1] = hash;
}
hash = PoseidonT3.poseidon(self.lastSubtrees[i]);
index /= 2;
}
self.root = hash;
self.numberOfLeaves += 1;
}
/// @dev Updates a leaf in the tree.
/// @param self: Tree data.
/// @param leaf: Leaf to be updated.
/// @param newLeaf: New leaf.
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
/// @param proofPathIndices: Path of the proof of membership.
function update(
IncrementalTreeData storage self,
uint256 leaf,
uint256 newLeaf,
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
) public {
require(
verify(self, leaf, proofSiblings, proofPathIndices),
"IncrementalBinaryTree: leaf is not part of the tree"
);
uint256 hash = newLeaf;
for (uint8 i = 0; i < self.depth; i++) {
if (proofPathIndices[i] == 0) {
if (proofSiblings[i] == self.lastSubtrees[i][1]) {
self.lastSubtrees[i][0] = hash;
}
hash = PoseidonT3.poseidon([hash, proofSiblings[i]]);
} else {
if (proofSiblings[i] == self.lastSubtrees[i][0]) {
self.lastSubtrees[i][1] = hash;
}
hash = PoseidonT3.poseidon([proofSiblings[i], hash]);
}
}
self.root = hash;
}
/// @dev Removes a leaf from the tree.
/// @param self: Tree data.
/// @param leaf: Leaf to be removed.
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
/// @param proofPathIndices: Path of the proof of membership.
function remove(
IncrementalTreeData storage self,
uint256 leaf,
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
) public {
require(
verify(self, leaf, proofSiblings, proofPathIndices),
"IncrementalBinaryTree: leaf is not part of the tree"
);
uint256 hash = self.zeroes[0];
for (uint8 i = 0; i < self.depth; i++) {
if (proofPathIndices[i] == 0) {
if (proofSiblings[i] == self.lastSubtrees[i][1]) {
self.lastSubtrees[i][0] = hash;
}
hash = PoseidonT3.poseidon([hash, proofSiblings[i]]);
} else {
if (proofSiblings[i] == self.lastSubtrees[i][0]) {
self.lastSubtrees[i][1] = hash;
}
hash = PoseidonT3.poseidon([proofSiblings[i], hash]);
}
}
self.root = hash;
}
/// @dev Verify if the path is correct and the leaf is part of the tree.
/// @param self: Tree data.
/// @param leaf: Leaf to be removed.
/// @param proofSiblings: Array of the sibling nodes of the proof of membership.
/// @param proofPathIndices: Path of the proof of membership.
/// @return True or false.
function verify(
IncrementalTreeData storage self,
uint256 leaf,
uint256[] calldata proofSiblings,
uint8[] calldata proofPathIndices
) private view returns (bool) {
require(leaf < SNARK_SCALAR_FIELD, "IncrementalBinaryTree: leaf must be < SNARK_SCALAR_FIELD");
require(
proofPathIndices.length == self.depth && proofSiblings.length == self.depth,
"IncrementalBinaryTree: length of path is not correct"
);
uint256 hash = leaf;
for (uint8 i = 0; i < self.depth; i++) {
require(
proofSiblings[i] < SNARK_SCALAR_FIELD,
"IncrementalBinaryTree: sibling node must be < SNARK_SCALAR_FIELD"
);
if (proofPathIndices[i] == 0) {
hash = PoseidonT3.poseidon([hash, proofSiblings[i]]);
} else {
hash = PoseidonT3.poseidon([proofSiblings[i], hash]);
}
}
return hash == self.root;
}
}