Skip to content

Commit

Permalink
rust docs and refinements
Browse files Browse the repository at this point in the history
  • Loading branch information
yshekel committed Oct 15, 2024
1 parent 8f28692 commit 9dd749d
Show file tree
Hide file tree
Showing 6 changed files with 408 additions and 54 deletions.
72 changes: 45 additions & 27 deletions docs/docs/icicle/primitives/merkle.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ A **Merkle tree** is a cryptographic data structure that allows for **efficient
- **Leaf nodes**, each containing a piece of data.
- **Internal nodes**, which store the **hashes of their child nodes**, leading up to the **root node** (the cryptographic commitment).

With ICICLE, you have the **flexibility** to build various tree topologies based on your needs. The user must define:

1. **Hasher per layer** ([Link to Hasher API](./hash.md)) with a **default input size**.
2. **Size of a leaf element** (in bytes): This defines the **granularity** of the data used for opening proofs.

---

## Tree Structure and Configuration

### Structure Definition

With ICICLE, you have the **flexibility** to build various tree topologies based on your needs. A tree is defined by:

1. **Hasher per layer** ([Link to Hasher API](./hash.md)) with a **default input size**.
2. **Size of a leaf element** (in bytes): This defines the **granularity** of the data used for opening proofs.

The **root node** is assumed to be a single node. The **height of the tree** is determined by the **number of layers**.
Each layer's **arity** is calculated as:

Expand All @@ -31,6 +31,11 @@ $$
{arity}_0 = \frac{layers[0].inputSize}{leafSize}
$$

:::note
Each layer has a shrinking-factor defined by $\frac{layer.outputSize}{layer.inputSize}$.
This factor is used to compute the input size, assuming a single root node.
:::

---

### Defining a Merkle Tree
Expand All @@ -47,6 +52,10 @@ static MerkleTree create(
### Building the Tree
The Merkle tree can be constructed from input data of any type, allowing flexibility in its usage. The size of the input must align with the tree structure defined by the hash layers and leaf size. If the input size does not match the expected size, padding may be applied.
Refer to the Padding Section for more details on how mismatched input sizes are handled.
```cpp
// icicle/merkle/merkle_tree.h
inline eIcicleError build(
Expand Down Expand Up @@ -77,12 +86,12 @@ const uint64_t leaf_size = 1024;
const uint32_t max_input_size = leaf_size * 16;
auto input = std::make_unique<uint64_t[]>(max_input_size / sizeof(uint64_t));

// Define hasher
auto layer0_hasher = Keccak256::create(leaf_size); // hash 1KB -> 32B
auto next_layer_hasher = Keccak256::create(2 * layer0_hasher.output_size()); // hash every 64B to 32B
// Define hashes
auto hash = Keccak256::create(leaf_size); // hash 1KB -> 32B
auto compress = Keccak256::create(2 * hasher.output_size()); // hash every 64B to 32B

// Construct the tree using the layer hashes and leaf-size
std::vector<Hash> hashes = {layer0_hasher, next_layer_hasher, next_layer_hasher, next_layer_hasher, next_layer_hasher};
std::vector<Hash> hashes = {hasher, compress, compress, compress, compress};
auto merkle_tree = MerkleTree::create(hashes, leaf_size);

// compute the tree
Expand All @@ -105,10 +114,10 @@ const uint32_t max_input_size = leaf_size * 16;
auto input = std::make_unique<uint64_t[]>(max_input_size / sizeof(uint64_t));
// note here we use Blake2S for the upper layer
auto layer0_hasher = Keccak256::create(leaf_size);
auto next_layer_hasher = Blake2s::create(4 * layer0_hasher.output_size());
auto hash = Keccak256::create(leaf_size);
auto compress = Blake2s::create(4 * hash.output_size());
std::vector<Hash> hashes = {layer0_hasher, next_layer_hasher};
std::vector<Hash> hashes = {hash, compress, compress};
auto merkle_tree = MerkleTree::create(hashes, leaf_size);
merkle_tree.build(input.get(), max_input_size / sizeof(uint64_t), default_merkle_tree_config());
Expand Down Expand Up @@ -142,6 +151,8 @@ merkle_tree.build(input.get(), max_input_size / sizeof(uint64_t), config);

## Root as Commitment

Retrieve the Merkle-root and serialize.

```cpp
/**
* @brief Returns a pair containing the pointer to the root (ON HOST) data and its size.
Expand All @@ -150,16 +161,27 @@ merkle_tree.build(input.get(), max_input_size / sizeof(uint64_t), config);
inline std::pair<const std::byte*, size_t> get_merkle_root() const;

auto [commitment, size] = merkle_tree.get_merkle_root();
serialize_commitment_application_code(...);
```
:::note
The commitment can be serialized to the proof.
:::
---
## Generating Merkle Proofs
### Definition
Merkle proofs are used to **prove the integrity of opened leaves** in a Merkle tree. A proof ensures that a specific leaf belongs to the committed data by enabling the verifier to reconstruct the **root hash (commitment)**.
A Merkle proof contains:
- **Leaf**: The data being verified.
- **Index** (leaf_idx): The position of the leaf in the original dataset.
- **Path**: A sequence of sibling hashes (tree nodes) needed to recompute the path from the leaf to the root.
![Merkle Pruned Phat Diagram](./merkle_diagrams/diagram1_path.png)
```cpp
// icicle/merkle/merkle_proof.h
Expand Down Expand Up @@ -198,36 +220,32 @@ The Merkle-path can be serialized to the proof along the leaf.
* @param merkle_proof The MerkleProof object includes the leaf, path, and the root.
* @param valid output valid bit. True if the proof is valid, false otherwise.
*/
eIcicleError verify(const MerkleProof& merkle_proof, bool& valid) const;
eIcicleError verify(const MerkleProof& merkle_proof, bool& valid) const
```
### Example: Verifying a Proof
```cpp
bool valid = false;
auto err = merkle_tree.verify(proof, valid);
```

---

## Pruned vs. Full Paths
## Pruned vs. Full Merkle-paths

A **Merkle path** is a collection of **sibling hashes** that allows the verifier to **reconstruct the root hash** from a specific leaf.
This enables anyone with the **path and root** to verify that the **leaf** belongs to the committed dataset.
There are two types of paths that can be computed:

- **Pruned Path:** Contains only necessary sibling hashes.
![Merkle Pruned Phat Diagram](./merkle_diagrams/diagram1_path.png)
```cpp
MerkleProof proof{};
auto err = merkle_tree.get_merkle_proof(
input.get(),
max_input_size / sizeof(uint64_t),
3 /*leaf-idx*/, true /*=pruned*/, // --> note the pruned flag here
default_merkle_tree_config(), proof);
```
- [**Pruned Path:**](#generating-merkle-proofs) Contains only necessary sibling hashes.
- **Full Path:** Contains all sibling nodes and intermediate hashes.


![Merkle Full Path Diagram](./merkle_diagrams/diagram1_path_full.png)

To compute a full path, specify `pruned=false`:

```cpp
MerkleProof proof{};
auto err = merkle_tree.get_merkle_proof(
Expand Down
10 changes: 5 additions & 5 deletions docs/docs/icicle/primitives/merkle_diagrams/diagram1.gv
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ digraph MerkleTree {
node [shape = circle; style = filled; color = lightblue; fontname = "Helvetica"; fontsize = 10;];

// Root node
Root [label = "Root\n (Commitment)";];
Root [label = "Root\n (Commitment) | Keccak256";];

L1_0 [label = "";];
L1_0 [label = "Keccak256";];
L1_1 [label = "";];

L2_0 [label = "";];
L2_0 [label = "Keccak256";];
L2_1 [label = "";];
L2_2 [label = "";];
L2_3 [label = "";];

L3_0 [label = "";];
L3_0 [label = "Keccak256";];
L3_1 [label = "";];
L3_2 [label = "";];
L3_3 [label = "";];
Expand All @@ -22,7 +22,7 @@ digraph MerkleTree {
L3_6 [label = "";];
L3_7 [label = "";];

L4_0 [label = "";];
L4_0 [label = "Keccak256";];
L4_1 [label = "";];
L4_2 [label = "";];
L4_3 [label = "";];
Expand Down
Binary file modified docs/docs/icicle/primitives/merkle_diagrams/diagram1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 52 additions & 21 deletions docs/docs/icicle/primitives/merkle_diagrams/diagram2.gv
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,30 @@ digraph MerkleTree {
node [shape = circle; style = filled; color = lightblue; fontname = "Helvetica"; fontsize = 10;];

// Root node
Root [label = "Root\n (Commitment)";];
Root [label = "Root\n (Commitment) | Blake2s";];

L1_0 [label = "";];
L1_0 [label = "Blake2s";];
L1_1 [label = "";];
L1_2 [label = "";];
L1_3 [label = "";];

L2_0 [label = "Keccak256";];
L2_1 [label = "";];
L2_2 [label = "";];
L2_3 [label = "";];
L2_4 [label = "";];
L2_5 [label = "";];
L2_6 [label = "";];
L2_7 [label = "";];
L2_8 [label = "";];
L2_9 [label = "";];
L2_10 [label = "";];
L2_11 [label = "";];
L2_12 [label = "";];
L2_13 [label = "";];
L2_14 [label = "";];
L2_15 [label = "";];

node [style = filled; fillcolor = lightgreen; shape = rect;];
Leaf_0 [label = "Leaf-0";];
Leaf_1 [label = "Leaf-1";];
Expand All @@ -35,24 +52,38 @@ digraph MerkleTree {
Root -> L1_2;
Root -> L1_3;

L1_0 -> L2_0;
L1_0 -> L2_1;
L1_0 -> L2_2;
L1_0 -> L2_3;
L1_1 -> L2_4;
L1_1 -> L2_5;
L1_1 -> L2_6;
L1_1 -> L2_7;
L1_2 -> L2_8;
L1_2 -> L2_9;
L1_2 -> L2_10;
L1_2 -> L2_11;
L1_3 -> L2_12;
L1_3 -> L2_13;
L1_3 -> L2_14;
L1_3 -> L2_15;

// Connections
L1_0 -> Leaf_0;
L1_0 -> Leaf_1;
L1_0 -> Leaf_2;
L1_0 -> Leaf_3;

L1_1 -> Leaf_4;
L1_1 -> Leaf_5;
L1_1 -> Leaf_6;
L1_1 -> Leaf_7;

L1_2 -> Leaf_8;
L1_2 -> Leaf_9;
L1_2 -> Leaf_10;
L1_2 -> Leaf_11;

L1_3 -> Leaf_12;
L1_3 -> Leaf_13;
L1_3 -> Leaf_14;
L1_3 -> Leaf_15;
L2_0 -> Leaf_0;
L2_1 -> Leaf_1;
L2_2 -> Leaf_2;
L2_3 -> Leaf_3;
L2_4 -> Leaf_4;
L2_5 -> Leaf_5;
L2_6 -> Leaf_6;
L2_7 -> Leaf_7;
L2_8 -> Leaf_8;
L2_9 -> Leaf_9;
L2_10 -> Leaf_10;
L2_11 -> Leaf_11;
L2_12 -> Leaf_12;
L2_13 -> Leaf_13;
L2_14 -> Leaf_14;
L2_15 -> Leaf_15;
}
Binary file modified docs/docs/icicle/primitives/merkle_diagrams/diagram2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 9dd749d

Please sign in to comment.