-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #234 from signorecello/zpedro/smt_pedersen
feat(circuits): add tests and Noir circuits for pedersen and poseidon2 proving
- Loading branch information
Showing
13 changed files
with
855 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] |
2 changes: 1 addition & 1 deletion
2
...circuits/noir/crates/smt_bn254/Nargo.toml → ...cuits/noir/crates/smt_pedersen/Nargo.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
118
packages/circuits/noir/crates/smt_pedersen/src/utils.nr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
Oops, something went wrong.