Skip to content

Commit

Permalink
Merge pull request #234 from signorecello/zpedro/smt_pedersen
Browse files Browse the repository at this point in the history
feat(circuits): add tests and Noir circuits for pedersen and poseidon2 proving
  • Loading branch information
cedoor authored Apr 9, 2024
2 parents 950dc5b + 6ab210f commit 7e78e57
Show file tree
Hide file tree
Showing 13 changed files with 855 additions and 125 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
],
"packageManager": "[email protected]",
"devDependencies": {
"@aztec/bb.js": "^0.32.0",
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.2",
"@types/glob": "^7.2.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/circuits/noir/Nargo.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[workspace]
members = ["crates/smt_bn254"]
members = ["crates/smt_pedersen", "crates/smt_poseidon", "crates/smt_poseidon2"]
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "smt_bn254"
name = "smt_pedersen"
authors = ["fabianschu"]
type = "lib"
compiler_version = ">=0.19.3"
200 changes: 200 additions & 0 deletions packages/circuits/noir/crates/smt_pedersen/src/lib.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
use dep::std::option::Option;

mod utils;

global TREE_DEPTH: u32 = 256;

/**
* Verifies a membership or a non-membership proof, ie it calculates the tree root
* based on an entry or matching entry and all siblings and compares that calculated root
* with the root that is passed to this function.
* @param entry Contains key and value of an entry: [key, value]
* @param matching_entry Contains [key, value] of a matching entry only for non-membership proofs
* @param siblings Contains array of siblings of entry / matching_entry
* @param root The expected root of the tree
*/
pub fn verify(entry: [Field; 2], matching_entry: [Option<Field>; 2], siblings: [Field; TREE_DEPTH], root: Field) {
let mut calculated_root: Field = 0;
let path = utils::key_to_path(entry[0]);
// if there is no matching_entry it is a membership proof
// if there is a matching_entry it is a non_membership proof
if matching_entry[0].is_none() | matching_entry[1].is_none() {
// membership proof: the root is calculated based on the entry, the siblings,
// and the path determined by the key of entry through consecutive hashing
calculated_root = utils::calculcate_root(entry, siblings, path);
} else {
// non-membership proof: the root is calculated based on the matching_entry, the siblings
// and the path that is determined by the key of entry. This makes sure that matching_entry is in fact
// a matching entry for entry meaning that it shares the same first bits as path
calculated_root = utils::calculcate_root([matching_entry[0].unwrap(), matching_entry[1].unwrap()], siblings, path);
}
assert(calculated_root == root);
}

/**
* Adds a NEW entry to an existing tree. Based on the siblings first validates the correctness of
* the old root. Then uses the new entry and the siblings to calculate the new tree root.
* NOTE: this function doesn't validate if the key for the new entry already exists in the tree, ie
* if the operation is actually an update. For this operation there is a separate function.
* @param entry Contains key and value of an entry: [key, value]
* @param old_root The root of the tree before the new entry is added
* @param siblings Contains array of siblings of entry / matching_entry
* @returns The new root after the addition
*/
pub fn add(new_entry: [Field; 2], old_root: Field, siblings: [Field; TREE_DEPTH]) -> Field {
// if the root node is zero the first leaf is added to the tree in which case
// the new root equals H(k,v,1)
// otherwise the correctness of the old root is validated based on the siblings after which
// the new root is calculated and returned
if (old_root == 0) {
utils::hash(new_entry[0], new_entry[1], true)
} else {
let (old, new) = utils::calculate_two_roots(new_entry, siblings);
assert(old == old_root);
new
}
}

/**
* Deletes an existing entry from a tree. Based on the siblings first does a membership proof
* of that existing entry and then calculates the new root (without the entry).
* @param entry Contains key and value of the to-be-deleted entry: [key, value]
* @param old_root The root of the tree if the entry is still included
* @param siblings Contains array of siblings of entry
* @returns The new root after the deletion
*/
pub fn delete(entry: [Field; 2], old_root: Field, siblings: [Field; TREE_DEPTH]) -> Field {
// proves membership of entry in the old root, then calculates and returns the new root
let (new, old) = utils::calculate_two_roots(entry, siblings);
assert(old == old_root);
new
}

/**
* Updates the value of an existing entry in a tree. Based on the siblings first does a membership proof
* first verifies the membership of the old entry. Then recalculates the new root.
* @param new_value The new value to be added (instead of old_entry[1])
* @param old_entry Contains key and value of the entry to be updated: [key, value]
* @param old_root The root of the tree before the update
* @param siblings Contains an array of siblings of old_entry
* @returns The new root after the update
*/
pub fn update(new_value: Field, old_entry: [Field; 2], old_root: Field, siblings: [Field; TREE_DEPTH]) -> Field {
let key = old_entry[0];
let old_value = old_entry[1];
// both the old entry and new entry share the same key that is used to calculate the path
let path = utils::key_to_path(key);
// old_parent is a container to temporarily store the nodes that ultimately lead to the OLD root
let mut old_parent: Field = utils::hash(key, old_value, true);
// new_parent is a container to temporarily store the nodes that ultimately lead to the NEW root
let mut new_parent: Field = utils::hash(key, new_value, true);
// starting from the bottom of the tree, for each level it checks whether there is a sibling and if
// that is the case, it hashes the two containers with the sibling and updates the containers with the
// resulting hashes until the uppermost level is reached aka the root node
for i in 0..TREE_DEPTH {
let sibling = siblings[i];
if sibling != 0 {
if path[i] == 0 {
new_parent = utils::hash(new_parent, sibling, false);
old_parent = utils::hash(old_parent, sibling, false);
} else {
new_parent = utils::hash(sibling, new_parent, false);
old_parent = utils::hash(sibling, old_parent, false);
}
}
}
assert(old_parent == old_root);
new_parent
}

#[test]
fn test_verify_membership_proof() {
let root = 10163036226218365628416274178218539053881692695713984759452839539868301499377;
let key = 20438969296305830531522370305156029982566273432331621236661483041446048135547;
let value = 17150136040889237739751319962368206600863150289695545292530539263327413090784;
let entry = [key, value];
let matching_entry = [Option::none(), Option::none()];
let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH];
siblings[253] = 16005544925904787818741660841048975184697157987475781759237359957800253937014;
siblings[254] = 652515607053222641420986801717363174355390181496524320106921145388058577161;
siblings[255] = 2708335068696962380008982791185936221130805655713417587265130133162794982262;
verify(entry, matching_entry, siblings, root);
}

#[test]
fn test_verify_non_membership_proof() {
let small_tree_root = 10163036226218365628416274178218539053881692695713984759452839539868301499377;
let key = 20438969296305830531522380305156029982566273432331621236661483041446048135547;
let value = 17150136040889237739751319962368206600863250289695545292530539263327413090784;
let entry = [key, value];
let matching_entry = [
Option::some(20438969296305830531522370305156029982566273432331621236661483041446048135547),
Option::some(17150136040889237739751319962368206600863150289695545292530539263327413090784)
];
let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH];
siblings[253] = 16005544925904787818741660841048975184697157987475781759237359957800253937014;
siblings[254] = 652515607053222641420986801717363174355390181496524320106921145388058577161;
siblings[255] = 2708335068696962380008982791185936221130805655713417587265130133162794982262;
verify(entry, matching_entry, siblings, small_tree_root);
}

#[test]
fn test_add_first_element() {
let key = 20438969296305830531522370305156029982566273432331621236661483041446048135547;
let value = 17150136040889237739751319962368206600863150289695545292530539263327413090784;
let entry = [key, value];
let siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH];
let zero_node = 0;
assert(add(entry, zero_node, siblings) == 2422708535743783816563452741494007579003622904961533867614614610167375232032);
}

#[test]
fn test_add_element_to_one_element_tree() {
let key = 7374494214024125590767526270082639043919066776944047470878693145844636921798;
let value = 3315292394704439116482935361251007857050519763420264982454883186141315324846;
let entry = [key, value];
let old_root = 2422708535743783816563452741494007579003622904961533867614614610167375232032;
let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH];
siblings[255] = 2422708535743783816563452741494007579003622904961533867614614610167375232032;
assert(add(entry, old_root, siblings) == 13995819305603022633355680906127521476353407789113491617487780281225566393218);
}

#[test]
fn test_add_element_to_existing_tree() {
let key = 12467743582502009806452203915647380852106587605639139696405928234368558796420;
let value = 7661601117049907361128926075270530269257730340678343102988736234309528818274;
let entry = [key, value];
let root = 13995819305603022633355680906127521476353407789113491617487780281225566393218;
let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH];
siblings[254] = 18033090709903916521866304938786912938158112601014366947614987606463992198712;
siblings[255] = 2422708535743783816563452741494007579003622904961533867614614610167375232032;
let big_tree_root = 8307334591379324778417663235463648615723981385559143500721691536202573318888;
assert(add(entry, root, siblings) == big_tree_root);
}

#[test]
fn test_delete() {
let key = 12467743582502009806452203915647380852106587605639139696405928234368558796420;
let value = 7661601117049907361128926075270530269257730340678343102988736234309528818274;
let entry = [key, value];
let big_tree_root = 8307334591379324778417663235463648615723981385559143500721691536202573318888;
let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH];
siblings[254] = 18033090709903916521866304938786912938158112601014366947614987606463992198712;
siblings[255] = 2422708535743783816563452741494007579003622904961533867614614610167375232032;
let small_tree_root = 13995819305603022633355680906127521476353407789113491617487780281225566393218;
assert(delete(entry, big_tree_root, siblings) == small_tree_root);
}

#[test]
fn test_update() {
let key = 12467743582502009806452203915647380852106587605639139696405928234368558796420;
let old_value = 7661601117049907361128926075270530269257730340678343102988736234309528818274;
let new_value = 7661601117049907361128926075270530269257730340678343102988736234309528818275;
let old_entry = [key, old_value];
let old_root = 8307334591379324778417663235463648615723981385559143500721691536202573318888;
let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH];
siblings[254] = 18033090709903916521866304938786912938158112601014366947614987606463992198712;
siblings[255] = 2422708535743783816563452741494007579003622904961533867614614610167375232032;
let big_tree_root = 15109801937676825792951435733056938044336635984778954078779396173428619028936;
assert(update(new_value, old_entry, old_root, siblings) == big_tree_root);
}
118 changes: 118 additions & 0 deletions packages/circuits/noir/crates/smt_pedersen/src/utils.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use dep::std::hash::pedersen_hash;
use crate::TREE_DEPTH;

/*
* 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) -> [u1] {
key.to_be_bits(TREE_DEPTH)
}

/*
* Calculates the poseidon bn254 hash. If a leaf node is created, the number 1 is appended to
* the hashed values as follows: H(k,v,1).
* @param left The left element of the hashing pair
* @param right The right element of the hashing pair
* @param is_leaf Whether what is created is a leaf node or not
* @returns The poseidon hash
*/
pub fn hash(left: Field, right: Field, is_leaf: bool) -> Field {
if (is_leaf) {
pedersen_hash([left, right, 1])
} else {
pedersen_hash([left, right])
}
}


/*
* Calculates the root for a given tree entry based on the passed array of siblings and the passed path.
* @param entry The key and value of an entry [k, v]
* @param siblings Contains the siblings from bottom to top
* @param path The position of the entry in the tree as represented by bits from bottom to top
* @returns The calculated root node
*/
pub fn calculcate_root(entry: [Field; 2], siblings: [Field; TREE_DEPTH], path: [u1]) -> Field {
// serves as container for hashes and is initialized to be the leaf node
let mut node = hash(entry[0], entry[1], true);
// iterates over the list of siblings until the first sibling is found
// arbitrarily assigns the sibling to be the left and the node to be the
// right element of the hashing pair unless the path indicates the opposite
// order in which case the order is changed. The new hash is stored in the container
// until the root node is reached and returned.
for i in 0..TREE_DEPTH {
let sibling = siblings[i];
if sibling != 0 {
let mut left = sibling;
let mut right = node;
if path[i] == 0 {
left = node;
right = sibling;
}
node = hash(left, right, false);
}
}
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
*/
pub fn calculate_two_roots(entry: [Field; 2], siblings: [Field; TREE_DEPTH]) -> (Field, Field) {
let path = key_to_path(entry[0]);
// long_path_node is a container for hashes to derive the root node for the tree that
// includes the entry
let mut long_path_node = hash(entry[0], entry[1], true);
// long_path_node is a container for hashes to derive the root node for the tree that
// doesn't include the entry
let mut short_path_node: Field = 0;
// iterate over the levels of the tree from bottom to top
for i in 0..TREE_DEPTH {
let sibling = siblings[i];
// After the first sibling is found, the processes are started to calculate the two root nodes.
// The calulcation of the root node that includes the entry is comparable to `calculate_root`.
// To calc the root node that doesn't include entry, the first sibling is put into the container
// and starting from each SUBSEQUENT iteration it is hashed with its sibling and the resulting hash
// again stored in the container until the root is reached
if sibling != 0 {
if siblings[i - 1] == 0 {
short_path_node = siblings[i];
}
if path[i] == 0 {
long_path_node = hash(long_path_node, sibling, false);
if(short_path_node != sibling) {
short_path_node = hash(short_path_node, sibling, false);
}
} else {
long_path_node = hash(sibling, long_path_node, false);
if(short_path_node != sibling) {
short_path_node = hash(sibling, short_path_node, false);
}
}
}
}
(short_path_node, long_path_node)
}

#[test]
fn test_hash_leaf_node() {
let key = 20438969296305830531522370305156029982566273432331621236661483041446048135547;
let value = 17150136040889237739751319962368206600863150289695545292530539263327413090784;
assert(hash(key, value, true) == 0x055b34560562b842e236f919e9a74ee345d7523d70f711e0ccdb22466c767c20);
}

#[test]
fn test_hash_node() {
let left = 7374494214024125590767526270082639043919066776944047470878693145844636921798;
let right = 3315292394704439116482935361251007857050519763420264982454883186141315324846;
assert(hash(left, right, false) == 0x05f8a24a037ad8567c042c14c5a8299649e0fe13df1cec40d381a3671bae9245);
}
5 changes: 5 additions & 0 deletions packages/circuits/noir/crates/smt_poseidon/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "smt_poseidon"
authors = ["fabianschu"]
type = "lib"
compiler_version = ">=0.19.3"
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ global TREE_DEPTH: u32 = 256;
* @param root The expected root of the tree
*/
pub fn verify(entry: [Field; 2], matching_entry: [Option<Field>; 2], siblings: [Field; TREE_DEPTH], root: Field) {
let mut calculcated_root: Field = 0;
let mut calculated_root: Field = 0;
let path = utils::key_to_path(entry[0]);
// if there is no matching_entry it is a membership proof
// if there is a matching_entry it is a non_membership proof
if matching_entry[0].is_none() | matching_entry[1].is_none() {
// membership proof: the root is calculated based on the entry, the siblings,
// and the path determined by the key of entry through consecutive hashing
calculcated_root = utils::calculcate_root(entry, siblings, path);
calculated_root = utils::calculcate_root(entry, siblings, path);
} else {
// non-membership proof: the root is calculated based on the matching_entry, the siblings
// and the path that is determined by the key of entry. This makes sure that matching_entry is in fact
// a matching entry for entry meaning that it shares the same first bits as path
calculcated_root = utils::calculcate_root([matching_entry[0].unwrap(), matching_entry[1].unwrap()], siblings, path);
calculated_root = utils::calculcate_root([matching_entry[0].unwrap(), matching_entry[1].unwrap()], siblings, path);
}
assert(calculcated_root == root);
assert(calculated_root == root);
}

/**
Expand Down Expand Up @@ -243,4 +243,4 @@ fn test_update() {
siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557;
let big_tree_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272;
assert(update(new_value, old_entry, old_root, siblings) == big_tree_root);
}
}
5 changes: 5 additions & 0 deletions packages/circuits/noir/crates/smt_poseidon2/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "smt_poseidon2"
authors = ["fabianschu"]
type = "lib"
compiler_version = ">=0.19.3"
Loading

0 comments on commit 7e78e57

Please sign in to comment.