|
1 | 1 | **Input:** |
2 | | -- A list of signatures: `&[Option<AssignedSchnorrSignature>]` |
| 2 | +- A list of signatures: `&[AssignedSchnorrSignature]` |
3 | 3 | - Collect [Schnorr signatures][AssignedSchnorrSignature]. |
4 | | - - Use `Option`, because there might not be a signature for every index, but we want them to be 'indexed' in agreement with the public keys. |
| 4 | + - All positions must contain a signature (either valid or dummy). |
| 5 | + - Dummy signatures should be assigned for parties that did not sign to maintain circuit structure consistency. |
5 | 6 | - A list of public keys: `&[AssignedEccPoint]` |
6 | 7 | - Public keys of all the eligible parties even if they do not participate. |
7 | 8 | - Public keys are [Assigned ECC points][AssignedEccPoint]. |
|
13 | 14 |
|
14 | 15 | **Goal of the Circuit:** |
15 | 16 | - Verify that for the given message and public keys |
16 | | - - There are exactly a threshold amount of valid signatures |
| 17 | + - There are at least a threshold amount of valid signatures |
17 | 18 | - Given list of public keys pertain the committed public key. |
18 | | -- Note that the proof must include only threshold-many valid signatures even if the prover has more valid signatures. |
| 19 | +- Note that the circuit verifies all signatures (including dummy ones) and counts only the valid ones. |
| 20 | +- The circuit ensures that at some point during iteration, the count of valid signatures reaches the threshold. |
19 | 21 |
|
20 | 22 | **Algorithm:** |
21 | 23 | 1. Check whether given list of public keys actually produce the committed public key: |
|
37 | 39 | .main_gate |
38 | 40 | .assert_equal(ctx, &hashed_pks, commited_pks)?; |
39 | 41 | ``` |
40 | | -2. Count the valid signatures. |
| 42 | +2. Count valid signatures and check if threshold is reached. |
41 | 43 | * Initialize a counter and set it to `0`, |
42 | | - * Iterate through the signatures and public keys, |
43 | | - * Verify each signature with respect to the related public key and the given message, |
44 | | - * Increase the counter by `1` for each valid signature. |
| 44 | + * Initialize a flag `is_enough_sigs` and set it to `0` (false), |
| 45 | + * Iterate through ALL signatures and public keys (including dummy signatures), |
| 46 | + * For each signature: |
| 47 | + - Verify the signature with respect to the related public key and the given message, |
| 48 | + - If verification succeeds, the signature contributes `1` to the counter; if it fails (dummy signature), it contributes `0`, |
| 49 | + - Add the verification result to the counter, |
| 50 | + - Check if `counter == threshold`, |
| 51 | + - Update `is_enough_sigs` to be `is_enough_sigs OR (counter == threshold)`, |
| 52 | + * After the loop, assert that `is_enough_sigs == 1`. |
45 | 53 | ```ignore |
46 | 54 | let mut counter = self |
47 | 55 | .schnorr_gate |
48 | 56 | .ecc_gate |
49 | 57 | .main_gate |
50 | 58 | .assign_constant(ctx, Base::ZERO)?; |
51 | 59 |
|
| 60 | + let mut is_enough_sigs = self |
| 61 | + .schnorr_gate |
| 62 | + .ecc_gate |
| 63 | + .main_gate |
| 64 | + .assign_constant(ctx, Base::ZERO)?; |
| 65 | +
|
52 | 66 | for (sig, pk) in signatures.iter().zip(pks.iter()) { |
53 | | - if let Some(signature) = sig { |
54 | | - self.schnorr_gate.verify(ctx, signature, pk, msg)?; |
55 | | - counter = self.schnorr_gate.ecc_gate.main_gate.add_constant( |
56 | | - ctx, |
57 | | - &counter, |
58 | | - Base::one(), |
59 | | - )?; |
60 | | - } |
| 67 | + // Verify signature - returns 1 if valid, 0 if invalid (dummy) |
| 68 | + let is_verified = self.schnorr_gate.verify(ctx, &sig, pk, msg)?; |
| 69 | +
|
| 70 | + // Add verification result to counter |
| 71 | + counter = self.schnorr_gate.ecc_gate.main_gate.add(ctx, &counter, &is_verified)?; |
| 72 | +
|
| 73 | + // Check if we've reached the threshold |
| 74 | + let is_threshold_reached = self.schnorr_gate.ecc_gate.main_gate.is_equal(ctx, &counter, threshold)?; |
| 75 | +
|
| 76 | + // Update flag: once true, stays true (OR preserves the state) |
| 77 | + is_enough_sigs = self.schnorr_gate.ecc_gate.main_gate.or(ctx, &is_threshold_reached, &is_enough_sigs)?; |
61 | 78 | } |
| 79 | +
|
| 80 | + // Assert that we reached the threshold at some point |
| 81 | + self.schnorr_gate |
| 82 | + .ecc_gate |
| 83 | + .main_gate |
| 84 | + .assert_equal_to_constant(ctx, &is_enough_sigs, Base::ONE)?; |
62 | 85 | ``` |
63 | | -3. Check if the resulting count and the given threshold are equal. |
64 | | -```ignore |
65 | | -self.schnorr_gate |
66 | | - .ecc_gate |
67 | | - .main_gate |
68 | | - .assert_equal(ctx, &counter, threshold)?; |
69 | | -``` |
| 86 | +
|
| 87 | + **Possible Optimizations:** |
| 88 | +
|
| 89 | + 1. **Verify only threshold-many signatures (instead of all N)** |
| 90 | + - Use lookup tables to match signatures to public keys, reducing verifications from N to threshold. |
| 91 | + - Postponed because it increases verification cost (critical for on-chain verification). |
| 92 | +
|
| 93 | + 2. **Single comparison of the counter at the end: `assert_greater(counter, threshold)`** |
| 94 | + - Replace N equality checks and N OR operations with one comparison. |
| 95 | + - `assert_greater` is not implemented yet, requires range proofs and bit decomposition. |
0 commit comments