kimchi/circuits/polynomials/
endosclmul.rs

1//! This module implements short Weierstrass curve
2//! endomorphism optimised variable base
3//! scalar multiplication custom Plonk polynomials.
4
5use crate::{
6    circuits::{
7        argument::{Argument, ArgumentEnv, ArgumentType},
8        berkeley_columns::{BerkeleyChallengeTerm, BerkeleyChallenges},
9        constraints::ConstraintSystem,
10        expr::{
11            self,
12            constraints::{boolean, ExprOps},
13            Cache,
14        },
15        gate::{CircuitGate, GateType},
16        wires::{GateWires, COLUMNS},
17    },
18    curve::KimchiCurve,
19    proof::{PointEvaluations, ProofEvaluations},
20};
21use ark_ff::{Field, PrimeField};
22use core::marker::PhantomData;
23
24//~ We implement custom gate constraints for short Weierstrass curve
25//~ endomorphism optimised variable base scalar multiplication.
26//~
27//~ Given a finite field $\mathbb{F}_{q}$ of order $q$, if the order is not a multiple of 2 nor 3, then an
28//~ elliptic curve over $\mathbb{F}_{q}$ in short Weierstrass form is represented by the set of points $(x,y)$
29//~ that satisfy the following equation with
30//~ $a,b\in\mathbb{F}_{q}$
31//~ and
32//~ $4a^3+27b^2\neq_{\mathbb{F}_q} 0 $:
33//~ $$E(\mathbb{F}_q): y^2 = x^3 + a x + b$$
34//~ If $P=(x_p, y_p)$ and $T=(x_t, y_t)$ are two points in the curve $E(\mathbb{F}_q)$, the goal of this
35//~ operation is to perform the operation $2P±T$ efficiently as $(P±T)+P$.
36//~
37//~ `S = (P + (b ? T : −T)) + P`
38//~
39//~ The same algorithm can be used to perform other scalar multiplications, meaning it is
40//~ not restricted to the case $2\cdot P$, but it can be used for any arbitrary $k\cdot P$. This is done
41//~ by decomposing the scalar $k$ into its binary representation.
42//~ Moreover, for every step, there will be a one-bit constraint meant to differentiate between addition and subtraction
43//~ for the operation $(P±T)+P$:
44//~
45//~ In particular, the constraints of this gate take care of 4 bits of the scalar within a single EVBSM row.
46//~ When the scalar is longer (which will usually be the case), multiple EVBSM rows will be concatenated.
47//~
48//~ |  Row  |  0 |  1 |  2 |  3 |  4 |  5 |  6 |   7 |   8 |   9 |  10 |  11 |  12 |  13 |  14 |  Type |
49//~ |-------|----|----|----|----|----|----|----|-----|-----|-----|-----|-----|-----|-----|-----|-------|
50//~ |     i | xT | yT |  Ø |  Ø | xP | yP | n  |  xR |  yR |  s1 | s3  | b1  |  b2 |  b3 |  b4 | EVBSM |
51//~ |   i+1 |  = |  = |    |    | xS | yS | n' | xR' | yR' | s1' | s3' | b1' | b2' | b3' | b4' | EVBSM |
52//~
53//~ The layout of this gate (and the next row) allows for this chained behavior where the output point
54//~ of the current row $S$ gets accumulated as one of the inputs of the following row, becoming $P$ in
55//~ the next constraints. Similarly, the scalar is decomposed into binary form and $n$ ($n'$ respectively)
56//~ will store the current accumulated value and the next one for the check.
57//~
58//~ For readability, we define the following variables for the constraints:
59//~
60//~ * `endo` $:=$ `EndoCoefficient`
61//~ * `xq1` $:= (1 + ($`endo`$ - 1)\cdot b_1) \cdot x_t$
62//~ * `xq2` $:= (1 + ($`endo`$ - 1)\cdot b_3) \cdot x_t$
63//~ * `yq1` $:= (2\cdot b_2 - 1) \cdot y_t$
64//~ * `yq2` $:= (2\cdot b_4 - 1) \cdot y_t$
65//~
66//~ These are the 11 constraints that correspond to each EVBSM gate,
67//~ which take care of 4 bits of the scalar within a single EVBSM row:
68//~
69//~ * First block:
70//~   * `(xq1 - xp) * s1 = yq1 - yp`
71//~   * `(2 * xp – s1^2 + xq1) * ((xp – xr) * s1 + yr + yp) = (xp – xr) * 2 * yp`
72//~   * `(yr + yp)^2 = (xp – xr)^2 * (s1^2 – xq1 + xr)`
73//~ * Second block:
74//~   * `(xq2 - xr) * s3 = yq2 - yr`
75//~   * `(2*xr – s3^2 + xq2) * ((xr – xs) * s3 + ys + yr) = (xr – xs) * 2 * yr`
76//~   * `(ys + yr)^2 = (xr – xs)^2 * (s3^2 – xq2 + xs)`
77//~ * Booleanity checks:
78//~   * Bit flag $b_1$: `0 = b1 * (b1 - 1)`
79//~   * Bit flag $b_2$: `0 = b2 * (b2 - 1)`
80//~   * Bit flag $b_3$: `0 = b3 * (b3 - 1)`
81//~   * Bit flag $b_4$: `0 = b4 * (b4 - 1)`
82//~ * Binary decomposition:
83//~   * Accumulated scalar: `n_next = 16 * n + 8 * b1 + 4 * b2 + 2 * b3 + b4`
84//~
85//~ The constraints above are derived from the following EC Affine arithmetic equations:
86//~
87//~ * (1) => $(x_{q_1} - x_p) \cdot s_1 = y_{q_1} - y_p$
88//~ * (2&3) => $(x_p – x_r) \cdot s_2 = y_r + y_p$
89//~ * (2) => $(2 \cdot x_p + x_{q_1} – s_1^2) \cdot (s_1 + s_2) = 2 \cdot y_p$
90//~   * <=> $(2 \cdot x_p – s_1^2 + x_{q_1}) \cdot ((x_p – x_r) \cdot s_1 + y_r + y_p) = (x_p – x_r) \cdot 2 \cdot y_p$
91//~ * (3) => $s_1^2 - s_2^2 = x_{q_1} - x_r$
92//~   * <=> $(y_r + y_p)^2 = (x_p – x_r)^2 \cdot (s_1^2 – x_{q_1} + x_r)$
93//~ *
94//~ * (4) => $(x_{q_2} - x_r) \cdot s_3 = y_{q_2} - y_r$
95//~ * (5&6) => $(x_r – x_s) \cdot s_4 = y_s + y_r$
96//~ * (5) => $(2 \cdot x_r + x_{q_2} – s_3^2) \cdot (s_3 + s_4) = 2 \cdot y_r$
97//~   * <=> $(2 \cdot x_r – s_3^2 + x_{q_2}) \cdot ((x_r – x_s) \cdot s_3 + y_s + y_r) = (x_r – x_s) \cdot 2 \cdot y_r$
98//~ * (6) => $s_3^2 – s_4^2 = x_{q_2} - x_s$
99//~   * <=> $(y_s + y_r)^2 = (x_r – x_s)^2 \cdot (s_3^2 – x_{q_2} + x_s)$
100//~
101//~ Defining $s_2$ and $s_4$ as
102//~
103//~ * $s_2 := \frac{2 \cdot y_P}{2 * x_P + x_T - s_1^2} - s_1$
104//~ * $s_4 := \frac{2 \cdot y_R}{2 * x_R + x_T - s_3^2} - s_3$
105//~
106//~ Gives the following equations when substituting the values of $s_2$ and $s_4$:
107//~
108//~ 1. `(xq1 - xp) * s1 = (2 * b1 - 1) * yt - yp`
109//~ 2. `(2 * xp – s1^2 + xq1) * ((xp – xr) * s1 + yr + yp) = (xp – xr) * 2 * yp`
110//~ 3. `(yr + yp)^2 = (xp – xr)^2 * (s1^2 – xq1 + xr)`
111//~
112//~ 4. `(xq2 - xr) * s3 = (2 * b2 - 1) * yt - yr`
113//~ 5. `(2 * xr – s3^2 + xq2) * ((xr – xs) * s3 + ys + yr) = (xr – xs) * 2 * yr`
114//~ 6. `(ys + yr)^2 = (xr – xs)^2 * (s3^2 – xq2 + xs)`
115//~
116
117/// Implementation of group endomorphism optimised
118/// variable base scalar multiplication custom Plonk constraints.
119impl<F: PrimeField> CircuitGate<F> {
120    pub fn create_endomul(wires: GateWires) -> Self {
121        CircuitGate::new(GateType::EndoMul, wires, vec![])
122    }
123
124    /// Verify the `EndoMul` gate.
125    ///
126    /// # Errors
127    ///
128    /// Will give error if `self.typ` is not `GateType::EndoMul`, or `constraint evaluation` fails.
129    pub fn verify_endomul<
130        const FULL_ROUNDS: usize,
131        G: KimchiCurve<FULL_ROUNDS, ScalarField = F>,
132    >(
133        &self,
134        row: usize,
135        witness: &[Vec<F>; COLUMNS],
136        cs: &ConstraintSystem<F>,
137    ) -> Result<(), String> {
138        ensure_eq!(self.typ, GateType::EndoMul, "incorrect gate type");
139
140        let this: [F; COLUMNS] = core::array::from_fn(|i| witness[i][row]);
141        let next: [F; COLUMNS] = core::array::from_fn(|i| witness[i][row + 1]);
142
143        let pt = F::from(123456u64);
144
145        let constants = expr::Constants {
146            mds: &G::sponge_params().mds,
147            endo_coefficient: cs.endo,
148            zk_rows: cs.zk_rows,
149        };
150        let challenges = BerkeleyChallenges {
151            alpha: F::zero(),
152            beta: F::zero(),
153            gamma: F::zero(),
154            joint_combiner: F::zero(),
155        };
156
157        let evals: ProofEvaluations<PointEvaluations<G::ScalarField>> =
158            ProofEvaluations::dummy_with_witness_evaluations(this, next);
159
160        let constraints = EndosclMul::constraints(&mut Cache::default());
161        for (i, c) in constraints.iter().enumerate() {
162            match c.evaluate_(cs.domain.d1, pt, &evals, &constants, &challenges) {
163                Ok(x) => {
164                    if x != F::zero() {
165                        return Err(format!("Bad endo equation {i}"));
166                    }
167                }
168                Err(e) => return Err(format!("evaluation failed: {e}")),
169            }
170        }
171
172        Ok(())
173    }
174
175    pub fn endomul(&self) -> F {
176        if self.typ == GateType::EndoMul {
177            F::one()
178        } else {
179            F::zero()
180        }
181    }
182}
183
184/// Implementation of the `EndosclMul` gate.
185#[derive(Default)]
186pub struct EndosclMul<F>(PhantomData<F>);
187
188impl<F> Argument<F> for EndosclMul<F>
189where
190    F: PrimeField,
191{
192    const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::EndoMul);
193    const CONSTRAINTS: u32 = 11;
194
195    fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
196        env: &ArgumentEnv<F, T>,
197        cache: &mut Cache,
198    ) -> Vec<T> {
199        let b1 = env.witness_curr(11);
200        let b2 = env.witness_curr(12);
201        let b3 = env.witness_curr(13);
202        let b4 = env.witness_curr(14);
203
204        let xt = env.witness_curr(0);
205        let yt = env.witness_curr(1);
206
207        let xs = env.witness_next(4);
208        let ys = env.witness_next(5);
209
210        let xp = env.witness_curr(4);
211        let yp = env.witness_curr(5);
212
213        let xr = env.witness_curr(7);
214        let yr = env.witness_curr(8);
215
216        let s1 = env.witness_curr(9);
217        let s3 = env.witness_curr(10);
218
219        let endo_minus_1 = env.endo_coefficient() - T::one();
220        let xq1 = cache.cache((T::one() + b1.clone() * endo_minus_1.clone()) * xt.clone());
221        let xq2 = cache.cache((T::one() + b3.clone() * endo_minus_1) * xt);
222
223        let yq1 = (b2.double() - T::one()) * yt.clone();
224        let yq2 = (b4.double() - T::one()) * yt;
225
226        let s1_squared = cache.cache(s1.square());
227        let s3_squared = cache.cache(s3.square());
228
229        // n_next = 16*n + 8*b1 + 4*b2 + 2*b3 + b4
230        let n = env.witness_curr(6);
231        let n_next = env.witness_next(6);
232        let n_constraint =
233            (((n.double() + b1.clone()).double() + b2.clone()).double() + b3.clone()).double()
234                + b4.clone()
235                - n_next;
236
237        let xp_xr = cache.cache(xp.clone() - xr.clone());
238        let xr_xs = cache.cache(xr.clone() - xs.clone());
239
240        let ys_yr = cache.cache(ys + yr.clone());
241        let yr_yp = cache.cache(yr.clone() + yp.clone());
242
243        vec![
244            // verify booleanity of the scalar bits
245            boolean(&b1),
246            boolean(&b2),
247            boolean(&b3),
248            boolean(&b4),
249            // (xq1 - xp) * s1 = yq1 - yp
250            ((xq1.clone() - xp.clone()) * s1.clone()) - (yq1 - yp.clone()),
251            // (2*xp – s1^2 + xq1) * ((xp - xr) * s1 + yr + yp) = (xp - xr) * 2*yp
252            (((xp.double() - s1_squared.clone()) + xq1.clone())
253                * ((xp_xr.clone() * s1) + yr_yp.clone()))
254                - (yp.double() * xp_xr.clone()),
255            // (yr + yp)^2 = (xp – xr)^2 * (s1^2 – xq1 + xr)
256            yr_yp.square() - (xp_xr.square() * ((s1_squared - xq1) + xr.clone())),
257            // (xq2 - xr) * s3 = yq2 - yr
258            ((xq2.clone() - xr.clone()) * s3.clone()) - (yq2 - yr.clone()),
259            // (2*xr – s3^2 + xq2) * ((xr – xs) * s3 + ys + yr) = (xr - xs) * 2*yr
260            (((xr.double() - s3_squared.clone()) + xq2.clone())
261                * ((xr_xs.clone() * s3) + ys_yr.clone()))
262                - (yr.double() * xr_xs.clone()),
263            // (ys + yr)^2 = (xr – xs)^2 * (s3^2 – xq2 + xs)
264            ys_yr.square() - (xr_xs.square() * ((s3_squared - xq2) + xs)),
265            n_constraint,
266        ]
267    }
268}
269
270/// The result of performing an endoscaling: the accumulated curve point
271/// and scalar.
272pub struct EndoMulResult<F> {
273    pub acc: (F, F),
274    pub n: F,
275}
276
277/// Generates the `witness_curr` values for a series of endoscaling constraints.
278///
279/// # Panics
280///
281/// Will panic if `bits` length does not match the requirement.
282pub fn gen_witness<F: Field + core::fmt::Display>(
283    w: &mut [Vec<F>; COLUMNS],
284    row0: usize,
285    endo: F,
286    base: (F, F),
287    bits: &[bool],
288    acc0: (F, F),
289) -> EndoMulResult<F> {
290    let bits_per_row = 4;
291    let rows = bits.len() / 4;
292    assert_eq!(0, bits.len() % 4);
293
294    let bits: Vec<_> = bits.iter().map(|x| F::from(u64::from(*x))).collect();
295    let one = F::one();
296
297    let mut acc = acc0;
298    let mut n_acc = F::zero();
299
300    // TODO: Could be more efficient
301    for i in 0..rows {
302        let b1 = bits[i * bits_per_row];
303        let b2 = bits[i * bits_per_row + 1];
304        let b3 = bits[i * bits_per_row + 2];
305        let b4 = bits[i * bits_per_row + 3];
306
307        let (xt, yt) = base;
308        let (xp, yp) = acc;
309
310        let xq1 = (one + (endo - one) * b1) * xt;
311        let yq1 = (b2.double() - one) * yt;
312
313        let s1 = (yq1 - yp) / (xq1 - xp);
314        let s1_squared = s1.square();
315        // (2*xp – s1^2 + xq) * ((xp – xr) * s1 + yr + yp) = (xp – xr) * 2*yp
316        // => 2 yp / (2*xp – s1^2 + xq) = s1 + (yr + yp) / (xp – xr)
317        // => 2 yp / (2*xp – s1^2 + xq) - s1 = (yr + yp) / (xp – xr)
318        //
319        // s2 := 2 yp / (2*xp – s1^2 + xq) - s1
320        //
321        // (yr + yp)^2 = (xp – xr)^2 * (s1^2 – xq1 + xr)
322        // => (s1^2 – xq1 + xr) = (yr + yp)^2 / (xp – xr)^2
323        //
324        // => xr = s2^2 - s1^2 + xq
325        // => yr = s2 * (xp - xr) - yp
326        let s2 = yp.double() / (xp.double() + xq1 - s1_squared) - s1;
327
328        // (xr, yr)
329        let xr = xq1 + s2.square() - s1_squared;
330        let yr = (xp - xr) * s2 - yp;
331
332        let xq2 = (one + (endo - one) * b3) * xt;
333        let yq2 = (b4.double() - one) * yt;
334        let s3 = (yq2 - yr) / (xq2 - xr);
335        let s3_squared = s3.square();
336        let s4 = yr.double() / (xr.double() + xq2 - s3_squared) - s3;
337
338        let xs = xq2 + s4.square() - s3_squared;
339        let ys = (xr - xs) * s4 - yr;
340
341        let row = i + row0;
342
343        w[0][row] = base.0;
344        w[1][row] = base.1;
345        w[4][row] = xp;
346        w[5][row] = yp;
347        w[6][row] = n_acc;
348        w[7][row] = xr;
349        w[8][row] = yr;
350        w[9][row] = s1;
351        w[10][row] = s3;
352        w[11][row] = b1;
353        w[12][row] = b2;
354        w[13][row] = b3;
355        w[14][row] = b4;
356
357        acc = (xs, ys);
358
359        n_acc.double_in_place();
360        n_acc += b1;
361        n_acc.double_in_place();
362        n_acc += b2;
363        n_acc.double_in_place();
364        n_acc += b3;
365        n_acc.double_in_place();
366        n_acc += b4;
367    }
368    w[4][row0 + rows] = acc.0;
369    w[5][row0 + rows] = acc.1;
370    w[6][row0 + rows] = n_acc;
371
372    EndoMulResult { acc, n: n_acc }
373}