Skip to content

Commit

Permalink
feat!: use a struct for namespace and rename namespaceID (#175)
Browse files Browse the repository at this point in the history
<!--
Please read and fill out this form before submitting your PR.

Please make sure you have reviewed our contributors guide before
submitting your
first PR.
-->

## Overview

<!-- 
Please provide an explanation of the PR, including the appropriate
context,
background, goal, and rationale. If there is an issue with this
information,
please provide a tl;dr and link the issue. 
-->

Closes #172
and #174


## Checklist

<!-- 
Please complete the checklist to ensure that the PR is ready to be
reviewed.

IMPORTANT:
PRs should be left in Draft until the below checklist is completed.
-->

- [ ] New and updated code has appropriate documentation
- [ ] New and updated code has new and/or updated testing
- [ ] Required CI checks are passing
- [ ] Visual proof for any user facing features like CLI or
documentation updates
- [ ] Linked issues closed with keywords

---------

Co-authored-by: Rootul P <rootulp@gmail.com>
  • Loading branch information
rach-id and rootulp committed Sep 8, 2023
1 parent 3bacf00 commit eb7a4e7
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 151 deletions.
8 changes: 5 additions & 3 deletions src/lib/tree/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ library Constants {
/// @dev The prefixes of leaves and nodes
bytes1 internal constant LEAF_PREFIX = 0x00;
bytes1 internal constant NODE_PREFIX = 0x01;
}

/// @dev Parity share namespace ID
NamespaceID internal constant PARITY_SHARE_NAMESPACE_ID =
NamespaceID.wrap(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
/// @dev Parity share namespace.
/// utility function to provide the parity share namespace as a Namespace struct.
function PARITY_SHARE_NAMESPACE() pure returns (Namespace memory) {
return Namespace(0xFF, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
}
37 changes: 27 additions & 10 deletions src/lib/tree/Types.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.19;

type NamespaceID is bytes29;
/// @notice A representation of the Celestia-app namespace ID and its version.
/// See: https://celestiaorg.github.io/celestia-app/specs/namespace.html
struct Namespace {
// The namespace version.
bytes1 version;
// The namespace ID.
bytes28 id;
}

using {equalTo, lessThan, greaterThan, toBytes} for Namespace global;

using {equality as ==} for NamespaceID global;
using {lessthan as <} for NamespaceID global;
using {greaterthan as >} for NamespaceID global;
function equalTo(Namespace memory l, Namespace memory r) pure returns (bool) {
return l.toBytes() == r.toBytes();
}

function lessThan(Namespace memory l, Namespace memory r) pure returns (bool) {
return l.toBytes() < r.toBytes();
}

function equality(NamespaceID l, NamespaceID r) pure returns (bool) {
return NamespaceID.unwrap(l) == NamespaceID.unwrap(r);
function greaterThan(Namespace memory l, Namespace memory r) pure returns (bool) {
return l.toBytes() > r.toBytes();
}

function lessthan(NamespaceID l, NamespaceID r) pure returns (bool) {
return NamespaceID.unwrap(l) < NamespaceID.unwrap(r);
function toBytes(Namespace memory n) pure returns (bytes29) {
return bytes29(abi.encodePacked(n.version, n.id));
}

function greaterthan(NamespaceID l, NamespaceID r) pure returns (bool) {
return NamespaceID.unwrap(l) > NamespaceID.unwrap(r);
function toNamespace(bytes29 n) pure returns (Namespace memory) {
bytes memory id = new bytes(28);
for (uint256 i = 1; i < 29; i++) {
id[i - 1] = n[i];
}
return Namespace(n[0], bytes28(id));
}
14 changes: 7 additions & 7 deletions src/lib/tree/namespace/NamespaceMerkleTree.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,18 @@ library NamespaceMerkleTree {
/// @notice Verify if element exists in Merkle tree, given data, proof, and root.
/// @param root The root of the tree in which the given leaf is verified.
/// @param proof Namespace Merkle proof for the leaf.
/// @param minmaxNID Namespace ID of the leaf.
/// @param namespace Namespace of the leaf.
/// @param data The data of the leaf to verify.
/// @return `true` if the proof is valid, `false` otherwise.
/// @dev proof.numLeaves is necessary to determine height of subtree containing the data to prove.
function verify(
NamespaceNode memory root,
NamespaceMerkleProof memory proof,
NamespaceID minmaxNID,
Namespace memory namespace,
bytes memory data
) internal pure returns (bool) {
// A sibling at height 1 is created by getting the leafDigest of the original data.
NamespaceNode memory node = leafDigest(minmaxNID, data);
NamespaceNode memory node = leafDigest(namespace, data);

// Since we're verifying a leaf, height parameter is 1.
return verifyInner(root, proof, node, 1);
Expand Down Expand Up @@ -137,19 +137,19 @@ library NamespaceMerkleTree {
/// @notice Verify if contiguous elements exists in Merkle tree, given leaves, mutliproof, and root.
/// @param root The root of the tree in which the given leaves are verified.
/// @param proof Namespace Merkle multiproof for the leaves.
/// @param minmaxNID Namespace ID of the leaves. All leaves must have the same namespace ID.
/// @param data The leaves to verify. Note: leaf data must be the _entire_ share (including namespace ID prefixing).
/// @param namespace Namespace of the leaves. All leaves must have the same namespace.
/// @param data The leaves to verify. Note: leaf data must be the _entire_ share (including namespace prefixing).
/// @return `true` if the proof is valid, `false` otherwise.
function verifyMulti(
NamespaceNode memory root,
NamespaceMerkleMultiproof memory proof,
NamespaceID minmaxNID,
Namespace memory namespace,
bytes[] memory data
) internal pure returns (bool) {
// Hash all the leaves to get leaf nodes.
NamespaceNode[] memory nodes = new NamespaceNode[](data.length);
for (uint256 i = 0; i < data.length; ++i) {
nodes[i] = leafDigest(minmaxNID, data[i]);
nodes[i] = leafDigest(namespace, data[i]);
}

// Verify inclusion of leaf nodes.
Expand Down
10 changes: 5 additions & 5 deletions src/lib/tree/namespace/NamespaceNode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import "../Types.sol";

/// @notice Namespace Merkle Tree node.
struct NamespaceNode {
// Minimum namespace ID.
NamespaceID min;
// Maximum namespace ID.
NamespaceID max;
// Minimum namespace.
Namespace min;
// Maximum namespace.
Namespace max;
// Node value.
bytes32 digest;
}
Expand All @@ -19,5 +19,5 @@ struct NamespaceNode {
/// @return `true` is equal, `false otherwise.
// solhint-disable-next-line func-visibility
function namespaceNodeEquals(NamespaceNode memory first, NamespaceNode memory second) pure returns (bool) {
return (first.min == second.min) && (first.max == second.max) && (first.digest == second.digest);
return first.min.equalTo(second.min) && first.max.equalTo(second.max) && (first.digest == second.digest);
}
38 changes: 24 additions & 14 deletions src/lib/tree/namespace/TreeHasher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import "./NamespaceNode.sol";

/// @notice Get the minimum namespace.
// solhint-disable-next-line func-visibility
function namespaceMin(NamespaceID l, NamespaceID r) pure returns (NamespaceID) {
if (l < r) {
function namespaceMin(Namespace memory l, Namespace memory r) pure returns (Namespace memory) {
if (l.lessThan(r)) {
return l;
} else {
return r;
Expand All @@ -17,22 +17,22 @@ function namespaceMin(NamespaceID l, NamespaceID r) pure returns (NamespaceID) {

/// @notice Get the maximum namespace.
// solhint-disable-next-line func-visibility
function namespaceMax(NamespaceID l, NamespaceID r) pure returns (NamespaceID) {
if (l > r) {
function namespaceMax(Namespace memory l, Namespace memory r) pure returns (Namespace memory) {
if (l.greaterThan(r)) {
return l;
} else {
return r;
}
}

/// @notice Hash a leaf node.
/// @param minmaxNID Namespace ID.
/// @param namespace Namespace of the leaf.
/// @param data Raw data of the leaf.
/// @dev More details in https://github.com/celestiaorg/celestia-specs/blob/master/src/specs/data_structures.md#namespace-merkle-tree
// solhint-disable-next-line func-visibility
function leafDigest(NamespaceID minmaxNID, bytes memory data) pure returns (NamespaceNode memory) {
bytes32 digest = sha256(abi.encodePacked(Constants.LEAF_PREFIX, minmaxNID, data));
NamespaceNode memory node = NamespaceNode(minmaxNID, minmaxNID, digest);
function leafDigest(Namespace memory namespace, bytes memory data) pure returns (NamespaceNode memory) {
bytes32 digest = sha256(abi.encodePacked(Constants.LEAF_PREFIX, namespace.toBytes(), data));
NamespaceNode memory node = NamespaceNode(namespace, namespace, digest);
return node;
}

Expand All @@ -42,17 +42,27 @@ function leafDigest(NamespaceID minmaxNID, bytes memory data) pure returns (Name
/// @dev More details in https://github.com/celestiaorg/celestia-specs/blob/master/src/specs/data_structures.md#namespace-merkle-tree
// solhint-disable-next-line func-visibility
function nodeDigest(NamespaceNode memory l, NamespaceNode memory r) pure returns (NamespaceNode memory) {
NamespaceID min = namespaceMin(l.min, r.min);
NamespaceID max;
if (l.min == Constants.PARITY_SHARE_NAMESPACE_ID) {
max = Constants.PARITY_SHARE_NAMESPACE_ID;
} else if (r.min == Constants.PARITY_SHARE_NAMESPACE_ID) {
Namespace memory min = namespaceMin(l.min, r.min);
Namespace memory max;
if (l.min.equalTo(PARITY_SHARE_NAMESPACE())) {
max = PARITY_SHARE_NAMESPACE();
} else if (r.min.equalTo(PARITY_SHARE_NAMESPACE())) {
max = l.max;
} else {
max = namespaceMax(l.max, r.max);
}

bytes32 digest = sha256(abi.encodePacked(Constants.NODE_PREFIX, l.min, l.max, l.digest, r.min, r.max, r.digest));
bytes32 digest = sha256(
abi.encodePacked(
Constants.NODE_PREFIX,
l.min.toBytes(),
l.max.toBytes(),
l.digest,
r.min.toBytes(),
r.max.toBytes(),
r.digest
)
);

NamespaceNode memory node = NamespaceNode(min, max, digest);
return node;
Expand Down
24 changes: 12 additions & 12 deletions src/lib/tree/namespace/test/NamespaceMerkleMultiproof.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "../NamespaceMerkleTree.sol";
/**
* TEST VECTORS
*
* Data blocks: namespace id, data
* Data blocks: namespace, data
* 0x0000000000000000000000000000000000000000000000000000000010 0x01
* 0x0000000000000000000000000000000000000000000000000000000010 0x02
* 0x0000000000000000000000000000000000000000000000000000000010 0x03
Expand Down Expand Up @@ -50,33 +50,33 @@ contract NamespaceMerkleMultiproofTest is DSTest {
function setUp() external {}

function assertEqNamespaceNode(NamespaceNode memory first, NamespaceNode memory second) internal {
assertEq(NamespaceID.unwrap(first.min), NamespaceID.unwrap(second.min));
assertEq(NamespaceID.unwrap(first.max), NamespaceID.unwrap(second.max));
assertTrue(first.min.equalTo(second.min));
assertTrue(first.max.equalTo(second.max));
assertEq(first.digest, second.digest);
}

/// @notice Verify inclusion of leaves 0 and 1.
function testVerifyMulti01() external {
NamespaceID nid = NamespaceID.wrap(0x0000000000000000000000000000000000000000000000000000000010);
Namespace memory nid = Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010);
NamespaceNode memory root = NamespaceNode(
NamespaceID.wrap(0x0000000000000000000000000000000000000000000000000000000010),
NamespaceID.wrap(0x0000000000000000000000000000000000000000000000000000000010),
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
0x5b3328b03a538d627db78668034089cb395f63d05b24fdf99558d36fe991d268
);
NamespaceNode[] memory sideNodes = new NamespaceNode[](3);
sideNodes[0] = NamespaceNode(
NamespaceID.wrap(0x0000000000000000000000000000000000000000000000000000000010),
NamespaceID.wrap(0x0000000000000000000000000000000000000000000000000000000010),
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
0xfdb4e3c872666aa9869a1d46c8a5a0e735becdf17c62b9c3ccf4258449475bda
);
sideNodes[1] = NamespaceNode(
NamespaceID.wrap(0x0000000000000000000000000000000000000000000000000000000010),
NamespaceID.wrap(0x0000000000000000000000000000000000000000000000000000000010),
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
Namespace(0x00, 0x00000000000000000000000000000000000000000000000000000010),
0xc350aeddd5ada629057034f15d4545065213a7a28f9f9b77bdc71c4225145920
);
sideNodes[2] = NamespaceNode(
Constants.PARITY_SHARE_NAMESPACE_ID,
Constants.PARITY_SHARE_NAMESPACE_ID,
PARITY_SHARE_NAMESPACE(),
PARITY_SHARE_NAMESPACE(),
0x5aa3e7ea31995fdd38f41015275229b290a8ee4810521db766ad457b9a8373d6
);

Expand Down
Loading

0 comments on commit eb7a4e7

Please sign in to comment.