blobstream-contracts 0.0.1-security → 3.1.0

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.

Files changed (64) hide show
  1. package/.codecov.yml +51 -0
  2. package/.github/CODEOWNERS +7 -0
  3. package/.github/dependabot.yml +18 -0
  4. package/.github/workflows/code-analysis.yml +41 -0
  5. package/.github/workflows/contract-inheritance-check.yml +28 -0
  6. package/.github/workflows/go-check.yml +25 -0
  7. package/.github/workflows/labels.yml +19 -0
  8. package/.github/workflows/lint.yml +37 -0
  9. package/.github/workflows/tests.yml +72 -0
  10. package/.gitmodules +12 -0
  11. package/.golangci.yml +64 -0
  12. package/.markdownlint.yaml +5 -0
  13. package/.markdownlint.yml +4 -0
  14. package/.markdownlintignore +1 -0
  15. package/.prettierrc.json +11 -0
  16. package/LICENSE +201 -0
  17. package/Makefile +18 -0
  18. package/README.md +102 -5
  19. package/docs/inclusion-proofs.md +69 -0
  20. package/foundry.toml +4 -0
  21. package/go.mod +34 -0
  22. package/go.sum +212 -0
  23. package/hardhat.config.ts +46 -0
  24. package/index.js +40 -0
  25. package/package.json +29 -3
  26. package/remappings.txt +6 -0
  27. package/scripts/Dockerfile_Environment +39 -0
  28. package/scripts/deploy.ts +12 -0
  29. package/scripts/gen.sh +34 -0
  30. package/scripts/upgradability_check.sh +22 -0
  31. package/slither.config.json +3 -0
  32. package/src/Blobstream.sol +366 -0
  33. package/src/Constants.sol +10 -0
  34. package/src/DataRootTuple.sol +15 -0
  35. package/src/IDAOracle.sol +18 -0
  36. package/src/lib/tree/Constants.sol +23 -0
  37. package/src/lib/tree/Types.sol +37 -0
  38. package/src/lib/tree/Utils.sol +106 -0
  39. package/src/lib/tree/binary/BinaryMerkleMultiproof.sol +12 -0
  40. package/src/lib/tree/binary/BinaryMerkleProof.sol +12 -0
  41. package/src/lib/tree/binary/BinaryMerkleTree.sol +256 -0
  42. package/src/lib/tree/binary/TreeHasher.sol +23 -0
  43. package/src/lib/tree/binary/test/BinaryMerkleTree.t.sol +365 -0
  44. package/src/lib/tree/binary/test/TreeHasher.t.sol +40 -0
  45. package/src/lib/tree/namespace/NamespaceMerkleMultiproof.sol +14 -0
  46. package/src/lib/tree/namespace/NamespaceMerkleProof.sol +14 -0
  47. package/src/lib/tree/namespace/NamespaceMerkleTree.sol +306 -0
  48. package/src/lib/tree/namespace/NamespaceNode.sol +23 -0
  49. package/src/lib/tree/namespace/TreeHasher.sol +69 -0
  50. package/src/lib/tree/namespace/test/NamespaceMerkleMultiproof.t.sol +108 -0
  51. package/src/lib/tree/namespace/test/NamespaceMerkleTree.t.sol +644 -0
  52. package/src/lib/tree/namespace/test/TreeHasher.t.sol +66 -0
  53. package/src/lib/tree/test/Utils.t.sol +48 -0
  54. package/src/lib/tree/test/blob.dat +0 -0
  55. package/src/lib/tree/test/header.dat +0 -0
  56. package/src/lib/tree/test/proofs.json +1 -0
  57. package/src/lib/verifier/DAVerifier.sol +328 -0
  58. package/src/lib/verifier/test/DAVerifier.t.sol +396 -0
  59. package/src/lib/verifier/test/RollupInclusionProofs.t.sol +589 -0
  60. package/src/test/Blobstream.t.sol +200 -0
  61. package/src/test/BlobstreamBenchmark.t.sol +137 -0
  62. package/tsconfig.json +11 -0
  63. package/wrappers/Blobstream.sol/wrapper.go +1325 -0
  64. 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
+ }