-
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,4 +43,3 @@ jobs: | |
run: | | ||
cd backend | ||
cargo run --release --example summa_solvency_flow | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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). | ||||||||||||||||||||||||||||||||||||||||
/// If the swap_bit is equal to 0, the values will remain the same on the next row (if `bool_and_swap_selector` is toggled). | ||||||||||||||||||||||||||||||||||||||||
/// * `s * (left_balance + right_balance - computed_sum)`. It constraints the computed sum to be equal to the sum of the left and right balances (if `sum_selector` is toggled). | ||||||||||||||||||||||||||||||||||||||||
#[derive(Debug, Clone)] | ||||||||||||||||||||||||||||||||||||||||
|
@@ -59,6 +59,22 @@ impl<const N_ASSETS: usize> MerkleSumTreeChip<N_ASSETS> { | |||||||||||||||||||||||||||||||||||||||
let element_l_next = meta.query_advice(col_a, Rotation::next()); | ||||||||||||||||||||||||||||||||||||||||
let element_r_next = meta.query_advice(col_b, Rotation::next()); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
// Audit: The constraint is aimed at checking the correct swap of the values. If swap_bit is 1, | ||||||||||||||||||||||||||||||||||||||||
// element_l_cur == element_r_next and element_r_cur == element_l_next. | ||||||||||||||||||||||||||||||||||||||||
// If swap_bit is 0, element_l_cur == element_l_next and element_r_cur == element_r_next. | ||||||||||||||||||||||||||||||||||||||||
// However, if we combine the two equations, there's a potential for unintended solutions | ||||||||||||||||||||||||||||||||||||||||
// that satisfy the composite equation without satisfying the intended individual constraints. | ||||||||||||||||||||||||||||||||||||||||
// For the case where swap_bit = 0: | ||||||||||||||||||||||||||||||||||||||||
// element_l_cur − element_l_next + element_r_next − element_r_cur = 0 | ||||||||||||||||||||||||||||||||||||||||
// Here, it's theoretically possible to cheat by picking element_l_cur, element_l_next, | ||||||||||||||||||||||||||||||||||||||||
// element_r_cur, and element_r_next such that they don't individually satisfy | ||||||||||||||||||||||||||||||||||||||||
// element_l_cur − element_l_next = 0 and element_l_cur − element_l_next = 0 | ||||||||||||||||||||||||||||||||||||||||
// 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 commentThe 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
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 commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. I see. For better understanding in the example, perhaps assigning 5 to There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
In the case in which summa-solvency/zk_prover/src/chips/merkle_sum_tree.rs Lines 62 to 67 in 7489c53
as 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 (
Applying this to summa, the two possible combinations are (
Let's consider the case
Now let's consider the malicious case
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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Agreed |
||||||||||||||||||||||||||||||||||||||||
// even though neither element_l_cur = element_l_next nor element_r_cur = element_r_next. | ||||||||||||||||||||||||||||||||||||||||
// Splitting the constraint into two separate constraints would prevent this and also make the constraints more human-readable. | ||||||||||||||||||||||||||||||||||||||||
let swap_constraint = s | ||||||||||||||||||||||||||||||||||||||||
* ((swap_bit | ||||||||||||||||||||||||||||||||||||||||
* Expression::Constant(Fp::from(2)) | ||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,7 @@ pub struct RangeCheckConfig<const N_BYTES: usize> { | |
lookup_enable_selector: Selector, | ||
} | ||
|
||
/// Helper chip that verfiies that the value witnessed in a given cell lies within a given range defined by N_BYTES. | ||
/// Helper chip that verifies that the value witnessed in a given cell lies within a given range defined by N_BYTES. | ||
/// For example, Let's say we want to constraint 0x1f2f3f4f to be within the range N_BYTES=4. | ||
/// | ||
/// `z(0) = 0x1f2f3f4f` | ||
|
@@ -47,7 +47,7 @@ pub struct RangeCheckConfig<const N_BYTES: usize> { | |
/// | ||
/// The column z contains the witnessed value to be checked at offset 0 | ||
/// At offset i, the column z contains the value z(i+1) = (z(i) - k(i)) / 2^8 (shift right by 8 bits) where k(i) is the i-th decomposition big-endian of `value` | ||
/// The contraints that are enforced are: | ||
/// The constraints that are enforced are: | ||
/// - z(i) - 2^8⋅z(i+1) ∈ lookup_u8 (enabled by lookup_enable_selector at offset [0, N_BYTES - 1]) | ||
/// - z(N_BYTES) == 0 | ||
#[derive(Debug, Clone)] | ||
|
@@ -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 commentThe 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 |
||
pub fn load(&self, layouter: &mut impl Layouter<Fp>) -> Result<(), Error> { | ||
let range = 1 << (8); | ||
|
||
|
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?