blobstream-contracts 0.0.1-security → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of blobstream-contracts might be problematic. Click here for more details.
- package/.codecov.yml +51 -0
- package/.github/CODEOWNERS +7 -0
- package/.github/dependabot.yml +18 -0
- package/.github/workflows/code-analysis.yml +41 -0
- package/.github/workflows/contract-inheritance-check.yml +28 -0
- package/.github/workflows/go-check.yml +25 -0
- package/.github/workflows/labels.yml +19 -0
- package/.github/workflows/lint.yml +37 -0
- package/.github/workflows/tests.yml +72 -0
- package/.gitmodules +12 -0
- package/.golangci.yml +64 -0
- package/.markdownlint.yaml +5 -0
- package/.markdownlint.yml +4 -0
- package/.markdownlintignore +1 -0
- package/.prettierrc.json +11 -0
- package/LICENSE +201 -0
- package/Makefile +18 -0
- package/README.md +102 -5
- package/docs/inclusion-proofs.md +69 -0
- package/foundry.toml +4 -0
- package/go.mod +34 -0
- package/go.sum +212 -0
- package/hardhat.config.ts +46 -0
- package/index.js +40 -0
- package/package.json +29 -3
- package/remappings.txt +6 -0
- package/scripts/Dockerfile_Environment +39 -0
- package/scripts/deploy.ts +12 -0
- package/scripts/gen.sh +34 -0
- package/scripts/upgradability_check.sh +22 -0
- package/slither.config.json +3 -0
- package/src/Blobstream.sol +366 -0
- package/src/Constants.sol +10 -0
- package/src/DataRootTuple.sol +15 -0
- package/src/IDAOracle.sol +18 -0
- package/src/lib/tree/Constants.sol +23 -0
- package/src/lib/tree/Types.sol +37 -0
- package/src/lib/tree/Utils.sol +106 -0
- package/src/lib/tree/binary/BinaryMerkleMultiproof.sol +12 -0
- package/src/lib/tree/binary/BinaryMerkleProof.sol +12 -0
- package/src/lib/tree/binary/BinaryMerkleTree.sol +256 -0
- package/src/lib/tree/binary/TreeHasher.sol +23 -0
- package/src/lib/tree/binary/test/BinaryMerkleTree.t.sol +365 -0
- package/src/lib/tree/binary/test/TreeHasher.t.sol +40 -0
- package/src/lib/tree/namespace/NamespaceMerkleMultiproof.sol +14 -0
- package/src/lib/tree/namespace/NamespaceMerkleProof.sol +14 -0
- package/src/lib/tree/namespace/NamespaceMerkleTree.sol +306 -0
- package/src/lib/tree/namespace/NamespaceNode.sol +23 -0
- package/src/lib/tree/namespace/TreeHasher.sol +69 -0
- package/src/lib/tree/namespace/test/NamespaceMerkleMultiproof.t.sol +108 -0
- package/src/lib/tree/namespace/test/NamespaceMerkleTree.t.sol +644 -0
- package/src/lib/tree/namespace/test/TreeHasher.t.sol +66 -0
- package/src/lib/tree/test/Utils.t.sol +48 -0
- package/src/lib/tree/test/blob.dat +0 -0
- package/src/lib/tree/test/header.dat +0 -0
- package/src/lib/tree/test/proofs.json +1 -0
- package/src/lib/verifier/DAVerifier.sol +328 -0
- package/src/lib/verifier/test/DAVerifier.t.sol +396 -0
- package/src/lib/verifier/test/RollupInclusionProofs.t.sol +589 -0
- package/src/test/Blobstream.t.sol +200 -0
- package/src/test/BlobstreamBenchmark.t.sol +137 -0
- package/tsconfig.json +11 -0
- package/wrappers/Blobstream.sol/wrapper.go +1325 -0
- package/wrappers/ERC1967Proxy.sol/wrapper.go +668 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
import "ds-test/test.sol";
|
5
|
+
|
6
|
+
import "../TreeHasher.sol";
|
7
|
+
|
8
|
+
contract TreeHasherTest is DSTest {
|
9
|
+
function setUp() external {}
|
10
|
+
|
11
|
+
function testLeafDigestEmpty() external {
|
12
|
+
bytes32 expected = 0x6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d;
|
13
|
+
bytes memory data;
|
14
|
+
bytes32 digest = leafDigest(data);
|
15
|
+
assertEq(digest, expected);
|
16
|
+
}
|
17
|
+
|
18
|
+
function testLeafDigestSome() external {
|
19
|
+
bytes32 expected = 0x48c90c8ae24688d6bef5d48a30c2cc8b6754335a8db21793cc0a8e3bed321729;
|
20
|
+
bytes memory data = hex"deadbeef";
|
21
|
+
bytes32 digest = leafDigest(data);
|
22
|
+
assertEq(digest, expected);
|
23
|
+
}
|
24
|
+
|
25
|
+
function testNodeDigestEmptyChildren() external {
|
26
|
+
bytes32 expected = 0xfe43d66afa4a9a5c4f9c9da89f4ffb52635c8f342e7ffb731d68e36c5982072a;
|
27
|
+
bytes32 left = 0x6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d;
|
28
|
+
bytes32 right = 0x6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d;
|
29
|
+
bytes32 digest = nodeDigest(left, right);
|
30
|
+
assertEq(digest, expected);
|
31
|
+
}
|
32
|
+
|
33
|
+
function testNodeDigestSomeChildren() external {
|
34
|
+
bytes32 expected = 0x62343bba7c4d6259f0d4863cdf476f1c0ac1b9fbe9244723a9b8b5c8aae72c38;
|
35
|
+
bytes32 left = 0xdb55da3fc3098e9c42311c6013304ff36b19ef73d12ea932054b5ad51df4f49d;
|
36
|
+
bytes32 right = 0xc75cb66ae28d8ebc6eded002c28a8ba0d06d3a78c6b5cbf9b2ade051f0775ac4;
|
37
|
+
bytes32 digest = nodeDigest(left, right);
|
38
|
+
assertEq(digest, expected);
|
39
|
+
}
|
40
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
import "./NamespaceNode.sol";
|
5
|
+
|
6
|
+
/// @notice Namespace Merkle Tree Multiproof structure. Proves multiple leaves.
|
7
|
+
struct NamespaceMerkleMultiproof {
|
8
|
+
// The (included) beginning key of the leaves to verify.
|
9
|
+
uint256 beginKey;
|
10
|
+
// The (excluded) ending key of the leaves to verify.
|
11
|
+
uint256 endKey;
|
12
|
+
// List of side nodes to verify and calculate tree.
|
13
|
+
NamespaceNode[] sideNodes;
|
14
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
import "./NamespaceNode.sol";
|
5
|
+
|
6
|
+
/// @notice Namespace Merkle Tree Proof structure.
|
7
|
+
struct NamespaceMerkleProof {
|
8
|
+
// List of side nodes to verify and calculate tree.
|
9
|
+
NamespaceNode[] sideNodes;
|
10
|
+
// The key of the leaf to verify.
|
11
|
+
uint256 key;
|
12
|
+
// The number of leaves in the tree
|
13
|
+
uint256 numLeaves;
|
14
|
+
}
|
@@ -0,0 +1,306 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
import "../Constants.sol";
|
5
|
+
import "../Types.sol";
|
6
|
+
import "../Utils.sol";
|
7
|
+
import "./NamespaceMerkleProof.sol";
|
8
|
+
import "./NamespaceMerkleMultiproof.sol";
|
9
|
+
import "./NamespaceNode.sol";
|
10
|
+
import "./TreeHasher.sol";
|
11
|
+
|
12
|
+
/// @title Namespace Merkle Tree.
|
13
|
+
library NamespaceMerkleTree {
|
14
|
+
/// @notice Verify if element exists in Merkle tree, given data, proof, and root.
|
15
|
+
/// @param root The root of the tree in which the given leaf is verified.
|
16
|
+
/// @param proof Namespace Merkle proof for the leaf.
|
17
|
+
/// @param namespace Namespace of the leaf.
|
18
|
+
/// @param data The data of the leaf to verify.
|
19
|
+
/// @return `true` if the proof is valid, `false` otherwise.
|
20
|
+
/// @dev proof.numLeaves is necessary to determine height of subtree containing the data to prove.
|
21
|
+
function verify(
|
22
|
+
NamespaceNode memory root,
|
23
|
+
NamespaceMerkleProof memory proof,
|
24
|
+
Namespace memory namespace,
|
25
|
+
bytes memory data
|
26
|
+
) internal pure returns (bool) {
|
27
|
+
// A sibling at height 1 is created by getting the leafDigest of the original data.
|
28
|
+
NamespaceNode memory node = leafDigest(namespace, data);
|
29
|
+
|
30
|
+
// Since we're verifying a leaf, height parameter is 1.
|
31
|
+
return verifyInner(root, proof, node, 1);
|
32
|
+
}
|
33
|
+
|
34
|
+
/// @notice Verify if inner node exists in Merkle tree, given node, proof, and root.
|
35
|
+
/// @param root The root of the tree in which the given leaf is verified.
|
36
|
+
/// @param proof Namespace Merkle proof for the leaf.
|
37
|
+
/// proof.key is any key in the subtree rooted at the inner node.
|
38
|
+
/// @param node The inner node to verify.
|
39
|
+
/// @param startingHeight Starting height of the proof.
|
40
|
+
/// @return `true` if the proof is valid, `false` otherwise.
|
41
|
+
/// @dev proof.numLeaves is necessary to determine height of subtree containing the data to prove.
|
42
|
+
function verifyInner(
|
43
|
+
NamespaceNode memory root,
|
44
|
+
NamespaceMerkleProof memory proof,
|
45
|
+
NamespaceNode memory node,
|
46
|
+
uint256 startingHeight
|
47
|
+
) internal pure returns (bool) {
|
48
|
+
// Check starting height is at least 1
|
49
|
+
if (startingHeight < 1) {
|
50
|
+
return false;
|
51
|
+
}
|
52
|
+
uint256 heightOffset = startingHeight - 1;
|
53
|
+
|
54
|
+
// Check proof is correct length for the key it is proving
|
55
|
+
if (proof.numLeaves <= 1) {
|
56
|
+
if (proof.sideNodes.length != 0) {
|
57
|
+
return false;
|
58
|
+
}
|
59
|
+
} else if (proof.sideNodes.length + heightOffset != pathLengthFromKey(proof.key, proof.numLeaves)) {
|
60
|
+
return false;
|
61
|
+
}
|
62
|
+
|
63
|
+
// Check key is in tree
|
64
|
+
if (proof.key >= proof.numLeaves) {
|
65
|
+
return false;
|
66
|
+
}
|
67
|
+
// Handle case where proof is empty: i.e, only one leaf exists, so verify hash(data) is root
|
68
|
+
if (proof.sideNodes.length == 0) {
|
69
|
+
if (proof.numLeaves == 1) {
|
70
|
+
return namespaceNodeEquals(root, node);
|
71
|
+
} else {
|
72
|
+
return false;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
// The case where inner node is actually the root of a tree with more than one node is not relevant
|
77
|
+
// to our use case, since the only case where an inner node is the root of the tree is when the tree
|
78
|
+
// has only one inner node. So, there is no need to handle that case.
|
79
|
+
|
80
|
+
uint256 height = startingHeight;
|
81
|
+
uint256 stableEnd = proof.key;
|
82
|
+
|
83
|
+
// While the current subtree (of height 'height') is complete, determine
|
84
|
+
// the position of the next sibling using the complete subtree algorithm.
|
85
|
+
// 'stableEnd' tells us the ending index of the last full subtree. It gets
|
86
|
+
// initialized to 'key' because the first full subtree was the
|
87
|
+
// subtree of height 1, created above (and had an ending index of
|
88
|
+
// 'key').
|
89
|
+
|
90
|
+
while (true) {
|
91
|
+
// Determine if the subtree is complete. This is accomplished by
|
92
|
+
// rounding down the key to the nearest 1 << 'height', adding 1
|
93
|
+
// << 'height', and comparing the result to the number of leaves in the
|
94
|
+
// Merkle tree.
|
95
|
+
|
96
|
+
uint256 subTreeStartIndex = (proof.key / (1 << height)) * (1 << height);
|
97
|
+
uint256 subTreeEndIndex = subTreeStartIndex + (1 << height) - 1;
|
98
|
+
|
99
|
+
// If the Merkle tree does not have a leaf at index
|
100
|
+
// 'subTreeEndIndex', then the subtree of the current height is not
|
101
|
+
// a complete subtree.
|
102
|
+
if (subTreeEndIndex >= proof.numLeaves) {
|
103
|
+
break;
|
104
|
+
}
|
105
|
+
stableEnd = subTreeEndIndex;
|
106
|
+
|
107
|
+
// Determine if the key is in the first or the second half of
|
108
|
+
// the subtree.
|
109
|
+
if (proof.sideNodes.length + heightOffset <= height - 1) {
|
110
|
+
return false;
|
111
|
+
}
|
112
|
+
if (proof.key - subTreeStartIndex < (1 << (height - 1))) {
|
113
|
+
node = nodeDigest(node, proof.sideNodes[height - heightOffset - 1]);
|
114
|
+
} else {
|
115
|
+
node = nodeDigest(proof.sideNodes[height - heightOffset - 1], node);
|
116
|
+
}
|
117
|
+
|
118
|
+
height += 1;
|
119
|
+
}
|
120
|
+
|
121
|
+
// Determine if the next hash belongs to an orphan that was elevated. This
|
122
|
+
// is the case IFF 'stableEnd' (the last index of the largest full subtree)
|
123
|
+
// is equal to the number of leaves in the Merkle tree.
|
124
|
+
if (stableEnd != proof.numLeaves - 1) {
|
125
|
+
if (proof.sideNodes.length <= height - heightOffset - 1) {
|
126
|
+
return false;
|
127
|
+
}
|
128
|
+
node = nodeDigest(node, proof.sideNodes[height - heightOffset - 1]);
|
129
|
+
height += 1;
|
130
|
+
}
|
131
|
+
// All remaining elements in the proof set will belong to a left sibling.
|
132
|
+
while (height - heightOffset - 1 < proof.sideNodes.length) {
|
133
|
+
node = nodeDigest(proof.sideNodes[height - heightOffset - 1], node);
|
134
|
+
height += 1;
|
135
|
+
}
|
136
|
+
|
137
|
+
return namespaceNodeEquals(root, node);
|
138
|
+
}
|
139
|
+
|
140
|
+
/// @notice Verify if contiguous elements exists in Merkle tree, given leaves, mutliproof, and root.
|
141
|
+
/// @param root The root of the tree in which the given leaves are verified.
|
142
|
+
/// @param proof Namespace Merkle multiproof for the leaves.
|
143
|
+
/// @param namespace Namespace of the leaves. All leaves must have the same namespace.
|
144
|
+
/// @param data The leaves to verify. Note: leaf data must be the _entire_ share (including namespace prefixing).
|
145
|
+
/// @return `true` if the proof is valid, `false` otherwise.
|
146
|
+
function verifyMulti(
|
147
|
+
NamespaceNode memory root,
|
148
|
+
NamespaceMerkleMultiproof memory proof,
|
149
|
+
Namespace memory namespace,
|
150
|
+
bytes[] memory data
|
151
|
+
) internal pure returns (bool) {
|
152
|
+
// Hash all the leaves to get leaf nodes.
|
153
|
+
NamespaceNode[] memory nodes = new NamespaceNode[](data.length);
|
154
|
+
for (uint256 i = 0; i < data.length; ++i) {
|
155
|
+
nodes[i] = leafDigest(namespace, data[i]);
|
156
|
+
}
|
157
|
+
|
158
|
+
// Verify inclusion of leaf nodes.
|
159
|
+
return verifyMultiHashes(root, proof, nodes);
|
160
|
+
}
|
161
|
+
|
162
|
+
/// @notice Verify if contiguous leaf hashes exists in Merkle tree, given leaf nodes, multiproof, and root.
|
163
|
+
/// @param root The root of the tree in which the given leaf nodes are verified.
|
164
|
+
/// @param proof Namespace Merkle multiproof for the leaves.
|
165
|
+
/// @param leafNodes The leaf nodes to verify.
|
166
|
+
/// @return `true` if the proof is valid, `false` otherwise.
|
167
|
+
function verifyMultiHashes(
|
168
|
+
NamespaceNode memory root,
|
169
|
+
NamespaceMerkleMultiproof memory proof,
|
170
|
+
NamespaceNode[] memory leafNodes
|
171
|
+
) internal pure returns (bool) {
|
172
|
+
uint256 leafIndex = 0;
|
173
|
+
NamespaceNode[] memory leftSubtrees = new NamespaceNode[](proof.sideNodes.length);
|
174
|
+
|
175
|
+
for (uint256 i = 0; leafIndex != proof.beginKey && i < proof.sideNodes.length; ++i) {
|
176
|
+
uint256 subtreeSize = _nextSubtreeSize(leafIndex, proof.beginKey);
|
177
|
+
leftSubtrees[i] = proof.sideNodes[i];
|
178
|
+
leafIndex += subtreeSize;
|
179
|
+
}
|
180
|
+
|
181
|
+
// estimate the leaf size of the subtree containing the proof range
|
182
|
+
uint256 proofRangeSubtreeEstimate = _getSplitPoint(proof.endKey) * 2;
|
183
|
+
if (proofRangeSubtreeEstimate < 1) {
|
184
|
+
proofRangeSubtreeEstimate = 1;
|
185
|
+
}
|
186
|
+
|
187
|
+
(NamespaceNode memory rootHash, uint256 proofHead,,) =
|
188
|
+
_computeRoot(proof, leafNodes, 0, proofRangeSubtreeEstimate, 0, 0);
|
189
|
+
for (uint256 i = proofHead; i < proof.sideNodes.length; ++i) {
|
190
|
+
rootHash = nodeDigest(rootHash, proof.sideNodes[i]);
|
191
|
+
}
|
192
|
+
|
193
|
+
return namespaceNodeEquals(rootHash, root);
|
194
|
+
}
|
195
|
+
|
196
|
+
/// @notice Computes the NMT root recursively.
|
197
|
+
/// @param proof Namespace Merkle multiproof for the leaves.
|
198
|
+
/// @param leafNodes Leaf nodes for which inclusion is proven.
|
199
|
+
/// @param begin Begin index, inclusive.
|
200
|
+
/// @param end End index, exclusive.
|
201
|
+
/// @param headProof Internal detail: head of proof sidenodes array. Used for recursion. Set to `0` on first call.
|
202
|
+
/// @param headLeaves Internal detail: head of leaves array. Used for recursion. Set to `0` on first call.
|
203
|
+
/// @return _ Subtree root.
|
204
|
+
/// @return _ New proof sidenodes array head. Used for recursion.
|
205
|
+
/// @return _ New leaves array head. Used for recursion.
|
206
|
+
/// @return _ If the subtree root is "nil."
|
207
|
+
function _computeRoot(
|
208
|
+
NamespaceMerkleMultiproof memory proof,
|
209
|
+
NamespaceNode[] memory leafNodes,
|
210
|
+
uint256 begin,
|
211
|
+
uint256 end,
|
212
|
+
uint256 headProof,
|
213
|
+
uint256 headLeaves
|
214
|
+
) private pure returns (NamespaceNode memory, uint256, uint256, bool) {
|
215
|
+
// reached a leaf
|
216
|
+
if (end - begin == 1) {
|
217
|
+
// if current range overlaps with proof range, pop and return a leaf
|
218
|
+
if (proof.beginKey <= begin && begin < proof.endKey) {
|
219
|
+
// Note: second return value is guaranteed to be `false` by
|
220
|
+
// construction.
|
221
|
+
return _popLeavesIfNonEmpty(leafNodes, headLeaves, leafNodes.length, headProof);
|
222
|
+
}
|
223
|
+
|
224
|
+
// if current range does not overlap with proof range,
|
225
|
+
// pop and return a proof node (leaf) if present,
|
226
|
+
// else return nil because leaf doesn't exist
|
227
|
+
return _popProofIfNonEmpty(proof.sideNodes, headProof, end, headLeaves);
|
228
|
+
}
|
229
|
+
|
230
|
+
// if current range does not overlap with proof range,
|
231
|
+
// pop and return a proof node if present,
|
232
|
+
// else return nil because subtree doesn't exist
|
233
|
+
if (end <= proof.beginKey || begin >= proof.endKey) {
|
234
|
+
return _popProofIfNonEmpty(proof.sideNodes, headProof, end, headLeaves);
|
235
|
+
}
|
236
|
+
|
237
|
+
// Recursively get left and right subtree
|
238
|
+
uint256 k = _getSplitPoint(end - begin);
|
239
|
+
(NamespaceNode memory left, uint256 newHeadProofLeft, uint256 newHeadLeavesLeft,) =
|
240
|
+
_computeRoot(proof, leafNodes, begin, begin + k, headProof, headLeaves);
|
241
|
+
(NamespaceNode memory right, uint256 newHeadProof, uint256 newHeadLeaves, bool rightIsNil) =
|
242
|
+
_computeRoot(proof, leafNodes, begin + k, end, newHeadProofLeft, newHeadLeavesLeft);
|
243
|
+
|
244
|
+
// only right leaf/subtree can be non-existent
|
245
|
+
if (rightIsNil == true) {
|
246
|
+
return (left, newHeadProof, newHeadLeaves, false);
|
247
|
+
}
|
248
|
+
NamespaceNode memory hash = nodeDigest(left, right);
|
249
|
+
return (hash, newHeadProof, newHeadLeaves, false);
|
250
|
+
}
|
251
|
+
|
252
|
+
/// @notice Pop from the leaf nodes array slice if it's not empty.
|
253
|
+
/// @param nodes Entire leaf nodes array.
|
254
|
+
/// @param headLeaves Head of leaf nodes array slice.
|
255
|
+
/// @param end End of leaf nodes array slice.
|
256
|
+
/// @param headProof Used only to return for recursion.
|
257
|
+
/// @return _ Popped node.
|
258
|
+
/// @return _ Head of proof sidenodes array slice (unchanged).
|
259
|
+
/// @return _ New head of leaf nodes array slice.
|
260
|
+
/// @return _ If the popped node is "nil."
|
261
|
+
function _popLeavesIfNonEmpty(NamespaceNode[] memory nodes, uint256 headLeaves, uint256 end, uint256 headProof)
|
262
|
+
private
|
263
|
+
pure
|
264
|
+
returns (NamespaceNode memory, uint256, uint256, bool)
|
265
|
+
{
|
266
|
+
(NamespaceNode memory node, uint256 newHead, bool isNil) = _popIfNonEmpty(nodes, headLeaves, end);
|
267
|
+
return (node, headProof, newHead, isNil);
|
268
|
+
}
|
269
|
+
|
270
|
+
/// @notice Pop from the proof sidenodes array slice if it's not empty.
|
271
|
+
/// @param nodes Entire proof sidenodes array.
|
272
|
+
/// @param headLeaves Head of proof sidenodes array slice.
|
273
|
+
/// @param end End of proof sidenodes array slice.
|
274
|
+
/// @param headProof Used only to return for recursion.
|
275
|
+
/// @return _ Popped node.
|
276
|
+
/// @return _ New head of proof sidenodes array slice.
|
277
|
+
/// @return _ Head of proof sidenodes array slice (unchanged).
|
278
|
+
/// @return _ If the popped node is "nil."
|
279
|
+
function _popProofIfNonEmpty(NamespaceNode[] memory nodes, uint256 headProof, uint256 end, uint256 headLeaves)
|
280
|
+
private
|
281
|
+
pure
|
282
|
+
returns (NamespaceNode memory, uint256, uint256, bool)
|
283
|
+
{
|
284
|
+
(NamespaceNode memory node, uint256 newHead, bool isNil) = _popIfNonEmpty(nodes, headProof, end);
|
285
|
+
return (node, newHead, headLeaves, isNil);
|
286
|
+
}
|
287
|
+
|
288
|
+
/// @notice Pop from an array slice if it's not empty.
|
289
|
+
/// @param nodes Entire array.
|
290
|
+
/// @param head Head of array slice.
|
291
|
+
/// @param end End of array slice.
|
292
|
+
/// @return _ Popped node.
|
293
|
+
/// @return _ New head of array slice.
|
294
|
+
/// @return _ If the popped node is "nil."
|
295
|
+
function _popIfNonEmpty(NamespaceNode[] memory nodes, uint256 head, uint256 end)
|
296
|
+
private
|
297
|
+
pure
|
298
|
+
returns (NamespaceNode memory, uint256, bool)
|
299
|
+
{
|
300
|
+
if (nodes.length == 0 || head >= nodes.length || head >= end) {
|
301
|
+
NamespaceNode memory node;
|
302
|
+
return (node, head, true);
|
303
|
+
}
|
304
|
+
return (nodes[head], head + 1, false);
|
305
|
+
}
|
306
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
import "../Types.sol";
|
5
|
+
|
6
|
+
/// @notice Namespace Merkle Tree node.
|
7
|
+
struct NamespaceNode {
|
8
|
+
// Minimum namespace.
|
9
|
+
Namespace min;
|
10
|
+
// Maximum namespace.
|
11
|
+
Namespace max;
|
12
|
+
// Node value.
|
13
|
+
bytes32 digest;
|
14
|
+
}
|
15
|
+
|
16
|
+
/// @notice Compares two `NamespaceNode`s.
|
17
|
+
/// @param first First node.
|
18
|
+
/// @param second Second node.
|
19
|
+
/// @return `true` is equal, `false otherwise.
|
20
|
+
// solhint-disable-next-line func-visibility
|
21
|
+
function namespaceNodeEquals(NamespaceNode memory first, NamespaceNode memory second) pure returns (bool) {
|
22
|
+
return first.min.equalTo(second.min) && first.max.equalTo(second.max) && (first.digest == second.digest);
|
23
|
+
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
import "../Constants.sol";
|
5
|
+
import "../Types.sol";
|
6
|
+
import "./NamespaceNode.sol";
|
7
|
+
|
8
|
+
/// @notice Get the minimum namespace.
|
9
|
+
// solhint-disable-next-line func-visibility
|
10
|
+
function namespaceMin(Namespace memory l, Namespace memory r) pure returns (Namespace memory) {
|
11
|
+
if (l.lessThan(r)) {
|
12
|
+
return l;
|
13
|
+
} else {
|
14
|
+
return r;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
/// @notice Get the maximum namespace.
|
19
|
+
// solhint-disable-next-line func-visibility
|
20
|
+
function namespaceMax(Namespace memory l, Namespace memory r) pure returns (Namespace memory) {
|
21
|
+
if (l.greaterThan(r)) {
|
22
|
+
return l;
|
23
|
+
} else {
|
24
|
+
return r;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
/// @notice Hash a leaf node.
|
29
|
+
/// @param namespace Namespace of the leaf.
|
30
|
+
/// @param data Raw data of the leaf.
|
31
|
+
/// @dev More details in https://github.com/celestiaorg/celestia-specs/blob/master/src/specs/data_structures.md#namespace-merkle-tree
|
32
|
+
// solhint-disable-next-line func-visibility
|
33
|
+
function leafDigest(Namespace memory namespace, bytes memory data) pure returns (NamespaceNode memory) {
|
34
|
+
bytes32 digest = sha256(abi.encodePacked(Constants.LEAF_PREFIX, namespace.toBytes(), data));
|
35
|
+
NamespaceNode memory node = NamespaceNode(namespace, namespace, digest);
|
36
|
+
return node;
|
37
|
+
}
|
38
|
+
|
39
|
+
/// @notice Hash an internal node.
|
40
|
+
/// @param l Left child.
|
41
|
+
/// @param r Right child.
|
42
|
+
/// @dev More details in https://github.com/celestiaorg/celestia-specs/blob/master/src/specs/data_structures.md#namespace-merkle-tree
|
43
|
+
// solhint-disable-next-line func-visibility
|
44
|
+
function nodeDigest(NamespaceNode memory l, NamespaceNode memory r) pure returns (NamespaceNode memory) {
|
45
|
+
Namespace memory min = namespaceMin(l.min, r.min);
|
46
|
+
Namespace memory max;
|
47
|
+
if (l.min.equalTo(PARITY_SHARE_NAMESPACE())) {
|
48
|
+
max = PARITY_SHARE_NAMESPACE();
|
49
|
+
} else if (r.min.equalTo(PARITY_SHARE_NAMESPACE())) {
|
50
|
+
max = l.max;
|
51
|
+
} else {
|
52
|
+
max = namespaceMax(l.max, r.max);
|
53
|
+
}
|
54
|
+
|
55
|
+
bytes32 digest = sha256(
|
56
|
+
abi.encodePacked(
|
57
|
+
Constants.NODE_PREFIX,
|
58
|
+
l.min.toBytes(),
|
59
|
+
l.max.toBytes(),
|
60
|
+
l.digest,
|
61
|
+
r.min.toBytes(),
|
62
|
+
r.max.toBytes(),
|
63
|
+
r.digest
|
64
|
+
)
|
65
|
+
);
|
66
|
+
|
67
|
+
NamespaceNode memory node = NamespaceNode(min, max, digest);
|
68
|
+
return node;
|
69
|
+
}
|
@@ -0,0 +1,108 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.22;
|
3
|
+
|
4
|
+
import "ds-test/test.sol";
|
5
|
+
|
6
|
+
import "../../Types.sol";
|
7
|
+
import "../NamespaceNode.sol";
|
8
|
+
import "../NamespaceMerkleMultiproof.sol";
|
9
|
+
import "../NamespaceMerkleTree.sol";
|
10
|
+
|
11
|
+
/**
|
12
|
+
* TEST VECTORS
|
13
|
+
*
|
14
|
+
* Data blocks: namespace, data
|
15
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x01
|
16
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x02
|
17
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x03
|
18
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x04
|
19
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x05
|
20
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x06
|
21
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x07
|
22
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x08
|
23
|
+
*
|
24
|
+
* Leaf nodes: min namespace, max namespace, data
|
25
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x0000000000000000000000000000000000000000000000000000000010 0xfdb4e3c872666aa9869a1d46c8a5a0e735becdf17c62b9c3ccf4258449475bda
|
26
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x0000000000000000000000000000000000000000000000000000000010 0x01a346b5c14a1b37e6c019eaff190f7a49718fb3036ec51360ee31de6ef58771
|
27
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x0000000000000000000000000000000000000000000000000000000010 0x80cb31e074d15b09950610d26b9447d82a4c9beb04499fb51be9549c1a67f09f
|
28
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x0000000000000000000000000000000000000000000000000000000010 0xc350aeddd5ada629057034f15d4545065213a7a28f9f9b77bdc71c4225145920
|
29
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x1617cc7010feae70f9ff07028da463c65ec19b1d6bafde31c7543718025e5efb
|
30
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x671157a4e268f7060abbdc4b48f091589555a0775a2694e6899833ec98fdb296
|
31
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x2669e36b48e95bd9903300e50c27c53984fc439f6235fade08e3f14e78a42aac
|
32
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x655790e24d376e9556a3cba9908a5d97f27faa050806ecfcb481861a83240bd5
|
33
|
+
*
|
34
|
+
* Inner nodes(depth = 2): min namespace, max namespace, data
|
35
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x0000000000000000000000000000000000000000000000000000000010 0x0ba8a1c0dcf8798d617eeed351a350d4d68792b6c42e9beaf54dd30136ca7e38
|
36
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x0000000000000000000000000000000000000000000000000000000010 0x6d43651bd68866cb3fc8d00512fa2ab570da16c2c5254a6a7671c0400b96441a
|
37
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x055a3ea75c438d752aeabbba94ed8fac93e0b32321256a65fde176dba14f5186
|
38
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x1b79ffd74644e8c287fe5f1dd70bc8ea02738697cebf2810ffb2dc5157485c40
|
39
|
+
*
|
40
|
+
* Inner nodes(depth = 1): min namespace, max namespace, data
|
41
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x0000000000000000000000000000000000000000000000000000000010 0x23fcbabf97fa3bbef73038559ca480d0de5237762e42cac08090c48713eef910
|
42
|
+
* 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 0x5aa3e7ea31995fdd38f41015275229b290a8ee4810521db766ad457b9a8373d6
|
43
|
+
*
|
44
|
+
* Root node: min namespace, max namespace, data
|
45
|
+
* 0x0000000000000000000000000000000000000000000000000000000010 0x0000000000000000000000000000000000000000000000000000000010 0x5b3328b03a538d627db78668034089cb395f63d05b24fdf99558d36fe991d268
|
46
|
+
*
|
47
|
+
*/
|
48
|
+
contract NamespaceMerkleMultiproofTest is DSTest {
|
49
|
+
function setUp() external {}
|
50
|
+
|
51
|
+
function assertEqNamespaceNode(NamespaceNode memory first, NamespaceNode memory second) internal {
|
52
|
+
assertTrue(first.min.equalTo(second.min));
|
53
|
+
assertTrue(first.max.equalTo(second.max));
|
54
|
+
assertEq(first.digest, second.digest);
|
55
|
+
}
|
56
|
+
|
57
|
+
/// @notice Verify inclusion of leaves 0 and 1.
|
58
|
+
function testVerifyMulti01() external {
|
59
|
+
Namespace memory nid = Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010);
|
60
|
+
NamespaceNode memory root = NamespaceNode(
|
61
|
+
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
|
62
|
+
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
|
63
|
+
0x5b3328b03a538d627db78668034089cb395f63d05b24fdf99558d36fe991d268
|
64
|
+
);
|
65
|
+
NamespaceNode[] memory sideNodes = new NamespaceNode[](3);
|
66
|
+
sideNodes[0] = NamespaceNode(
|
67
|
+
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
|
68
|
+
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
|
69
|
+
0xfdb4e3c872666aa9869a1d46c8a5a0e735becdf17c62b9c3ccf4258449475bda
|
70
|
+
);
|
71
|
+
sideNodes[1] = NamespaceNode(
|
72
|
+
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
|
73
|
+
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
|
74
|
+
0xc350aeddd5ada629057034f15d4545065213a7a28f9f9b77bdc71c4225145920
|
75
|
+
);
|
76
|
+
sideNodes[2] = NamespaceNode(
|
77
|
+
PARITY_SHARE_NAMESPACE(),
|
78
|
+
PARITY_SHARE_NAMESPACE(),
|
79
|
+
0x5aa3e7ea31995fdd38f41015275229b290a8ee4810521db766ad457b9a8373d6
|
80
|
+
);
|
81
|
+
|
82
|
+
uint256 beginKey = 1;
|
83
|
+
uint256 endKey = 3;
|
84
|
+
NamespaceMerkleMultiproof memory proof = NamespaceMerkleMultiproof(beginKey, endKey, sideNodes);
|
85
|
+
bytes[] memory data = new bytes[](2);
|
86
|
+
data[0] = hex"02";
|
87
|
+
data[1] = hex"03";
|
88
|
+
bool isValid = NamespaceMerkleTree.verifyMulti(root, proof, nid, data);
|
89
|
+
assertTrue(isValid);
|
90
|
+
}
|
91
|
+
|
92
|
+
function testLoadFromBytes() external {
|
93
|
+
// the bytes were generated here https://github.com/S1nus/hyperchain-da/blob/main/src/clients/celestia/evm_types.rs#L132
|
94
|
+
bytes memory proofData =
|
95
|
+
hex"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000e2c251c19c0cd38681c6263a7bbbb27bfe727fb71bebe4b68f75c275dade4550ff00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000ff00000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000039af53e89275fe860e67ef0cc55ad18a936a7f623c8897e541f20bcce166491f";
|
96
|
+
NamespaceMerkleMultiproof memory proof = abi.decode(proofData, (NamespaceMerkleMultiproof));
|
97
|
+
bytes memory rootData =
|
98
|
+
hex"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010203040500000000746e218305fe3dbbef65feceed939fe8dd93c88b06c95473fbe344fb864060f3";
|
99
|
+
NamespaceNode memory root = abi.decode(rootData, (NamespaceNode));
|
100
|
+
bytes memory namespaceData =
|
101
|
+
hex"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010203040500000000";
|
102
|
+
Namespace memory ns = abi.decode(namespaceData, (Namespace));
|
103
|
+
bytes memory sharesData =
|
104
|
+
hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001800000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000074000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000b800000000000000000000000000000000000000000000000000000000000000da00000000000000000000000000000000000000000000000000000000000000fc000000000000000000000000000000000000000000000000000000000000011e00000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000162000000000000000000000000000000000000000000000000000000000000018400000000000000000000000000000000000000000000000000000000000001a600000000000000000000000000000000000000000000000000000000000001c800000000000000000000000000000000000000000000000000000000000001ea000000000000000000000000000000000000000000000000000000000000020c000000000000000000000000000000000000000000000000000000000000022e00000000000000000000000000000000000000000000000000000000000002500000000000000000000000000000000000000000000000000000000000000272000000000000000000000000000000000000000000000000000000000000029400000000000000000000000000000000000000000000000000000000000002b600000000000000000000000000000000000000000000000000000000000002d800000000000000000000000000000000000000000000000000000000000002fa000000000000000000000000000000000000000000000000000000000000031c000000000000000000000000000000000000000000000000000000000000033e000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000102030405010002000062fa597733dd88ed6adf3006884f2cea58ad6d077ed150ccc974518fff9998a3f6b7b73671fa75e1eabe83f8edd3886d57d272c7734421496a9730f1c9b8ce0b5f78423145fd96ccaab713e4414703faecbbc5320d413ada35c7d7adf7b4ec941fdd74af478f51799d11ed3f116c69dea74e4a9d85bcbcec9050a3d276d92a5209d5e35fe51834ae72433ed76084ca94c175db7ac4dd962a6d644b54e0880a1862d778e3ca06bf6ec9af6d6e12d1de967ad3f8d11a971d6f1a35574dc9cba39fcbe69bb4612c23d2e6c4a6ca054057a9f51d3b571879c87007613dece3f6ee6bd9b272d18af4dcd8085bbb2f1a1f9d45331961fc7e2b343ac86b3c251ceebd0dd84efe83d05ce2142a4a412af19294ee14b743b8682fe840e2dbe4c8ea9052867186dcd6b3b0b53dfc6aebfc10550b0b8086439fd2cd9b3f3a8121fe7a9d5a8dd642dbbe30abf261d6be720c3c94295da4d52fd30e352aabf397f1608a89d8d554ea4fe51418ae892f101039226eaf6c8648c86fb1544ea38645521b7580c62207fbdbc9d1300ddcab6c60c327ed46cd35c5feb69c1488a8eeeab081376979514c015a43db477f3b073fbf53c70378132fa3e69cd05239c854050da5c6acf331566c533f541a368f3f586759d9ba77dc503ecfe9dd3bf6721dc2ae7b0a940000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000010203040500e1dbdf5ab90f1aea6f80753449f93bf3867590dfa3411e7585f0aef4667f5613e79ee633c91b8fdcbc6de17ad1f564841aeb4e0a9a7c7707887155ac5ebd1cac1fc48e42622fedb50a48518e996102f902e604b8cc1fa585348771dc1258c67e849ee16fe15249039aae14f74b85072a0a22056071190428154ac0be1eaa9ea37259e834363d37f61e4e642a88e52b2b1bec5f8a49be39f687ddfd44c568df7078c167c67ff278beaf8f678ecb14a2cc54d43781ddf4a292b2419a3b824d141654fd556a57f14e46dfd4a6525f828f2953b0346243b48baead0322f1e24f9d698452ab85511f201dbb8ae518a8acd8f1efa1bf4612f1c2bed7991065a2ad1a68bede1f8eb457f440af82c3d8b15f2d4042e94e805e6637c976368a9554f09c6cca733708addb644e5840c1aeff45ce882089576f0411e060a50f00974e6dca4d148c405f82ec8b26a7db1b0fae1f52494683e33b529550dc31761bc5a4d2ba196bc4feb5de8819670ea97aca651f73055da23490131105510e80a20e4f6aed203a034f47688361b03cf9464d24c68c5c4f3d864314ebc0127c86b78384e634307017f66984479ace45bff1d246d9359689d8c598f3edd37019aaf5b22dce9d7faea6ecf24803c1554750215cdc1f83f61c82458e6b0386862781eca4f81b932ad9f9000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001020304050033254f51051ed24f80e2770d8d5b34fa56a70b9f586461d65d9cea629e514858236e1b2a41b86a500bb8864ad41037df8656e9d6c7b9e13188eafb3c6937069752e3b8a0d97a895bbbe377d9048403b1ba8580935baf6b0228f19f3a8568fc860be08f4da7321bbed8e864a196e9d3aad22f46ddb64376045f590180b801f8d82960e526bc90e3c87008925a9b7c13f8a1efc0677404e3301968e1ff9ff7f014409a0cf0f9ea21ef0e284e37d08c9dbc48effcf9b0c76f6c74d450d0429d431e14734d85fe57415851e1f15ee3ea5a2ba908d319b7164788bc4feb13d623b52b716d5db2b945eaf9c7c6f962d0d043af6090849c67819c9179ef908378121141acb3bb648a5d3ebf67ae76b1bf8c759b54026cd4abfd69a3222c2171727c914bd7ac3250d8011b08e22eed4caff4226b9f6719dec5828981fdca37f7d16cd5bf701dd5a2928da312bb757cb6e42be291e62b58224938fca85cd1be6687cde1a368d71aa207af361695a340d0372cf0706a28811ebbd1376b6bbd83d9277a4f9a8d39d51d410f62280954463e41569b94168bb81deaebf971f4cae8003f861a066aa9878e8a4c730a918f0ef407240adcac6a9792bf8e9708dbb26a49ab43f378c70f1229d1a9a37b411af2d8a016d891896541743d58cf1d5b61f013fda5e05f120c0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000010203040500208ccb01c06235a82b5b8ab66234cde7121d3635453a366fd241fa3ac478cb72ab4862cc9f75d43b455b1e64ea2cb907c76a21bc4f006936140badd70b3550a50096f7520a304b87f89142a0a771c98506ec707cc80e370565fbf6c9ba9b8ff7e4fca91ac66c68a4aa0fa892dfe09a7c754dd794d620d1b407d671b7c94774c0eaea7eff78cef8b8a9a9f53a70e98924c8b748931417d3d6a13e83fd388e76aa50537384857d0017b6c7df551a05368b9382e86232273e39bb9468d5d3618a3bbe7ce0ab4fdccef39b9e6372d5b641efbe6f210b968a12304283b6d2b137319a4270889174d068c37933fed088afa0b3a17825cf0e617b25ffb77c2898bd24271d452569848c3e7f2980e3d700243c98ca5ef274ae9aaca4e8e42a68dba4db948e1b1b84f7909379d6c1cae58caf0fd239f8e69d6a84b2ae87d3f1dd620374bd573ed6537a714ecae80f5fa6c952342b886af7faff333fe585939d7cb30598b65343aadf1e7e9e4890baceacd5338c0447a49ce38b46035dab6610e1941dec75e7fa63aaeaff0875ed491c23740816c3ab183553e64951edb6e144308b0aa0c50cb2fbdafefda7fc918ac6dbbaa2f708f52d3500d6d6e08921d2a4faebcbfb0c0c842697f5dc4cfec5a2da204968bcfc0640a19567c601246b7485ddfc2248d188aa0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000010203040500486488afba74dfb7cabd301ef887b9a9a5bc5af2f7bd67dbb4e6a798e521435b8d153fd3e0e59f41422e1b2feb439a03fd367371709a5d5d4b3bbecf0803fcab8628dbdd9fb7f95476593b573f7a0b44d0c9eb220d6d1094f4da7b4f1ac5aa7a114bda242e521e8806848d640fcad27424840dc39b34f95175b43ca8e34f2d3c052ff307f9051e8fe513bdc035769d3f20a9721b627898244759313893bf86381244467e73619eff78998add86f08ca61fc2a15637b456c5aee0c9096d141c293df9b6d270a80849dbed876f8e8d85d5cea65613e8b34f764a7327da4339c1bdd86c86f9bc9e209376b2f935b242f703876efb0e73a73f4efcbf5e6d3d7df2a61eba4657c4f47248510652636cb708b6e1ac8a08ba3697dc5a9a8f27278f7aeb494a112f9aa4f3db916c5b08d91b2313d721b2b500c5f8af40b199a2f9e1825f598589a9d68b5e7824a71901d1d639c7c5a9e12ea2fa7f533cd008eafaaf3dbe349a03360e276a9cd76edb32e1a8bede0d93fefdb9d09d57d4df061eb7117d62b9b57a8db4d73d796fad5d86985548fda567041c2a86d2489a80e2d7edc0a4933feb37f6f4098e92ceb48fa3c5f2e1ebd43785a82204ca82ccbadb7c0821611d029017f76705f4bddf0c489533dcd4939731a84ff2e3f5641a11c35e9e5510c7d5470000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000010203040500c36175fe339267ece45eb5e30bee1873cf9dcb3758d4e7671e89938ec873566e439722fdc479ce9353379747030709c031b16b66821ea0e5e2e3febbe96e06ae1fda90521ea7af7b97decd8b0d8cf185a9e9bd43c34ddf266560a747f8575491f42c30dc8e652858be905aa7cc3f603076acfa6f32151b6ba5c72f809e1499ceb7b5c390bf489d5df5d3d1833259c8c2f5dbf1490dbe7a9d069c5e58ca875abe61069ec90be96e3a2bb66522ebb97652358a4cb5b1d161518ce5802e7341c2f3226111d93228eef2df4df68807cd9b42a46ddc7a037df7af23a38400be941b199b5115acc48fc83a841e81ddbf0b0800dbd15148c454a5b6fc2c798e175ad19410a7b2fd1fcbe93ad727d48c7033a3ee13b0c042f33148035e3b948eba0a2038223752931270a06ae0bdb801f00d2e5b77a13b79d9b91d9dc39b77d2989661019c58f16f2516c7e965c566ca65537c0afcb4b9399cff7534cc94d376082504055195c32cfc3b6c898b846b43e1e9e3e701c2f4d4476dc719afa3c23b7af9cf2a8d34d9739b552e4cb362e22c34c572064f3dbf4565fb3c9714dc843bd98a2bcda004ec95a15a953c470dc6c49451d7781aa4e13330d83145c3cb67fdd2aaaeb9cf91ce9e99e26be711dd8d07f86481aa11046ea6ce5ff6b1029d5d72fe7af1f396e30000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000010203040500bccdca79e8adbe5eefa150f90d0cb8928e72c4a9a13c469c9215d0e8610ebd625c05c593d180800ba820cf614e850197983f41f2e7e569e84db094c59fd48fc7009e974bdfc52de20a299e5b70770793839bc214a26cc823e83b0c27a7366c818ca7686b2848fa80fdab0d380b2e79bba555c8936142e16fa2a87f2ce1ffe3371798b5ec4d33f1bdb930008119a093a7971ff6f01717d7200bd21ca2c5b43774d79dfed4f92a2a1db9a8043bb05c415648b49d730f0b9f5605e3fd274d486d2145737d6b67896168a63b831d1320a683274e688d46dac0fa58e9caceec37264eb38b033173065ccc05f4c0ca63ad207f513bbf4b6044842f537529043cb80d49254e285e3fb2b00b83efcd34232596b40d7f8202ede0728432639077a426cd0b187221c430bc2d0279049672772f6f08a1ad42115fb6159bd98841c7ccbc6496fefbd81d8221f4353bef986084687d12509ed063e9ae3c4eff51883bbe03bf7ff2e37769025dddc54802936fe9dc9b7df017bfab2c38e6b5134ee5291cd6a49ce9d994304ea3ea6e166790518fdadb03a777a5f343b503737d051227d5b71272bd941d7ed7806780d574b994ed7a4f67af3a11d319d3187e8ac9c7b47f42f04f91d4ec8780c047c1d9e5e82af395ca35a2db13d78781b09ca250c8a30fdc824b93a300000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000102030405004c631aaa4c78f1dffcdd5487999f66208701a4e9ac005cab03055caffc413c39efc9a82f7ab8f777e9dbc1308a886800e6d1bc5dd4bfb3a755d4fc85cdf003ba2e8ff541e1c05457b0c8e36baa7b9f1c01a3622da6e3314bfc65151d638fef51b50719cbe7ec1b1dae20bbb4afe7e0555de22fdc97792ef5da51e2a318ed632b0a31289fb7e6900bc0649b9d8ea811dcb6e88d2a7487a67b45c48ca50555e672de6633f45cfaeabb9fd1b069a8d39850dc4194a714658412574f94fdab932a2a4576808fcf96f3916f8c45b31a25be93e2bac65b96467739ef83c26ed298f5cfa19b0ee9aa11d24cbed978a47f766a2ed67bba65ac39928fd2e84d9f5a1890e945c063c1cd25330067bb1d70e24141734c0223f972894385982f2d049b238138f3c3cc96907d9f2b37fa62e2cf17bdeaa9a660781040bbb44c266369dc5351cd821c9c38c88bece22a184522688a5a6d854b6f718b41cc5232acf4e1db0d8c58c3742b994efa4b281f9ded618f3f2fd74d3fe850a715498b1a12c1ce549f0060ae72bcb5a5b7d43dda1b81f5c82f02d56914fc1e525ce4ccc1ac4535b95e6826060d54e62a9748cd16f083620e57d9bd3ec71f326cf907b282582826eda0d38296bc51d37f0824d4a78b52c2ddcda69e67d997cb7ab90637fb505db0c1512adf6f5700000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000102030405002d835ed2c10e86f32f0de72110f20814ebe06908efb7c0677eae662556779e8a5a2b1178e793222ccfb29521c2ef5f9c9925a110c801f772bb66cf59681d57fde2706f4e407d88044d9182732394cf6c4508751c0b944a9c881e48d68e0d76dea46d656023bc9cf46521ade1d0c5475ffa3bef39b9c6abf32db2681a18fe9a34d26380f68fb70ac861ad1759e12e8288f5a3a5a30aded1c85c48811aff446a3d8ee1fe48571b8fca7129f1ccd07a2e66126195e4a92633bdd343c37baca1b4a787a91548e1726bd0bd60f94411fc5cdc08695704c4156c5a4a69d0bbfd8a26e5645571aa38f95d0158c812a9f67ed9902af7221e4244d103eedbd7bc6053006c4f3d62533f15a3ac34e0982885f843aef4560c59a3576935288b7ed2a8eb0cb6399f1a9a2e606ad7837dd800a0c84bccf5c1a8cc88b72d91b2fbad94d8e2133705a6180d04400e88a2cc4ddf329349b9fdcc8f54427b176f16eb66e887e0bd56f5ce4e68d3f74d4559e1c083de61217e13439e310d5fbdbcbf4d9a02d379f6a8a762a104812d955bde12a0f7eda719cb9bf3810724ed3ea8bb337650fd6a7c6897e54bb129f5e816c96373298ef31696720554f4a44a481be6b2c8271e7e5e868a3e9c2590c0b37374daee305d7a2992dc031a1364a305ce14ceb79eaff0aef6f8ab000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001020304050066adfb5723be1269b7227f672bd574980fd4fc2a9035e8085bfd07bd39ac18fb247b245138296935c1daffa1da37b6d5fc1fd9dbff8ec743d016030030faf261351758bc0842d0d38e0d00d693b5d1670633a5139a425f27e5ad95f78ca4c9b6585c3857a57066fb78eb645d26cfec4a8ec14dcb39ce140a98577563330a7206ae46bf1f5b1d05690ad68e6ba80afe57edc228964df22059863de5de5b6e82ebd0f3fa4eb078958b6fa028469b50ef7df652b033cafe4a45c809c14c50004c470c1f4977caef2d763adec7c82202feea12aed25a1f8e693e3e45d033caf76b9a25e7651572e9d36cf4fb8919f891294c31e1af489037bca414f87063198441415695e3d528f8f9a5de9a238aa6fc937ce32e4f20da1c87a530a81fc8a2366dbaaa0f635a1eefbdd23fd733137d67a3173d29a66040ab21d26c9696ba96ad96672f14359def8c4f58b74a682e7a6e3f8e6e497432fcfb620aa26896c8d5ec62efcdd24a234c3c0bd68f8d9169ebb8a1fe6e63e1f1178a3e95403441e858112b53b536d409578356629c0b70d62b5cea2080a49b1d96de3eb73427dcb4c3f74eb9255558e4b20ca697d080a3836eb3edd7a5934342d8cbad30e97d3df1ba986cb59b5944c44ca3ae64343e197964a452a4e1877827d42182c95c7bc9d9f6a23ffa158a00000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000102030405004e4dd270eea9d9d291d629caa483e2740ee038c593bd1fbae59d3f74d0dd602680a067b2854b6e88e3309c12f769554b3de9646f5e6bacc23f3c7292a8156c81397087e2ef818728ea00dde59443438645d1b5d5fea1c09246056d77b923577ce9ee89371c8a9a63ffcbd3751e8293cbe0a6723b88cc0473a4b36943b42e82d5b74de7934faddfc3a7ea560ecfb5876342526face485e93bd9ce0dfb7b784972b9af6f23a6b5d2928470090d285e570f9203c30969fa27b35363a3c343f299625b484e3e7c3e0c288474a378e27a039d5503717f61f53eaff44ba0f7a1fa34a88b86be4fdd1cdb56b8922dcbb0a13fd1cfdbe48ade7ecf405e22b3a22784941803eec57b14c268d257605e79b6cc437fdb439287c8216fc00dea6764e8885613f6d1dd87d4d0cea7c4a260c62f6da4eff1032b2d89856fc590de2418e5fdaab74d05e9fa2a69c5f3909447e6e861d036cb4b40def7def1d31f268155e115ee4774f92edab2b77b4f66d89476ec1133564681f02768b1fe85776aed2f7b33c0c013195172f719fa6ac53455fc62de418050337b8b544dd643ecd6a2d6a93221509697e9c7063133fa3857c9ed61bdb88dcba6a3a3149fabac150732b90dfb2d355c6f51b1dc2920a44fd7334de40ff7f575074c4ee3ad3233893c9c4a4b1751a90678000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001020304050044c5b8a64edbbf02acd324a68c9af5eddc673ca22e2b0a6c0999c4a65d0cbb7b7e895c1fd27f0dd7e02e7a3d7de089f9f872b76ae8542c7aab980cccde33cc6b1e264d2811cd0b4fc3fc0b3ae44309c64f94b426b651aec612d9086f7a2d8485cffadc66a8460f4e509de43deffd809d4a22ec9de9aeec048bbe40130770c526dfd29c51ca76200c0396a768bc19c8e6e5cabfca95f7fb4b5f8c3edc02a7f5fed744b120ae5efc5ed61ba43b40b6247d3b07f002537b9a892b7d7f9b3d9ad02ab63fcce000a506de1ac0332a701cbac430b7af70b2f348d626b474b3fdb18a3664405266ce9ea6f4475bf65361745526649779f56fa64f9941ac19ffb5408775b2b46fb189871024f117a5c5a458a3547e13ebc0c0fa23542bda4723ff7069209e244be7980d73203b757e9c24d1c19cbfefa301e8cff1fe05c0c6d3f569a0e92ab4d7a39e67cabb1162f30e24f8136a592963c6119bec8383967328ce13555239c8fe17a82b133ef7735baaf816362cdfae15976a410c5dc422034ce27442f603295ff67672558a0d08ab96af8636ede79a9b86e5cc3a86451c4156531ef3d70caf526f3bf0bab7cb9398dc42305bd9a3eddadeadadff75c9cd77b22ebf8fb5b54619da88aa7bd80b41b197736c82fb3f95ae5f64fdedb0d7c9d3a066aac28c30e3000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001020304050047e4ab7c5a7379e8c8c64861e77901bba82c1daccaee980b4398a5b1a757edf414b96cd7837dc41dd1355d95ef24f0734e50ade6eb40a8ea87d3195066aeec85547ecb791a3b4ca31c1dc5d79963bd893b3ca4ebcd35a1da708643e56c9b29581a99e78a2d636af3aa2e41eca349fe8ab4404800fbc2eb017afd8305abac58f6d0e6078d94563ec26621a210c0d959d06ebbd0029a0f7271b47082434768c34c49b9156506a3142d9e365c2589932e714c85dd086343c7d3717fc7309a4a1d50a7e461484b96932c8de7f1e247c313b8cdd70e5dc6e26e1773ebca6cc4d3af15e2ffea0ec07832a693b0dda6f9d642e275d26223f0ad55a5559ef1405bc39c35907c0b6c005d5cb4d86363d5171513ee7c3f8179879407c291c8edd2bbaaaafb0c6f322ccda9333121635e4c7293a721e0403b0c538d4f7225ab83610b34b34dfecdd72c39ad961dfdba5ab967db8568d2e7db3f095bab10e769e3d5c9081c9915d529b296ec6dbd506368e2e41b02c857e369214760aeb9f98aa89d0cc61385a3a6031ffbf9f54f1bf8e7cca3af97bb88bd32aa8e747832bb00b86b69919d2b126bb0e7624ae0614e7c652ec10882125a35fdfaf2e5fcccd2dfb2981dd0b3ebbe2c2a94b3348b677be48794c8f6dee236dcf30cba2bd32bf1a223c500d1a70672a50000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000010203040500e0f6b3728d5fac458602fa049286db8508d3498c2b1a23d5230887496ec8e39c3929bc2c875218545f0cf20fb125d52a6581011cc3f3e10a3bdfbc6fce8704859d775a0712ae17ab64d96bbe92fa748518971b518194f47ea7212dd506a3d1054c0a645b72bcefdbec6c576956a496735ead5b4d9d34efe4edbd652a3ac1b67c02467bfcee6d69fbc45bfdbfd305b930005e3a824cf2c8116ba6301a6cc0881e7515eeca8100ff03dc1cb5dc6062e57d11aa8442b941ca8d17d3ecdd62e8c5aa017964913db37b67a7e14dd9324001884ba53c2a789e66f161733a65ef7457fd548b19545e442eeb8599f542453d53c78794855397ccf92d14745aab810320ebf75a87c6bc959a2766fbbd8c95190c7b026601fa29dce1bf7ba4bb01cc33cc0509e8abb79b0c9b133ee88bdd2dd302823711e2b5c86d33c608bd0b6fa7d20dfded25b38f441d540be89295c3f32f6f9110a297de36ece45370ccb2ab232514eac6ab944bf740169fbfd795092a3e6e102c5621c7b3773d27df3de05d09034f56151dde534c91aadb948fa5cabbfa743a72e38af3d99072e904196060ab43706518d19afa7bef64e2c45b181649592ff2c7e28b169ff319db33c78e9d22dc78dfc9a2ccd559c0879edc259002902850b1905ed540a731e07c89b63890cad5f5e68e1100000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000102030405006e5d993d7b2e9169dd24bedac1f2234dfb834ffeeb194e4c0d66849fbb7e7f12e7abcc1cf78e5dff3eea04b6668f8f3c0502f1274256fd062ab717968a7350b985c645e8afe830d1fdebd1f1ec05ef4f1bce8970c66f8d7dec488a27b7b4a47c03d1002c16db5df035f0536ef396f5a8349535db914887acad2c81e797cf5165cf898e7b96ee2322d6955dca1966e3664500733e8669b67b9c2508ed3ac3dfd1acd1ba70fe0ebff8853c4ddceaa05846bf4a1f8152beeffadb3716d66e4d2454fa625c73df5e2f8a9e8e345a70232b4ccb63b4b7d1244b278b6d99fa7914acb762ee9e1ae83d21ddc43cced2ecfcf7379498bcb0f998005b6c0232cc617e1e3f71f5a815bf8498c10ad10ca6f22750ba8e386ee3849ed03bb41c7279b1ea5cb06cb7bd96c04bc678590b1fcc9e0026e5ffe19f5f1dff0fa21e0982d21018f67df952443f7601fd5ab1e0d70962e3f827bc7ef3020d33dd5021ec4037c93fd3fac65dd606fa1871c8b37398d7923ea53cd9920c7b5c8219e44c63b6699a7754dd83eb89c3778f3fad7ecf807a9f55160cea3980762b2551ca6dcfa5b0c1b866640bc257fcf183dcb5a93e30629c80f67fc2cc10d10d7f0df4c03342536448e0600aeb9eed8280ef49edcdbd555113b49437f7b316585fcd4b3300d39af20b3d78c0cf000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001020304050063eeb0957f01c454106495d7b26e867584a25ddae259f6928be73356ab406881bfd6f7d5d2bca8c7a895e5566ccaa07d0443a4897d5bdc6e907a876b1b18e0f4ab608ac53fe2fffabc5af4360bfc01a9e47e61ae3ecd6af632a62271f76e8d36ea68c2332cdf306de37c363d5286fc471fe79a27f154ef81ace5c6d82a6dd0d2d448ab92fcdadfcb51e724df3eb62fbe0230fc6c241523eb84d79a7be0932de25ff878b87b7ba3062a944a844fb23d6520871386526129a3ba7e68d3a7fa3e5615028c6e7b3e0aa6b2eddb9674969df79eda1642ee5fd38a12945ef4b43756a70d3fe494ae3cb55267084260c467a28fc4d2723f75d6419f9798e06d0c3c99bd558151e1a6b9866b0c865ddf3a4ce5e80fb137f4c8786be8ca678f842ddf75d493f2fccb2b2f3ec7e719c04347599b03d63a417bedbfd284f5bc3c92d9348a7e49e98f31fa6e538a36d5ed83e2cf0de8d63ee55dd0664441db752d23f596dcedb768403c1ec1cc1ee87e3eb4cc935cb4b7bd49d9fb89f66f99a5e573a9d24084019a8d881b78ac86352edb64c750d534fad7c745272d5db6d420caa86748e0186130d949abf5365095bf9c6fbed81a90e2c092c7d63c5ea8a4f7a5e1eaa84fe68f4e81c71c57123b55549992949e673ae1aaa4e34a12057ebc83355dd8ef2f9116fd00000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000102030405008c73508d68bd43b3ad768f06ac477742e7f095406a7e2d075141af3aa5774006dcbf44a8c768e0b5be4c1fa4e5e367057f6e6f0c9c9c3bb8d94e23a39467b7c4effb2465f561b9202500121a1bc2bdbd2be8ff08ccba62b33d5f159f5349a630b0dcf5d0918a2a9a28db3561cbaf3cba04f67a3ddef69dca4517c39a663e69ba5c825a13bd3ba1173808bf3cdae5d2006675329246e061962fdd9a9652078e587e48bc0ad1703610c8efeee76b9fa06fa5ea34bfb901e1d8bf9455a5d16ba7937381df7a56230a45e5416d4235bb57ef1aaa9091b253309d13a9c8673de5e1bbaca92b641a74df48e869b8ac28730066bbde5c93f9bf0fa98c8baa4498715ad5121624241b6d01f89df218253f09bf1df4c4369e57554478a3b1333199887c52c5c48330af59e4d2c3291bd8fd318453d017d34ab72f66a055aec91a92ec370bf8ce1367dcc95c928cd1e95be28964c57f8c603a2cc1b916ae40a50f0d1899b3f86a26310d5be8f9412b7fcbf7ec69dc0d9a6e00ba4ae0fc09ca72d0fb6ee85662eda292103ff144a4c01538ce8a92c0c837c26fa60e89976f472300d83a7bb0fd78355fce0cac9e83032b93cbab10b749c973422686c14f033c905e6127eb74f1b3f45943864bbed731fca5539ea5564f4d14e5404c4df6f7ad815ea1bc2bcd42ee0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000010203040500f2bcc3a1688f125bf5e7a3b594daca5429ebd530967b9f3071b13d382e03f9826bb66e183a303ebd8cabd5774a592b299bda0c88bd808e8c50a6bf3dac80f4192e43f3a572561e42ad2802cb69b81c8fc25be97a9e921e8614afcf6729fa39e8414d4d0af743df421777a30c31cbd2fe3ae4fea6fa83bfec293d270d0b772b195840bf526f52801d539c6ab9b1c2916b884f373f4ad19920fee6982871d448df7281db9b1a8cf8f93c7d04dc77e5cc8e521bca1e480b18c3d382f948d5455677c841158676eb59ca1e7e563ca0283cf15b6ad3653f3cae4d0b6e437f5a5174f520d8cefb469b95753a4407205e8447120a244f165b5a82dac240e6b35581d018c2ba31b5f52e84c05eb751ff9d4ae74673c81dafb615edf88249d27e2d30741ef14a8b9a21115ef30783930dac58e641f1d94593a859e12aa411b851ec6af21c710527ae2be10beb257d98e0eed8d8cce76653a85d88d5894f98f5e71bed01704b454c1145cb9ef92144c2e1ca2c29c34f40c4e08a74ac2b0720a7120eabc0d4fe0d0d7070d7b3b3c9c5c5d9c6fce21deeb91c1200539ec3de54c88690af0794a73127b97d438e1b81f83912e7f5349902247735d71e97037db50c7820c9714338497a45cf05f9fb6eb751c4c352ff6e63b45cd94d464edd17f70a8786dc0b24dea8000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001020304050078c14e6b4c256b8a3988c1e3d0c306bd5696846352ecf78e81d81aa6b21a2c6b205205ee6288836d85b278d84622a2bf77379b26724ddc8dc9084bf0c08067d2213ab6db1a51f55eba2fed718c690d00418b7d8c3fdc47d25d6c97ec0244190a76b555daf6f2c28e7b1adb8e630bff885fe8998ec803691393f473018d78f191b07134f5dca9c489884baa13d3ed5a0dac703077ac172f0424be161d9c24e0361f4f103a108fce37830025ad626a7c890c657f450359403c2bf398f76a53d5b0a3a745ad8f7211ac4fb16eafd0888eb3be9435dfea0ce9141aec0171b5704a11434d60558af5d9e8358b8cdffb831c68f1f4d688af532ba586e10eb363f8709f4094067e6698f11f539a386083422372405eaca952b0db0a7cbf8c23bfab855140340f677806e435747e85c4ce8e1c42099c011cd81fb3c970efc7007b4e0033d7e0a543e0f4bfc938b2ed1a9bc444395d95961b4d5705237ff1b90a2cf3ededd8d59c59e4f45e1ef496e72f67c8cdb5b67940b3af5734be1d540e90bcc67b290e1b6846707b8a33f16b2bee9a288c16b9d64cdf80d5630e4a1d78e6be774159942bec7fd77c2e8af94c839cecee43f3a3546242936bbe1d9d7f3fe47672a0a6a79630d4f903f9440d7ea532211c9f00e20eb16d3d57f187263d21affcc70ed76edd00000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000102030405005ff3f55296b2e6a090ce3c3f5c9ab0ca005bc742253eec035996288e1eb2f1c14e3a34e223b3678cc7ec3d4b8f1c4aef8c2cee1eec53222e22ab2006f6c2d90d6dd41f54b89e74077a5a3aadb9a514c6661e71531cbc7323c17d38536193b1fbb793df712379319a3d8ea6f2baae04674c6b11a069c610b151b576ca86659144aa7699a28d7524278f7a8dfc65bb20459946f655c582f08008ae4d52e31a4457c6449cd965d1ab7e030105801f0b824038ad1399acdde31d76154260c4af4d63ec7eefdb0ab144655656dc5b35aed239997099e01aefa53bd0bdc2e6ce0ccb893ee96d1f1e5d4d324d6658dae414de9106527b828826a923c5b4e66951bdf0d657c7633a70fe661f0b52085389c800f0816798c3ac856ca4320c727a14e6afc27217bbabab39d7387bc0fd8da393b379a414c9b32c83dadf90f253851350e008828d52be3b0b80439100d0b09a1014311790317f82d9df56d21b009a298db2a3b14b9246931b0b1cd44dcf355b9f2a161ff4ef4e0814f7c5ecbc41ec1b03ed0740256ed92717e107692415250abbf0c5a7b19b14d3ffdc02ef357b0af1d395177bf72d7aa988d8f06c3b3270b23eaed5e69b60926f9f1fbaaacb7d1fd831e38a2d6b66376110a44b7fd3f27bd499358d40f65c5ed16b1ed56735d1102d4fc2d5a78600000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000102030405008eb5cbc3310a5616f80a02faee89ddc3b7c749820b3912a5ba6230c61e06c0b8bbce05aae764a70afa2a8ea9b892625ff7caf5a3a64871a7bb2df875480a8b7a8a29b5dc19d467e523c1bc05ab269621b027bc658ac7b8564150c2ae3f10eae5763272e12e416964098010a5572571226750f7c6679d550477ca912f0788f766414eaa98b3a78f3f2587f50af42567843d8d2d05b0e533c75ab688f2fc8e26fc0ae2039ba31571eefbdd966099b32630cd3832cf631e4e0f2b302efb53568b2dff619799b2e430198fd8e3809120df39dc780760911ac339d3e3ace44adc0c2e686abb84a0a6fedeb05b2639d1e7bd01afff9c6cc65306a90b9f8ee4158f7fe2ef7713a3c0aec2a3054d4f63a6ff34c6cd1b345abbd10a7777377bb4adc2eaacb8c919265e1adf55246f4947142e20ded7eccc62994218670eaadd50f7303ef5ffe0226add1384417670d0ccce654369fe4bedd91592695951f99bf8d46b2579212fb2025171f1e0d24e1855350462737db5ddccd43df5175f88d06fce48834ff9ee5a2855b135067abec190c1fe034445ff2048aed8cc5075edb4188560ac20fd6f951b43d7d8e7cd039503ef45893c68f3e34d18783f6c44c6229638f50c170240b108790df125dcdc3eadd9dc2ac9d10a6aadf3f19c545f6feb0dae501683f3190000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000010203040500207b05e646fe73f9ee01e0a40ee60b6ea3bba9c72bb0d09f98d85a8a5c878d4fe55fae334927e5d92c720f78937a8eae734aee8010d048d79ce7100342d7559706c54a26a6522756a000e380da6f9264946e67e6e7dafa8e161306982ab17e3fff0083680c1e3745e0e36672a1bb381bb7e60e75d626ce4335874ab76f1a47559509ab24eed775a33c83187026574a8dfa471c23f19b035bab1ca66b785a998e6eaaf788885d55d02fd15fdcc6f990cae95378d1b17fa1f2f1b4a47ae14eeae409438ebc00c130c7d097ece498593ddedd4467d13ae3e2c068209c14f3fba52cd4595a046ef063fcfe47fb3524e1f943d5212b9ec28912306ba3377a80c707d11b798c71e6b1c71198301d390fea98760b10dd802855d7da35dd0c8aa700f38dbe467275900b4729259f9aa3fa101896746342d815979cc48b9a6f4e0a1d1ae1f9b9a107b562660875977b5b3f2805a312dcfaadf783d3690761d1d433ad7dfb23a76e935a6024f8560cff9d401fbf07e60206d7b76dc4f93ad1c575a1e5fb9d3a02c4da56524dc4e75bfa08c6dc06dcc2755508221240aeac4237be600cc8471f9210f984dbafab4ddaa40613b777fa1b061fc7656cc2a2b818e9abbc70f6d0e7dbf54e8c123d5fc5827675377f099059a020b659211cd9429a1cd6efea06c149a60000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000010203040500f13ffe4db389b33fe827405c01b891725b0e4ebadd7a5c7c854f2c926f2561ca87925e3d3e79d35e0d6ccf477f45f90cd9a7b163350b434cbdf558ae3689a5fba7006ec081cf24c86b03315e30612aefecebe134ad7bc358c9456ed03076f96017483c8c0e7a170351b95dde4254dfd636828134a702bd9ae58ab6252b2e3c8b345dfcf5f1d7a915a85616d87450db125bc31a6c9766bfdbd15a3f7cc5a9ad71ab78e96b2feec79cabd03434006939e3802895c2fe3495477557155007f929de0e3361208c1d29a31f133e0113a97ff4fefc7544373163d87d8814cfbb61fa92a4f315e589c42afd91122442a051eb094d5a05864c46565874219edb9fbc7be16de221189a9cc3089234f82bf38fc867c701c24cce85cc2254c7c651b99bd2f44982f25887c5558587b84efa1bd0a9faa6937fa894870c55bc3c2a439742477262f92bba140489899419a346d3523162ec77e38ddd075d7c8db60b4294d2ceb558afe85260a351687c57db458690756fe44b2ee2208d64f4367f55cfbc8a09366dec5c81a2d28e27d8e28a9a8cd906e1cfddd1c5656449e8169a94bd7581e257a6b22e5502509b62736325f7bde81e6ccdf8a6975c14dc16993841203370714044d845ad71c05a4cbd326ebfa2a250a0b0a776fc20d7c995dcd3b8ef5e45c8b57a510000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000010203040500eb255b1e81a8d373e217958598cab650d9ea6c5bff43f848a33121de7438fd7dc6147eb50d661722fa318e09fc73e6966e68f28c94b495a4c3eccbb8fa281dfcffea18b0dc085305d9ffd646c077e0759c28230dbfa759588d2a4ef82ce75361fe7b376d263d85633389e202560131ae5e45a2926bd078f05f3817b21554328e63777f300387118fec62f15685fe3e1b545ec5911b3ec4f18b72463749794ce6cc9eb94bb14723a4536d4b7a61ad7d4b700d0e24dce75d812232d34de31e6ac5c448324c95b780d7483066b084be04b387b6ad3a0393dcb673f5eb05aad3104941208288d9319e816ac806b88e125bc1bb47d87e2b1cb0ee4c24272a3d4985afd00167fbd98edf2a23501edfb170e305ac18425ac40c5d0a0caf809f9fbb9aaf216f76747315937f506c576f05e871fa63226b94e4deefc8000664e41982236762686c0da9f277fd68ca750e6ccf3b8a9c0409a784431f6a27cb00e7e539f401f6cc3a3fb159ee8bf635d3327f3d352e6e0f9e0612392a591ffc51b54293fa073e5e177ede1a18f0d4f59ac6c658281892da7551aef389b47562c92c5c0aae6a175e43ba2f2c8ac4e15db1c778a53d0a6728cec2aca6d8afeeb9d5e1637b2cf634bbd804b883d28cf78d0f4e1b0494ae57303227bac4254a42e293737a302af8ae49";
|
105
|
+
bytes[] memory shares = abi.decode(sharesData, (bytes[]));
|
106
|
+
assertTrue(NamespaceMerkleTree.verifyMulti(root, proof, ns, shares));
|
107
|
+
}
|
108
|
+
}
|