1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
use std::convert::TryFrom;
use std::ops::{AddAssign, MulAssign};

use blstrs::Compress;
use ff::{Field, PrimeField};
use group::{prime::PrimeCurveAffine, Curve};
use log::{debug, info};
use rayon::prelude::*;
use serde::Serialize;

use super::{
    commit,
    commit::{VKey, WKey},
    compress, inner_product,
    poly::DensePolynomial,
    structured_scalar_power,
    transcript::Transcript,
    AggregateProof, AggregateProofAndInstance, GipaProof, KZGOpening, ProverSRS,
    ProverSRSInputAggregation, TippMippProof,
};
use crate::groth16::{aggregate::AggregateVersion, multiscalar::*, Proof};
use bellpepper_core::SynthesisError;
use pairing::{Engine, MultiMillerLoop};

/// Aggregate `n` zkSnark proofs, where `n` must be a power of two.
/// WARNING: transcript_include represents everything that should be included in
/// the transcript from outside the boundary of this function. This is especially
/// relevant for ALL public inputs of ALL individual proofs. In the regular case,
/// one should input ALL public inputs from ALL proofs aggregated. However, IF ALL the
/// public inputs are **fixed, and public before the aggregation time**, then there is
/// no need to hash those. The reason we specify this extra assumption is because hashing
/// the public inputs from the decoded form can take quite some time depending on the
/// number of proofs and public inputs (+100ms in our case). In the case of Filecoin, the only
/// non-fixed part of the public inputs are the challenges derived from a seed. Even though this
/// seed comes from a random beeacon, we are hashing this as a safety precaution.
pub fn aggregate_proofs<E>(
    srs: &ProverSRS<E>,
    transcript_include: &[u8],
    proofs: &[Proof<E>],
    version: AggregateVersion,
) -> Result<AggregateProof<E>, SynthesisError>
where
    E: MultiMillerLoop + std::fmt::Debug,
    E::Fr: Serialize,
    <E::Fr as PrimeField>::Repr: Send + Sync,
    <E as Engine>::Gt: Compress + Serialize,
    E::G1: Serialize,
    E::G1Affine: Serialize,
    E::G2Affine: Serialize,
{
    info!("aggregate_proofs [version {}]", version);
    if proofs.len() < 2 {
        return Err(SynthesisError::MalformedProofs(
            "aggregating less than 2 proofs is not allowed".to_string(),
        ));
    }
    if !proofs.len().is_power_of_two() {
        return Err(SynthesisError::NonPowerOfTwo);
    }

    if !srs.has_correct_len(proofs.len()) {
        return Err(SynthesisError::MalformedSrs);
    }
    // We first commit to A B and C - these commitments are what the verifier
    // will use later to verify the TIPP and MIPP proofs
    par! {
        let a = proofs.iter().map(|proof| proof.a).collect::<Vec<_>>(),
        let b = proofs.iter().map(|proof| proof.b).collect::<Vec<_>>(),
        let c = proofs.iter().map(|proof| proof.c).collect::<Vec<_>>()
    };

    // A and B are committed together in this scheme
    // we need to take the reference so the macro doesn't consume the value
    // first
    let refa = &a;
    let refb = &b;
    let refc = &c;
    try_par! {
        let com_ab = commit::pair::<E>(&srs.vkey, &srs.wkey, refa, refb),
        let com_c = commit::single_g1::<E>(&srs.vkey, refc)
    };

    let hcom = Transcript::<E>::new("hcom")
        .write(&com_ab)
        .write(&com_c)
        .into_challenge();

    // Derive a random scalar to perform a linear combination of proofs
    let r = Transcript::<E>::new("random-r")
        .write(&hcom)
        .write(&transcript_include)
        .into_challenge();

    // 1,r, r^2, r^3, r^4 ...
    let r_vec: Vec<E::Fr> = structured_scalar_power(proofs.len(), &*r);
    // 1,r^-1, r^-2, r^-3
    let r_inv = r_vec
        .par_iter()
        .map(|ri| ri.invert().unwrap())
        .collect::<Vec<_>>();

    // B^{r}
    let b_r = b
        .par_iter()
        .zip(r_vec.par_iter())
        .map(|(bi, ri)| (bi.to_curve() * ri).to_affine())
        .collect::<Vec<_>>();
    let refb_r = &b_r;
    let refr_vec = &r_vec;
    try_par! {
        // compute A * B^r for the verifier
        let ip_ab = inner_product::pairing::<E>(refa, refb_r),
        // compute C^r for the verifier
        let agg_c = inner_product::multiexponentiation::<E::G1Affine>(refc, refr_vec)
    };

    // w^{r^{-1}}
    let wkey_r_inv = srs.wkey.scale(&r_inv)?;

    // we prove tipp and mipp using the same recursive loop
    let tmipp = prove_tipp_mipp::<E>(
        srs,
        &a,
        &b_r,
        &c,
        &wkey_r_inv,
        &r_vec,
        &ip_ab,
        &agg_c,
        &hcom,
        version,
    )?;
    debug_assert!({
        let computed_com_ab = commit::pair::<E>(&srs.vkey, &wkey_r_inv, &a, &b_r).unwrap();
        com_ab == computed_com_ab
    });

    Ok(AggregateProof {
        com_ab,
        com_c,
        ip_ab,
        agg_c,
        tmipp,
    })
}

pub fn aggregate_proofs_and_instances<E: Engine + std::fmt::Debug>(
    srs: &ProverSRSInputAggregation<E>,
    transcript_include: &[u8],
    statements: &[Vec<E::Fr>],
    proofs: &[Proof<E>],
    version: AggregateVersion,
) -> Result<AggregateProofAndInstance<E>, SynthesisError>
where
    E: MultiMillerLoop + std::fmt::Debug,
    E::Fr: Serialize,
    <E::Fr as PrimeField>::Repr: Send + Sync,
    <E as Engine>::Gt: Compress + Serialize,
    E::G1: Serialize,
    E::G1Affine: Serialize,
    E::G2Affine: Serialize,
{
    if statements.len() < 2 {
        return Err(SynthesisError::MalformedProofs(
            "aggregating less than 2 proofs is not allowed".to_string(),
        ));
    }
    assert!(statements.len() > 1);
    let n = statements[0].len();
    for s in statements {
        if s.len() != n {
            return Err(SynthesisError::MalformedProofs(
                "all statements must be equally sized".to_string(),
            ));
        }
    }
    let mut com_f: Vec<E::G1> = Vec::new();
    let mut com_w0: Vec<E::G1> = Vec::new();
    let mut com_wd: Vec<E::G1> = Vec::new();
    let mut f_eval: Vec<E::Fr> = Vec::new();
    let mut f_eval_proof: Vec<E::G1> = Vec::new();

    let mut poly_f: Vec<Vec<E::Fr>> = Vec::new();

    for j in 0..n / 2 {
        let mut poly_f_j: Vec<E::Fr> = Vec::new();
        let mut poly_w0: Vec<E::Fr> = Vec::new();
        for i in 0..proofs.len() {
            poly_f_j.push(statements[i][j]);
            if i < (proofs.len() - 1) {
                poly_w0.push(statements[i + 1][j]);
            }
        }

        let poly_f_cp = poly_f_j.clone();
        let poly_f_cp_2 = poly_f_j.clone();

        let calc_multiscalar = |left: &[E::Fr], table, len: usize| {
            let getter = |i: usize| -> <E::Fr as PrimeField>::Repr { left[i].to_repr() };
            par_multiscalar::<_, E::G1Affine>(
                &ScalarList::Getter(getter, len),
                table,
                std::mem::size_of::<<E::Fr as PrimeField>::Repr>() * 8,
            )
        };

        // compute F
        let com_f_j = calc_multiscalar(&poly_f_j, &srs.g_alpha_powers_table, proofs.len());

        // check that a0 is the zero coefficient of F
        let com_w0_j = calc_multiscalar(&poly_w0, &srs.g_alpha_powers_table, proofs.len() - 1);

        // Check F^x^(n - d) exists i.e. that F is bounded
        let com_wd_j = calc_multiscalar(&poly_f_cp, &srs.g_alpha_powers_end_table, proofs.len());

        com_f.push(com_f_j);
        com_w0.push(-com_w0_j);
        com_wd.push(-com_wd_j);
        poly_f.push(poly_f_cp_2);
    }

    let transcript_new = Transcript::<E>::new("transcript-with-coms")
        .write(&com_f)
        .write(&com_w0)
        .write(&com_wd)
        .write(&transcript_include)
        .into_bytes();

    let pi_agg = aggregate_proofs(srs, &transcript_new, proofs, version).unwrap();

    let hcom = Transcript::<E>::new("hcom")
        .write(&pi_agg.com_ab)
        .write(&pi_agg.com_c)
        .into_challenge();

    // Random linear combination of proofs
    let r = Transcript::<E>::new("random-r")
        .write(&hcom)
        .write(&transcript_new)
        .into_challenge();

    for poly_f_j in poly_f {
        let mut poly_eval = E::Fr::ZERO;
        let mut r_pow = E::Fr::ONE;
        for poly_f_j_i in &poly_f_j {
            poly_eval += *poly_f_j_i * r_pow;
            r_pow *= &*r;
        }
        f_eval.push(poly_eval);

        f_eval_proof.push(
            create_kzg_opening_for_instance::<E>(
                &srs.g_alpha_powers_table,
                DensePolynomial::from_coeffs(poly_f_j.clone()),
                poly_eval,
                &*r,
            )
            .unwrap(),
        );
    }

    Ok(AggregateProofAndInstance {
        num_inputs: u32::try_from(n).expect("too many statements"),
        pi_agg,
        com_f,
        com_w0,
        com_wd,
        f_eval,
        f_eval_proof,
    })
}

/// Proves a TIPP relation between A and B as well as a MIPP relation with C and
/// r. Commitment keys must be of size of A, B and C. In the context of Groth16
/// aggregation, we have that B = B^r and wkey is scaled by r^{-1}. The
/// commitment key v is used to commit to A and C recursively in GIPA such that
/// only one KZG proof is needed for v. In the original paper version, since the
/// challenges of GIPA would be different, two KZG proofs would be needed.
#[allow(clippy::too_many_arguments)]
fn prove_tipp_mipp<E>(
    srs: &ProverSRS<E>,
    a: &[E::G1Affine],
    b: &[E::G2Affine],
    c: &[E::G1Affine],
    wkey: &WKey<E>, // scaled key w^r^-1
    r_vec: &[E::Fr],
    ip_ab: &<E as Engine>::Gt,
    agg_c: &E::G1,
    hcom: &E::Fr,
    version: AggregateVersion,
) -> Result<TippMippProof<E>, SynthesisError>
where
    E: MultiMillerLoop,
    E::Fr: Serialize,
    <E::Fr as PrimeField>::Repr: Send + Sync,
    <E as Engine>::Gt: Compress + Serialize,
    E::G1: Serialize,
    E::G1Affine: Serialize,
    E::G2Affine: Serialize,
{
    let r_shift = r_vec[1];
    // Run GIPA
    let (proof, mut challenges, mut challenges_inv, extra_challenge) =
        gipa_tipp_mipp::<E>(a, b, c, &srs.vkey, wkey, r_vec, ip_ab, agg_c, hcom, version)?;

    // Prove final commitment keys are wellformed
    // we reverse the transcript so the polynomial in kzg opening is constructed
    // correctly - the formula indicates x_{l-j}. Also for deriving KZG
    // challenge point, input must be the last challenge.
    challenges.reverse();
    challenges_inv.reverse();
    let r_inverse = r_shift.invert().unwrap();

    // KZG challenge point
    let z = match version {
        AggregateVersion::V1 => Transcript::<E>::new("random-z")
            .write(&challenges[0])
            .write(&proof.final_vkey.0)
            .write(&proof.final_vkey.1)
            .write(&proof.final_wkey.0)
            .write(&proof.final_wkey.1)
            .into_challenge(),
        AggregateVersion::V2 => Transcript::<E>::new("random-z")
            .write(&extra_challenge)
            .write(&proof.final_vkey.0)
            .write(&proof.final_vkey.1)
            .write(&proof.final_wkey.0)
            .write(&proof.final_wkey.1)
            .write(&proof.final_a)
            .write(&proof.final_b)
            .write(&proof.final_c)
            .into_challenge(),
    };

    // Complete KZG proofs
    par! {
        let vkey_opening = prove_commitment_v(
            &srs.h_alpha_powers_table,
            &srs.h_beta_powers_table,
            srs.n,
            &challenges_inv,
            &z,
        ),
        let wkey_opening = prove_commitment_w(
            &srs.g_alpha_powers_table,
            &srs.g_beta_powers_table,
            srs.n,
            &challenges,
            &r_inverse,
            &z,
        )
    };

    Ok(TippMippProof {
        gipa: proof,
        vkey_opening: vkey_opening?,
        wkey_opening: wkey_opening?,
    })
}

/// gipa_tipp_mipp peforms the recursion of the GIPA protocol for TIPP and MIPP.
/// It returns a proof containing all intermdiate committed values, as well as
/// the challenges generated necessary to do the polynomial commitment proof
/// later in TIPP.
/// The extra challenge at the end is used to bridge the two proofs mipp/tipp
/// and the KZG checks.
#[allow(
    clippy::many_single_char_names,
    clippy::type_complexity,
    clippy::too_many_arguments
)]
fn gipa_tipp_mipp<E>(
    a: &[E::G1Affine],
    b: &[E::G2Affine],
    c: &[E::G1Affine],
    vkey: &VKey<E>,
    wkey: &WKey<E>, // scaled key w^r^-1
    r: &[E::Fr],
    ip_ab: &<E as Engine>::Gt,
    agg_c: &E::G1,
    hcom: &E::Fr,
    version: AggregateVersion,
) -> Result<(GipaProof<E>, Vec<E::Fr>, Vec<E::Fr>, E::Fr), SynthesisError>
where
    E: MultiMillerLoop,
    E::Fr: Serialize,
    <E::Fr as PrimeField>::Repr: Sync,
    <E as Engine>::Gt: Serialize,
    E::G1: Serialize,
    E::G1Affine: Serialize,
    E::G2Affine: Serialize,
{
    // the values of vectors A and B rescaled at each step of the loop
    let (mut m_a, mut m_b) = (a.to_vec(), b.to_vec());
    // the values of vectors C and r rescaled at each step of the loop
    let (mut m_c, mut m_r) = (c.to_vec(), r.to_vec());
    // the values of the commitment keys rescaled at each step of the loop
    let (mut vkey, mut wkey) = (vkey.clone(), wkey.clone());

    // storing the values for including in the proof
    let mut comms_ab = Vec::new();
    let mut comms_c = Vec::new();
    let mut z_ab = Vec::new();
    let mut z_c = Vec::new();
    let mut challenges: Vec<E::Fr> = Vec::new();
    let mut challenges_inv: Vec<E::Fr> = Vec::new();

    let mut c_inv: E::Fr = *Transcript::<E>::new("gipa-0")
        .write(hcom)
        .write(ip_ab)
        .write(agg_c)
        .write(&r[1])
        .into_challenge();
    let mut c = c_inv.invert().unwrap();

    let mut i = 0;

    while m_a.len() > 1 {
        // recursive step
        // Recurse with problem of half size
        let split = m_a.len() / 2;

        // TIPP ///
        let (a_left, a_right) = m_a.split_at_mut(split);
        let (b_left, b_right) = m_b.split_at_mut(split);
        // MIPP ///
        // c[:n']   c[n':]
        let (c_left, c_right) = m_c.split_at_mut(split);
        // r[:n']   r[:n']
        let (r_left, r_right) = m_r.split_at_mut(split);

        let (vk_left, vk_right) = vkey.split(split);
        let (wk_left, wk_right) = wkey.split(split);

        // since we do this in parallel we take reference first so it can be
        // moved within the macro's rayon scope.
        let (rvk_left, rvk_right) = (&vk_left, &vk_right);
        let (rwk_left, rwk_right) = (&wk_left, &wk_right);
        let (ra_left, ra_right) = (&a_left, &a_right);
        let (rb_left, rb_right) = (&b_left, &b_right);
        let (rc_left, rc_right) = (&c_left, &c_right);
        let (rr_left, rr_right) = (&r_left, &r_right);
        // See section 3.3 for paper version with equivalent names
        try_par! {
            // TIPP part
            let tab_l = commit::pair::<E>(rvk_left, rwk_right, ra_right, rb_left),
            let tab_r = commit::pair::<E>(rvk_right, rwk_left, ra_left, rb_right),
            // \prod e(A_right,B_left)
            let zab_l = inner_product::pairing::<E>(ra_right, rb_left),
            let zab_r = inner_product::pairing::<E>(ra_left, rb_right),

            // MIPP part
            // z_l = c[n':] ^ r[:n']
            let zc_l = inner_product::multiexponentiation::<E::G1Affine>(rc_right, rr_left),
            // Z_r = c[:n'] ^ r[n':]
            let zc_r = inner_product::multiexponentiation::<E::G1Affine>(rc_left, rr_right),
            // u_l = c[n':] * v[:n']
            let tuc_l = commit::single_g1::<E>(rvk_left, rc_right),
            // u_r = c[:n'] * v[n':]
            let tuc_r = commit::single_g1::<E>(rvk_right, rc_left)
        };

        // Fiat-Shamir challenge
        // combine both TIPP and MIPP transcript
        if i == 0 {
            match version {
                AggregateVersion::V1 => {
                    // already generated c_inv and c outside of the loop
                }
                AggregateVersion::V2 => {
                    // in this version we do fiat shamir with the first inputs
                    c_inv = *Transcript::<E>::new("gipa-0")
                        .write(&c_inv)
                        .write(&zab_l)
                        .write(&zab_r)
                        .write(&zc_l)
                        .write(&zc_r)
                        .write(&tab_l.0)
                        .write(&tab_l.1)
                        .write(&tab_r.0)
                        .write(&tab_r.1)
                        .write(&tuc_l.0)
                        .write(&tuc_l.1)
                        .write(&tuc_r.0)
                        .write(&tuc_r.1)
                        .into_challenge();

                    c = c_inv.invert().unwrap();
                }
            };
        } else {
            c_inv = *Transcript::<E>::new(&format!("gipa-{}", i))
                .write(&c_inv)
                .write(&zab_l)
                .write(&zab_r)
                .write(&zc_l)
                .write(&zc_r)
                .write(&tab_l.0)
                .write(&tab_l.1)
                .write(&tab_r.0)
                .write(&tab_r.1)
                .write(&tuc_l.0)
                .write(&tuc_l.1)
                .write(&tuc_r.0)
                .write(&tuc_r.1)
                .into_challenge();

            // Optimization for multiexponentiation to rescale G2 elements with
            // 128-bit challenge Swap 'c' and 'c_inv' since can't control bit size
            // of c_inv
            c = c_inv.invert().unwrap();
        }
        info!("prover: challenge {} -> {:?}", i, c);

        // Set up values for next step of recursion
        // A[:n'] + A[n':] ^ x
        compress(&mut m_a, split, &c);
        // B[:n'] + B[n':] ^ x^-1
        compress(&mut m_b, split, &c_inv);

        // c[:n'] + c[n':]^x
        compress(&mut m_c, split, &c);
        r_left
            .par_iter_mut()
            .zip(r_right.par_iter_mut())
            .for_each(|(r_l, r_r)| {
                // r[:n'] + r[n':]^x^-1
                r_r.mul_assign(&c_inv);
                r_l.add_assign(*r_r);
            });
        let len = r_left.len();
        m_r.resize(len, E::Fr::ZERO); // shrink to new size

        // v_left + v_right^x^-1
        vkey = vk_left.compress(&vk_right, &c_inv)?;
        // w_left + w_right^x
        wkey = wk_left.compress(&wk_right, &c)?;

        comms_ab.push((tab_l, tab_r));
        comms_c.push((tuc_l, tuc_r));
        z_ab.push((zab_l, zab_r));
        z_c.push((zc_l, zc_r));
        challenges.push(c);
        challenges_inv.push(c_inv);

        i += 1;
    }

    assert!(m_a.len() == 1 && m_b.len() == 1);
    assert!(m_c.len() == 1 && m_r.len() == 1);
    assert!(vkey.a.len() == 1 && vkey.b.len() == 1);
    assert!(wkey.a.len() == 1 && wkey.b.len() == 1);

    let (final_a, final_b, final_c) = (m_a[0], m_b[0], m_c[0]);
    let (final_vkey, final_wkey) = (vkey.first(), wkey.first());
    let (final_zab_l, final_zab_r) = z_ab.last().unwrap();
    let (final_zc_l, final_zc_r) = z_c.last().unwrap();
    let (final_tab_l, final_tab_r) = comms_ab.last().unwrap();
    let (final_tuc_l, final_tuc_r) = comms_c.last().unwrap();
    // This extra challenge is simply done to make the bridge between the
    // MIPP/TIPP proofs and the KZG proofs, but is not used in TIPP/MIPP.
    let extra_challenge = *Transcript::<E>::new("gipa-extra-link")
        .write(&challenges.last().unwrap())
        .write(&final_a)
        .write(&final_b)
        .write(&final_c)
        .write(&final_zab_l)
        .write(&final_zab_r)
        .write(&final_zc_l)
        .write(&final_zc_r)
        .write(&final_tab_l.0)
        .write(&final_tab_l.1)
        .write(&final_tab_r.0)
        .write(&final_tab_r.1)
        .write(&final_tuc_l.0)
        .write(&final_tuc_l.1)
        .write(&final_tuc_r.0)
        .write(&final_tuc_r.1)
        .into_challenge();
    debug!("prover: extra challenge {:?}", extra_challenge);
    Ok((
        GipaProof {
            nproofs: a.len() as u32, // TODO: ensure u32
            comms_ab,
            comms_c,
            z_ab,
            z_c,
            final_a,
            final_b,
            final_c,
            final_vkey,
            final_wkey,
        },
        challenges,
        challenges_inv,
        extra_challenge,
    ))
}

fn prove_commitment_v<G>(
    srs_powers_alpha_table: &dyn MultiscalarPrecomp<G>,
    srs_powers_beta_table: &dyn MultiscalarPrecomp<G>,
    n: usize,
    transcript: &[G::Scalar],
    kzg_challenge: &G::Scalar,
) -> Result<KZGOpening<G>, SynthesisError>
where
    G: PrimeCurveAffine,
    <G::Scalar as PrimeField>::Repr: Send + Sync,
{
    // f_v
    let vkey_poly = DensePolynomial::from_coeffs(polynomial_coefficients_from_transcript(
        transcript,
        &G::Scalar::ONE,
    ));

    // f_v(z)
    let vkey_poly_z = polynomial_evaluation_product_form_from_transcript(
        transcript,
        kzg_challenge,
        &G::Scalar::ONE,
    );

    create_kzg_opening(
        srs_powers_alpha_table,
        srs_powers_beta_table,
        n,
        vkey_poly,
        vkey_poly_z,
        kzg_challenge,
    )
}

fn prove_commitment_w<G>(
    srs_powers_alpha_table: &dyn MultiscalarPrecomp<G>,
    srs_powers_beta_table: &dyn MultiscalarPrecomp<G>,
    n: usize,
    transcript: &[G::Scalar],
    r_shift: &G::Scalar,
    kzg_challenge: &G::Scalar,
) -> Result<KZGOpening<G>, SynthesisError>
where
    G: PrimeCurveAffine,
    <G::Scalar as PrimeField>::Repr: Send + Sync,
{
    // this computes f(X) = \prod (1 + x (rX)^{2^j})
    let mut fcoeffs = polynomial_coefficients_from_transcript(transcript, r_shift);
    // this computes f_w(X) = X^n * f(X) - it simply shifts all coefficients to by n
    let mut fwcoeffs = vec![G::Scalar::ZERO; n];
    fwcoeffs.append(&mut fcoeffs);
    let fw = DensePolynomial::from_coeffs(fwcoeffs);

    par! {
        // this computes f(z)
        let fz = polynomial_evaluation_product_form_from_transcript(transcript, kzg_challenge, r_shift),
        // this computes the "shift" z^n
        let zn = kzg_challenge.pow_vartime(&[n as u64])
    };
    // this computes f_w(z) by multiplying by zn
    let mut fwz = fz;
    fwz.mul_assign(&zn);

    create_kzg_opening(
        srs_powers_alpha_table,
        srs_powers_beta_table,
        2 * n, // here we have twice the coefficients size
        fw,
        fwz,
        kzg_challenge,
    )
}

fn create_kzg_opening_for_instance<E: Engine>(
    srs_powers_alpha_table: &dyn MultiscalarPrecomp<E::G1Affine>, // h^alpha^i
    poly: DensePolynomial<E::Fr>,
    eval_poly: E::Fr,
    kzg_challenge: &E::Fr,
) -> Result<E::G1, SynthesisError> {
    let neg_kzg_challenge = -*kzg_challenge;

    // f_v(X) - f_v(z) / (X - z)
    let quotient_polynomial = &(&poly - &DensePolynomial::from_coeffs(vec![eval_poly]))
        / &(DensePolynomial::from_coeffs(vec![neg_kzg_challenge, E::Fr::ONE]));

    let quotient_polynomial_coeffs = quotient_polynomial.into_coeffs();

    //// Compute the proof that F opens to f_v(z) at z as g^{quotient_polynomial(alpha)}.
    let pi_open = {
        let getter =
            |i: usize| -> <E::Fr as PrimeField>::Repr { quotient_polynomial_coeffs[i].to_repr() };

        par_multiscalar::<_, E::G1Affine>(
            &ScalarList::Getter(getter, quotient_polynomial_coeffs.len()),
            srs_powers_alpha_table,
            std::mem::size_of::<<E::Fr as PrimeField>::Repr>() * 8,
        )
    };

    Ok(pi_open)
}

/// Returns the KZG opening proof for the given commitment key. Specifically, it
/// returns $g^{f(alpha) - f(z) / (alpha - z)}$ for $a$ and $b$.
fn create_kzg_opening<G>(
    srs_powers_alpha_table: &dyn MultiscalarPrecomp<G>, // h^alpha^i
    srs_powers_beta_table: &dyn MultiscalarPrecomp<G>,  // h^beta^i
    srs_powers_len: usize,
    poly: DensePolynomial<G::Scalar>,
    eval_poly: G::Scalar,
    kzg_challenge: &G::Scalar,
) -> Result<KZGOpening<G>, SynthesisError>
where
    G: PrimeCurveAffine,
    <G::Scalar as PrimeField>::Repr: Send + Sync,
{
    let neg_kzg_challenge = -*kzg_challenge;

    if poly.coeffs().len() != srs_powers_len {
        return Err(SynthesisError::MalformedSrs);
    }

    // f_v(X) - f_v(z) / (X - z)
    let quotient_polynomial = &(&poly - &DensePolynomial::from_coeffs(vec![eval_poly]))
        / &(DensePolynomial::from_coeffs(vec![neg_kzg_challenge, G::Scalar::ONE]));

    let quotient_polynomial_coeffs = quotient_polynomial.into_coeffs();

    // multiexponentiation inner_product, inlined to optimize
    let quotient_polynomial_coeffs_len = quotient_polynomial_coeffs.len();
    let getter = |i: usize| -> <G::Scalar as PrimeField>::Repr {
        if i >= quotient_polynomial_coeffs_len {
            return G::Scalar::ZERO.to_repr();
        }
        quotient_polynomial_coeffs[i].to_repr()
    };

    // we do one proof over h^a and one proof over h^b (or g^a and g^b depending
    // on the curve we are on). that's the extra cost of the commitment scheme
    // used which is compatible with Groth16 CRS insteaf of the original paper
    // of Bunz'19
    Ok(rayon::join(
        || {
            par_multiscalar::<_, G>(
                &ScalarList::Getter(getter, srs_powers_len),
                srs_powers_alpha_table,
                std::mem::size_of::<<G::Scalar as PrimeField>::Repr>() * 8,
            )
            .to_affine()
        },
        || {
            par_multiscalar::<_, G>(
                &ScalarList::Getter(getter, srs_powers_len),
                srs_powers_beta_table,
                std::mem::size_of::<<G::Scalar as PrimeField>::Repr>() * 8,
            )
            .to_affine()
        },
    ))
}

/// It returns the evaluation of the polynomial $\prod (1 + x_{l-j}(rX)^{2j}$ at
/// the point z, where transcript contains the reversed order of all challenges (the x).
/// THe challenges must be in reversed order for the correct evaluation of the
/// polynomial in O(logn)
pub(super) fn polynomial_evaluation_product_form_from_transcript<F: Field>(
    transcript: &[F],
    z: &F,
    r_shift: &F,
) -> F {
    // this is the term (rz) that will get squared at each step to produce the
    // $(rz)^{2j}$ of the formula
    let mut power_zr = *z;
    power_zr.mul_assign(r_shift);

    let one = F::ONE;

    let mut res = one + (transcript[0] * power_zr);
    for x in &transcript[1..] {
        power_zr = power_zr.square();
        res.mul_assign(one + (*x * power_zr));
    }

    res
}

// Compute the coefficients of the polynomial $\prod_{j=0}^{l-1} (1 + x_{l-j}(rX)^{2j})$
// It does this in logarithmic time directly; here is an example with 2
// challenges:
//
//     We wish to compute $(1+x_1ra)(1+x_0(ra)^2) = 1 +  x_1ra + x_0(ra)^2 + x_0x_1(ra)^3$
//     Algorithm: $c_{-1} = [1]$; $c_j = c_{i-1} \| (x_{l-j} * c_{i-1})$; $r = r*r$
//     $c_0 = c_{-1} \| (x_1 * r * c_{-1}) = [1] \| [rx_1] = [1, rx_1]$, $r = r^2$
//     $c_1 = c_0 \| (x_0 * r^2c_0) = [1, rx_1] \| [x_0r^2, x_0x_1r^3] = [1, x_1r, x_0r^2, x_0x_1r^3]$
//     which is equivalent to $f(a) = 1 + x_1ra + x_0(ra)^2 + x_0x_1r^2a^3$
//
// This method expects the coefficients in reverse order so transcript[i] =
// x_{l-j}.
// f(Y) = Y^n * \prod (1 + x_{l-j-1} (r_shiftY^{2^j}))
fn polynomial_coefficients_from_transcript<F: Field>(transcript: &[F], r_shift: &F) -> Vec<F> {
    let mut coefficients = vec![F::ONE];
    let mut power_2_r = *r_shift;

    for (i, x) in transcript.iter().enumerate() {
        let n = coefficients.len();
        if i > 0 {
            power_2_r = power_2_r.square();
        }
        for j in 0..n {
            let coeff = coefficients[j] * (*x * power_2_r);
            coefficients.push(coeff);
        }
    }

    coefficients
}