-
Notifications
You must be signed in to change notification settings - Fork 35
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
Add circuit audit comments #191
Conversation
@@ -20,7 +20,7 @@ pub struct MerkleSumTreeConfig { | |||
/// Chip that performs various constraints related to a Merkle Sum Tree data structure such as: | |||
/// | |||
/// * `s * swap_bit * (1 - swap_bit) = 0` (if `bool_and_swap_selector` is toggled). It basically enforces that swap_bit is either a 0 or 1. | |||
/// * `s * (swap_bit * 2 * (elelment_r_cur - elelment_l_cur) - (elelment_l_next - elelment_l_cur) - (elelment_r_cur - elelment_r_next)) = 0`. Enforces that if the swap_bit is equal to 1, the values will be swapped on the next row (if `bool_and_swap_selector` is toggled). | |||
/// * `s * (swap_bit * 2 * (element_r_cur - element_l_cur) - (element_l_next - element_l_cur) - (element_r_cur - element_r_next)) = 0`. Enforces that if the swap_bit is equal to 1, the values will be swapped on the next row (if `bool_and_swap_selector` is toggled). |
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 you be able to split two lines?
// but still satisfy the combined equation. | ||
// For example, if element_l_cur = 3 and element_l_next = 1, and element_r_cur = 2 and element_r_next = 4, | ||
// the composite equation would still be satisfied because: | ||
// 3 − 1 + 4 − 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.
I understand the issue you've pointed out. A malicious prover could potentially exploit the switch in values at this point:
summa-solvency/zk_prover/src/chips/merkle_sum_tree.rs
Lines 223 to 235 in 7489c53
// Extract the value from the cell | |
let mut l1_val = l1.value().copied(); | |
let mut r1_val = r1.value().copied(); | |
// perform the swap according to the swap bit | |
// if swap_bit is 0 return (l1, r1) else return (r1, l1) | |
swap_bit.value().copied().map(|x| { | |
(l1_val, r1_val) = if x == Fp::zero() { | |
(l1_val, r1_val) | |
} else { | |
(r1_val, l1_val) | |
}; | |
}); |
However, considering the example provided, it seems that the constraint would not be satisfied.
Please correct me if I'm missing something or if I'm wrong.
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.
The example is for the case when the swap bit is zero: swap_bit * 2 * (element_r_cur - element_l_cur) - (element_l_next - element_l_cur) - (element_r_cur - element_r_next) = - (element_l_next - element_l_cur) - (element_r_cur - element_r_next) = element_l_cur - element_l_next + element_r_next - element_r_cur
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.
For example, 3 - 4 + 2 - 1 = 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.
I see. For better understanding in the example, perhaps assigning 5 to element_l_next
instead of 1 would make it clearer.
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.
Thanks for the comment. I think this is a security issue. For example setting:
element_l_cur = 3
element_r_cur = 1
element_l_next = 4
element_r_next = 2
In the case in which swap_bit = 0
would satisfy this constraint
summa-solvency/zk_prover/src/chips/merkle_sum_tree.rs
Lines 62 to 67 in 7489c53
let swap_constraint = s | |
* ((swap_bit | |
* Expression::Constant(Fp::from(2)) | |
* (element_r_cur.clone() - element_l_cur.clone()) | |
- (element_l_next - element_l_cur)) | |
- (element_r_cur - element_r_next)); |
as (0 * 2 * (1 - 3)) - (4 - 3) - ( 1 - 2) = 0
Which should not happen.
I had a look into how semaphore enforces the swapping constraint => https://github.com/semaphore-protocol/semaphore/blob/main/packages/circuits/tree.circom#L19-L34
In particular, they wire the two possible combinations (c[0] = [hash, sibling]
and c[1] = sibling, hash
) together with the path_index
into the MultiMux1(2)
template, which enforces the following constraints:
out[0] <== (c[0][1] - c[0][0])*s + c[0][0]
out[1] <== (c[1][1] - c[1][0])*s + c[1][0]
Applying this to summa, the two possible combinations are (element_l_cur, element_r_cur
and element_r_cur, element_l_cur
). Therefore we can set the following constraints:
element_l_next = (element_r_cur - element_l_cur)*s + element_l_cur
element_r_next = (element_l_cur - element_r_cur)*s + element_r_cur
Let's consider the case
element_l_cur = 3
element_r_cur = 4
element_l_next = 4
element_r_next = 3
swap_bit = 1
4 = (4 - 3) * 1 + 3
3 = (3 - 4) * 1 + 4
Now let's consider the malicious case
element_l_cur = 3
element_r_cur = 1
element_l_next = 4
element_r_next = 2
swap_bit = 0
4 != (1- 3) * 0 + 3
3 != (3 - 1) * 0 + 1
Would it be enough to fix the issue? What do you guys think?
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.
Yes, I think this is the solution.
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.
Agreed
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.
I replied to the two audit issues. Let's keep the PR open until we reach an agreement especially for the swapping constraint issue. When we are good, I'm gonna apply the solution for the two issues (together with the clippy fixes) in a separated PR
@@ -155,6 +155,7 @@ impl<const N_BYTES: usize> RangeCheckChip<N_BYTES> { | |||
} | |||
|
|||
/// Loads the lookup table with values from `0` to `2^8 - 1` | |||
// Audit: Is there a way to share the range check lookup table between all chip instantiations? |
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.
Yes, I agree that the lookup table should be build at a higher level and then passed downwards to the range_check
chip.
There are two circuit audit comments starting with // Audit:
There are also minor typo fixes as well as some changes suggested by clippy.