-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMerkleTreeWithHistory.sol
147 lines (123 loc) · 4.47 KB
/
MerkleTreeWithHistory.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
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;
interface IHasher {
function poseidon(bytes32[2] calldata leftRight)
external
pure
returns (bytes32);
}
contract MerkleTreeWithHistory {
uint256 public constant FIELD_SIZE =
21888242871839275222246405745257275088548364400416034343698204186575808495617;
uint256 public constant ZERO_VALUE =
21663839004416932945382355908790599225266501822907911457504978515578255421292; // = keccak256("tornado") % FIELD_SIZE
IHasher public hasher;
uint32 public immutable levels;
// the following variables are made public for easier testing and debugging and
// are not supposed to be accessed in regular code
bytes32[] public filledSubtrees;
bytes32[] public zeros;
uint32 public currentRootIndex = 0;
uint32 public nextIndex = 0;
uint32 public constant ROOT_HISTORY_SIZE = 100;
bytes32[ROOT_HISTORY_SIZE] public roots;
constructor(uint32 _treeLevels, address _hasher) {
require(_treeLevels > 0, "_treeLevels should be greater than zero");
require(_treeLevels < 32, "_treeLevels should be less than 32");
hasher = IHasher(_hasher);
levels = _treeLevels;
bytes32 currentZero = bytes32(ZERO_VALUE);
zeros.push(currentZero);
filledSubtrees.push(currentZero);
for (uint32 i = 1; i < _treeLevels; i++) {
currentZero = hashLeftRight(currentZero, currentZero);
zeros.push(currentZero);
filledSubtrees.push(currentZero);
}
roots[0] = hashLeftRight(currentZero, currentZero);
}
/**
@dev Hash 2 tree leaves, returns poseidon(_left, _right)
*/
function hashLeftRight(bytes32 _left, bytes32 _right)
public
view
returns (bytes32)
{
require(
uint256(_left) < FIELD_SIZE,
"_left should be inside the field"
);
require(
uint256(_right) < FIELD_SIZE,
"_right should be inside the field"
);
bytes32[2] memory leftright = [_left, _right];
return hasher.poseidon(leftright);
}
function _insert(bytes32 _leaf) internal returns (uint32 index) {
uint32 currentIndex = nextIndex;
require(
currentIndex != uint32(2)**levels,
"Merkle tree is full. No more leafs can be added"
);
nextIndex += 1;
bytes32 currentLevelHash = _leaf;
bytes32 left;
bytes32 right;
for (uint32 i = 0; i < levels; i++) {
if (currentIndex % 2 == 0) {
left = currentLevelHash;
right = zeros[i];
filledSubtrees[i] = currentLevelHash;
} else {
left = filledSubtrees[i];
right = currentLevelHash;
}
currentLevelHash = hashLeftRight(left, right);
currentIndex /= 2;
}
currentRootIndex = (currentRootIndex + 1) % ROOT_HISTORY_SIZE;
roots[currentRootIndex] = currentLevelHash;
return nextIndex - 1;
}
/**
@dev Updates one of the leaves in the merkle tree. The validity of the update
must be performed by the caller.
@param currentPath The current merkle path of the leaf that needs to be updated
@param updatedPath The updated merkle path of the leaf that needs to be updated
*/
function _update(bytes32[] memory currentPath, bytes32[] memory updatedPath) internal {
currentRootIndex = (currentRootIndex + 1) % ROOT_HISTORY_SIZE;
roots[currentRootIndex] = updatedPath[levels];
for (uint i = levels - 1; i > 0; i--) {
if (currentPath[i] == filledSubtrees[i]) {
filledSubtrees[i] = updatedPath[i];
} else {
return;
}
}
if (currentPath[0] == filledSubtrees[0]) {
filledSubtrees[0] = updatedPath[0];
}
}
/**
@dev Whether the root is present in the root history
*/
function isKnownRoot(bytes32 _root) public view returns (bool) {
if (_root == 0) return false;
uint32 i = currentRootIndex;
do {
if (_root == roots[i]) return true;
if (i == 0) i = ROOT_HISTORY_SIZE;
i--;
} while (i != currentRootIndex);
return false;
}
/**
@dev Returns the last root
*/
function getLastRoot() public view returns (bytes32) {
return roots[currentRootIndex];
}
}