Skip to content
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

Closed
wants to merge 4 commits into from
Closed

Conversation

alxkzmn
Copy link
Contributor

@alxkzmn alxkzmn commented Nov 24, 2023

There are two circuit audit comments starting with // Audit:

There are also minor typo fixes as well as some changes suggested by clippy.

@alxkzmn alxkzmn marked this pull request as draft November 24, 2023 08:10
@alxkzmn alxkzmn marked this pull request as ready for review November 24, 2023 10:21
@@ -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).
Copy link
Member

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
Copy link
Member

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:

// 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.

$$ 1 * 2 * ( 3 - 2 ) - ( 1 - 3) - ( 2 - 4) = 2 \neq 0 $$

Please correct me if I'm missing something or if I'm wrong.

Copy link
Contributor Author

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

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

@enricobottazzi enricobottazzi Nov 27, 2023

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

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?

Copy link
Contributor Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

Copy link
Member

@enricobottazzi enricobottazzi left a 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?
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants