-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds sparse merkle tree in noname stdlib
#249
base: main
Are you sure you want to change the base?
Conversation
Hey @katat can you take a look? |
Thanks! Will take a look soon. |
hey it seems the checks failed due to enforce formating |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work!
I just had a round of review, and will need another rounds. I left some comments.
I think some of the arithmetic logic can be simplified using ifelse block.
More documentation on the arithmetic logic in the compute_roots
and next_state
, if they can't be simplified ifelse
, would be useful.
/// Field: mimc7 hash for key = 1 and values = [key,value] | ||
fn compute_leaf_hash(key: Field, value: Field) -> Field { | ||
let hash_values = [key, value]; | ||
return mimc::mimc7_hash(hash_values, 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it correct to provide 1 as the secret key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this was in the iden3 specification I think this is mainly for domain seperation of the internal and leaf hash functions other than that I cannot find a reason why
/// `is_update`: whether the operation is an update operation | ||
/// # Returns | ||
/// `[Bool;6]`: the next state | ||
fn next_state( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'd better to have more documentation on the logic for each transition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. It was written at the top but I will repeat it below
let new_key_bits = bits::to_bits(LEN , new_key); | ||
|
||
let mut level_inserted = old_level_inserted(enabled,siblings); | ||
let mut states = [[false;6]; LEN]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it be easier if it is a field value to represent the different states intead of using an array of booleans?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah but that makes the states computation a bit more difficult as in this case I can directly do boolean operations as my state transitions are dictated by booleans.
Also the assertion that there is only one state at a time
I tried the single state variable earlier but it got too difficult but when we do #248 it will be way easier maybe we can refactor then?
One more point is that circomlib
also uses 6 variables for some reason maybe it has less constraints this way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay. Finally got a chance to go through the code logic :D
Lgtm, @mimoo might want to have another round.
I am wondering if we can encapsulate the code a bit more using structs. The idea is like:
struct SMT {
root: Field,
...
}
// init smt with a root
fn SMT.new(root, ...) -> SMT {
...
}
// differentiate the operations via different methods
// once updated, it should update the SMT.root internally
fn SMT.insert(...)
fn SMT.update(...)
fn SMT.delete(...)
// for verifying the membership
fn SMT.verify_membership(...)
fn SMT.verify_non_membership(...)
For the non membership verification logic, I am wondering what are the assumptions.
There seems no comparisons in terms of branch navigations among the following two keys in order to indicate the key
doesn't exist because of the proof of exists of the not_found_key
.
Is this something agreed externally in practice, or actually something missing in the circuit? In other word, how can it prove the key
doesn't exist by proving its relationship with not_found_key
, which has been proven exist already.
|
||
|
||
|
||
// State Constants for computing new roots |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// State Constants for computing new roots | |
// States for computing new roots |
done[LEN - 2] = level_inserted[LEN - 1]; | ||
|
||
for idx in 1..(LEN-1) { | ||
let is_sibling_zero = siblings[(LEN - 2) - idx] == 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'd better to have a var to represent the reversed index:
let reversed_idx = (LEN - 1) - idx;
let mut done = [false; LEN]; | ||
|
||
|
||
level_inserted[(LEN - 1)] = !(siblings[LEN- 2] == 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
level_inserted[(LEN - 1)] = !(siblings[LEN- 2] == 0); | |
level_inserted[LEN - 1] = !(siblings[LEN - 2] == 0); |
/// This function get the level where the key will be inserted. level 0 is the root , 1 is next and so on.. | ||
/// To find this level all the child level must have a sibling of 0 and | ||
/// the parent level has a sibling != 0. The root is assumed to have a parent level with a sibling != 0 | ||
fn level_inserted(siblings: [Field;LEN]) -> [Bool;LEN] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't sibling need to be [Field; LEN - 1]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup there is no need for the full array but then I would have to slice the array which would be expensive so I think it is better to do this correct me if I'm wrong
|
||
|
||
level_inserted[(LEN - 1)] = !(siblings[LEN- 2] == 0); | ||
done[LEN - 2] = level_inserted[LEN - 1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be better to use a mutable bool variable for done
instead of array.
Then in the for loop
for idx in 1..(LEN - 1) {
...
done = done || level_inserted[(LEN - 1) - idx];
...
}
let not_found_leaf = compute_leaf_hash(not_found_key,not_found_val); | ||
let leaf = compute_leaf_hash(key,value); | ||
|
||
let nfk_bits = bits::to_bits(LEN , not_found_key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is nfk_bits
going to be used somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe add a comment here to note this is just for range checking.
Hey @katat the run fails as the test do not pass due to Line 1066 in a9a83b8
As my code fails due to unmatching types if Field {const : true} and Field { const: false} if I change it to something like this :
let is_match = match (&then_mono.typ, &else_mono.typ) {
(Some(then_ty), Some(else_ty)) => then_ty.match_expected(else_ty, false),
// handle all the case
}; Then it works |
This looks better I will refactor the code to something like this
When proving non membership proofs we want to give a merkle proof (root + siblings) to the position where the key should have been which can be a zero or a occupied leaf the
The not found key is used to compute the the |
but how does this relate to proving |
Oh yeah, nice spot! |
It is fine to just create a pr and point it to the comment above for reference.
Nice diagram, thanks!
|
This pr adds the
sparse merkle tree
tostdlib
and closes #223The testing was done against the output of
circomlibjs
you can find the script here.The approach taken is almost identical to the circom's implementation mentioned in the issue.