From 5a9edcada156fd109bd065f6dc4f8601a9a692cf Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:10:28 +0100 Subject: [PATCH 01/15] chore: sets up project structure --- packages/circuits/noir/Nargo.toml | 2 ++ packages/circuits/noir/crates/smt_bn254/Nargo.toml | 5 +++++ packages/circuits/noir/crates/smt_bn254/src/lib.nr | 11 +++++++++++ packages/circuits/noir/crates/smt_bn254/src/utils.nr | 0 4 files changed, 18 insertions(+) create mode 100644 packages/circuits/noir/Nargo.toml create mode 100644 packages/circuits/noir/crates/smt_bn254/Nargo.toml create mode 100644 packages/circuits/noir/crates/smt_bn254/src/lib.nr create mode 100644 packages/circuits/noir/crates/smt_bn254/src/utils.nr diff --git a/packages/circuits/noir/Nargo.toml b/packages/circuits/noir/Nargo.toml new file mode 100644 index 000000000..60e8a4b40 --- /dev/null +++ b/packages/circuits/noir/Nargo.toml @@ -0,0 +1,2 @@ +[workspace] +members = ["crates/smt_bn254"] \ No newline at end of file diff --git a/packages/circuits/noir/crates/smt_bn254/Nargo.toml b/packages/circuits/noir/crates/smt_bn254/Nargo.toml new file mode 100644 index 000000000..bace4ce60 --- /dev/null +++ b/packages/circuits/noir/crates/smt_bn254/Nargo.toml @@ -0,0 +1,5 @@ +[package] +name = "smt_bn254" +authors = ["fabianschu"] +type = "lib" +compiler_version = ">=0.19.3" diff --git a/packages/circuits/noir/crates/smt_bn254/src/lib.nr b/packages/circuits/noir/crates/smt_bn254/src/lib.nr new file mode 100644 index 000000000..b874c958a --- /dev/null +++ b/packages/circuits/noir/crates/smt_bn254/src/lib.nr @@ -0,0 +1,11 @@ +fn not_equal(x: Field, y: Field) -> bool { + x != y +} + +#[test] +fn test_not_equal() { + assert(not_equal(1, 2)); + + // Uncomment to make test fail + // assert(not_equal(1, 1)); +} diff --git a/packages/circuits/noir/crates/smt_bn254/src/utils.nr b/packages/circuits/noir/crates/smt_bn254/src/utils.nr new file mode 100644 index 000000000..e69de29bb From e23f452cc6339d55e9ecae0a356a0cb69ecec731 Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:16:11 +0100 Subject: [PATCH 02/15] feat: adds hash function --- .../circuits/noir/crates/smt_bn254/src/lib.nr | 2 ++ .../noir/crates/smt_bn254/src/utils.nr | 23 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/packages/circuits/noir/crates/smt_bn254/src/lib.nr b/packages/circuits/noir/crates/smt_bn254/src/lib.nr index b874c958a..c45d5342a 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/lib.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/lib.nr @@ -1,3 +1,5 @@ +mod utils; + fn not_equal(x: Field, y: Field) -> bool { x != y } diff --git a/packages/circuits/noir/crates/smt_bn254/src/utils.nr b/packages/circuits/noir/crates/smt_bn254/src/utils.nr index e69de29bb..d6b6209f2 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/utils.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/utils.nr @@ -0,0 +1,23 @@ +use dep::std::hash::poseidon; + +fn hash(left: Field, right: Field, is_leaf: bool) -> Field { + if (is_leaf) { + poseidon::bn254::hash_3([left, right, 1]) + } else { + poseidon::bn254::hash_2([left, right]) + } +} + +#[test] +fn test_hash_leaf_node() { + let key = 20438969296305830531522370305156029982566273432331621236661483041446048135547; + let value = 17150136040889237739751319962368206600863150289695545292530539263327413090784; + assert(hash(key, value, true) == 7842913321420301106140788486336995496832503825951977327575501561489697540557); +} + +#[test] +fn test_hash_node() { + let left = 7901139023013500965671892970738327280683439536483910503527659926438417204955; + let right = 7842913321420301106140788486336995496832503825951977327575501561489697540557; + assert(hash(left, right, false) == 4657474665007910823901096287220097081233671466281873230928277896829046731272); +} \ No newline at end of file From e580b2362fc9232a04b54fab26916a2ca7287ed1 Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:18:19 +0100 Subject: [PATCH 03/15] feat: adds key_to_path --- packages/circuits/noir/crates/smt_bn254/src/utils.nr | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/circuits/noir/crates/smt_bn254/src/utils.nr b/packages/circuits/noir/crates/smt_bn254/src/utils.nr index d6b6209f2..dadd694d7 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/utils.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/utils.nr @@ -1,5 +1,9 @@ use dep::std::hash::poseidon; +fn key_to_path(key: Field) -> [u1] { + key.to_be_bits(256) +} + fn hash(left: Field, right: Field, is_leaf: bool) -> Field { if (is_leaf) { poseidon::bn254::hash_3([left, right, 1]) From c9d1bb57d343825100bd978b7d39ec21f84da768 Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:22:05 +0100 Subject: [PATCH 04/15] feat: adds verify --- .../circuits/noir/crates/smt_bn254/src/lib.nr | 45 ++++++++++++++++--- .../noir/crates/smt_bn254/src/utils.nr | 23 ++++++++-- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/packages/circuits/noir/crates/smt_bn254/src/lib.nr b/packages/circuits/noir/crates/smt_bn254/src/lib.nr index c45d5342a..57d6529c5 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/lib.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/lib.nr @@ -1,13 +1,44 @@ +use dep::std::option::Option; + mod utils; -fn not_equal(x: Field, y: Field) -> bool { - x != y +pub fn verify(entry: [Field; 2], matching_entry: [Option; 2], siblings: [Field; 256], root: Field) { + let mut calculcated_root: Field = 0; + let leaf_node = utils::hash(entry[0], entry[1], true); + let path = utils::key_to_path(entry[0]); + if matching_entry[0].is_none() | matching_entry[1].is_none() { + calculcated_root = utils::calculcate_root(leaf_node, siblings, path); + } else { + calculcated_root = utils::calculcate_root(utils::hash(matching_entry[0].unwrap(), matching_entry[1].unwrap(), true), siblings, path); + } + assert(calculcated_root == root); } #[test] -fn test_not_equal() { - assert(not_equal(1, 2)); - - // Uncomment to make test fail - // assert(not_equal(1, 1)); +fn test_verify_membership_proof() { + let root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; + let key = 18746990989203767017840856832962652635369613415011636432610873672704085238844; + let value = 10223238458026721676606706894638558676629446348345239719814856822628482567791; + let entry = [key, value]; + let matching_entry = [Option::none(), Option::none()]; + let mut siblings: [Field; 256] = [0; 256]; + siblings[254] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; + siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; + verify(entry, matching_entry, siblings, root); } + +#[test] +fn test_verify_non_membership_proof() { + let root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; + let key = 8459688297517826598613412977307486050019239051864711035321718508109192087854; + let value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; + let entry = [key, value]; + let matching_entry = [ + Option::some(13924553918840562069536446401916499801909138643922241340476956069386532478098), + Option::some(13761779908325789083343687318102407319424329800042729673292939195255502025802) + ]; + let mut siblings: [Field; 256] = [0; 256]; + siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; + siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; + verify(entry, matching_entry, siblings, root); +} \ No newline at end of file diff --git a/packages/circuits/noir/crates/smt_bn254/src/utils.nr b/packages/circuits/noir/crates/smt_bn254/src/utils.nr index dadd694d7..255ff97d0 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/utils.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/utils.nr @@ -1,10 +1,10 @@ use dep::std::hash::poseidon; -fn key_to_path(key: Field) -> [u1] { +pub fn key_to_path(key: Field) -> [u1] { key.to_be_bits(256) } -fn hash(left: Field, right: Field, is_leaf: bool) -> Field { +pub fn hash(left: Field, right: Field, is_leaf: bool) -> Field { if (is_leaf) { poseidon::bn254::hash_3([left, right, 1]) } else { @@ -12,6 +12,23 @@ fn hash(left: Field, right: Field, is_leaf: bool) -> Field { } } +pub fn calculcate_root(mut node: Field, siblings: [Field; 256], path: [u1]) -> Field { + for i in 0..256 { + let sibling = siblings[i]; + if sibling != 0 { + let mut left = sibling; + let mut right = node; + let own_position = path[i]; + if own_position == 0 { + left = node; + right = sibling; + } + node = hash(left, right, false); + } + } + node +} + #[test] fn test_hash_leaf_node() { let key = 20438969296305830531522370305156029982566273432331621236661483041446048135547; @@ -24,4 +41,4 @@ fn test_hash_node() { let left = 7901139023013500965671892970738327280683439536483910503527659926438417204955; let right = 7842913321420301106140788486336995496832503825951977327575501561489697540557; assert(hash(left, right, false) == 4657474665007910823901096287220097081233671466281873230928277896829046731272); -} \ No newline at end of file +} From b8159169fae6181057301a529aa447828ebe672a Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:25:47 +0100 Subject: [PATCH 05/15] feat: update --- .../circuits/noir/crates/smt_bn254/src/lib.nr | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/circuits/noir/crates/smt_bn254/src/lib.nr b/packages/circuits/noir/crates/smt_bn254/src/lib.nr index 57d6529c5..906210033 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/lib.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/lib.nr @@ -14,6 +14,28 @@ pub fn verify(entry: [Field; 2], matching_entry: [Option; 2], siblings: [ assert(calculcated_root == root); } +pub fn update(new_value: Field, old_entry: [Field; 2], old_root: Field, siblings: [Field; 256]) -> Field { + let key = old_entry[0]; + let old_value = old_entry[1]; + let mut old_parent: Field = utils::hash(key, old_value, true); + let mut new_parent: Field = utils::hash(key, new_value, true); + let path = utils::key_to_path(key); + for i in 0..256 { + 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 = 3532809757480436997969526334543526996242857122876262144596246439822675654128; @@ -41,4 +63,19 @@ fn test_verify_non_membership_proof() { siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; verify(entry, matching_entry, siblings, root); +} + +#[test] +fn test_update() { + let key = 8459688297517826598613412977307486050019239051864711035321718508109192087854; + let old_value = 8510347201346963732943571140849185725417245763047403804445415726302354045169; + let new_value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; + let old_entry = [key, old_value]; + let old_root = 4202917944688591919039016743999516589372052081571553696755434379850460220435; + let mut siblings: [Field; 256] = [0; 256]; + siblings[253] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; + siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; + siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; + let expected_new_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272; + assert(update(new_value, old_entry, old_root, siblings) == expected_new_root); } \ No newline at end of file From 80cfe98ee2100d2d07789816a71d48319cbcd440 Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:31:31 +0100 Subject: [PATCH 06/15] feat: add & delete --- .../circuits/noir/crates/smt_bn254/src/lib.nr | 73 +++++++++++++++++++ .../noir/crates/smt_bn254/src/utils.nr | 26 +++++++ 2 files changed, 99 insertions(+) diff --git a/packages/circuits/noir/crates/smt_bn254/src/lib.nr b/packages/circuits/noir/crates/smt_bn254/src/lib.nr index 906210033..3a8484da4 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/lib.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/lib.nr @@ -14,6 +14,28 @@ pub fn verify(entry: [Field; 2], matching_entry: [Option; 2], siblings: [ assert(calculcated_root == root); } +pub fn add(entry: [Field; 2], old_root: Field, siblings: [Field; 256]) -> Field { + let key = entry[0]; + let value = entry[1]; + if (old_root == 0) { + utils::hash(key, value, true) + } else { + let path = utils::key_to_path(key); + let (old, new) = utils::calculate_two_roots(utils::hash(key, value, true), siblings, path); + assert(old == old_root); + new + } +} + +pub fn delete(entry: [Field; 2], old_root: Field, siblings: [Field; 256]) -> Field { + let key = entry[0]; + let value = entry[1]; + let path = utils::key_to_path(key); + let (new, old) = utils::calculate_two_roots(utils::hash(key, value, true), siblings, path); + assert(old == old_root); + new +} + pub fn update(new_value: Field, old_entry: [Field; 2], old_root: Field, siblings: [Field; 256]) -> Field { let key = old_entry[0]; let old_value = old_entry[1]; @@ -65,6 +87,57 @@ fn test_verify_non_membership_proof() { verify(entry, matching_entry, siblings, root); } +#[test] +fn test_add_first_element() { + let key = 20438969296305830531522370305156029982566273432331621236661483041446048135547; + let value = 17150136040889237739751319962368206600863150289695545292530539263327413090784; + let entry = [key, value]; + let siblings: [Field; 256] = [0; 256]; + let zero_node = 0; + assert(add(entry, zero_node, siblings) == 7842913321420301106140788486336995496832503825951977327575501561489697540557); +} + + +#[test] +fn test_add_element_to_one_element_tree() { + let key = 8459688297517826598613412977307486050019239051864711035321718508109192087854; + let value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; + let entry = [key, value]; + let old_root = 7842913321420301106140788486336995496832503825951977327575501561489697540557; + let mut siblings: [Field; 256] = [0; 256]; + siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; + let expected_new_root = 6309163561753770186763792861087421800063032915545949912480764922611421686766; + assert(add(entry, old_root, siblings) == expected_new_root); +} + +#[test] +fn test_add_element_to_existing_tree() { + let key = 8459688297517826598613412977307486050019239051864711035321718508109192087854; + let value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; + let entry = [key, value]; + let old_root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; + let mut siblings: [Field; 256] = [0; 256]; + siblings[253] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; + siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; + siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; + let expected_new_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272; + assert(add(entry, old_root, siblings) == expected_new_root); +} + +#[test] +fn test_delete() { + let key = 8459688297517826598613412977307486050019239051864711035321718508109192087854; + let value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; + let entry = [key, value]; + let old_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272; + let mut siblings: [Field; 256] = [0; 256]; + siblings[253] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; + siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; + siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; + let expected_new_root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; + assert(delete(entry, old_root, siblings) == expected_new_root); +} + #[test] fn test_update() { let key = 8459688297517826598613412977307486050019239051864711035321718508109192087854; diff --git a/packages/circuits/noir/crates/smt_bn254/src/utils.nr b/packages/circuits/noir/crates/smt_bn254/src/utils.nr index 255ff97d0..0faa78050 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/utils.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/utils.nr @@ -29,6 +29,32 @@ pub fn calculcate_root(mut node: Field, siblings: [Field; 256], path: [u1]) -> F node } +pub fn calculate_two_roots(mut long_path_node: Field, siblings: [Field; 256], path: [u1]) -> (Field, Field) { + let mut short_path_node: Field = 0; + for i in 0..256 { + let sibling = siblings[i]; + if sibling != 0 { + if siblings[i - 1] == 0 { + short_path_node = siblings[i]; + } + let sibling = siblings[i]; + let own_position = path[i]; + if own_position == 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; From 915a859b4ec1a42b2b54b6a34b6696565febe2ce Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Fri, 1 Dec 2023 22:44:15 +0100 Subject: [PATCH 07/15] refactor: move hashing and path calculation to utils --- .../circuits/noir/crates/smt_bn254/src/lib.nr | 18 +++++------------- .../noir/crates/smt_bn254/src/utils.nr | 8 ++++++-- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/circuits/noir/crates/smt_bn254/src/lib.nr b/packages/circuits/noir/crates/smt_bn254/src/lib.nr index 3a8484da4..f5cba72ee 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/lib.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/lib.nr @@ -4,34 +4,26 @@ mod utils; pub fn verify(entry: [Field; 2], matching_entry: [Option; 2], siblings: [Field; 256], root: Field) { let mut calculcated_root: Field = 0; - let leaf_node = utils::hash(entry[0], entry[1], true); - let path = utils::key_to_path(entry[0]); if matching_entry[0].is_none() | matching_entry[1].is_none() { - calculcated_root = utils::calculcate_root(leaf_node, siblings, path); + calculcated_root = utils::calculcate_root(entry, siblings); } else { - calculcated_root = utils::calculcate_root(utils::hash(matching_entry[0].unwrap(), matching_entry[1].unwrap(), true), siblings, path); + calculcated_root = utils::calculcate_root([matching_entry[0].unwrap(), matching_entry[1].unwrap()], siblings); } assert(calculcated_root == root); } pub fn add(entry: [Field; 2], old_root: Field, siblings: [Field; 256]) -> Field { - let key = entry[0]; - let value = entry[1]; if (old_root == 0) { - utils::hash(key, value, true) + utils::hash(entry[0], entry[1], true) } else { - let path = utils::key_to_path(key); - let (old, new) = utils::calculate_two_roots(utils::hash(key, value, true), siblings, path); + let (old, new) = utils::calculate_two_roots(entry, siblings); assert(old == old_root); new } } pub fn delete(entry: [Field; 2], old_root: Field, siblings: [Field; 256]) -> Field { - let key = entry[0]; - let value = entry[1]; - let path = utils::key_to_path(key); - let (new, old) = utils::calculate_two_roots(utils::hash(key, value, true), siblings, path); + let (new, old) = utils::calculate_two_roots(entry, siblings); assert(old == old_root); new } diff --git a/packages/circuits/noir/crates/smt_bn254/src/utils.nr b/packages/circuits/noir/crates/smt_bn254/src/utils.nr index 0faa78050..0c578f096 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/utils.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/utils.nr @@ -12,7 +12,9 @@ pub fn hash(left: Field, right: Field, is_leaf: bool) -> Field { } } -pub fn calculcate_root(mut node: Field, siblings: [Field; 256], path: [u1]) -> Field { +pub fn calculcate_root(entry: [Field; 2], siblings: [Field; 256]) -> Field { + let path = key_to_path(entry[0]); + let mut node = hash(entry[0], entry[1], true); for i in 0..256 { let sibling = siblings[i]; if sibling != 0 { @@ -29,7 +31,9 @@ pub fn calculcate_root(mut node: Field, siblings: [Field; 256], path: [u1]) -> F node } -pub fn calculate_two_roots(mut long_path_node: Field, siblings: [Field; 256], path: [u1]) -> (Field, Field) { +pub fn calculate_two_roots(entry: [Field; 2], siblings: [Field; 256]) -> (Field, Field) { + let path = key_to_path(entry[0]); + let mut long_path_node = hash(entry[0], entry[1], true); let mut short_path_node: Field = 0; for i in 0..256 { let sibling = siblings[i]; From 9f7ced4ecfb2b4c7eb91079677eac092fb4ac460 Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Fri, 1 Dec 2023 23:10:14 +0100 Subject: [PATCH 08/15] ci: adds workflow for nargo tests --- .github/workflows/production.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 0203b9349..64e746687 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -99,6 +99,12 @@ jobs: path-to-lcov: ./coverage/${{ matrix.type }}/lcov.info parallel: true + - name: Install Nargo + uses: noir-lang/noirup@v0.1.3 + + - name: Run nargo test + run: cd packages/circuits/noir/ && nargo test + coverage: runs-on: ubuntu-latest needs: test From 373f8ef644fea74913b35b1bf9e3b31b896bf2eb Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Fri, 1 Dec 2023 23:21:59 +0100 Subject: [PATCH 09/15] ci: runs nargo tests from script --- .github/workflows/production.yml | 6 ------ .github/workflows/pull-requests.yml | 3 +++ packages/circuits/package.json | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 64e746687..0203b9349 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -99,12 +99,6 @@ jobs: path-to-lcov: ./coverage/${{ matrix.type }}/lcov.info parallel: true - - name: Install Nargo - uses: noir-lang/noirup@v0.1.3 - - - name: Run nargo test - run: cd packages/circuits/noir/ && nargo test - coverage: runs-on: ubuntu-latest needs: test diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 08df69724..0c002c078 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -64,6 +64,9 @@ jobs: - name: Setup Circom run: wget https://github.com/iden3/circom/releases/latest/download/circom-linux-amd64 && sudo mv ./circom-linux-amd64 /usr/bin/circom && sudo chmod +x /usr/bin/circom + - name: Install Nargo + uses: noir-lang/noirup@v0.1.3 + - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT diff --git a/packages/circuits/package.json b/packages/circuits/package.json index 6e0f284c9..56dd92ee5 100644 --- a/packages/circuits/package.json +++ b/packages/circuits/package.json @@ -16,7 +16,7 @@ "scripts": { "circom:compile": "circomkit compile", "circom:setup": "circomkit setup", - "test": "mocha" + "test": "mocha && cd noir && nargo test" }, "dependencies": { "circomlib": "^2.0.5" From 45f1c2b3144ac7dd8f7d3a380bfac336714f627e Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Sat, 2 Dec 2023 16:40:04 +0100 Subject: [PATCH 10/15] chore: adds tree visualisations --- .../circuits/noir/crates/smt_bn254/src/lib.nr | 73 +++++++++++++++---- 1 file changed, 58 insertions(+), 15 deletions(-) diff --git a/packages/circuits/noir/crates/smt_bn254/src/lib.nr b/packages/circuits/noir/crates/smt_bn254/src/lib.nr index f5cba72ee..e78a973f2 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/lib.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/lib.nr @@ -50,9 +50,54 @@ pub fn update(new_value: Field, old_entry: [Field; 2], old_root: Field, siblings new_parent } +/* +Visual representations of the trees used in the tests for reference + +The big tree corresponds to the tree that is used for +testing in @zk-kit/smt: + +big_tree_root: 46574...31272 +├── 1: 78429...40557 +│ ├── 1 +│ ├── v: 17150...90784 +│ └── k: 20438...35547 +└── 0: + ├── 1: 74148...2867 + │ ├── 1: 89272...68433 || This leaf + │ │ ├── 1 || is missing + │ │ ├── v: 85103...45170 || for the + │ │ └── k: 84596...08785 || small_tree_root + │ └── 0: 18126...22196 + │ ├── 1 + │ ├── v: 13761...25802 + │ └── k: 13924...78098 + └── 0: 79011...20495 + ├── 1 + ├── v: 10223...67791 + └── k: 18746...38844 + +The small tree lacks one leaf as indicated in the previous +tree and looks as follows: + +small_tree_root: 35328...54128 +├── 1: 78429...40557 +│ ├── 1 +│ ├── v: 17150...90784 +│ └── k: 20438...35547 +└── 0: + ├── 1: 18126...22196 + │ ├── 1 + │ ├── v: 13761...25802 + │ └── k: 13924...78098 + └── 0: 79011...20495 + ├── 1 + ├── v: 10223...67791 + └── k: 18746...38844 +*/ + #[test] fn test_verify_membership_proof() { - let root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; + let small_tree_root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; let key = 18746990989203767017840856832962652635369613415011636432610873672704085238844; let value = 10223238458026721676606706894638558676629446348345239719814856822628482567791; let entry = [key, value]; @@ -60,12 +105,12 @@ fn test_verify_membership_proof() { let mut siblings: [Field; 256] = [0; 256]; siblings[254] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; - verify(entry, matching_entry, siblings, root); + verify(entry, matching_entry, siblings, small_tree_root); } #[test] fn test_verify_non_membership_proof() { - let root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; + let small_tree_root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; let key = 8459688297517826598613412977307486050019239051864711035321718508109192087854; let value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; let entry = [key, value]; @@ -76,7 +121,7 @@ fn test_verify_non_membership_proof() { let mut siblings: [Field; 256] = [0; 256]; siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; - verify(entry, matching_entry, siblings, root); + verify(entry, matching_entry, siblings, small_tree_root); } #[test] @@ -89,7 +134,6 @@ fn test_add_first_element() { assert(add(entry, zero_node, siblings) == 7842913321420301106140788486336995496832503825951977327575501561489697540557); } - #[test] fn test_add_element_to_one_element_tree() { let key = 8459688297517826598613412977307486050019239051864711035321718508109192087854; @@ -98,8 +142,7 @@ fn test_add_element_to_one_element_tree() { let old_root = 7842913321420301106140788486336995496832503825951977327575501561489697540557; let mut siblings: [Field; 256] = [0; 256]; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; - let expected_new_root = 6309163561753770186763792861087421800063032915545949912480764922611421686766; - assert(add(entry, old_root, siblings) == expected_new_root); + assert(add(entry, old_root, siblings) == 6309163561753770186763792861087421800063032915545949912480764922611421686766); } #[test] @@ -107,13 +150,13 @@ fn test_add_element_to_existing_tree() { let key = 8459688297517826598613412977307486050019239051864711035321718508109192087854; let value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; let entry = [key, value]; - let old_root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; + let small_tree_root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; let mut siblings: [Field; 256] = [0; 256]; siblings[253] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; - let expected_new_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272; - assert(add(entry, old_root, siblings) == expected_new_root); + let big_tree_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272; + assert(add(entry, small_tree_root, siblings) == big_tree_root); } #[test] @@ -121,13 +164,13 @@ fn test_delete() { let key = 8459688297517826598613412977307486050019239051864711035321718508109192087854; let value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; let entry = [key, value]; - let old_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272; + let big_tree_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272; let mut siblings: [Field; 256] = [0; 256]; siblings[253] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; - let expected_new_root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; - assert(delete(entry, old_root, siblings) == expected_new_root); + let small_tree_root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; + assert(delete(entry, big_tree_root, siblings) == small_tree_root); } #[test] @@ -141,6 +184,6 @@ fn test_update() { siblings[253] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; - let expected_new_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272; - assert(update(new_value, old_entry, old_root, siblings) == expected_new_root); + let big_tree_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272; + assert(update(new_value, old_entry, old_root, siblings) == big_tree_root); } \ No newline at end of file From 2b38a9de10597329d0e0b3c2dd9c917e420430e9 Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Sat, 2 Dec 2023 18:19:14 +0100 Subject: [PATCH 11/15] refactor: tree depth as global var --- .../circuits/noir/crates/smt_bn254/src/lib.nr | 26 ++++++++++--------- .../noir/crates/smt_bn254/src/utils.nr | 11 ++++---- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/circuits/noir/crates/smt_bn254/src/lib.nr b/packages/circuits/noir/crates/smt_bn254/src/lib.nr index e78a973f2..1e4a1f33a 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/lib.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/lib.nr @@ -2,7 +2,9 @@ use dep::std::option::Option; mod utils; -pub fn verify(entry: [Field; 2], matching_entry: [Option; 2], siblings: [Field; 256], root: Field) { +global TREE_DEPTH: u32 = 256; + +pub fn verify(entry: [Field; 2], matching_entry: [Option; 2], siblings: [Field; TREE_DEPTH], root: Field) { let mut calculcated_root: Field = 0; if matching_entry[0].is_none() | matching_entry[1].is_none() { calculcated_root = utils::calculcate_root(entry, siblings); @@ -12,7 +14,7 @@ pub fn verify(entry: [Field; 2], matching_entry: [Option; 2], siblings: [ assert(calculcated_root == root); } -pub fn add(entry: [Field; 2], old_root: Field, siblings: [Field; 256]) -> Field { +pub fn add(entry: [Field; 2], old_root: Field, siblings: [Field; TREE_DEPTH]) -> Field { if (old_root == 0) { utils::hash(entry[0], entry[1], true) } else { @@ -22,19 +24,19 @@ pub fn add(entry: [Field; 2], old_root: Field, siblings: [Field; 256]) -> Field } } -pub fn delete(entry: [Field; 2], old_root: Field, siblings: [Field; 256]) -> Field { +pub fn delete(entry: [Field; 2], old_root: Field, siblings: [Field; TREE_DEPTH]) -> Field { let (new, old) = utils::calculate_two_roots(entry, siblings); assert(old == old_root); new } -pub fn update(new_value: Field, old_entry: [Field; 2], old_root: Field, siblings: [Field; 256]) -> Field { +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]; let mut old_parent: Field = utils::hash(key, old_value, true); let mut new_parent: Field = utils::hash(key, new_value, true); let path = utils::key_to_path(key); - for i in 0..256 { + for i in 0..TREE_DEPTH { let sibling = siblings[i]; if sibling != 0 { if path[i] == 0 { @@ -102,7 +104,7 @@ fn test_verify_membership_proof() { let value = 10223238458026721676606706894638558676629446348345239719814856822628482567791; let entry = [key, value]; let matching_entry = [Option::none(), Option::none()]; - let mut siblings: [Field; 256] = [0; 256]; + let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH]; siblings[254] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; verify(entry, matching_entry, siblings, small_tree_root); @@ -118,7 +120,7 @@ fn test_verify_non_membership_proof() { Option::some(13924553918840562069536446401916499801909138643922241340476956069386532478098), Option::some(13761779908325789083343687318102407319424329800042729673292939195255502025802) ]; - let mut siblings: [Field; 256] = [0; 256]; + let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH]; siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; verify(entry, matching_entry, siblings, small_tree_root); @@ -129,7 +131,7 @@ fn test_add_first_element() { let key = 20438969296305830531522370305156029982566273432331621236661483041446048135547; let value = 17150136040889237739751319962368206600863150289695545292530539263327413090784; let entry = [key, value]; - let siblings: [Field; 256] = [0; 256]; + let siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH]; let zero_node = 0; assert(add(entry, zero_node, siblings) == 7842913321420301106140788486336995496832503825951977327575501561489697540557); } @@ -140,7 +142,7 @@ fn test_add_element_to_one_element_tree() { let value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; let entry = [key, value]; let old_root = 7842913321420301106140788486336995496832503825951977327575501561489697540557; - let mut siblings: [Field; 256] = [0; 256]; + let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH]; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; assert(add(entry, old_root, siblings) == 6309163561753770186763792861087421800063032915545949912480764922611421686766); } @@ -151,7 +153,7 @@ fn test_add_element_to_existing_tree() { let value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; let entry = [key, value]; let small_tree_root = 3532809757480436997969526334543526996242857122876262144596246439822675654128; - let mut siblings: [Field; 256] = [0; 256]; + let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH]; siblings[253] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; @@ -165,7 +167,7 @@ fn test_delete() { let value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; let entry = [key, value]; let big_tree_root = 4657474665007910823901096287220097081233671466281873230928277896829046731272; - let mut siblings: [Field; 256] = [0; 256]; + let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH]; siblings[253] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; @@ -180,7 +182,7 @@ fn test_update() { let new_value = 8510347201346963732943571140849185725417245763047403804445415726302354045170; let old_entry = [key, old_value]; let old_root = 4202917944688591919039016743999516589372052081571553696755434379850460220435; - let mut siblings: [Field; 256] = [0; 256]; + let mut siblings: [Field; TREE_DEPTH] = [0; TREE_DEPTH]; siblings[253] = 18126944477260144816572365299295230808286197301459941187567621915186392922196; siblings[254] = 14443001516360873457302534246953033880503978184674311810335857314606403404583; siblings[255] = 7842913321420301106140788486336995496832503825951977327575501561489697540557; diff --git a/packages/circuits/noir/crates/smt_bn254/src/utils.nr b/packages/circuits/noir/crates/smt_bn254/src/utils.nr index 0c578f096..65ccda432 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/utils.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/utils.nr @@ -1,7 +1,8 @@ use dep::std::hash::poseidon; +use crate::TREE_DEPTH; pub fn key_to_path(key: Field) -> [u1] { - key.to_be_bits(256) + key.to_be_bits(TREE_DEPTH) } pub fn hash(left: Field, right: Field, is_leaf: bool) -> Field { @@ -12,10 +13,10 @@ pub fn hash(left: Field, right: Field, is_leaf: bool) -> Field { } } -pub fn calculcate_root(entry: [Field; 2], siblings: [Field; 256]) -> Field { +pub fn calculcate_root(entry: [Field; 2], siblings: [Field; TREE_DEPTH]) -> Field { let path = key_to_path(entry[0]); let mut node = hash(entry[0], entry[1], true); - for i in 0..256 { + for i in 0..TREE_DEPTH { let sibling = siblings[i]; if sibling != 0 { let mut left = sibling; @@ -31,11 +32,11 @@ pub fn calculcate_root(entry: [Field; 2], siblings: [Field; 256]) -> Field { node } -pub fn calculate_two_roots(entry: [Field; 2], siblings: [Field; 256]) -> (Field, Field) { +pub fn calculate_two_roots(entry: [Field; 2], siblings: [Field; TREE_DEPTH]) -> (Field, Field) { let path = key_to_path(entry[0]); let mut long_path_node = hash(entry[0], entry[1], true); let mut short_path_node: Field = 0; - for i in 0..256 { + for i in 0..TREE_DEPTH { let sibling = siblings[i]; if sibling != 0 { if siblings[i - 1] == 0 { From 9650c9f09f2bc79cc20f9e5ce7e4bc88637b7fb3 Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Sat, 2 Dec 2023 18:27:31 +0100 Subject: [PATCH 12/15] fix: use entry key for path in non-membership proof --- packages/circuits/noir/crates/smt_bn254/src/lib.nr | 5 +++-- packages/circuits/noir/crates/smt_bn254/src/utils.nr | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/circuits/noir/crates/smt_bn254/src/lib.nr b/packages/circuits/noir/crates/smt_bn254/src/lib.nr index 1e4a1f33a..c85721c59 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/lib.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/lib.nr @@ -6,10 +6,11 @@ global TREE_DEPTH: u32 = 256; pub fn verify(entry: [Field; 2], matching_entry: [Option; 2], siblings: [Field; TREE_DEPTH], root: Field) { let mut calculcated_root: Field = 0; + let path = utils::key_to_path(entry[0]); if matching_entry[0].is_none() | matching_entry[1].is_none() { - calculcated_root = utils::calculcate_root(entry, siblings); + calculcated_root = utils::calculcate_root(entry, siblings, path); } else { - calculcated_root = utils::calculcate_root([matching_entry[0].unwrap(), matching_entry[1].unwrap()], siblings); + calculcated_root = utils::calculcate_root([matching_entry[0].unwrap(), matching_entry[1].unwrap()], siblings, path); } assert(calculcated_root == root); } diff --git a/packages/circuits/noir/crates/smt_bn254/src/utils.nr b/packages/circuits/noir/crates/smt_bn254/src/utils.nr index 65ccda432..0d6ca80bd 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/utils.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/utils.nr @@ -13,8 +13,7 @@ pub fn hash(left: Field, right: Field, is_leaf: bool) -> Field { } } -pub fn calculcate_root(entry: [Field; 2], siblings: [Field; TREE_DEPTH]) -> Field { - let path = key_to_path(entry[0]); +pub fn calculcate_root(entry: [Field; 2], siblings: [Field; TREE_DEPTH], path: [u1]) -> Field { let mut node = hash(entry[0], entry[1], true); for i in 0..TREE_DEPTH { let sibling = siblings[i]; From 0e1a24e53bd58624bfcd1d8135b5598591d8c331 Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Sat, 2 Dec 2023 20:52:17 +0100 Subject: [PATCH 13/15] docs: adds comments --- .../circuits/noir/crates/smt_bn254/src/lib.nr | 62 +++++++++++++++++-- .../noir/crates/smt_bn254/src/utils.nr | 54 ++++++++++++++-- 2 files changed, 107 insertions(+), 9 deletions(-) diff --git a/packages/circuits/noir/crates/smt_bn254/src/lib.nr b/packages/circuits/noir/crates/smt_bn254/src/lib.nr index c85721c59..ca7a714bd 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/lib.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/lib.nr @@ -4,39 +4,93 @@ 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; 2], siblings: [Field; TREE_DEPTH], root: Field) { let mut calculcated_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); } 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); } assert(calculcated_root == root); } -pub fn add(entry: [Field; 2], old_root: Field, siblings: [Field; TREE_DEPTH]) -> Field { +/** + * 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(entry[0], entry[1], true) + utils::hash(new_entry[0], new_entry[1], true) } else { - let (old, new) = utils::calculate_two_roots(entry, siblings); + 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 sigbils 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); - let path = utils::key_to_path(key); + // starting from the botton 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 { diff --git a/packages/circuits/noir/crates/smt_bn254/src/utils.nr b/packages/circuits/noir/crates/smt_bn254/src/utils.nr index 0d6ca80bd..c9e5c48e8 100644 --- a/packages/circuits/noir/crates/smt_bn254/src/utils.nr +++ b/packages/circuits/noir/crates/smt_bn254/src/utils.nr @@ -1,10 +1,24 @@ use dep::std::hash::poseidon; 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) { poseidon::bn254::hash_3([left, right, 1]) @@ -13,15 +27,28 @@ pub fn hash(left: Field, right: Field, is_leaf: bool) -> Field { } } + +/* + * 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; - let own_position = path[i]; - if own_position == 0 { + if path[i] == 0 { left = node; right = sibling; } @@ -31,19 +58,36 @@ pub fn calculcate_root(entry: [Field; 2], siblings: [Field; TREE_DEPTH], path: [ 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 modifiying 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]; } - let sibling = siblings[i]; - let own_position = path[i]; - if own_position == 0 { + 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); From 32d0aa469a92e95e9f2d650c3dee7c1e34dc3dfa Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Sat, 2 Dec 2023 21:11:51 +0100 Subject: [PATCH 14/15] docs: add to README --- packages/circuits/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/circuits/README.md b/packages/circuits/README.md index 7ac1fb160..a8c406cbe 100644 --- a/packages/circuits/README.md +++ b/packages/circuits/README.md @@ -36,6 +36,12 @@ - Circom: - [PoseidonProof](./circom/poseidon-proof.circom): It proves the possession of a Poseidon pre-image without revealing the pre-image itself. - [BinaryMerkleRoot](./circom/binary-merkle-root.circom): It calculates the root of a binary Merkle tree using a provided proof-of-membership. +- Noir: + - [Sparse Merkle Tree PoseidonBN254](./noir/crates/smt_bn254/src/lib.nr): A reusable library of functions related to Sparse Merkle Trees based on the JS implementation of [@zk-kit/smt](../smt). The library uses the Poseidon hash to implement the following functions: + - verifying membership and non-membership proofs + - adding a new entry to a SMT + - updating an entry of an SMT + - deleting an existing entry from an SMT ## 🛠 Install @@ -52,3 +58,12 @@ or yarn: ```bash yarn add @zk-kit/circuits ``` + +### Using Nargo (for Noir circuits) + +In your Nargo.toml file, add the following dependency: + +```toml +[dependencies] +smt_bn254 = { tag = "v0.1.0", git = "https://github.com/privacy-scaling-explorations/zk-kit", directory="crates/smt_bn254" } +``` From 072335f285245ebf7e72ef2c50c4ca0e6298d12e Mon Sep 17 00:00:00 2001 From: Fabian Scherer <48454910+fabianschu@users.noreply.github.com> Date: Sat, 2 Dec 2023 21:50:08 +0100 Subject: [PATCH 15/15] docs: correct link for installation via Nargo --- packages/circuits/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/circuits/README.md b/packages/circuits/README.md index a8c406cbe..01dc94e1a 100644 --- a/packages/circuits/README.md +++ b/packages/circuits/README.md @@ -65,5 +65,5 @@ In your Nargo.toml file, add the following dependency: ```toml [dependencies] -smt_bn254 = { tag = "v0.1.0", git = "https://github.com/privacy-scaling-explorations/zk-kit", directory="crates/smt_bn254" } +smt_bn254 = { tag = "v0.1.0", git = "https://github.com/privacy-scaling-explorations/zk-kit/packages/circuits/noir", directory="crates/smt_bn254" } ```