Skip to content

Commit ba8c49b

Browse files
committed
Schnorr trick for efficiency
1 parent 615a7cb commit ba8c49b

File tree

5 files changed

+214
-28
lines changed

5 files changed

+214
-28
lines changed

prover/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ group = "0.13.0"
2424
uint = "0.9.5"
2525
num-bigint = "0.4.3"
2626
num-traits = "0.2.15"
27+
num-integer = "0.1.45"
2728
rand = "0.8.5"
2829

2930
[dev-dependencies]

prover/benches/ConstrainCounts.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ In this document we keep track of constraint count.
1515
* 3860
1616
* 2824 with fixed base - 28% improvement
1717
* 2834 with low order check - still good
18+
* 1944 with trick in mult (not in Schnorr)
19+
* 1581 with trick in Schnorr equation
1820
* ATMS signature (102/72):
1921
* 283090 (3930 per threshold signature)
2022
* 208500 with fixed base in schnorr - 2890 per threshold - 27% improvement
21-
23+
* 145388 with trick in mult (not in Schnorr)
24+
* 118892 with trick in Schnorr equation
25+

prover/src/ecc/chip.rs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub struct EccConfig {
9191
scalar_mul: Column<Advice>,
9292

9393
/// Addition
94-
add: CondAddConfig,
94+
pub(crate) add: CondAddConfig,
9595

9696
/// Witness point
9797
witness_point: witness_point::Config,
@@ -101,7 +101,7 @@ pub struct EccConfig {
101101
#[derive(Clone, Debug)]
102102
pub struct EccChip {
103103
pub main_gate: MainGate<Base>,
104-
config: EccConfig,
104+
pub(crate) config: EccConfig,
105105
}
106106

107107
impl EccChip {
@@ -358,7 +358,6 @@ impl EccInstructions<AffinePoint> for EccChip {
358358
scalar: &Self::ScalarVar, // todo: we might want to have a type for scalar
359359
base: &Self::Point,
360360
) -> Result<Self::Point, Error> {
361-
self.main_gate.break_here(ctx)?;
362361
// Decompose scalar into bits
363362
let mut decomposition = self
364363
.main_gate
@@ -379,7 +378,11 @@ impl EccInstructions<AffinePoint> for EccChip {
379378
Value::known(Base::ONE),
380379
)?;
381380
ctx.assign_fixed(|| "base", self.main_gate.config.sb, Base::ONE)?;
382-
ctx.assign_fixed(|| "s_constant", self.main_gate.config.s_constant, - Base::ONE)?;
381+
ctx.assign_fixed(
382+
|| "s_constant",
383+
self.main_gate.config.s_constant,
384+
-Base::ONE,
385+
)?;
383386
ctx.next();
384387

385388
// We copy the aggregator to its right position
@@ -418,16 +421,10 @@ impl EccInstructions<AffinePoint> for EccChip {
418421
assigned_aggr = self.cond_add(ctx, &assigned_aggr, &assigned_aggr_q, &b)?;
419422

420423
// Now we conditionally perform addition. We begin by copying the base point to the `q` cell
421-
let base_x = ctx.copy_advice(
422-
|| "x point cond add",
423-
self.config.add.x_q,
424-
base.x.clone(),
425-
)?;
426-
let base_y = ctx.copy_advice(
427-
|| "y point cond add",
428-
self.config.add.y_q,
429-
base.y.clone(),
430-
)?;
424+
let base_x =
425+
ctx.copy_advice(|| "x point cond add", self.config.add.x_q, base.x.clone())?;
426+
let base_y =
427+
ctx.copy_advice(|| "y point cond add", self.config.add.y_q, base.y.clone())?;
431428

432429
let base_q = AssignedEccPoint {
433430
x: base_x,
@@ -442,7 +439,6 @@ impl EccInstructions<AffinePoint> for EccChip {
442439
}
443440
ctx.next();
444441

445-
self.main_gate.break_here(ctx)?;
446442
Ok(assigned_aggr)
447443
}
448444

prover/src/signatures/atms.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ mod tests {
252252

253253
#[test]
254254
fn atms_signature() {
255-
// const K: u32 = 23;
255+
// const K: u32 = 22; // we are 600_000 constraints above 2^21
256256
// const NUM_PARTIES: usize = 2001; // todo: multiple of three so Rescue does not complain. We should do some padding
257257
// const THRESHOLD: usize = 1602;
258258

@@ -301,6 +301,6 @@ mod tests {
301301
let prover =
302302
MockProver::run(K, &circuit, pi).expect("Failed to run ATMS verifier mock prover");
303303

304-
assert!(prover.verify().is_ok());
304+
prover.assert_satisfied();
305305
}
306306
}

prover/src/signatures/schnorr.rs

Lines changed: 195 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
//! Schnorr signature verification
22
33
use crate::ecc::chip::{AssignedEccPoint, EccChip, EccConfig, EccInstructions, ScalarVar};
4-
use crate::instructions::MainGateInstructions;
4+
use crate::instructions::{MainGateInstructions, Term};
5+
use num_integer::Integer;
6+
57
use crate::rescue::{
68
RescueCrhfGate, RescueCrhfGateConfig, RescueCrhfInstructions, RescueParametersBls,
79
};
8-
use crate::util::RegionCtx;
10+
use crate::util::{big_to_fe, fe_to_big, RegionCtx};
911
use crate::AssignedValue;
10-
use ff::Field;
12+
use ff::{Field, PrimeField};
1113
use group::prime::PrimeCurveAffine;
1214
use group::{Curve, Group};
1315
use halo2_proofs::circuit::{Chip, Value};
@@ -74,6 +76,11 @@ impl SchnorrVerifierGate {
7476
let four_pk = self.ecc_gate.add(ctx, &two_pk, &two_pk)?;
7577
let eight_pk = self.ecc_gate.add(ctx, &four_pk, &four_pk)?;
7678

79+
let assigned_generator = self.ecc_gate.witness_point(
80+
ctx,
81+
&Value::known(ExtendedPoint::from(SubgroupPoint::generator()).to_affine()),
82+
)?;
83+
7784
let one = self
7885
.ecc_gate
7986
.main_gate
@@ -85,21 +92,197 @@ impl SchnorrVerifierGate {
8592
.assert_not_equal(ctx, &eight_pk.y, &one)?;
8693

8794
let input_hash = [signature.0.x.clone(), pk.x.clone(), msg.clone()];
88-
let challenge = self.rescue_hash_gate.hash(ctx, &input_hash)?;
95+
let challenge = self.rescue_hash_gate.hash(ctx, &input_hash)?; // larger than mod with high prob
8996

90-
let lhs = self.ecc_gate.fixed_mul(
97+
let lhs = self.combined_mul(
9198
ctx,
92-
&signature.1,
93-
ExtendedPoint::from(SubgroupPoint::generator()).to_affine(),
99+
&signature.1 .0,
100+
&challenge,
101+
&assigned_generator,
102+
pk,
94103
)?;
95-
let rhs_1 = self.ecc_gate.mul(ctx, &ScalarVar(challenge), pk)?;
96-
let rhs = self.ecc_gate.add(ctx, &signature.0, &rhs_1)?;
97104

98-
self.ecc_gate.constrain_equal(ctx, &lhs, &rhs)?;
105+
self.ecc_gate.constrain_equal(ctx, &lhs, &signature.0)?;
99106

100107
Ok(())
101108
}
102109

110+
// We need to negate the second scalar prior to the addition
111+
fn combined_mul(
112+
&self,
113+
ctx: &mut RegionCtx<'_, Base>,
114+
scalar_1: &AssignedValue<Base>,
115+
scalar_2: &AssignedValue<Base>,
116+
base_1: &AssignedEccPoint,
117+
base_2: &AssignedEccPoint,
118+
) -> Result<AssignedEccPoint, Error> {
119+
// We compute a combined mul for `signature.1 * generator - challenge * pk`. For that
120+
// we first negate the challenge, and the compute use combined_mul. We negate with
121+
// respect to the Scalar modulus bytes. However, we allow the challenge to be above the
122+
// modulus, because the loss in security is not a concern. However, this slightly complicates
123+
// this negation.
124+
// We need to find (a,b) = challenge.div_remainder(modulus), then compute
125+
// neg_challenge = modulus - b, and prove that challenge + neg_challenge = (a + 1) * modulus
126+
// With this we should save ~250 constraints, so probably worth it.
127+
128+
let jub_jub_scalar_bytes = [
129+
183, 44, 247, 214, 94, 14, 151, 208, 130, 16, 200, 204, 147, 32, 104, 166, 0, 59, 52,
130+
1, 1, 59, 103, 6, 169, 175, 51, 101, 234, 180, 125, 14,
131+
];
132+
133+
let jubjub_mod =
134+
Base::from_bytes(&jub_jub_scalar_bytes).expect("Failed to deserialise modulus"); // This will not fail as jubjub mod is smaller than BLS
135+
136+
let mult_remainder = scalar_2.value().map(|&val| {
137+
let (mult, remainder) = fe_to_big(val).div_rem(&fe_to_big(jubjub_mod));
138+
[big_to_fe(mult), big_to_fe(remainder)]
139+
}).transpose_vec(2);
140+
141+
self.ecc_gate.main_gate.assert_zero_sum(
142+
ctx,
143+
&[Term::Assigned(scalar_2, - Base::ONE), Term::Unassigned(mult_remainder[0], jubjub_mod), Term::Unassigned(mult_remainder[1], Base::ONE)],
144+
Base::ZERO
145+
)?;
146+
147+
let neg_scalar_2 = self
148+
.ecc_gate
149+
.main_gate
150+
.assign_value(ctx, mult_remainder[1].map(|val| jubjub_mod - val))?;
151+
152+
self.ecc_gate.main_gate.assert_zero_sum(
153+
ctx,
154+
&[Term::Assigned(scalar_2, Base::ONE), Term::Assigned(&neg_scalar_2, Base::ONE), Term::Unassigned(mult_remainder[0] + Value::known(Base::ONE), - jubjub_mod)],
155+
Base::ZERO
156+
)?;
157+
158+
// Decompose scalar into bits
159+
let mut decomposition_1 =
160+
self.ecc_gate
161+
.main_gate
162+
.to_bits(ctx, &scalar_1, Base::NUM_BITS as usize)?;
163+
decomposition_1.reverse(); // to get MSB first
164+
165+
let mut decomposition_2 =
166+
self.ecc_gate
167+
.main_gate
168+
.to_bits(ctx, &neg_scalar_2, Base::NUM_BITS as usize)?;
169+
decomposition_2.reverse(); // to get MSB first
170+
171+
// Initialise the aggregator at zero
172+
let assigned_0x = ctx.assign_advice(
173+
|| "x of zero",
174+
self.ecc_gate.config.add.x_pr,
175+
Value::known(Base::ZERO),
176+
)?;
177+
ctx.next();
178+
179+
let assigned_0y = ctx.assign_advice(
180+
|| "y of zero",
181+
self.ecc_gate.config.add.y_pr,
182+
Value::known(Base::ONE),
183+
)?;
184+
ctx.assign_fixed(|| "base", self.ecc_gate.main_gate.config.sb, Base::ONE)?;
185+
ctx.assign_fixed(
186+
|| "s_constant",
187+
self.ecc_gate.main_gate.config.s_constant,
188+
-Base::ONE,
189+
)?;
190+
ctx.next();
191+
192+
// We copy the aggregator to its right position
193+
let assigned_aggr_x = ctx.copy_advice(
194+
|| "x aggregator",
195+
self.ecc_gate.config.add.x_pr,
196+
assigned_0x,
197+
)?;
198+
let assigned_aggr_y = ctx.copy_advice(
199+
|| "y aggregator",
200+
self.ecc_gate.config.add.y_pr,
201+
assigned_0y.clone(),
202+
)?;
203+
204+
let mut assigned_aggr = AssignedEccPoint {
205+
x: assigned_aggr_x,
206+
y: assigned_aggr_y,
207+
};
208+
209+
for (bit_1, bit_2) in decomposition_1.into_iter().zip(decomposition_2.into_iter()) {
210+
// We copy the aggregator into the `q` cell for doubling
211+
let assigned_aggr_x = ctx.copy_advice(
212+
|| "x aggregator double",
213+
self.ecc_gate.config.add.x_q,
214+
assigned_aggr.x.clone(),
215+
)?;
216+
let assigned_aggr_y = ctx.copy_advice(
217+
|| "y aggregator double",
218+
self.ecc_gate.config.add.y_q,
219+
assigned_aggr.y.clone(),
220+
)?;
221+
222+
let assigned_aggr_q = AssignedEccPoint {
223+
x: assigned_aggr_x,
224+
y: assigned_aggr_y,
225+
};
226+
227+
// We copy one for always performing doubling
228+
let b = ctx.copy_advice(|| "one", self.ecc_gate.config.add.b, assigned_0y.clone())?;
229+
230+
// We perform doubling
231+
assigned_aggr = self
232+
.ecc_gate
233+
.cond_add(ctx, &assigned_aggr, &assigned_aggr_q, &b)?;
234+
235+
// Now we conditionally perform addition of the first point. We begin by copying the base point to the `q` cell
236+
let base_x = ctx.copy_advice(
237+
|| "x point cond add",
238+
self.ecc_gate.config.add.x_q,
239+
base_1.x.clone(),
240+
)?;
241+
let base_y = ctx.copy_advice(
242+
|| "y point cond add",
243+
self.ecc_gate.config.add.y_q,
244+
base_1.y.clone(),
245+
)?;
246+
247+
let base_q = AssignedEccPoint {
248+
x: base_x,
249+
y: base_y,
250+
};
251+
252+
// We now copy the bit to its right position
253+
let b = ctx.copy_advice(|| "b1", self.ecc_gate.config.add.b, bit_1)?;
254+
255+
// Aggr = Aggr + cond_add
256+
assigned_aggr = self.ecc_gate.cond_add(ctx, &assigned_aggr, &base_q, &b)?;
257+
258+
// Now we conditionally perform addition of the second point. We begin by copying the base point to the `q` cell
259+
let base_x = ctx.copy_advice(
260+
|| "x point cond add",
261+
self.ecc_gate.config.add.x_q,
262+
base_2.x.clone(),
263+
)?;
264+
let base_y = ctx.copy_advice(
265+
|| "y point cond add",
266+
self.ecc_gate.config.add.y_q,
267+
base_2.y.clone(),
268+
)?;
269+
270+
let base_q = AssignedEccPoint {
271+
x: base_x,
272+
y: base_y,
273+
};
274+
275+
// We now copy the bit to its right position
276+
let b = ctx.copy_advice(|| "b2", self.ecc_gate.config.add.b, bit_2)?;
277+
278+
// Aggr = Aggr + cond_add
279+
assigned_aggr = self.ecc_gate.cond_add(ctx, &assigned_aggr, &base_q, &b)?;
280+
}
281+
ctx.next();
282+
283+
Ok(assigned_aggr)
284+
}
285+
103286
/// Assign a schnorr signature
104287
pub fn assign_sig(
105288
&self,
@@ -189,6 +372,7 @@ mod tests {
189372
let mut ctx = RegionCtx::new(region, offset);
190373
let assigned_sig =
191374
schnorr_gate.assign_sig(&mut ctx, &Value::known(self.signature))?;
375+
192376
let assigned_msg = schnorr_gate
193377
.ecc_gate
194378
.main_gate
@@ -234,6 +418,7 @@ mod tests {
234418
let prover =
235419
MockProver::run(K, &circuit, pi).expect("Failed to run Schnorr verifier mock prover");
236420

421+
prover.assert_satisfied();
237422
assert!(prover.verify().is_ok());
238423

239424
// We try to verify for a different message (the hash of the PI

0 commit comments

Comments
 (0)