Skip to content

Commit

Permalink
change format, todo merkle test
Browse files Browse the repository at this point in the history
  • Loading branch information
erhant committed Dec 28, 2024
1 parent d8b00fa commit 1e8f004
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 134 deletions.
7 changes: 4 additions & 3 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Summary

- [Introduction](./README.md)
- [Preliminary](./preliminary/README.md)
- [Group Theory](./preliminary/group-theory.md)
- [Commitments](./preliminary/commitments.md)
- [Concepts](./concepts/README.md)
- [Group Theory](./concepts/group-theory.md)
- [Commitments](./concepts/commitments.md)
- [Merkle Trees](./concepts/merkle-trees.md)
- [Arithmetic](./arithmetic/README.md)
- [Multiplication](./arithmetic/multiplication.md)
- [Addition](./arithmetic/addition.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Preliminaries
# Concepts

Although not mandatory, you may want to check out some preliminary theory to better understand some concepts within Circom.
Although not mandatory, you may want to check out some preliminary theory & concepts to better understand the world of circuits within Circom.

- [Group Theory](./group-theory.md)
- [Commitments](./commitments.md)
- [Merkle Trees](./merkle-trees.md)
File renamed without changes.
File renamed without changes.
108 changes: 108 additions & 0 deletions book/src/concepts/merkle-trees.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Merkle Trees

If you have been in the world of crypto for a while, it is highly likely that you have heard the term [Merkle Tree](https://brilliant.org/wiki/merkle-tree/), also known as Merkle Hash Tree.

## Definition

A Merkle Tree is a hash-based data structure, and can serve as a **cryptographic commitment scheme**.

You can commit to a set of values using a Merkle Tree, such as:

- Evaluations of a function
- Coefficients of a polynomial
- Files in your database

Here is an example, where we commit to a vector $\vec{v} = [m, y, v, e, c, t, o, r]$ using a Merkle Tree:

```mermaid
graph BT
subgraph Merkle Tree
h1["h1 (Merkle Root)"]
h2 --> h1; h3 --> h1
h4 --> h2; h5 --> h2
h6 --> h3; h7 --> h3
h8 --> h4; h9 --> h4
h10 --> h5; h11 --> h5
h12 --> h6; h13 --> h6
h14 --> h7; h15 --> h7
end
m -- hash --> h8
y -- hash --> h9
v -- hash --> h10
e -- hash --> h11
c -- hash --> h12
t -- hash --> h13
o -- hash --> h14
r -- hash --> h15
```

In a Merkle Tree, every node is made up of the hash of its children. In this example binary tree, that would be:

- $h_1 = H(h_2, h_3)$
- $h_2 = H(h_4, h_5)$
- $h_3 = H(h_6, h_7)$
- and so on.

The leaf nodes are the hashes of elements of the committed set of data. The final hash $h_1$ at the root of the tree is called the **Merkle Root**.

> Merkle Trees are often implemented as binary trees, but the concept works for $n$-ary trees as well, where each node has $n$ children.
## Merkle Proof

At some point, we may be asked to show that indeed some element of the comitted data exists at some position. For our example above, a verifier could ask "is there really a $t$ at position 6?".

A naive method here would be give the entire comitted set of data, and let them prove the Merkle Root; however, we can do much better than that! With Merkle Trees, we can answer such queries in a much more efficient way, and without revealing any other data than the one we are asked to reveal.

The trick is to provide the hashes needed to compute all the way from the requested element up to the Merkle Root. In total, we only need to provide one hash per level (in the case of a binary-tree Merkle Tree) and the verifier can compute the root!

For instance, to show that we have $t$ at position 6, we need to provide the hashes that are used to compute the parent nodes.

- $h_{13} = H(t)$ is computed by verifier.
- $h_6 = H(h_{12}, h_{13})$ requires $h_{12}$ to be provided.
- $h_3 = H(h_6, h_7)$ requires $h_7$ to be provided.
- $h_1 = H(h_2, h_3)$ requires $h_2$ to be provided.
- $h_1$ is our commitment, which the verifier knows already.

The proof is visualized below for the same tree, where the values known & computed by the verifier are colored green and the values provided by the prover are colored blue:

```mermaid
graph BT
classDef g fill:#afa
classDef b fill:#aaf
subgraph Merkle Tree
h1["h1 (Merkle Root)"]:::g
h2:::b --> h1
h3:::g --> h1
h4[fa:fa-eye-slash] --> h2
h5[fa:fa-eye-slash] --> h2
h6:::g --> h3
h7:::b --> h3
h8[fa:fa-eye-slash] --> h4
h9[fa:fa-eye-slash] --> h4
h10[fa:fa-eye-slash] --> h5
h11[fa:fa-eye-slash] --> h5
h12:::b --> h6
h13:::g --> h6
h14[fa:fa-eye-slash] --> h7
h15[fa:fa-eye-slash] --> h7
end
t:::g -- hash --> h13
```

You see, we only needed to provide 3 hashes here, although our data had 8 elements! In fact, if you have $n$ elements you only need to provide $\log_2{n}$ elements to the verifier, this is so much more efficient than the naive method of sending all the data to the verifier.

> A Merkle Root can serve as a cryptographic **commitment** to a set of data.
>
> - It is **hiding** because you can't find the preimage of an hash efficiently.
> - It is **binding** because assuming otherwise would require you to find a hash-collision efficiently, which is known to be intractable.
>
> To **reveal** that some value is part of the comitted set of data at a specific point, you only need to reveal the path from that node to the root, along with the value itself, as described [above](#merkle-proof).
## Further Reading

- The original paper is ["A Digital Signature Based on a Conventional Encryption Function"](https://people.eecs.berkeley.edu/~raluca/cs261-f15/readings/me) by Ralph Merkle.
- ["Providing Authentication and Integrity in Outsourced Databases using Merkle Hash Tree's"](https://people.eecs.berkeley.edu/~raluca/cs261-f15/readings/merkleodb.pdf) by Mykletun, Narasimha, Tsudik is another paper that is slightly easier to read.
- RareSkills Merkle Tree second-preimage attack [blog post](https://www.rareskills.io/post/merkle-tree-second-preimage-attack).
109 changes: 1 addition & 108 deletions book/src/merkle-trees/README.md
Original file line number Diff line number Diff line change
@@ -1,116 +1,9 @@
# Merkle Trees

If you have been in the world of crypto for a while, it is highly likely that you have heard the term [Merkle Tree](https://brilliant.org/wiki/merkle-tree/), also known as Merkle Hash Tree.

They play a huge role in many applications, especially in Blockchain; and there are several types of it, such as:
There are several types of Merkle Trees:

- [Binary Merkle Tree](./bmt.md)
- [Complete Binary Merkle Tree](./cbmt.md)
- [Sparse Merkle Tree](./smt.md)
- [Incremential Merkle Tree](./imt.md)
- [Merkle Mountain Range](./mmr.md)

## Merkle Tree Basics

A Merkle Tree is a hash-based data structure, and can serve as a **cryptographic commitment scheme**.

You can commit to a set of values using a Merkle Tree, such as:

- Evaluations of a function
- Coefficients of a polynomial
- Files in your database

Here is an example, where we commit to a vector $\vec{v} = [m, y, v, e, c, t, o, r]$ using a Merkle Tree:

```mermaid
graph BT
subgraph Merkle Tree
h1["h1 (Merkle Root)"]
h2 --> h1; h3 --> h1
h4 --> h2; h5 --> h2
h6 --> h3; h7 --> h3
h8 --> h4; h9 --> h4
h10 --> h5; h11 --> h5
h12 --> h6; h13 --> h6
h14 --> h7; h15 --> h7
end
m -- hash --> h8
y -- hash --> h9
v -- hash --> h10
e -- hash --> h11
c -- hash --> h12
t -- hash --> h13
o -- hash --> h14
r -- hash --> h15
```

In a Merkle Tree, every node is made up of the hash of its children. In this example binary tree, that would be:

- $h_1 = H(h_2, h_3)$
- $h_2 = H(h_4, h_5)$
- $h_3 = H(h_6, h_7)$
- and so on.

The leaf nodes are the hashes of elements of the committed set of data. The final hash $h_1$ at the root of the tree is called the **Merkle Root**.

> Merkle Trees are often implemented as binary trees, but the concept works for $n$-ary trees as well, where each node has $n$ children.
## Merkle Proof

At some point, we may be asked to show that indeed some element of the comitted data exists at some position. For our example above, a verifier could ask "is there really a $t$ at position 6?".

A naive method here would be give the entire comitted set of data, and let them prove the Merkle Root; however, we can do much better than that! With Merkle Trees, we can answer such queries in a much more efficient way, and without revealing any other data than the one we are asked to reveal.

The trick is to provide the hashes needed to compute all the way from the requested element up to the Merkle Root. In total, we only need to provide one hash per level (in the case of a binary-tree Merkle Tree) and the verifier can compute the root!

For instance, to show that we have $t$ at position 6, we need to provide the hashes that are used to compute the parent nodes.

- $h_{13} = H(t)$ is computed by verifier.
- $h_6 = H(h_{12}, h_{13})$ requires $h_{12}$ to be provided.
- $h_3 = H(h_6, h_7)$ requires $h_7$ to be provided.
- $h_1 = H(h_2, h_3)$ requires $h_2$ to be provided.
- $h_1$ is our commitment, which the verifier knows already.

The proof is visualized below for the same tree, where the values known & computed by the verifier are colored green and the values provided by the prover are colored blue:

```mermaid
graph BT
classDef g fill:#afa
classDef b fill:#aaf
subgraph Merkle Tree
h1["h1 (Merkle Root)"]:::g
h2:::b --> h1
h3:::g --> h1
h4[fa:fa-eye-slash] --> h2
h5[fa:fa-eye-slash] --> h2
h6:::g --> h3
h7:::b --> h3
h8[fa:fa-eye-slash] --> h4
h9[fa:fa-eye-slash] --> h4
h10[fa:fa-eye-slash] --> h5
h11[fa:fa-eye-slash] --> h5
h12:::b --> h6
h13:::g --> h6
h14[fa:fa-eye-slash] --> h7
h15[fa:fa-eye-slash] --> h7
end
t:::g -- hash --> h13
```

You see, we only needed to provide 3 hashes here, although our data had 8 elements! In fact, if you have $n$ elements you only need to provide $\log_2{n}$ elements to the verifier, this is so much more efficient than the naive method of sending all the data to the verifier.

> A Merkle Root can serve as a cryptographic **commitment** to a set of data.
>
> - It is **hiding** because you can't find the preimage of an hash efficiently.
> - It is **binding** because assuming otherwise would require you to find a hash-collision efficiently, which is known to be intractable.
>
> To **reveal** that some value is part of the comitted set of data at a specific point, you only need to reveal the path from that node to the root, along with the value itself, as described [above](#merkle-proof).
## Further Reading

- The original paper is ["A Digital Signature Based on a Conventional Encryption Function"](https://people.eecs.berkeley.edu/~raluca/cs261-f15/readings/me) by Ralph Merkle.
- ["Providing Authentication and Integrity in Outsourced Databases using Merkle Hash Tree's"](https://people.eecs.berkeley.edu/~raluca/cs261-f15/readings/merkleodb.pdf) by Mykletun, Narasimha, Tsudik is another paper that is slightly easier to read.
- RareSkills Merkle Tree second-preimage attack [blog post](https://www.rareskills.io/post/merkle-tree-second-preimage-attack).
1 change: 1 addition & 0 deletions book/src/merkle-trees/dmt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Dense Merkle Tree 🚧
40 changes: 39 additions & 1 deletion circuits/merkle-trees/bmt.circom
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pragma circom 2.1.0;

include "circomlib/circuits/poseidon.circom";
include "../control-flow/index.circom";

// Binary Merkle Tree
//
Expand All @@ -24,7 +25,7 @@ template BinaryMerkleTree(n) {

// compute hashes of leaves
for (var i = 0; i < NUM_LEAVES; i++) {
nodes[(NUM_NODES - 1) - i] <== Poseidon(1)([leafs[i]]);
nodes[(NUM_NODES - 1) - i] <== Poseidon(1)([leafs[(NUM_LEAVES - 1) - i]]);
}

// build the tree from the leaves to the root in reverse
Expand All @@ -34,3 +35,40 @@ template BinaryMerkleTree(n) {

root <== nodes[0];
}

// Binary Merkle Tree
//
// Parameters:
// - n: depth of the tree such that number of leaves is 2^n
//
// Inputs:
// - leafs: the leaves of the tree
//
// Outputs:
// - root: the root of the tree
//
template BinaryMerkleProof(n) {
assert(n > 0);
signal input in;
signal input siblings[n];
signal input indices[n];
signal output root;

// the path from the leaf to the root
signal path[n];

// the last element in the path is the leaf hash
path[n-1] <== Poseidon(1)([in]);

// compute the "audit path" w.r.t indices
signal children[n-1][2];
for (var i = n-1; i != 0; i--) {
// index 0: sibling is on the right
// index 1: sibling is on the left
children[i-1] <== Switch()(indices[i], [path[i], siblings[i]]);
path[i-1] <== Poseidon(2)(children[i-1]);
}

// the last element in the path is the root hash
root <== path[0];
}
6 changes: 6 additions & 0 deletions circuits/test/merkle-trees/bmt-1-proof.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// auto-generated by circomkit
pragma circom 2.0.0;

include "../../merkle-trees/bmt.circom";

component main = BinaryMerkleTree(1);
6 changes: 6 additions & 0 deletions circuits/test/merkle-trees/bmt-2-proof.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// auto-generated by circomkit
pragma circom 2.0.0;

include "../../merkle-trees/bmt.circom";

component main = BinaryMerkleProof(2);
Loading

0 comments on commit 1e8f004

Please sign in to comment.