Skip to main content

poly_commitment/
combine.rs

1//! Batch elliptic curve algorithms based on the batch-affine principle.
2//!
3//! The principle is the following:
4//!
5//! Usually, affine coordinates are not used because curve operations require
6//! division, which is very inefficient. However, if one is performing a large
7//! number of curve operations at the same time, then the inverses can be computed
8//! efficiently using the *batch inversion algorithm* which allows you to compute
9//! the inverses for an array of elements at a cost of 3 multiplications per element.
10//!
11//! With the reduced cost of inversion, in settings where you are computing many
12//! parallel elliptic curve operations, it is actually cheaper to use affine coordinates.
13//!
14//! Most algorithms in this module take an argument `denominators: &mut Vec<F>` which
15//! is a scratch array used for performing inversions. It is passed around to
16//! avoid re-allocating such a scratch array within each algorithm.
17
18use alloc::{vec, vec::Vec};
19use ark_ec::{
20    models::short_weierstrass::Affine as SWJAffine, short_weierstrass::SWCurveConfig,
21    AdditiveGroup, AffineRepr, CurveGroup,
22};
23use ark_ff::{BitIteratorBE, Field, One, PrimeField, Zero};
24use core::ops::AddAssign;
25use itertools::Itertools;
26use mina_poseidon::sponge::ScalarChallenge;
27#[cfg(feature = "parallel")]
28use rayon::prelude::*;
29
30fn add_pairs_in_place<P: SWCurveConfig>(pairs: &mut Vec<SWJAffine<P>>) {
31    let len = if pairs.len().is_multiple_of(2) {
32        pairs.len()
33    } else {
34        pairs.len() - 1
35    };
36    let mut denominators = pairs
37        .chunks_exact_mut(2)
38        .map(|p| {
39            if p[0].x == p[1].x {
40                if p[1].y.is_zero() {
41                    P::BaseField::one()
42                } else {
43                    p[1].y.double()
44                }
45            } else {
46                p[0].x - p[1].x
47            }
48        })
49        .collect::<Vec<_>>();
50
51    ark_ff::batch_inversion::<P::BaseField>(&mut denominators);
52
53    for (i, d) in (0..len).step_by(2).zip(denominators.iter()) {
54        let j = i / 2;
55        if pairs[i + 1].is_zero() {
56            pairs[j] = pairs[i];
57        } else if pairs[i].is_zero() {
58            pairs[j] = pairs[i + 1];
59        } else if pairs[i + 1].x == pairs[i].x
60            && (pairs[i + 1].y != pairs[i].y || pairs[i + 1].y.is_zero())
61        {
62            pairs[j] = SWJAffine::<P>::zero();
63        } else if pairs[i + 1].x == pairs[i].x && pairs[i + 1].y == pairs[i].y {
64            let sq = pairs[i].x.square();
65            let s = (sq.double() + sq + P::COEFF_A) * d;
66            let x = s.square() - pairs[i].x.double();
67            let y = -pairs[i].y - (s * (x - pairs[i].x));
68            pairs[j].x = x;
69            pairs[j].y = y;
70        } else {
71            let s = (pairs[i].y - pairs[i + 1].y) * d;
72            let x = s.square() - pairs[i].x - pairs[i + 1].x;
73            let y = -pairs[i].y - (s * (x - pairs[i].x));
74            pairs[j].x = x;
75            pairs[j].y = y;
76        }
77    }
78
79    let len = pairs.len();
80    if len % 2 == 1 {
81        pairs[len / 2] = pairs[len - 1];
82        pairs.truncate(len / 2 + 1);
83    } else {
84        pairs.truncate(len / 2);
85    }
86}
87
88/// Given arrays of curve points `v0` and `v1` do `v0[i] += v1[i]` for each i,
89/// assuming that for each `i`, `v0[i].x != v1[i].x` so we can use the ordinary
90/// addition formula and don't have to handle the edge cases of doubling and
91/// hitting the point at infinity.
92fn batch_add_assign_no_branch<P: SWCurveConfig>(
93    denominators: &mut [P::BaseField],
94    v0: &mut [SWJAffine<P>],
95    v1: &[SWJAffine<P>],
96) {
97    o1_utils::cfg_iter_mut!(denominators)
98        .enumerate()
99        .for_each(|(i, denom)| {
100            let p0 = v0[i];
101            let p1 = v1[i];
102            let d = p0.x - p1.x;
103            *denom = d;
104        });
105
106    ark_ff::batch_inversion::<P::BaseField>(denominators);
107
108    o1_utils::cfg_iter!(denominators)
109        .zip(o1_utils::cfg_iter_mut!(v0))
110        .zip(o1_utils::cfg_iter!(v1))
111        .for_each(|((d, p0), p1)| {
112            let s = (p0.y - p1.y) * d;
113            let x = s.square() - p0.x - p1.x;
114            let y = -p0.y - (s * (x - p0.x));
115            p0.x = x;
116            p0.y = y;
117        });
118}
119
120/// Given arrays of curve points `v0` and `v1` do `v0[i] += v1[i]` for each i.
121pub fn batch_add_assign<P: SWCurveConfig>(
122    denominators: &mut [P::BaseField],
123    v0: &mut [SWJAffine<P>],
124    v1: &[SWJAffine<P>],
125) {
126    o1_utils::cfg_iter_mut!(denominators)
127        .zip(o1_utils::cfg_iter!(v0))
128        .zip(o1_utils::cfg_iter!(v1))
129        .for_each(|((denom, p0), p1)| {
130            let d = if p0.x == p1.x {
131                if p1.y.is_zero() {
132                    P::BaseField::one()
133                } else {
134                    p1.y.double()
135                }
136            } else {
137                p0.x - p1.x
138            };
139            *denom = d;
140        });
141
142    ark_ff::batch_inversion::<P::BaseField>(denominators);
143
144    o1_utils::cfg_iter!(denominators)
145        .zip(o1_utils::cfg_iter_mut!(v0))
146        .zip(o1_utils::cfg_iter!(v1))
147        .for_each(|((d, p0), p1)| {
148            if p1.is_zero() {
149            } else if p0.is_zero() {
150                *p0 = *p1;
151            } else if p1.x == p0.x && (p1.y != p0.y || p1.y == P::BaseField::zero()) {
152                *p0 = SWJAffine::<P>::zero();
153            } else if p1.x == p0.x && p1.y == p0.y {
154                let sq = p0.x.square();
155                let s = (sq.double() + sq + P::COEFF_A) * d;
156                let x = s.square() - p0.x.double();
157                let y = -p0.y - (s * (x - p0.x));
158                p0.x = x;
159                p0.y = y;
160            } else {
161                let s = (p0.y - p1.y) * d;
162                let x = s.square() - p0.x - p1.x;
163                let y = -p0.y - (s * (x - p0.x));
164                p0.x = x;
165                p0.y = y;
166            }
167        });
168}
169
170fn affine_window_combine_base<P: SWCurveConfig>(
171    g1: &[SWJAffine<P>],
172    g2: &[SWJAffine<P>],
173    x1: P::ScalarField,
174    x2: P::ScalarField,
175) -> Vec<SWJAffine<P>> {
176    let g1g2 = {
177        let mut v: Vec<_> = (0..2 * g1.len())
178            .map(|i| {
179                let j = i / 2;
180                if i % 2 == 0 {
181                    g1[j]
182                } else {
183                    g2[j]
184                }
185            })
186            .collect();
187        add_pairs_in_place(&mut v);
188        v
189    };
190    assert!(g1g2.len() == g1.len());
191
192    let windows1 = BitIteratorBE::new(x1.into_bigint()).tuples();
193    let windows2 = BitIteratorBE::new(x2.into_bigint()).tuples();
194
195    let mut points = vec![SWJAffine::<P>::zero(); g1.len()];
196
197    let mut denominators = vec![P::BaseField::zero(); g1.len()];
198
199    let [g01_00, g10_00, g11_00, g00_01, g01_01, g10_01, g11_01, g00_10, g01_10, g10_10, g11_10, g00_11, g01_11, g10_11, g11_11] =
200        affine_shamir_window_table(&mut denominators, g1, g2);
201
202    for ((hi_1, lo_1), (hi_2, lo_2)) in windows1.zip(windows2) {
203        // double in place
204        for _ in 0..2 {
205            for i in 0..g1.len() {
206                denominators[i] = points[i].y.double();
207            }
208            ark_ff::batch_inversion::<P::BaseField>(&mut denominators);
209
210            // TODO: Use less memory
211            for i in 0..g1.len() {
212                let d = denominators[i];
213                let sq = points[i].x.square();
214                let s = (sq.double() + sq + P::COEFF_A) * d;
215                let x = s.square() - points[i].x.double();
216                let y = -points[i].y - (s * (x - points[i].x));
217                points[i].x = x;
218                points[i].y = y;
219            }
220        }
221
222        match ((hi_1, lo_1), (hi_2, lo_2)) {
223            ((false, false), (false, false)) => (),
224            ((false, true), (false, false)) => {
225                batch_add_assign(&mut denominators, &mut points, &g01_00);
226            }
227            ((true, false), (false, false)) => {
228                batch_add_assign(&mut denominators, &mut points, &g10_00);
229            }
230            ((true, true), (false, false)) => {
231                batch_add_assign(&mut denominators, &mut points, &g11_00);
232            }
233
234            ((false, false), (false, true)) => {
235                batch_add_assign(&mut denominators, &mut points, &g00_01);
236            }
237            ((false, true), (false, true)) => {
238                batch_add_assign(&mut denominators, &mut points, &g01_01);
239            }
240            ((true, false), (false, true)) => {
241                batch_add_assign(&mut denominators, &mut points, &g10_01);
242            }
243            ((true, true), (false, true)) => {
244                batch_add_assign(&mut denominators, &mut points, &g11_01);
245            }
246
247            ((false, false), (true, false)) => {
248                batch_add_assign(&mut denominators, &mut points, &g00_10);
249            }
250            ((false, true), (true, false)) => {
251                batch_add_assign(&mut denominators, &mut points, &g01_10);
252            }
253            ((true, false), (true, false)) => {
254                batch_add_assign(&mut denominators, &mut points, &g10_10);
255            }
256            ((true, true), (true, false)) => {
257                batch_add_assign(&mut denominators, &mut points, &g11_10);
258            }
259
260            ((false, false), (true, true)) => {
261                batch_add_assign(&mut denominators, &mut points, &g00_11);
262            }
263            ((false, true), (true, true)) => {
264                batch_add_assign(&mut denominators, &mut points, &g01_11);
265            }
266            ((true, false), (true, true)) => {
267                batch_add_assign(&mut denominators, &mut points, &g10_11);
268            }
269            ((true, true), (true, true)) => {
270                batch_add_assign(&mut denominators, &mut points, &g11_11);
271            }
272        }
273    }
274    points
275}
276
277fn batch_endo_in_place<P: SWCurveConfig>(endo_coeff: P::BaseField, ps: &mut [SWJAffine<P>]) {
278    o1_utils::cfg_iter_mut!(ps).for_each(|p| p.x *= endo_coeff);
279}
280
281fn batch_negate_in_place<P: SWCurveConfig>(ps: &mut [SWJAffine<P>]) {
282    o1_utils::cfg_iter_mut!(ps).for_each(|p| {
283        p.y = -p.y;
284    });
285}
286
287/// Uses a batch version of Algorithm 1 of
288/// <https://eprint.iacr.org/2019/1021.pdf> (on page 19) to compute `g1 +
289/// g2.scale(chal.to_field(endo_coeff))`
290fn affine_window_combine_one_endo_base<P: SWCurveConfig>(
291    endo_coeff: P::BaseField,
292    g1: &[SWJAffine<P>],
293    g2: &[SWJAffine<P>],
294    chal: &ScalarChallenge<P::ScalarField>,
295) -> Vec<SWJAffine<P>> {
296    fn assign<A: Copy>(dst: &mut [A], src: &[A]) {
297        let n = dst.len();
298        dst[..n].clone_from_slice(&src[..n]);
299    }
300
301    fn get_bit(limbs_lsb: &[u64], i: u64) -> u64 {
302        let limb = i / 64;
303        let j = i % 64;
304        (limbs_lsb[limb as usize] >> j) & 1
305    }
306
307    let rep = chal.inner().into_bigint();
308    let r = rep.as_ref();
309
310    let mut denominators = vec![P::BaseField::zero(); g1.len()];
311    // acc = 2 (phi(g2) + g2)
312    let mut points = g2.to_vec();
313    batch_endo_in_place(endo_coeff, &mut points);
314    batch_add_assign_no_branch(&mut denominators, &mut points, g2);
315    batch_double_in_place(&mut denominators, &mut points);
316
317    let mut tmp_s = g2.to_vec();
318    let mut tmp_acc = g2.to_vec();
319    for i in (0..(128 / 2)).rev() {
320        // s = g2
321        assign(&mut tmp_s, g2);
322        // tmp = acc
323        assign(&mut tmp_acc, &points);
324
325        let r_2i = get_bit(r, 2 * i);
326        if r_2i == 0 {
327            batch_negate_in_place(&mut tmp_s);
328        }
329        if get_bit(r, 2 * i + 1) == 1 {
330            batch_endo_in_place(endo_coeff, &mut tmp_s);
331        }
332
333        // acc = (acc + s) + acc
334        batch_add_assign_no_branch(&mut denominators, &mut points, &tmp_s);
335        batch_add_assign_no_branch(&mut denominators, &mut points, &tmp_acc);
336    }
337    // acc += g1
338    batch_add_assign(&mut denominators, &mut points, g1);
339    points
340}
341
342/// Double an array of curve points in-place.
343fn batch_double_in_place<P: SWCurveConfig>(
344    denominators: &mut [P::BaseField],
345    points: &mut [SWJAffine<P>],
346) {
347    o1_utils::cfg_iter_mut!(denominators)
348        .zip(o1_utils::cfg_iter!(points))
349        .for_each(|(d, p)| {
350            *d = p.y.double();
351        });
352    ark_ff::batch_inversion::<P::BaseField>(denominators);
353
354    // TODO: Use less memory
355    o1_utils::cfg_iter!(denominators)
356        .zip(o1_utils::cfg_iter_mut!(points))
357        .for_each(|(d, p)| {
358            let sq = p.x.square();
359            let s = (sq.double() + sq + P::COEFF_A) * d;
360            let x = s.square() - p.x.double();
361            let y = -p.y - (s * (x - p.x));
362            p.x = x;
363            p.y = y;
364        });
365}
366
367fn affine_window_combine_one_base<P: SWCurveConfig>(
368    g1: &[SWJAffine<P>],
369    g2: &[SWJAffine<P>],
370    x2: P::ScalarField,
371) -> Vec<SWJAffine<P>> {
372    let windows2 = BitIteratorBE::new(x2.into_bigint()).tuples();
373
374    let mut points = vec![SWJAffine::<P>::zero(); g1.len()];
375
376    let mut denominators = vec![P::BaseField::zero(); g1.len()];
377
378    let [g01, g10, g11] = affine_shamir_window_table_one(&mut denominators, g2);
379
380    for (hi_2, lo_2) in windows2 {
381        // double in place
382        for _ in 0..2 {
383            for i in 0..g1.len() {
384                denominators[i] = points[i].y.double();
385            }
386            ark_ff::batch_inversion::<P::BaseField>(&mut denominators);
387
388            // TODO: Use less memory
389            for i in 0..g1.len() {
390                let d = denominators[i];
391                let sq = points[i].x.square();
392                let s = (sq.double() + sq + P::COEFF_A) * d;
393                let x = s.square() - points[i].x.double();
394                let y = -points[i].y - (s * (x - points[i].x));
395                points[i].x = x;
396                points[i].y = y;
397            }
398        }
399
400        match (hi_2, lo_2) {
401            (false, false) => (),
402            (false, true) => batch_add_assign(&mut denominators, &mut points, &g01),
403            (true, false) => batch_add_assign(&mut denominators, &mut points, &g10),
404            (true, true) => {
405                batch_add_assign(&mut denominators, &mut points, &g11);
406            }
407        }
408    }
409
410    batch_add_assign(&mut denominators, &mut points, g1);
411
412    points
413}
414
415pub fn affine_window_combine<P: SWCurveConfig>(
416    g1: &[SWJAffine<P>],
417    g2: &[SWJAffine<P>],
418    x1: P::ScalarField,
419    x2: P::ScalarField,
420) -> Vec<SWJAffine<P>> {
421    const CHUNK_SIZE: usize = 10_000;
422    let b: Vec<_> = g1.chunks(CHUNK_SIZE).zip(g2.chunks(CHUNK_SIZE)).collect();
423    let v: Vec<_> = o1_utils::cfg_into_iter!(b)
424        .map(|(v1, v2)| affine_window_combine_base(v1, v2, x1, x2))
425        .collect();
426    v.concat()
427}
428
429/// Given vectors of curve points `g1` and `g2`, compute a vector whose ith
430/// entry is `g1[i] + g2[i].scale(chal.to_field(endo_coeff))`
431///
432/// Internally, it uses the curve endomorphism to speed up this operation.
433pub fn affine_window_combine_one_endo<P: SWCurveConfig>(
434    endo_coeff: P::BaseField,
435    g1: &[SWJAffine<P>],
436    g2: &[SWJAffine<P>],
437    chal: &ScalarChallenge<P::ScalarField>,
438) -> Vec<SWJAffine<P>> {
439    const CHUNK_SIZE: usize = 4096;
440    let b: Vec<_> = g1.chunks(CHUNK_SIZE).zip(g2.chunks(CHUNK_SIZE)).collect();
441    let v: Vec<_> = o1_utils::cfg_into_iter!(b)
442        .map(|(v1, v2)| affine_window_combine_one_endo_base(endo_coeff, v1, v2, chal))
443        .collect();
444    v.concat()
445}
446pub fn affine_window_combine_one<P: SWCurveConfig>(
447    g1: &[SWJAffine<P>],
448    g2: &[SWJAffine<P>],
449    x2: P::ScalarField,
450) -> Vec<SWJAffine<P>> {
451    const CHUNK_SIZE: usize = 10_000;
452    let b: Vec<_> = g1.chunks(CHUNK_SIZE).zip(g2.chunks(CHUNK_SIZE)).collect();
453    let v: Vec<_> = o1_utils::cfg_into_iter!(b)
454        .map(|(v1, v2)| affine_window_combine_one_base(v1, v2, x2))
455        .collect();
456    v.concat()
457}
458
459pub fn window_combine<G: AffineRepr>(
460    g_lo: &[G],
461    g_hi: &[G],
462    x_lo: G::ScalarField,
463    x_hi: G::ScalarField,
464) -> Vec<G> {
465    let mut g_proj: Vec<G::Group> = {
466        let pairs: Vec<_> = g_lo.iter().zip(g_hi).collect();
467        o1_utils::cfg_into_iter!(pairs)
468            .map(|(lo, hi)| window_shamir::<G>(x_lo, *lo, x_hi, *hi))
469            .collect()
470    };
471    G::Group::normalize_batch(g_proj.as_mut_slice())
472}
473
474pub fn affine_shamir_window_table<P: SWCurveConfig>(
475    denominators: &mut [P::BaseField],
476    g1: &[SWJAffine<P>],
477    g2: &[SWJAffine<P>],
478) -> [Vec<SWJAffine<P>>; 15] {
479    fn assign<A: Copy>(dst: &mut [A], src: &[A]) {
480        let n = dst.len();
481        dst[..n].clone_from_slice(&src[..n]);
482    }
483
484    let n = g1.len();
485
486    let mut res: [Vec<_>; 15] = [
487        vec![SWJAffine::<P>::zero(); n],
488        vec![SWJAffine::<P>::zero(); n],
489        vec![SWJAffine::<P>::zero(); n],
490        vec![SWJAffine::<P>::zero(); n],
491        vec![SWJAffine::<P>::zero(); n],
492        vec![SWJAffine::<P>::zero(); n],
493        vec![SWJAffine::<P>::zero(); n],
494        vec![SWJAffine::<P>::zero(); n],
495        vec![SWJAffine::<P>::zero(); n],
496        vec![SWJAffine::<P>::zero(); n],
497        vec![SWJAffine::<P>::zero(); n],
498        vec![SWJAffine::<P>::zero(); n],
499        vec![SWJAffine::<P>::zero(); n],
500        vec![SWJAffine::<P>::zero(); n],
501        vec![SWJAffine::<P>::zero(); n],
502    ];
503
504    let [g01_00, g10_00, g11_00, g00_01, g01_01, g10_01, g11_01, g00_10, g01_10, g10_10, g11_10, g00_11, g01_11, g10_11, g11_11] =
505        &mut res;
506
507    assign(g01_00, g1);
508
509    assign(g10_00, g1);
510    batch_add_assign(denominators, g10_00, g1);
511
512    assign(g11_00, g10_00);
513    batch_add_assign(denominators, g11_00, g1);
514
515    assign(g00_01, g2);
516
517    assign(g01_01, g00_01);
518    batch_add_assign(denominators, g01_01, g1);
519
520    assign(g10_01, g01_01);
521    batch_add_assign(denominators, g10_01, g1);
522
523    assign(g11_01, g10_01);
524    batch_add_assign(denominators, g11_01, g1);
525
526    assign(g00_10, g00_01);
527    batch_add_assign(denominators, g00_10, g2);
528
529    assign(g01_10, g00_10);
530    batch_add_assign(denominators, g01_10, g1);
531
532    assign(g10_10, g01_10);
533    batch_add_assign(denominators, g10_10, g1);
534
535    assign(g11_10, g10_10);
536    batch_add_assign(denominators, g11_10, g1);
537
538    assign(g00_11, g00_10);
539    batch_add_assign(denominators, g00_11, g2);
540
541    assign(g01_11, g00_11);
542    batch_add_assign(denominators, g01_11, g1);
543
544    assign(g10_11, g01_11);
545    batch_add_assign(denominators, g10_11, g1);
546
547    assign(g11_11, g10_11);
548    batch_add_assign(denominators, g11_11, g1);
549
550    res
551}
552
553pub fn affine_shamir_window_table_one<P: SWCurveConfig>(
554    denominators: &mut [P::BaseField],
555    g1: &[SWJAffine<P>],
556) -> [Vec<SWJAffine<P>>; 3] {
557    fn assign<A: Copy>(dst: &mut [A], src: &[A]) {
558        let n = dst.len();
559        dst[..n].clone_from_slice(&src[..n]);
560    }
561
562    let n = g1.len();
563
564    let mut res: [Vec<_>; 3] = [
565        vec![SWJAffine::<P>::zero(); n],
566        vec![SWJAffine::<P>::zero(); n],
567        vec![SWJAffine::<P>::zero(); n],
568    ];
569
570    let [g01, g10, g11] = &mut res;
571
572    assign(g01, g1);
573
574    assign(g10, g1);
575    batch_add_assign(denominators, g10, g1);
576
577    assign(g11, g10);
578    batch_add_assign(denominators, g11, g1);
579
580    res
581}
582
583fn window_shamir<G: AffineRepr>(x1: G::ScalarField, g1: G, x2: G::ScalarField, g2: G) -> G::Group {
584    let [_g00_00, g01_00, g10_00, g11_00, g00_01, g01_01, g10_01, g11_01, g00_10, g01_10, g10_10, g11_10, g00_11, g01_11, g10_11, g11_11] =
585        shamir_window_table(g1, g2);
586
587    let windows1 = BitIteratorBE::new(x1.into_bigint()).tuples();
588    let windows2 = BitIteratorBE::new(x2.into_bigint()).tuples();
589
590    let mut res = G::Group::zero();
591
592    for ((hi_1, lo_1), (hi_2, lo_2)) in windows1.zip(windows2) {
593        res.double_in_place();
594        res.double_in_place();
595        match ((hi_1, lo_1), (hi_2, lo_2)) {
596            ((false, false), (false, false)) => (),
597            ((false, true), (false, false)) => res.add_assign(&g01_00),
598            ((true, false), (false, false)) => res.add_assign(&g10_00),
599            ((true, true), (false, false)) => res.add_assign(&g11_00),
600
601            ((false, false), (false, true)) => res.add_assign(&g00_01),
602            ((false, true), (false, true)) => res.add_assign(&g01_01),
603            ((true, false), (false, true)) => res.add_assign(&g10_01),
604            ((true, true), (false, true)) => res.add_assign(&g11_01),
605
606            ((false, false), (true, false)) => res.add_assign(&g00_10),
607            ((false, true), (true, false)) => res.add_assign(&g01_10),
608            ((true, false), (true, false)) => res.add_assign(&g10_10),
609            ((true, true), (true, false)) => res.add_assign(&g11_10),
610
611            ((false, false), (true, true)) => res.add_assign(&g00_11),
612            ((false, true), (true, true)) => res.add_assign(&g01_11),
613            ((true, false), (true, true)) => res.add_assign(&g10_11),
614            ((true, true), (true, true)) => res.add_assign(&g11_11),
615        }
616    }
617
618    res
619}
620
621pub fn shamir_window_table<G: AffineRepr>(g1: G, g2: G) -> [G; 16] {
622    let g00_00 = G::generator().into_group();
623    let g01_00 = g1.into_group();
624    let g10_00 = {
625        let mut g = g01_00;
626        g.add_assign(&g1);
627        g
628    };
629    let g11_00 = {
630        let mut g = g10_00;
631        g.add_assign(&g1);
632        g
633    };
634
635    let g00_01 = g2.into_group();
636    let g01_01 = {
637        let mut g = g00_01;
638        g.add_assign(&g1);
639        g
640    };
641    let g10_01 = {
642        let mut g = g01_01;
643        g.add_assign(&g1);
644        g
645    };
646    let g11_01 = {
647        let mut g = g10_01;
648        g.add_assign(&g1);
649        g
650    };
651
652    let g00_10 = {
653        let mut g = g00_01;
654        g.add_assign(&g2);
655        g
656    };
657    let g01_10 = {
658        let mut g = g00_10;
659        g.add_assign(&g1);
660        g
661    };
662    let g10_10 = {
663        let mut g = g01_10;
664        g.add_assign(&g1);
665        g
666    };
667    let g11_10 = {
668        let mut g = g10_10;
669        g.add_assign(&g1);
670        g
671    };
672    let g00_11 = {
673        let mut g = g00_10;
674        g.add_assign(&g2);
675        g
676    };
677    let g01_11 = {
678        let mut g = g00_11;
679        g.add_assign(&g1);
680        g
681    };
682    let g10_11 = {
683        let mut g = g01_11;
684        g.add_assign(&g1);
685        g
686    };
687    let g11_11 = {
688        let mut g = g10_11;
689        g.add_assign(&g1);
690        g
691    };
692
693    let mut v = vec![
694        g00_00, g01_00, g10_00, g11_00, g00_01, g01_01, g10_01, g11_01, g00_10, g01_10, g10_10,
695        g11_10, g00_11, g01_11, g10_11, g11_11,
696    ];
697    let v: Vec<_> = G::Group::normalize_batch(v.as_mut_slice());
698    [
699        v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], v[9], v[10], v[11], v[12], v[13],
700        v[14], v[15],
701    ]
702}