Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

verifyMultiNamespaced #326

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
1 change: 1 addition & 0 deletions lib/forge-std
Submodule forge-std added at 978ac6
2 changes: 1 addition & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/contracts/
ds-test/=lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/
forge-std/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/src/
forge-std/=lib/forge-std/src/
openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/
openzeppelin-contracts/=lib/openzeppelin-contracts/
28 changes: 28 additions & 0 deletions src/lib/tree/Utils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,31 @@ function _getSplitPoint(uint256 x) pure returns (uint256) {
}
return k;
}

/// @notice Returns the size of the subtree adjacent to `begin` that does
/// not overlap `end`.
/// @param begin Begin index, inclusive.
/// @param end End index, exclusive.
function nextSubtreeSize(uint256 begin, uint256 end) pure returns (uint256) {
uint256 ideal = bitsTrailingZeroes(begin);
uint256 max = _bitsLen(end - begin) - 1;
if (ideal > max) {
return 1 << max;
}
return 1 << ideal;
}

/// @notice Returns the number of trailing zero bits in `x`; the result is
/// 256 for `x` == 0.
/// @param x Number.
function bitsTrailingZeroes(uint256 x) pure returns (uint256) {
uint256 mask = 1;
uint256 count = 0;

while (x != 0 && mask & x == 0) {
count++;
x >>= 1;
}

return count;
}
12 changes: 12 additions & 0 deletions src/lib/tree/binary/BinaryMerkleMultiproof.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;

/// @notice Merkle Tree Proof structure.
struct BinaryMerkleMultiproof {
// List of side nodes to verify and calculate tree.
bytes32[] sideNodes;
// The (included) beginning key of the leaves to verify.
uint256 beginKey;
// The (excluded) ending key of the leaves to verify.
uint256 endKey;
}
136 changes: 136 additions & 0 deletions src/lib/tree/binary/BinaryMerkleTree.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import "../Constants.sol";
import "../Utils.sol";
import "./TreeHasher.sol";
import "./BinaryMerkleProof.sol";
import "./BinaryMerkleMultiproof.sol";
import "../namespace/NamespaceNode.sol";

/// @title Binary Merkle Tree.
library BinaryMerkleTree {
Expand Down Expand Up @@ -77,6 +79,140 @@ library BinaryMerkleTree {
return (computedHash == root, ErrorCodes.NoError);
}

function verifyMulti(bytes32 root, BinaryMerkleMultiproof memory proof, bytes[] memory data)
internal
pure
returns (bool)
{
bytes32[] memory nodes = new bytes32[](data.length);
for (uint256 i = 0; i < data.length; i++) {
nodes[i] = leafDigest(data[i]);
}

return verifyMultiHashes(root, proof, nodes);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for empty data arrays.

The function verifyMulti does not handle the case where the data array is empty, which could lead to unexpected behavior.

+        require(data.length > 0, "Data array cannot be empty");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function verifyMulti(bytes32 root, BinaryMerkleMultiproof memory proof, bytes[] memory data)
internal
pure
returns (bool)
{
bytes32[] memory nodes = new bytes32[](data.length);
for (uint256 i = 0; i < data.length; i++) {
nodes[i] = leafDigest(data[i]);
}
return verifyMultiHashes(root, proof, nodes);
}
function verifyMulti(bytes32 root, BinaryMerkleMultiproof memory proof, bytes[] memory data)
internal
pure
returns (bool)
{
require(data.length > 0, "Data array cannot be empty");
bytes32[] memory nodes = new bytes32[](data.length);
for (uint256 i = 0; i < data.length; i++) {
nodes[i] = leafDigest(data[i]);
}
return verifyMultiHashes(root, proof, nodes);
}


/*
This helps with gas efficiency so we can avoid unnecessary copying and looping through the NamespaceNodes
when we go to verify the row inclusion multiproof
*/
function verifyMultiNamespaced(bytes32 root, BinaryMerkleMultiproof memory proof, NamespaceNode[] memory data)
internal
pure
returns (bool)
{
bytes32[] memory nodes = new bytes32[](data.length);
for (uint256 i = 0; i < data.length; i++) {
// the bytes.concat(...) converts a bytes32 into a bytes
nodes[i] = leafDigest(bytes.concat(data[i].digest));
}

return verifyMultiHashes(root, proof, nodes);
}
Comment on lines +99 to +111
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for empty data arrays.

The function verifyMultiNamespaced does not handle the case where the data array is empty, which could lead to unexpected behavior.

+        require(data.length > 0, "Data array cannot be empty");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function verifyMultiNamespaced(bytes32 root, BinaryMerkleMultiproof memory proof, NamespaceNode[] memory data)
internal
pure
returns (bool)
{
bytes32[] memory nodes = new bytes32[](data.length);
for (uint256 i = 0; i < data.length; i++) {
// the bytes.concat(...) converts a bytes32 into a bytes
nodes[i] = leafDigest(bytes.concat(data[i].digest));
}
return verifyMultiHashes(root, proof, nodes);
}
function verifyMultiNamespaced(bytes32 root, BinaryMerkleMultiproof memory proof, NamespaceNode[] memory data)
internal
pure
returns (bool)
{
require(data.length > 0, "Data array cannot be empty");
bytes32[] memory nodes = new bytes32[](data.length);
for (uint256 i = 0; i < data.length; i++) {
// the bytes.concat(...) converts a bytes32 into a bytes
nodes[i] = leafDigest(bytes.concat(data[i].digest));
}
return verifyMultiHashes(root, proof, nodes);
}


function verifyMultiHashes(bytes32 root, BinaryMerkleMultiproof memory proof, bytes32[] memory leafNodes)
internal
pure
returns (bool)
{
uint256 leafIndex = 0;
bytes32[] memory leftSubtrees = new bytes32[](proof.sideNodes.length);

for (uint256 i = 0; leafIndex != proof.beginKey && i < proof.sideNodes.length; ++i) {
uint256 subtreeSize = nextSubtreeSize(leafIndex, proof.beginKey);
leftSubtrees[i] = proof.sideNodes[i];
leafIndex += subtreeSize;
}

uint256 proofRangeSubtreeEstimate = _getSplitPoint(proof.endKey) * 2;
if (proofRangeSubtreeEstimate < 1) {
proofRangeSubtreeEstimate = 1;
}

(bytes32 rootHash, uint256 proofHead,,) =
_computeRootMulti(proof, leafNodes, 0, proofRangeSubtreeEstimate, 0, 0);
for (uint256 i = proofHead; i < proof.sideNodes.length; ++i) {
rootHash = nodeDigest(rootHash, proof.sideNodes[i]);
}

return (rootHash == root);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for empty leaf nodes.

The function verifyMultiHashes does not handle the case where the leafNodes array is empty, which could lead to unexpected behavior.

+        require(leafNodes.length > 0, "Leaf nodes array cannot be empty");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function verifyMultiHashes(bytes32 root, BinaryMerkleMultiproof memory proof, bytes32[] memory leafNodes)
internal
pure
returns (bool)
{
uint256 leafIndex = 0;
bytes32[] memory leftSubtrees = new bytes32[](proof.sideNodes.length);
for (uint256 i = 0; leafIndex != proof.beginKey && i < proof.sideNodes.length; ++i) {
uint256 subtreeSize = nextSubtreeSize(leafIndex, proof.beginKey);
leftSubtrees[i] = proof.sideNodes[i];
leafIndex += subtreeSize;
}
uint256 proofRangeSubtreeEstimate = _getSplitPoint(proof.endKey) * 2;
if (proofRangeSubtreeEstimate < 1) {
proofRangeSubtreeEstimate = 1;
}
(bytes32 rootHash, uint256 proofHead,,) =
_computeRootMulti(proof, leafNodes, 0, proofRangeSubtreeEstimate, 0, 0);
for (uint256 i = proofHead; i < proof.sideNodes.length; ++i) {
rootHash = nodeDigest(rootHash, proof.sideNodes[i]);
}
return (rootHash == root);
}
function verifyMultiHashes(bytes32 root, BinaryMerkleMultiproof memory proof, bytes32[] memory leafNodes)
internal
pure
returns (bool)
{
require(leafNodes.length > 0, "Leaf nodes array cannot be empty");
uint256 leafIndex = 0;
bytes32[] memory leftSubtrees = new bytes32[](proof.sideNodes.length);
for (uint256 i = 0; leafIndex != proof.beginKey && i < proof.sideNodes.length; ++i) {
uint256 subtreeSize = nextSubtreeSize(leafIndex, proof.beginKey);
leftSubtrees[i] = proof.sideNodes[i];
leafIndex += subtreeSize;
}
uint256 proofRangeSubtreeEstimate = _getSplitPoint(proof.endKey) * 2;
if (proofRangeSubtreeEstimate < 1) {
proofRangeSubtreeEstimate = 1;
}
(bytes32 rootHash, uint256 proofHead,,) =
_computeRootMulti(proof, leafNodes, 0, proofRangeSubtreeEstimate, 0, 0);
for (uint256 i = proofHead; i < proof.sideNodes.length; ++i) {
rootHash = nodeDigest(rootHash, proof.sideNodes[i]);
}
return (rootHash == root);
}


function _computeRootMulti(
BinaryMerkleMultiproof memory proof,
bytes32[] memory leafNodes,
uint256 begin,
uint256 end,
uint256 headProof,
uint256 headLeaves
) private pure returns (bytes32, uint256, uint256, bool) {
// reached a leaf
if (end - begin == 1) {
// if current range overlaps with proof range, pop and return a leaf
if (proof.beginKey <= begin && begin < proof.endKey) {
// Note: second return value is guaranteed to be `false` by
// construction.
return _popLeavesIfNonEmpty(leafNodes, headLeaves, leafNodes.length, headProof);
}

// if current range does not overlap with proof range,
// pop and return a proof node (leaf) if present,
// else return nil because leaf doesn't exist
return _popProofIfNonEmpty(proof.sideNodes, headProof, end, headLeaves);
}

// if current range does not overlap with proof range,
// pop and return a proof node if present,
// else return nil because subtree doesn't exist
if (end <= proof.beginKey || begin >= proof.endKey) {
return _popProofIfNonEmpty(proof.sideNodes, headProof, end, headLeaves);
}

// Recursively get left and right subtree
uint256 k = _getSplitPoint(end - begin);
(bytes32 left, uint256 newHeadProofLeft, uint256 newHeadLeavesLeft,) =
_computeRootMulti(proof, leafNodes, begin, begin + k, headProof, headLeaves);
(bytes32 right, uint256 newHeadProof, uint256 newHeadLeaves, bool rightIsNil) =
_computeRootMulti(proof, leafNodes, begin + k, end, newHeadProofLeft, newHeadLeavesLeft);

// only right leaf/subtree can be non-existent
if (rightIsNil == true) {
return (left, newHeadProof, newHeadLeaves, false);
}
bytes32 hash = nodeDigest(left, right);
return (hash, newHeadProof, newHeadLeaves, false);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error handling for empty proof or leaf nodes.

The function _computeRootMulti does not handle the case where the proof or leafNodes arrays are empty, which could lead to unexpected behavior.

+        require(proof.sideNodes.length > 0, "Proof side nodes array cannot be empty");
+        require(leafNodes.length > 0, "Leaf nodes array cannot be empty");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function _computeRootMulti(
BinaryMerkleMultiproof memory proof,
bytes32[] memory leafNodes,
uint256 begin,
uint256 end,
uint256 headProof,
uint256 headLeaves
) private pure returns (bytes32, uint256, uint256, bool) {
// reached a leaf
if (end - begin == 1) {
// if current range overlaps with proof range, pop and return a leaf
if (proof.beginKey <= begin && begin < proof.endKey) {
// Note: second return value is guaranteed to be `false` by
// construction.
return _popLeavesIfNonEmpty(leafNodes, headLeaves, leafNodes.length, headProof);
}
// if current range does not overlap with proof range,
// pop and return a proof node (leaf) if present,
// else return nil because leaf doesn't exist
return _popProofIfNonEmpty(proof.sideNodes, headProof, end, headLeaves);
}
// if current range does not overlap with proof range,
// pop and return a proof node if present,
// else return nil because subtree doesn't exist
if (end <= proof.beginKey || begin >= proof.endKey) {
return _popProofIfNonEmpty(proof.sideNodes, headProof, end, headLeaves);
}
// Recursively get left and right subtree
uint256 k = _getSplitPoint(end - begin);
(bytes32 left, uint256 newHeadProofLeft, uint256 newHeadLeavesLeft,) =
_computeRootMulti(proof, leafNodes, begin, begin + k, headProof, headLeaves);
(bytes32 right, uint256 newHeadProof, uint256 newHeadLeaves, bool rightIsNil) =
_computeRootMulti(proof, leafNodes, begin + k, end, newHeadProofLeft, newHeadLeavesLeft);
// only right leaf/subtree can be non-existent
if (rightIsNil == true) {
return (left, newHeadProof, newHeadLeaves, false);
}
bytes32 hash = nodeDigest(left, right);
return (hash, newHeadProof, newHeadLeaves, false);
}
function _computeRootMulti(
BinaryMerkleMultiproof memory proof,
bytes32[] memory leafNodes,
uint256 begin,
uint256 end,
uint256 headProof,
uint256 headLeaves
) private pure returns (bytes32, uint256, uint256, bool) {
require(proof.sideNodes.length > 0, "Proof side nodes array cannot be empty");
require(leafNodes.length > 0, "Leaf nodes array cannot be empty");
// reached a leaf
if (end - begin == 1) {
// if current range overlaps with proof range, pop and return a leaf
if (proof.beginKey <= begin && begin < proof.endKey) {
// Note: second return value is guaranteed to be `false` by
// construction.
return _popLeavesIfNonEmpty(leafNodes, headLeaves, leafNodes.length, headProof);
}
// if current range does not overlap with proof range,
// pop and return a proof node (leaf) if present,
// else return nil because leaf doesn't exist
return _popProofIfNonEmpty(proof.sideNodes, headProof, end, headLeaves);
}
// if current range does not overlap with proof range,
// pop and return a proof node if present,
// else return nil because subtree doesn't exist
if (end <= proof.beginKey || begin >= proof.endKey) {
return _popProofIfNonEmpty(proof.sideNodes, headProof, end, headLeaves);
}
// Recursively get left and right subtree
uint256 k = _getSplitPoint(end - begin);
(bytes32 left, uint256 newHeadProofLeft, uint256 newHeadLeavesLeft,) =
_computeRootMulti(proof, leafNodes, begin, begin + k, headProof, headLeaves);
(bytes32 right, uint256 newHeadProof, uint256 newHeadLeaves, bool rightIsNil) =
_computeRootMulti(proof, leafNodes, begin + k, end, newHeadProofLeft, newHeadLeavesLeft);
// only right leaf/subtree can be non-existent
if (rightIsNil == true) {
return (left, newHeadProof, newHeadLeaves, false);
}
bytes32 hash = nodeDigest(left, right);
return (hash, newHeadProof, newHeadLeaves, false);
}


function _popProofIfNonEmpty(bytes32[] memory nodes, uint256 headProof, uint256 end, uint256 headLeaves)
private
pure
returns (bytes32, uint256, uint256, bool)
{
(bytes32 node, uint256 newHead, bool isNil) = _popIfNonEmpty(nodes, headProof, end);
return (node, newHead, headLeaves, isNil);
}

function _popLeavesIfNonEmpty(bytes32[] memory nodes, uint256 headLeaves, uint256 end, uint256 headProof)
private
pure
returns (bytes32, uint256, uint256, bool)
{
(bytes32 node, uint256 newHead, bool isNil) = _popIfNonEmpty(nodes, headLeaves, end);
return (node, headProof, newHead, isNil);
}

function _popIfNonEmpty(bytes32[] memory nodes, uint256 head, uint256 end)
private
pure
returns (bytes32, uint256, bool)
{
if (nodes.length == 0 || head >= nodes.length || head >= end) {
bytes32 node;
return (node, head, true);
}
return (nodes[head], head + 1, false);
}

/// @notice Use the leafHash and innerHashes to get the root merkle hash.
/// If the length of the innerHashes slice isn't exactly correct, the result is nil.
/// Recursive impl.
Expand Down
15 changes: 15 additions & 0 deletions src/lib/tree/binary/test/BinaryMerkleTree.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "forge-std/Vm.sol";

import "../BinaryMerkleProof.sol";
import "../BinaryMerkleTree.sol";
import "../BinaryMerkleMultiproof.sol";

/**
* TEST VECTORS
Expand Down Expand Up @@ -333,4 +334,18 @@ contract BinaryMerkleProofTest is DSTest {
vm.expectRevert("Invalid range: _begin or _end are out of bounds");
BinaryMerkleTree.slice(data, 2, 5);
}

// header.dat, blob.dat, and proofs.json test vectors included in ../../test/ and serialized to hex bytes using Rust
// The hard-coded serialized proofs and data were generated in Rust, with this code
// https://github.com/S1nus/hyperchain-da/blob/main/src/clients/celestia/evm_types.rs#L132
function testMultiproof() public {
bytes memory proofData =
hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000006ce29bcde696f84e35c5626904542a549b080e92603243b34794242473940706917519bf954f5b30495af5c8cdb9983e6319104badc1ea811ed2c421018a3ad7821ea268d3540deab8f9b2024464618610c9a7083620badcf505bda647cc8e9f82bfc87d990d8344f6efd44fcb09b46b87f9a92230d41329452efee8656c6760a9ad9f3a95af971e89e2a80b255bb56d5aae15de69803b52aa5079b33374b16e16178fc62a2f2ce6bf21909c0a0edea9525486e0ece65bff23499342cca38dd62";
BinaryMerkleMultiproof memory multiproof = abi.decode(proofData, (BinaryMerkleMultiproof));
bytes32 dataroot = hex"ef8920d86519bd5f8ce3c802b84fc9b9512483e4d4a5c9608b44af4d6639f7d1";
bytes memory leafData =
hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000102030405746e218305fe3dbbef65feceed939fe8dd93c88b06c95473fbe344fb864060f3000000000000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000102030405000000000000000000000000000000000000000000000000010203040555cd7fb524ae792c9d4bc8946d07209728c533a3e14d4e7c0c95c0b150d0c284000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405505c1e7c897461a152e152f1ff3ecc358fefdf1f69448ab1165b6ca76836933b000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405100a0548893d8eab0322f34f45ac84785cdf50dfab5102a12d958e6031bacebe000000000000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000102030405000000000000000000000000000000000000000000000000010203040566e5eb1da67430f204a3c5615591f71316695c7ec1f1f713cde7e936d4a43ec1000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405d2a5de6299e28c2fec359a2718599f5ac22c2948a71d26a438295e531b6f4cb5000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405688c5238e50c0a8a556bfabff31bef1fa9cdd812c9fd4dcee5c2a0836f687fbf000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405b55a5b1efc2a22cdbfa21d050bd67147ff2b936c68354eb1a83bcdf14eb57e38000000000000000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000010203040500000000000000000000000000000000000000000067480c4a88c4d129947e11c33fa811daa791771e591dd933498d1212d46b8cde9c34c28831b0b532000000000000";
bytes[] memory leaves = abi.decode(leafData, (bytes[]));
assertTrue(BinaryMerkleTree.verifyMulti(dataroot, multiproof, leaves));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add validation for decoded data.

The function testMultiproof does not validate the decoded multiproof and leaves data, which could lead to unexpected behavior.

+        require(multiproof.sideNodes.length > 0, "Multiproof side nodes array cannot be empty");
+        require(leaves.length > 0, "Leaves array cannot be empty");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function testMultiproof() public {
bytes memory proofData =
hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000006ce29bcde696f84e35c5626904542a549b080e92603243b34794242473940706917519bf954f5b30495af5c8cdb9983e6319104badc1ea811ed2c421018a3ad7821ea268d3540deab8f9b2024464618610c9a7083620badcf505bda647cc8e9f82bfc87d990d8344f6efd44fcb09b46b87f9a92230d41329452efee8656c6760a9ad9f3a95af971e89e2a80b255bb56d5aae15de69803b52aa5079b33374b16e16178fc62a2f2ce6bf21909c0a0edea9525486e0ece65bff23499342cca38dd62";
BinaryMerkleMultiproof memory multiproof = abi.decode(proofData, (BinaryMerkleMultiproof));
bytes32 dataroot = hex"ef8920d86519bd5f8ce3c802b84fc9b9512483e4d4a5c9608b44af4d6639f7d1";
bytes memory leafData =
hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000102030405746e218305fe3dbbef65feceed939fe8dd93c88b06c95473fbe344fb864060f3000000000000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000102030405000000000000000000000000000000000000000000000000010203040555cd7fb524ae792c9d4bc8946d07209728c533a3e14d4e7c0c95c0b150d0c284000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405505c1e7c897461a152e152f1ff3ecc358fefdf1f69448ab1165b6ca76836933b000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405100a0548893d8eab0322f34f45ac84785cdf50dfab5102a12d958e6031bacebe000000000000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000102030405000000000000000000000000000000000000000000000000010203040566e5eb1da67430f204a3c5615591f71316695c7ec1f1f713cde7e936d4a43ec1000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405d2a5de6299e28c2fec359a2718599f5ac22c2948a71d26a438295e531b6f4cb5000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405688c5238e50c0a8a556bfabff31bef1fa9cdd812c9fd4dcee5c2a0836f687fbf000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405b55a5b1efc2a22cdbfa21d050bd67147ff2b936c68354eb1a83bcdf14eb57e38000000000000000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000010203040500000000000000000000000000000000000000000067480c4a88c4d129947e11c33fa811daa791771e591dd933498d1212d46b8cde9c34c28831b0b532000000000000";
bytes[] memory leaves = abi.decode(leafData, (bytes[]));
assertTrue(BinaryMerkleTree.verifyMulti(dataroot, multiproof, leaves));
}
function testMultiproof() public {
bytes memory proofData =
hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000006ce29bcde696f84e35c5626904542a549b080e92603243b34794242473940706917519bf954f5b30495af5c8cdb9983e6319104badc1ea811ed2c421018a3ad7821ea268d3540deab8f9b2024464618610c9a7083620badcf505bda647cc8e9f82bfc87d990d8344f6efd44fcb09b46b87f9a92230d41329452efee8656c6760a9ad9f3a95af971e89e2a80b255bb56d5aae15de69803b52aa5079b33374b16e16178fc62a2f2ce6bf21909c0a0edea9525486e0ece65bff23499342cca38dd62";
BinaryMerkleMultiproof memory multiproof = abi.decode(proofData, (BinaryMerkleMultiproof));
bytes32 dataroot = hex"ef8920d86519bd5f8ce3c802b84fc9b9512483e4d4a5c9608b44af4d6639f7d1";
bytes memory leafData =
hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000102030405746e218305fe3dbbef65feceed939fe8dd93c88b06c95473fbe344fb864060f3000000000000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000102030405000000000000000000000000000000000000000000000000010203040555cd7fb524ae792c9d4bc8946d07209728c533a3e14d4e7c0c95c0b150d0c284000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405505c1e7c897461a152e152f1ff3ecc358fefdf1f69448ab1165b6ca76836933b000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405100a0548893d8eab0322f34f45ac84785cdf50dfab5102a12d958e6031bacebe000000000000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000102030405000000000000000000000000000000000000000000000000010203040566e5eb1da67430f204a3c5615591f71316695c7ec1f1f713cde7e936d4a43ec1000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405d2a5de6299e28c2fec359a2718599f5ac22c2948a71d26a438295e531b6f4cb5000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405688c5238e50c0a8a556bfabff31bef1fa9cdd812c9fd4dcee5c2a0836f687fbf000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405b55a5b1efc2a22cdbfa21d050bd67147ff2b936c68354eb1a83bcdf14eb57e38000000000000000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000010203040500000000000000000000000000000000000000000067480c4a88c4d129947e11c33fa811daa791771e591dd933498d1212d46b8cde9c34c28831b0b532000000000000";
BinaryMerkleMultiproof memory multiproof = abi.decode(proofData, (BinaryMerkleMultiproof));
require(multiproof.sideNodes.length > 0, "Multiproof side nodes array cannot be empty");
bytes32 dataroot = hex"ef8920d86519bd5f8ce3c802b84fc9b9512483e4d4a5c9608b44af4d6639f7d1";
bytes memory leafData =
hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a0000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000520000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000102030405746e218305fe3dbbef65feceed939fe8dd93c88b06c95473fbe344fb864060f3000000000000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000102030405000000000000000000000000000000000000000000000000010203040555cd7fb524ae792c9d4bc8946d07209728c533a3e14d4e7c0c95c0b150d0c284000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405505c1e7c897461a152e152f1ff3ecc358fefdf1f69448ab1165b6ca76836933b000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405100a0548893d8eab0322f34f45ac84785cdf50dfab5102a12d958e6031bacebe000000000000000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000102030405000000000000000000000000000000000000000000000000010203040566e5eb1da67430f204a3c5615591f71316695c7ec1f1f713cde7e936d4a43ec1000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405d2a5de6299e28c2fec359a2718599f5ac22c2948a71d26a438295e531b6f4cb5000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405688c5238e50c0a8a556bfabff31bef1fa9cdd812c9fd4dcee5c2a0836f687fbf000000000000000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000001020304050000000000000000000000000000000000000000000000000102030405b55a5b1efc2a22cdbfa21d050bd67147ff2b936c68354eb1a83bcdf14eb57e38000000000000000000000000000000000000000000000000000000000000000000000000005a000000000000000000000000000000000000000000000000010203040500000000000000000000000000000000000000000067480c4a88c4d129947e11c33fa811daa791771e591dd933498d1212d46b8cde9c34c28831b0b532000000000000";
bytes[] memory leaves = abi.decode(leafData, (bytes[]));
require(leaves.length > 0, "Leaves array cannot be empty");
assertTrue(BinaryMerkleTree.verifyMulti(dataroot, multiproof, leaves));
}

}
4 changes: 2 additions & 2 deletions src/lib/tree/namespace/NamespaceMerkleMultiproof.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import "./NamespaceNode.sol";

/// @notice Namespace Merkle Tree Multiproof structure. Proves multiple leaves.
struct NamespaceMerkleMultiproof {
// The beginning key of the leaves to verify.
// The (included) beginning key of the leaves to verify.
uint256 beginKey;
// The ending key of the leaves to verify.
// The (excluded) ending key of the leaves to verify.
uint256 endKey;
// List of side nodes to verify and calculate tree.
NamespaceNode[] sideNodes;
Expand Down
28 changes: 0 additions & 28 deletions src/lib/tree/namespace/NamespaceMerkleTree.sol
Original file line number Diff line number Diff line change
Expand Up @@ -193,34 +193,6 @@ library NamespaceMerkleTree {
return namespaceNodeEquals(rootHash, root);
}

/// @notice Returns the size of the subtree adjacent to `begin` that does
/// not overlap `end`.
/// @param begin Begin index, inclusive.
/// @param end End index, exclusive.
function _nextSubtreeSize(uint256 begin, uint256 end) private pure returns (uint256) {
uint256 ideal = _bitsTrailingZeroes(begin);
uint256 max = _bitsLen(end - begin) - 1;
if (ideal > max) {
return 1 << max;
}
return 1 << ideal;
}

/// @notice Returns the number of trailing zero bits in `x`; the result is
/// 256 for `x` == 0.
/// @param x Number.
function _bitsTrailingZeroes(uint256 x) private pure returns (uint256) {
uint256 mask = 1;
uint256 count = 0;

while (x != 0 && mask & x == 0) {
count++;
x >>= 1;
}

return count;
}

/// @notice Computes the NMT root recursively.
/// @param proof Namespace Merkle multiproof for the leaves.
/// @param leafNodes Leaf nodes for which inclusion is proven.
Expand Down
20 changes: 20 additions & 0 deletions src/lib/tree/namespace/test/NamespaceMerkleMultiproof.t.sol

Large diffs are not rendered by default.

Binary file added src/lib/tree/test/blob.dat
Binary file not shown.
Binary file added src/lib/tree/test/header.dat
Binary file not shown.
1 change: 1 addition & 0 deletions src/lib/tree/test/proofs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"start":8,"end":32,"nodes":["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOLCUcGcDNOGgcYmOnu7snv+cn+3G+vkto91wnXa3kVQ","/////////////////////////////////////////////////////////////////////////////zmvU+iSdf6GDmfvDMVa0YqTan9iPIiX5UHyC8zhZkkf"],"leaf_hash":"","is_max_namespace_ignored":true},{"start":0,"end":32,"nodes":["/////////////////////////////////////////////////////////////////////////////xZ6gFJq4RO/FIE75WZbKQOZmS3FCVTEVM/dKR/kzDZz"],"leaf_hash":"","is_max_namespace_ignored":true},{"start":0,"end":32,"nodes":["/////////////////////////////////////////////////////////////////////////////5MEmpTXPlH6UVRm8X2csA+EaccobRIwYWyOF5ls5abx"],"leaf_hash":"","is_max_namespace_ignored":true},{"start":0,"end":32,"nodes":["/////////////////////////////////////////////////////////////////////////////3wn2BGIhHap4sg/oUMt6THYs/c8kj+mFXPoFL3NxC9I"],"leaf_hash":"","is_max_namespace_ignored":true},{"start":0,"end":32,"nodes":["//////////////////////////////////////////////////////////////////////////////vTJmXZEYdLpfIuqDC7XrkhCLaw6GE1Iz1EzwDecR57"],"leaf_hash":"","is_max_namespace_ignored":true},{"start":0,"end":32,"nodes":["/////////////////////////////////////////////////////////////////////////////+HJEAbaxVdhII24yEvjUz8rAJmA8T9ratZdZHUASsLe"],"leaf_hash":"","is_max_namespace_ignored":true},{"start":0,"end":32,"nodes":["/////////////////////////////////////////////////////////////////////////////4K3KpNnwd8qcf40yM88DYQ087APK4Kjc98+WAchu59l"],"leaf_hash":"","is_max_namespace_ignored":true},{"start":0,"end":32,"nodes":["/////////////////////////////////////////////////////////////////////////////5wXTQR9kfP9JmlezblQvri0MM7UccRF1qUZ/2ELgyMf"],"leaf_hash":"","is_max_namespace_ignored":true},{"start":0,"end":24,"nodes":["AAAAAAAAAAAAAAAAAAAAAAAAAAAABpbeXXkKu9gAAAAAAAAAAAAAAAAAAAAAAAAAAABnSAxKiMTRKTLbUSuhrp5YtSI7UOw4sUkTDR1mrRutu+xxGAq64vXA","/////////////////////////////////////////////////////////////////////////////9+uMDUChYyrcrRy2fQ9h15MuGF69AmtPLH3FGuTFCUX"],"leaf_hash":"","is_max_namespace_ignored":true}]