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

feat: implement incremental merkle tree #9

Closed
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,21 @@
"devDependencies": {
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.2",
"@types/chai": "^4",
"@types/mocha": "^10",
YashBit marked this conversation as resolved.
Show resolved Hide resolved
"changelogithub": "patch:changelogithub@npm%3A0.13.3#~/.yarn/patches/changelogithub-npm-0.13.3-1783949906.patch",
"czg": "^1.9.1",
"dprint": "^0.46.3",
"husky": "^9.0.11",
"lint-staged": "^15.2.2"
"lint-staged": "^15.2.2",
"ts-node": "^10.9.2",
"typescript": "^5.5.4"
},
"dependencies": {
"@noir-lang/backend_barretenberg": "^0.27.0",
"@noir-lang/noir_js": "^0.27.0",
"@noir-lang/noir_wasm": "^0.32.0",
"chai": "^4.5.0",
"mocha": "^10.7.0"
}
}
59 changes: 59 additions & 0 deletions packages/merkle-trees/src/incremental_merkle.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use crate::{IMT_Creator, IncrementalMembershipProver, Modifier, NonMembershipProver};

mod tests;
mod tree;

struct IncrementalMerkleTree {
hasher: fn([Field; 2]) -> Field,
root: Field,
}

impl IMT_Creator for IncrementalMerkleTree {
fn default(root: Field, hasher: fn([Field; 2]) -> Field) -> Self {
Self { root, hasher }
}
}

impl IncrementalMembershipProver<Field, HashPath> for IncrementalMerkleTree {
fn membership(self, level: u32, leaf: Field, siblings: (Field, [Field])) {
let root = self.calculate_root_dynamic(level, leaf, siblings);
assert(self.root == root);
}
}

impl Modifier<Field, HashPath> for IncrementalMerkleTree {
fn add(&mut self, leaf: Field, siblings: (Field, [Field])) {
let (old, new) = self.calculate_two_roots(leaf, siblings);

assert(old == self.root);
self.root = new;
}

fn delete(&mut self, leaf: Field, siblings: (Field, [Field])) {
let (new, old) = self.calculate_two_roots(leaf, siblings);
assert(old == self.root);
self.root = new;
}

fn update(&mut self, leaf: Field, old_leaf: Field, siblings: (Field, [Field])) {
let index_bits = siblings.0.to_le_bits(siblings.1.len() as u32);

let mut old_parent: Field = old_leaf;
let mut new_parent: Field = leaf;

for i in 0..siblings.1.len() {
let sibling = siblings.1[i];
if sibling != 0 {
if index_bits[i] == 0 {
new_parent = (self.hasher)([new_parent, sibling]);
old_parent = (self.hasher)([old_parent, sibling]);
} else {
new_parent = (self.hasher)([sibling, new_parent]);
old_parent = (self.hasher)([sibling, old_parent]);
}
}
}
assert(old_parent == self.root);
self.root = new_parent;
}
}
1 change: 1 addition & 0 deletions packages/merkle-trees/src/incremental_merkle/tests.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod poseidon;
90 changes: 90 additions & 0 deletions packages/merkle-trees/src/incremental_merkle/tests/poseidon.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use crate::incremental_merkle::IncrementalMerkleTree;
use dep::std::hash::poseidon::bn254::hash_2;

fn poseidon_hasher(leaves: [Field; 2]) -> Field {
hash_2([leaves[0], leaves[1]])
}


/*

Test Suite:
1. test_incremental_merkle_tree_membership
2. test_incremental_merkle_tree_add
3. test_incremental_merkle_tree__delete
4. test_incremental_merkle_tree_update
5. ttest_incremental_merkle_tree_insufficient_depth
6. test_incremental_merkle_tree_exceeding_depth


*/



#[test]
fn test_incremental_merkle_tree_membership() {
let root = 0x1910f234d14bea7c640841c9fd0d765e8599a4cd527285590e4159e66b912be1;
let imt = IncrementalMerkleTree::from(root, poseidon_hasher);
let level: u32 = 4;
let leaf = 0x00;
let paths = (
0x00,
&[
0x01,
0x26059ac500f935d65bf50b096f757fe1dcb3568822d4e4cb7a8dc95f7bbd24f7,
0x04,
],
);

imt.membership(level, leaf, paths);
}

#[test]
fn test_merkle_tree_add() {
let old_root = 0x1910f234d14bea7c640841c9fd0d765e8599a4cd527285590e4159e66b912be1;
let mut mt = IncrementalMerkleTree::from(old_root, poseidon_hasher);

let leaf = 0x26059ac500f935d65bf50b096f757fe1dcb3568822d4e4cb7a8dc95f7bbd24f7;
let paths = (
0x01,
&[0x1910f234d14bea7c640841c9fd0d765e8599a4cd527285590e4159e66b912be1],
);
mt.add(leaf, paths);
}

#[test]
fn test_merkle_tree_delete() {
let old_root = 0x1e994ad2e1d1d8a4da2aed653184349f089ad06d16849b156c23634d2888a377;
let mut mt = IncrementalMerkleTree::from(old_root, poseidon_hasher);

let leaf = 0x26059ac500f935d65bf50b096f757fe1dcb3568822d4e4cb7a8dc95f7bbd24f7;
let paths = (
0x00,
&[0x1910f234d14bea7c640841c9fd0d765e8599a4cd527285590e4159e66b912be1],
);
mt.delete(leaf, paths);

assert(mt.root == 0x1910f234d14bea7c640841c9fd0d765e8599a4cd527285590e4159e66b912be1);
}

#[test]
fn test_merkle_tree_update() {
let old_root = 0x1e994ad2e1d1d8a4da2aed653184349f089ad06d16849b156c23634d2888a377;
let mut mt = IncrementalMerkleTree::from(old_root, poseidon_hasher);

let old_leaf = 0x26059ac500f935d65bf50b096f757fe1dcb3568822d4e4cb7a8dc95f7bbd24f7;
let leaf = 0xd98561fb02ca04d00801dfdc118b2a24cea0351963587712a28d368041370e1;
let paths = (
0x00,
&[0x1910f234d14bea7c640841c9fd0d765e8599a4cd527285590e4159e66b912be1],
);
mt.update(leaf, old_leaf, paths);

assert(
mt.root
== poseidon_hasher([
0xd98561fb02ca04d00801dfdc118b2a24cea0351963587712a28d368041370e1,
0x1910f234d14bea7c640841c9fd0d765e8599a4cd527285590e4159e66b912be1,
]),
);
}
87 changes: 87 additions & 0 deletions packages/merkle-trees/src/incremental_merkle/tree.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use crate::incremental_merkle::IncrementalMerkleTree;
use crate::DynamicCalculator;

// /*
// * Transforms the key into into a big endian array of bits so that when determining the position
// * of a tree entry starting from the root node, the first array element to look at is the last.
// * @param key The key of a tree entry
// * @returns The path that determines the position of a key in the tree
// */
pub fn key_to_path(key: Field, length: u32) -> [u1] {
key.to_be_bits(length)
}

pub fn min(a: u32, b: u32) -> u32 {
if a < b { a } else { b }
}


impl DynamicCalculator<Field> for IncrementalMerkleTree {

fn calculate_root_dynamic(
self,
max_depth: u32,
leaf: Field,
siblings: (Field, [Field])
) -> Field {
let actual_depth = siblings.1.len() as u32;
let depth_to_process = min(max_depth, actual_depth);
let index_bits = siblings.0.to_le_bits(siblings.1.len() as u32);
let mut node = leaf;
// Iterate over the depth specified
for i in 0..depth_to_process {
let sibling = siblings.1[i];
if sibling != 0 {
let mut left = sibling;
let mut right = node;
if index_bits[i] == 0 {
left = node;
right = sibling;
}
node = (self.hasher)([left, right]);
}
}
node
}


/*
* Calculates two roots for a given leaf entry based on the passed array of siblings: one root
* for if the leaf entry was included in the tree and one for if the leaf entry was not included
* in the tree. This is useful for efficiently proving the membership of leaf entries for a
* tree while simultaneously modifying the tree.
* @param entry The key and value of an entry [k, v]
* @param siblings Contains the siblings from bottom to top
* @returns Two root nodes: the first one doesn't include entry, the second does
*/
fn calculate_two_roots(self, leaf: Field, siblings: (Field, [Field])) -> (Field, Field) {
let index_bits = siblings.0.to_le_bits(siblings.1.len() as u32);

let mut root_with_leaf = leaf;
let mut root_without_leaf = 0;

for i in 0..siblings.1.len() {
let sibling = siblings.1[i];

if (sibling != 0) {
if i == siblings.1.len() - 1 {
root_without_leaf = siblings.1[i];
}

if (index_bits[i] == 0) {
root_with_leaf = (self.hasher)([root_with_leaf, sibling]);

if (root_without_leaf != sibling) {
root_without_leaf = (self.hasher)([root_without_leaf, sibling]);
}
} else {
root_with_leaf = (self.hasher)([sibling, root_with_leaf]);
if (root_without_leaf != sibling) {
root_without_leaf = (self.hasher)([sibling, root_without_leaf]);
}
};
}
}
(root_without_leaf, root_with_leaf)
}
}
37 changes: 37 additions & 0 deletions packages/merkle-trees/src/lib.nr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod globals;
mod incremental_merkle;
mod merkle;
mod sparse_merkle;

Expand All @@ -7,6 +8,41 @@ trait Calculator<T> {
fn calculate_two_roots(self, entry: T, siblings: (Field, [Field])) -> (Field, Field);
}

trait DynamicCalculator<T> {
fn calculate_root_dynamic(self, max_depth: u32, entry: T, siblings: (Field, [Field])) -> Field;
fn calculate_two_roots(self, entry: T, siblings: (Field, [Field])) -> (Field, Field);
}

trait IncrementalMembershipProver<T, U> {
/**
* Proves that a leaf is a member of the Incremental Merkle Tree.
* @param leaf The leaf to prove
* @param path The hash path and indices
*/
fn membership(self, level: u32, entry: T, path: U);
}

trait IMT_Creator {
fn default(root: Field, hasher: fn([Field; 2]) -> Field) -> Self;

/**
* Imports an existing Incremental Merkle Tree (IncrementalMerkleTree) instance.
* @param hasher The hash function that is used to hash the nodes of the tree
* @param root The root of the tree
*/
fn from(root: Field, hasher: fn([Field; 2]) -> Field) -> Self {
Self::default(root, hasher)
}

/**
* Creates a new Incremental Merkle Tree (IncrementalMerkleTree) instance.
* @param hasher The hash function that is used to hash the nodes of the tree
*/
fn new(hasher: fn([Field; 2]) -> Field) -> Self {
Self::from(0, hasher)
}
}

trait SMT_Creator {
fn default(
root: Field,
Expand Down Expand Up @@ -57,6 +93,7 @@ trait MT_Creator {
}
}


trait MembershipProver<T, U> {
/**
* Proves that a leaf is a member of the tree.
Expand Down
20 changes: 20 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"outDir": "./dist",
"rootDir": ".",
"module": "commonjs",
"target": "es6",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true, // Add this line
"types": ["mocha", "node"]
},
"include": [
"./src/**/*.ts",
"./tests/**/*.ts"
],
"exclude": [
"./dist"
]
}
Loading
Loading