Skip to content

Commit e93d2ac

Browse files
authored
Fix ATMS verifier for circuit reusability across signature sets (#28)
1 parent c76c11e commit e93d2ac

File tree

6 files changed

+452
-92
lines changed

6 files changed

+452
-92
lines changed

prover/benches/atms.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,16 @@ impl Circuit<Base> for BenchCircuitAtmsSignature {
7272
.iter()
7373
.map(|&signature| {
7474
if let Some(sig) = signature {
75-
Some(
76-
atms_gate
77-
.schnorr_gate
78-
.assign_sig(&mut ctx, &Value::known(sig))
79-
.ok()?,
80-
)
75+
atms_gate
76+
.schnorr_gate
77+
.assign_sig(&mut ctx, &Value::known(sig))
8178
} else {
82-
None
79+
atms_gate
80+
.schnorr_gate
81+
.assign_dummy_sig(&mut ctx)
8382
}
8483
})
85-
.collect::<Vec<_>>();
84+
.collect::<Result<Vec<_>, Error>>()?;
8685
let assigned_pks = self
8786
.pks
8887
.iter()

prover/docs/signatures/atms/example.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,16 @@
6969
.iter()
7070
.map(|&signature| {
7171
if let Some(sig) = signature {
72-
Some(
73-
atms_gate
74-
.schnorr_gate
75-
.assign_sig(&mut ctx, &Value::known(sig))
76-
.ok()?,
77-
)
72+
atms_gate
73+
.schnorr_gate
74+
.assign_sig(&mut ctx, &Value::known(sig))
7875
} else {
79-
None
76+
atms_gate
77+
.schnorr_gate
78+
.assign_dummy_sig(&mut ctx)
8079
}
8180
})
82-
.collect::<Vec<_>>();
81+
.collect::<Result<Vec<_>, Error>>()?;
8382
let assigned_pks = self
8483
.pks
8584
.iter()
Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
**Input:**
2-
- A list of signatures: `&[Option<AssignedSchnorrSignature>]`
2+
- A list of signatures: `&[AssignedSchnorrSignature]`
33
- 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.
56
- A list of public keys: `&[AssignedEccPoint]`
67
- Public keys of all the eligible parties even if they do not participate.
78
- Public keys are [Assigned ECC points][AssignedEccPoint].
@@ -13,9 +14,10 @@
1314

1415
**Goal of the Circuit:**
1516
- 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
1718
- 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.
1921

2022
**Algorithm:**
2123
1. Check whether given list of public keys actually produce the committed public key:
@@ -37,33 +39,57 @@
3739
.main_gate
3840
.assert_equal(ctx, &hashed_pks, commited_pks)?;
3941
```
40-
2. Count the valid signatures.
42+
2. Count valid signatures and check if threshold is reached.
4143
* 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`.
4553
```ignore
4654
let mut counter = self
4755
.schnorr_gate
4856
.ecc_gate
4957
.main_gate
5058
.assign_constant(ctx, Base::ZERO)?;
5159
60+
let mut is_enough_sigs = self
61+
.schnorr_gate
62+
.ecc_gate
63+
.main_gate
64+
.assign_constant(ctx, Base::ZERO)?;
65+
5266
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)?;
6178
}
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)?;
6285
```
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.

prover/src/ecc/chip.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ pub trait EccInstructions<C: AffinePoint>: Chip<C::Base> + Clone + Debug {
197197
b: &Self::Point,
198198
) -> Result<(), Error>;
199199

200+
/// Returns `1` if point `a` is equal in value to point `b`, otherwise `0`.
201+
fn is_equal(
202+
&self,
203+
ctx: &mut RegionCtx<'_, JubjubBase>,
204+
a: &Self::Point,
205+
b: &Self::Point,
206+
) -> Result<AssignedCondition<JubjubBase>, Error>;
207+
200208
/// Witnesses the given point as a private input to the circuit.
201209
/// This allows the point to be the identity, mapped to (0, 0) in
202210
/// affine coordinates.
@@ -308,6 +316,19 @@ impl EccInstructions<JubjubAffine> for EccChip {
308316
Ok(())
309317
}
310318

319+
fn is_equal(
320+
&self,
321+
ctx: &mut RegionCtx<'_, JubjubBase>,
322+
a: &Self::Point,
323+
b: &Self::Point,
324+
) -> Result<AssignedCondition<JubjubBase>, Error> {
325+
let x_is_equal = self.main_gate.is_equal(ctx, &a.x, &b.x)?;
326+
let y_is_equal = self.main_gate.is_equal(ctx, &a.y, &b.y)?;
327+
let both_equal = self.main_gate.and(ctx, &x_is_equal, &y_is_equal)?;
328+
329+
Ok(both_equal)
330+
}
331+
311332
fn witness_point(
312333
&self,
313334
ctx: &mut RegionCtx<'_, JubjubBase>,

0 commit comments

Comments
 (0)