poly_commitment/
ipa.rs

1//! This module contains the implementation of the polynomial commitment scheme
2//! called the Inner Product Argument (IPA) as described in [Efficient
3//! Zero-Knowledge Arguments for Arithmetic Circuits in the Discrete Log
4//! Setting](https://eprint.iacr.org/2016/263)
5
6use crate::{
7    commitment::{
8        b_poly, b_poly_coefficients, combine_commitments, shift_scalar, squeeze_challenge,
9        squeeze_prechallenge, BatchEvaluationProof, CommitmentCurve, EndoCurve,
10    },
11    error::CommitmentError,
12    hash_map_cache::HashMapCache,
13    utils::combine_polys,
14    BlindedCommitment, PolyComm, PolynomialsToCombine, SRS as SRSTrait,
15};
16use ark_ec::{AffineRepr, CurveGroup, VariableBaseMSM};
17use ark_ff::{BigInteger, Field, One, PrimeField, UniformRand, Zero};
18use ark_poly::{
19    univariate::DensePolynomial, EvaluationDomain, Evaluations, Radix2EvaluationDomain as D,
20};
21use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
22use blake2::{Blake2b512, Digest};
23use groupmap::GroupMap;
24use mina_poseidon::{sponge::ScalarChallenge, FqSponge};
25use o1_utils::{
26    field_helpers::{inner_prod, pows},
27    math,
28};
29use rand::{CryptoRng, RngCore};
30use rayon::prelude::*;
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33use std::{cmp::min, iter::Iterator, ops::AddAssign};
34
35#[serde_as]
36#[derive(Debug, Clone, Default, Serialize, Deserialize)]
37#[serde(bound = "G: CanonicalDeserialize + CanonicalSerialize")]
38pub struct SRS<G> {
39    /// The vector of group elements for committing to polynomials in
40    /// coefficient form.
41    #[serde_as(as = "Vec<o1_utils::serialization::SerdeAs>")]
42    pub g: Vec<G>,
43
44    /// A group element used for blinding commitments
45    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
46    pub h: G,
47
48    // TODO: the following field should be separated, as they are optimization
49    // values
50    /// Commitments to Lagrange bases, per domain size
51    #[serde(skip)]
52    pub lagrange_bases: HashMapCache<usize, Vec<PolyComm<G>>>,
53}
54
55impl<G> PartialEq for SRS<G>
56where
57    G: PartialEq,
58{
59    fn eq(&self, other: &Self) -> bool {
60        self.g == other.g && self.h == other.h
61    }
62}
63
64pub fn endos<G: CommitmentCurve>() -> (G::BaseField, G::ScalarField)
65where
66    G::BaseField: PrimeField,
67{
68    let endo_q: G::BaseField = mina_poseidon::sponge::endo_coefficient();
69    let endo_r = {
70        let potential_endo_r: G::ScalarField = mina_poseidon::sponge::endo_coefficient();
71        let t = G::generator();
72        let (x, y) = t.to_coordinates().unwrap();
73        let phi_t = G::of_coordinates(x * endo_q, y);
74        if t.mul(potential_endo_r) == phi_t.into_group() {
75            potential_endo_r
76        } else {
77            potential_endo_r * potential_endo_r
78        }
79    };
80    (endo_q, endo_r)
81}
82
83fn point_of_random_bytes<G: CommitmentCurve>(map: &G::Map, random_bytes: &[u8]) -> G
84where
85    G::BaseField: Field,
86{
87    // packing in bit-representation
88    const N: usize = 31;
89    let extension_degree = G::BaseField::extension_degree() as usize;
90
91    let mut base_fields = Vec::with_capacity(N * extension_degree);
92
93    for base_count in 0..extension_degree {
94        let mut bits = [false; 8 * N];
95        let offset = base_count * N;
96        for i in 0..N {
97            for j in 0..8 {
98                bits[8 * i + j] = (random_bytes[offset + i] >> j) & 1 == 1;
99            }
100        }
101
102        let n =
103            <<G::BaseField as Field>::BasePrimeField as PrimeField>::BigInt::from_bits_be(&bits);
104        let t = <<G::BaseField as Field>::BasePrimeField as PrimeField>::from_bigint(n)
105            .expect("packing code has a bug");
106        base_fields.push(t)
107    }
108
109    let t = G::BaseField::from_base_prime_field_elems(base_fields).unwrap();
110
111    let (x, y) = map.to_group(t);
112    G::of_coordinates(x, y).mul_by_cofactor()
113}
114
115/// Additional methods for the SRS structure
116impl<G: CommitmentCurve> SRS<G> {
117    /// This function verifies a batch of polynomial commitment opening proofs.
118    /// Return `true` if the verification is successful, `false` otherwise.
119    pub fn verify<EFqSponge, RNG, const FULL_ROUNDS: usize>(
120        &self,
121        group_map: &G::Map,
122        batch: &mut [BatchEvaluationProof<
123            G,
124            EFqSponge,
125            OpeningProof<G, FULL_ROUNDS>,
126            FULL_ROUNDS,
127        >],
128        rng: &mut RNG,
129    ) -> bool
130    where
131        EFqSponge: FqSponge<G::BaseField, G, G::ScalarField, FULL_ROUNDS>,
132        RNG: RngCore + CryptoRng,
133        G::BaseField: PrimeField,
134    {
135        // Verifier checks for all i,
136        // c_i Q_i + delta_i = z1_i (G_i + b_i U_i) + z2_i H
137        //
138        // if we sample evalscale at random, it suffices to check
139        //
140        // 0 == sum_i evalscale^i (c_i Q_i + delta_i - ( z1_i (G_i + b_i U_i) + z2_i H ))
141        //
142        // and because each G_i is a multiexp on the same array self.g, we
143        // can batch the multiexp across proofs.
144        //
145        // So for each proof in the batch, we add onto our big multiexp the
146        // following terms
147        // evalscale^i c_i Q_i
148        // evalscale^i delta_i
149        // - (evalscale^i z1_i) G_i
150        // - (evalscale^i z2_i) H
151        // - (evalscale^i z1_i b_i) U_i
152
153        // We also check that the sg component of the proof is equal to the
154        // polynomial commitment to the "s" array
155
156        let nonzero_length = self.g.len();
157
158        let max_rounds = math::ceil_log2(nonzero_length);
159
160        let padded_length = 1 << max_rounds;
161
162        let (_, endo_r) = endos::<G>();
163
164        // TODO: This will need adjusting
165        let padding = padded_length - nonzero_length;
166        let mut points = vec![self.h];
167        points.extend(self.g.clone());
168        points.extend(vec![G::zero(); padding]);
169
170        let mut scalars = vec![G::ScalarField::zero(); padded_length + 1];
171        assert_eq!(scalars.len(), points.len());
172
173        // sample randomiser to scale the proofs with
174        let rand_base = G::ScalarField::rand(rng);
175        let sg_rand_base = G::ScalarField::rand(rng);
176
177        let mut rand_base_i = G::ScalarField::one();
178        let mut sg_rand_base_i = G::ScalarField::one();
179
180        for BatchEvaluationProof {
181            sponge,
182            evaluation_points,
183            polyscale,
184            evalscale,
185            evaluations,
186            opening,
187            combined_inner_product,
188        } in batch.iter_mut()
189        {
190            sponge.absorb_fr(&[shift_scalar::<G>(*combined_inner_product)]);
191
192            let u_base: G = {
193                let t = sponge.challenge_fq();
194                let (x, y) = group_map.to_group(t);
195                G::of_coordinates(x, y)
196            };
197
198            let Challenges { chal, chal_inv } = opening.challenges::<EFqSponge>(&endo_r, sponge);
199
200            sponge.absorb_g(&[opening.delta]);
201            let c = ScalarChallenge(sponge.challenge()).to_field(&endo_r);
202
203            // < s, sum_i evalscale^i pows(evaluation_point[i]) >
204            // ==
205            // sum_i evalscale^i < s, pows(evaluation_point[i]) >
206            let b0 = {
207                let mut scale = G::ScalarField::one();
208                let mut res = G::ScalarField::zero();
209                for &e in evaluation_points.iter() {
210                    let term = b_poly(&chal, e);
211                    res += &(scale * term);
212                    scale *= *evalscale;
213                }
214                res
215            };
216
217            let s = b_poly_coefficients(&chal);
218
219            let neg_rand_base_i = -rand_base_i;
220
221            // TERM
222            // - rand_base_i z1 G
223            //
224            // we also add -sg_rand_base_i * G to check correctness of sg.
225            points.push(opening.sg);
226            scalars.push(neg_rand_base_i * opening.z1 - sg_rand_base_i);
227
228            // Here we add
229            // sg_rand_base_i * ( < s, self.g > )
230            // =
231            // < sg_rand_base_i s, self.g >
232            //
233            // to check correctness of the sg component.
234            {
235                let terms: Vec<_> = s.par_iter().map(|s| sg_rand_base_i * s).collect();
236
237                for (i, term) in terms.iter().enumerate() {
238                    scalars[i + 1] += term;
239                }
240            }
241
242            // TERM
243            // - rand_base_i * z2 * H
244            scalars[0] -= &(rand_base_i * opening.z2);
245
246            // TERM
247            // -rand_base_i * (z1 * b0 * U)
248            scalars.push(neg_rand_base_i * (opening.z1 * b0));
249            points.push(u_base);
250
251            // TERM
252            // rand_base_i c_i Q_i
253            // = rand_base_i c_i
254            //   (sum_j (chal_invs[j] L_j + chals[j] R_j) + P_prime)
255            // where P_prime = combined commitment + combined_inner_product * U
256            let rand_base_i_c_i = c * rand_base_i;
257            for ((l, r), (u_inv, u)) in opening.lr.iter().zip(chal_inv.iter().zip(chal.iter())) {
258                points.push(*l);
259                scalars.push(rand_base_i_c_i * u_inv);
260
261                points.push(*r);
262                scalars.push(rand_base_i_c_i * u);
263            }
264
265            // TERM
266            // sum_j evalscale^j (sum_i polyscale^i f_i) (elm_j)
267            // == sum_j sum_i evalscale^j polyscale^i f_i(elm_j)
268            // == sum_i polyscale^i sum_j evalscale^j f_i(elm_j)
269            combine_commitments(
270                evaluations,
271                &mut scalars,
272                &mut points,
273                *polyscale,
274                rand_base_i_c_i,
275            );
276
277            scalars.push(rand_base_i_c_i * *combined_inner_product);
278            points.push(u_base);
279
280            scalars.push(rand_base_i);
281            points.push(opening.delta);
282
283            rand_base_i *= &rand_base;
284            sg_rand_base_i *= &sg_rand_base;
285        }
286
287        // Verify the equation in two chunks, which is optimal for our SRS size.
288        // (see the comment to the `benchmark_msm_parallel_vesta` MSM benchmark)
289        let chunk_size = points.len() / 2;
290        let msm_res = points
291            .into_par_iter()
292            .chunks(chunk_size)
293            .zip(scalars.into_par_iter().chunks(chunk_size))
294            .map(|(bases, coeffs)| {
295                let coeffs_bigint = coeffs
296                    .into_iter()
297                    .map(|c| c.into_bigint())
298                    .collect::<Vec<_>>();
299                G::Group::msm_bigint(&bases, &coeffs_bigint)
300            })
301            .reduce(G::Group::zero, |mut l, r| {
302                l += r;
303                l
304            });
305
306        msm_res == G::Group::zero()
307    }
308
309    /// This function creates a trusted-setup SRS instance for circuits with
310    /// number of rows up to `depth`.
311    ///
312    /// # Safety
313    ///
314    /// This function is unsafe because it creates a trusted setup and the toxic
315    /// waste is passed as a parameter.
316    pub unsafe fn create_trusted_setup(x: G::ScalarField, depth: usize) -> Self {
317        let m = G::Map::setup();
318
319        let mut x_pow = G::ScalarField::one();
320        let g: Vec<_> = (0..depth)
321            .map(|_| {
322                let res = G::generator().mul(x_pow);
323                x_pow *= x;
324                res.into_affine()
325            })
326            .collect();
327
328        // Compute a blinder
329        let h = {
330            let mut h = Blake2b512::new();
331            h.update("srs_misc".as_bytes());
332            // FIXME: This is for retrocompatibility with a previous version
333            // that was using a list initialisation. It is not necessary.
334            h.update(0_u32.to_be_bytes());
335            point_of_random_bytes(&m, &h.finalize())
336        };
337
338        Self {
339            g,
340            h,
341            lagrange_bases: HashMapCache::new(),
342        }
343    }
344}
345
346impl<G: CommitmentCurve> SRS<G>
347where
348    <G as CommitmentCurve>::Map: Sync,
349    G::BaseField: PrimeField,
350{
351    /// This function creates SRS instance for circuits with number of rows up
352    /// to `depth`.
353    pub fn create_parallel(depth: usize) -> Self {
354        let m = G::Map::setup();
355
356        let g: Vec<_> = (0..depth)
357            .into_par_iter()
358            .map(|i| {
359                let mut h = Blake2b512::new();
360                h.update((i as u32).to_be_bytes());
361                point_of_random_bytes(&m, &h.finalize())
362            })
363            .collect();
364
365        // Compute a blinder
366        let h = {
367            let mut h = Blake2b512::new();
368            h.update("srs_misc".as_bytes());
369            // FIXME: This is for retrocompatibility with a previous version
370            // that was using a list initialisation. It is not necessary.
371            h.update(0_u32.to_be_bytes());
372            point_of_random_bytes(&m, &h.finalize())
373        };
374
375        Self {
376            g,
377            h,
378            lagrange_bases: HashMapCache::new(),
379        }
380    }
381}
382
383impl<G> SRSTrait<G> for SRS<G>
384where
385    G: CommitmentCurve,
386{
387    /// The maximum polynomial degree that can be committed to
388    fn max_poly_size(&self) -> usize {
389        self.g.len()
390    }
391
392    fn blinding_commitment(&self) -> G {
393        self.h
394    }
395
396    /// Turns a non-hiding polynomial commitment into a hiding polynomial
397    /// commitment. Transforms each given `<a, G>` into `(<a, G> + wH, w)` with
398    /// a random `w` per commitment.
399    fn mask(
400        &self,
401        comm: PolyComm<G>,
402        rng: &mut (impl RngCore + CryptoRng),
403    ) -> BlindedCommitment<G> {
404        let blinders = comm.map(|_| G::ScalarField::rand(rng));
405        self.mask_custom(comm, &blinders).unwrap()
406    }
407
408    fn mask_custom(
409        &self,
410        com: PolyComm<G>,
411        blinders: &PolyComm<G::ScalarField>,
412    ) -> Result<BlindedCommitment<G>, CommitmentError> {
413        let commitment = com
414            .zip(blinders)
415            .ok_or_else(|| CommitmentError::BlindersDontMatch(blinders.len(), com.len()))?
416            .map(|(g, b)| {
417                let mut g_masked = self.h.mul(b);
418                g_masked.add_assign(&g);
419                g_masked.into_affine()
420            });
421        Ok(BlindedCommitment {
422            commitment,
423            blinders: blinders.clone(),
424        })
425    }
426
427    fn commit_non_hiding(
428        &self,
429        plnm: &DensePolynomial<G::ScalarField>,
430        num_chunks: usize,
431    ) -> PolyComm<G> {
432        let is_zero = plnm.is_zero();
433
434        // chunk while committing
435        let mut chunks: Vec<_> = if is_zero {
436            vec![G::zero()]
437        } else if plnm.len() < self.g.len() {
438            vec![G::Group::msm(&self.g[..plnm.len()], &plnm.coeffs)
439                .unwrap()
440                .into_affine()]
441        } else if plnm.len() == self.g.len() {
442            // when processing a single chunk, it's faster to parallelise
443            // vertically in 2 threads (see the comment to the
444            // `benchmark_msm_parallel_vesta` MSM benchmark)
445            let n = self.g.len();
446            let (r1, r2) = rayon::join(
447                || G::Group::msm(&self.g[..n / 2], &plnm.coeffs[..n / 2]).unwrap(),
448                || G::Group::msm(&self.g[n / 2..n], &plnm.coeffs[n / 2..n]).unwrap(),
449            );
450
451            vec![(r1 + r2).into_affine()]
452        } else {
453            // otherwise it's better to parallelise horizontally along chunks
454            plnm.into_par_iter()
455                .chunks(self.g.len())
456                .map(|chunk| {
457                    let chunk_coeffs = chunk
458                        .into_iter()
459                        .map(|c| c.into_bigint())
460                        .collect::<Vec<_>>();
461                    let chunk_res = G::Group::msm_bigint(&self.g, &chunk_coeffs);
462                    chunk_res.into_affine()
463                })
464                .collect()
465        };
466
467        for _ in chunks.len()..num_chunks {
468            chunks.push(G::zero());
469        }
470
471        PolyComm::<G>::new(chunks)
472    }
473
474    fn commit(
475        &self,
476        plnm: &DensePolynomial<G::ScalarField>,
477        num_chunks: usize,
478        rng: &mut (impl RngCore + CryptoRng),
479    ) -> BlindedCommitment<G> {
480        self.mask(self.commit_non_hiding(plnm, num_chunks), rng)
481    }
482
483    fn commit_custom(
484        &self,
485        plnm: &DensePolynomial<G::ScalarField>,
486        num_chunks: usize,
487        blinders: &PolyComm<G::ScalarField>,
488    ) -> Result<BlindedCommitment<G>, CommitmentError> {
489        self.mask_custom(self.commit_non_hiding(plnm, num_chunks), blinders)
490    }
491
492    fn commit_evaluations_non_hiding(
493        &self,
494        domain: D<G::ScalarField>,
495        plnm: &Evaluations<G::ScalarField, D<G::ScalarField>>,
496    ) -> PolyComm<G> {
497        let basis = self.get_lagrange_basis(domain);
498        let commit_evaluations = |evals: &Vec<G::ScalarField>, basis: &Vec<PolyComm<G>>| {
499            PolyComm::<G>::multi_scalar_mul(&basis.iter().collect::<Vec<_>>()[..], &evals[..])
500        };
501        match domain.size.cmp(&plnm.domain().size) {
502            std::cmp::Ordering::Less => {
503                let s = (plnm.domain().size / domain.size) as usize;
504                let v: Vec<_> = (0..(domain.size())).map(|i| plnm.evals[s * i]).collect();
505                commit_evaluations(&v, basis)
506            }
507            std::cmp::Ordering::Equal => commit_evaluations(&plnm.evals, basis),
508            std::cmp::Ordering::Greater => {
509                panic!("desired commitment domain size ({}) greater than evaluations' domain size ({}):", domain.size, plnm.domain().size)
510            }
511        }
512    }
513
514    fn commit_evaluations(
515        &self,
516        domain: D<G::ScalarField>,
517        plnm: &Evaluations<G::ScalarField, D<G::ScalarField>>,
518        rng: &mut (impl RngCore + CryptoRng),
519    ) -> BlindedCommitment<G> {
520        self.mask(self.commit_evaluations_non_hiding(domain, plnm), rng)
521    }
522
523    fn commit_evaluations_custom(
524        &self,
525        domain: D<G::ScalarField>,
526        plnm: &Evaluations<G::ScalarField, D<G::ScalarField>>,
527        blinders: &PolyComm<G::ScalarField>,
528    ) -> Result<BlindedCommitment<G>, CommitmentError> {
529        self.mask_custom(self.commit_evaluations_non_hiding(domain, plnm), blinders)
530    }
531
532    fn create(depth: usize) -> Self {
533        let m = G::Map::setup();
534
535        let g: Vec<_> = (0..depth)
536            .map(|i| {
537                let mut h = Blake2b512::new();
538                h.update((i as u32).to_be_bytes());
539                point_of_random_bytes(&m, &h.finalize())
540            })
541            .collect();
542
543        // Compute a blinder
544        let h = {
545            let mut h = Blake2b512::new();
546            h.update("srs_misc".as_bytes());
547            // FIXME: This is for retrocompatibility with a previous version
548            // that was using a list initialisation. It is not necessary.
549            h.update(0_u32.to_be_bytes());
550            point_of_random_bytes(&m, &h.finalize())
551        };
552
553        Self {
554            g,
555            h,
556            lagrange_bases: HashMapCache::new(),
557        }
558    }
559
560    fn get_lagrange_basis_from_domain_size(&self, domain_size: usize) -> &Vec<PolyComm<G>> {
561        self.lagrange_bases.get_or_generate(domain_size, || {
562            self.lagrange_basis(D::new(domain_size).unwrap())
563        })
564    }
565
566    fn get_lagrange_basis(&self, domain: D<G::ScalarField>) -> &Vec<PolyComm<G>> {
567        self.lagrange_bases
568            .get_or_generate(domain.size(), || self.lagrange_basis(domain))
569    }
570
571    fn size(&self) -> usize {
572        self.g.len()
573    }
574}
575
576impl<G: CommitmentCurve> SRS<G> {
577    #[allow(clippy::type_complexity)]
578    #[allow(clippy::many_single_char_names)]
579    // NB: a slight modification to the original protocol is done when absorbing
580    // the first prover message to improve the efficiency in a recursive
581    // setting.
582    pub fn open<EFqSponge, RNG, D: EvaluationDomain<G::ScalarField>, const FULL_ROUNDS: usize>(
583        &self,
584        group_map: &G::Map,
585        plnms: PolynomialsToCombine<G, D>,
586        elm: &[G::ScalarField],
587        polyscale: G::ScalarField,
588        evalscale: G::ScalarField,
589        mut sponge: EFqSponge,
590        rng: &mut RNG,
591    ) -> OpeningProof<G, FULL_ROUNDS>
592    where
593        EFqSponge: Clone + FqSponge<G::BaseField, G, G::ScalarField, FULL_ROUNDS>,
594        RNG: RngCore + CryptoRng,
595        G::BaseField: PrimeField,
596        G: EndoCurve,
597    {
598        let (endo_q, endo_r) = endos::<G>();
599
600        let rounds = math::ceil_log2(self.g.len());
601        let padded_length = 1 << rounds;
602
603        // TODO: Trim this to the degree of the largest polynomial
604        // TODO: We do always suppose we have a power of 2 for the SRS in
605        // practice. Therefore, padding equals zero, and this code can be
606        // removed. Only a current test case uses a SRS with a non-power of 2.
607        let padding = padded_length - self.g.len();
608        let mut g = self.g.clone();
609        g.extend(vec![G::zero(); padding]);
610
611        // Combines polynomials roughly as follows: p(X) := ∑_i polyscale^i p_i(X)
612        //
613        // `blinding_factor` is a combined set of commitments that are
614        // paired with polynomials in `plnms`. In kimchi, these input
615        // commitments are poly com blinders, so often `[G::ScalarField::one();
616        // num_chunks]` or zeroes.
617        let (p, blinding_factor) = combine_polys::<G, D>(plnms, polyscale, self.g.len());
618
619        // The initial evaluation vector for polynomial commitment b_init is not
620        // just the powers of a single point as in the original IPA
621        // (1,ζ,ζ^2,...)
622        //
623        // but rather a vector of linearly combined powers with `evalscale` as
624        // recombiner.
625        //
626        // b_init[j] = Σ_i evalscale^i elm_i^j
627        //           = ζ^j + evalscale * ζ^j ω^j (in the specific case of challenges (ζ,ζω))
628        //
629        // So in our case b_init is the following vector:
630        //    1 + evalscale
631        //    ζ + evalscale * ζ ω
632        //    ζ^2 + evalscale * (ζ ω)^2
633        //    ζ^3 + evalscale * (ζ ω)^3
634        //    ...
635        let b_init = {
636            // randomise/scale the eval powers
637            let mut scale = G::ScalarField::one();
638            let mut res: Vec<G::ScalarField> =
639                (0..padded_length).map(|_| G::ScalarField::zero()).collect();
640            for e in elm {
641                for (i, t) in pows(padded_length, *e).iter().enumerate() {
642                    res[i] += &(scale * t);
643                }
644                scale *= &evalscale;
645            }
646            res
647        };
648
649        // Combined polynomial p(X) evaluated at the combined eval point b_init.
650        let combined_inner_product = p
651            .coeffs
652            .iter()
653            .zip(b_init.iter())
654            .map(|(a, b)| *a * b)
655            .fold(G::ScalarField::zero(), |acc, x| acc + x);
656
657        // Usually, the prover sends `combined_inner_product`` to the verifier
658        // So we should absorb `combined_inner_product``
659        // However it is more efficient in the recursion circuit
660        // to absorb a slightly modified version of it.
661        // As a reminder, in a recursive setting, the challenges are given as a
662        // public input and verified in the next iteration.
663        // See the `shift_scalar`` doc.
664        sponge.absorb_fr(&[shift_scalar::<G>(combined_inner_product)]);
665
666        // Generate another randomisation base U; our commitments will be w.r.t
667        // bases {G_i},H,U.
668        let u_base: G = {
669            let t = sponge.challenge_fq();
670            let (x, y) = group_map.to_group(t);
671            G::of_coordinates(x, y)
672        };
673
674        let mut a = p.coeffs;
675        assert!(padded_length >= a.len());
676        a.extend(vec![G::ScalarField::zero(); padded_length - a.len()]);
677
678        let mut b = b_init;
679
680        let mut lr = vec![];
681
682        let mut blinders = vec![];
683
684        let mut chals = vec![];
685        let mut chal_invs = vec![];
686
687        // The main IPA folding loop that has log iterations.
688        for _ in 0..rounds {
689            let n = g.len() / 2;
690            // Pedersen bases
691            let (g_lo, g_hi) = (&g[0..n], &g[n..]);
692            // Polynomial coefficients
693            let (a_lo, a_hi) = (&a[0..n], &a[n..]);
694            // Evaluation points
695            let (b_lo, b_hi) = (&b[0..n], &b[n..]);
696
697            // Blinders for L/R
698            let rand_l = <G::ScalarField as UniformRand>::rand(rng);
699            let rand_r = <G::ScalarField as UniformRand>::rand(rng);
700
701            // Pedersen commitment to a_lo,rand_l,<a_hi,b_lo>
702            let l = G::Group::msm_bigint(
703                &[g_lo, &[self.h, u_base]].concat(),
704                &[a_hi, &[rand_l, inner_prod(a_hi, b_lo)]]
705                    .concat()
706                    .iter()
707                    .map(|x| x.into_bigint())
708                    .collect::<Vec<_>>(),
709            )
710            .into_affine();
711
712            let r = G::Group::msm_bigint(
713                &[g_hi, &[self.h, u_base]].concat(),
714                &[a_lo, &[rand_r, inner_prod(a_lo, b_hi)]]
715                    .concat()
716                    .iter()
717                    .map(|x| x.into_bigint())
718                    .collect::<Vec<_>>(),
719            )
720            .into_affine();
721
722            lr.push((l, r));
723            blinders.push((rand_l, rand_r));
724
725            sponge.absorb_g(&[l]);
726            sponge.absorb_g(&[r]);
727
728            // Round #i challenges;
729            // - not to be confused with "u_base"
730            // - not to be confused with "u" as "polyscale"
731            let u_pre = squeeze_prechallenge(&mut sponge);
732            let u = u_pre.to_field(&endo_r);
733            let u_inv = u.inverse().unwrap();
734
735            chals.push(u);
736            chal_invs.push(u_inv);
737
738            // IPA-folding polynomial coefficients
739            a = a_hi
740                .par_iter()
741                .zip(a_lo)
742                .map(|(&hi, &lo)| {
743                    // lo + u_inv * hi
744                    let mut res = hi;
745                    res *= u_inv;
746                    res += &lo;
747                    res
748                })
749                .collect();
750
751            // IPA-folding evaluation points
752            b = b_lo
753                .par_iter()
754                .zip(b_hi)
755                .map(|(&lo, &hi)| {
756                    // lo + u * hi
757                    let mut res = hi;
758                    res *= u;
759                    res += &lo;
760                    res
761                })
762                .collect();
763
764            // IPA-folding bases
765            g = G::combine_one_endo(endo_r, endo_q, g_lo, g_hi, u_pre);
766        }
767
768        assert!(
769            g.len() == 1 && a.len() == 1 && b.len() == 1,
770            "IPA commitment folding must produce single elements after log rounds"
771        );
772        let a0 = a[0];
773        let b0 = b[0];
774        let g0 = g[0];
775
776        // Compute r_prime, a folded blinder. It combines blinders on
777        // each individual step of the IPA folding process together
778        // with the final blinding_factor of the polynomial.
779        //
780        // r_prime := ∑_i (rand_l[i] * u[i]^{-1} + rand_r * u[i])
781        //          + blinding_factor
782        //
783        // where u is a vector of folding challenges, and rand_l/rand_r are
784        // intermediate L/R blinders.
785        let r_prime = blinders
786            .iter()
787            .zip(chals.iter().zip(chal_invs.iter()))
788            .map(|((rand_l, rand_r), (u, u_inv))| ((*rand_l) * u_inv) + (*rand_r * u))
789            .fold(blinding_factor, |acc, x| acc + x);
790
791        let d = <G::ScalarField as UniformRand>::rand(rng);
792        let r_delta = <G::ScalarField as UniformRand>::rand(rng);
793
794        // Compute delta, the commitment
795        // delta = [d] G0 + \
796        //         [b0*d] U_base + \
797        //         [r_delta] H^r (as a group element, in additive notation)
798        let delta = ((g0.into_group() + (u_base.mul(b0))).into_affine().mul(d)
799            + self.h.mul(r_delta))
800        .into_affine();
801
802        sponge.absorb_g(&[delta]);
803        let c = ScalarChallenge(sponge.challenge()).to_field(&endo_r);
804
805        // (?) Schnorr-like responses showing the knowledge of r_prime and a0.
806        let z1 = a0 * c + d;
807        let z2 = r_prime * c + r_delta;
808
809        OpeningProof {
810            delta,
811            lr,
812            z1,
813            z2,
814            sg: g0,
815        }
816    }
817
818    fn lagrange_basis(&self, domain: D<G::ScalarField>) -> Vec<PolyComm<G>> {
819        let n = domain.size();
820
821        // Let V be a vector space over the field F.
822        //
823        // Given
824        // - a domain [ 1, w, w^2, ..., w^{n - 1} ]
825        // - a vector v := [ v_0, ..., v_{n - 1} ] in V^n
826        //
827        // the FFT algorithm computes the matrix application
828        //
829        // u = M(w) * v
830        //
831        // where
832        // M(w) =
833        //   1 1       1           ... 1
834        //   1 w       w^2         ... w^{n-1}
835        //   ...
836        //   1 w^{n-1} (w^2)^{n-1} ... (w^{n-1})^{n-1}
837        //
838        // The IFFT algorithm computes
839        //
840        // v = M(w)^{-1} * u
841        //
842        // Let's see how we can use this algorithm to compute the lagrange basis
843        // commitments.
844        //
845        // Let V be the vector space F[x] of polynomials in x over F.
846        // Let v in V be the vector [ L_0, ..., L_{n - 1} ] where L_i is the i^{th}
847        // normalized Lagrange polynomial (where L_i(w^j) = j == i ? 1 : 0).
848        //
849        // Consider the rows of M(w) * v. Let me write out the matrix and vector
850        // so you can see more easily.
851        //
852        //   | 1 1       1           ... 1               |   | L_0     |
853        //   | 1 w       w^2         ... w^{n-1}         | * | L_1     |
854        //   | ...                                       |   | ...     |
855        //   | 1 w^{n-1} (w^2)^{n-1} ... (w^{n-1})^{n-1} |   | L_{n-1} |
856        //
857        // The 0th row is L_0 + L1 + ... + L_{n - 1}. So, it's the polynomial
858        // that has the value 1 on every element of the domain.
859        // In other words, it's the polynomial 1.
860        //
861        // The 1st row is L_0 + w L_1 + ... + w^{n - 1} L_{n - 1}. So, it's the
862        // polynomial which has value w^i on w^i.
863        // In other words, it's the polynomial x.
864        //
865        // In general, you can see that row i is in fact the polynomial x^i.
866        //
867        // Thus, M(w) * v is the vector u, where u = [ 1, x, x^2, ..., x^n ]
868        //
869        // Therefore, the IFFT algorithm, when applied to the vector u (the
870        // standard monomial basis) will yield the vector v of the (normalized)
871        // Lagrange polynomials.
872        //
873        // Now, because the polynomial commitment scheme is additively
874        // homomorphic, and because the commitment to the polynomial x^i is just
875        // self.g[i], we can obtain commitments to the normalized Lagrange
876        // polynomials by applying IFFT to the vector self.g[0..n].
877        //
878        //
879        // Further still, we can do the same trick for 'chunked' polynomials.
880        //
881        // Recall that a chunked polynomial is some f of degree k*n - 1 with
882        // f(x) = f_0(x) + x^n f_1(x) + ... + x^{(k-1) n} f_{k-1}(x)
883        // where each f_i has degree n-1.
884        //
885        // In the above, if we set u = [ 1, x^2, ... x^{n-1}, 0, 0, .., 0 ]
886        // then we effectively 'zero out' any polynomial terms higher than
887        // x^{n-1}, leaving us with the 'partial Lagrange polynomials' that
888        // contribute to f_0.
889        //
890        // Similarly, u = [ 0, 0, ..., 0, 1, x^2, ..., x^{n-1}, 0, 0, ..., 0]
891        // with n leading zeros 'zeroes out' all terms except the 'partial
892        // Lagrange polynomials' that contribute to f_1, and likewise for each
893        // f_i.
894        //
895        // By computing each of these, and recollecting the terms as a vector of
896        // polynomial commitments, we obtain a chunked commitment to the L_i
897        // polynomials.
898        let srs_size = self.g.len();
899        let num_elems = n.div_ceil(srs_size);
900        let mut chunks = Vec::with_capacity(num_elems);
901
902        // For each chunk
903        for i in 0..num_elems {
904            // Initialize the vector with zero curve points
905            let mut lg: Vec<<G as AffineRepr>::Group> = vec![<G as AffineRepr>::Group::zero(); n];
906            // Overwrite the terms corresponding to that chunk with the SRS
907            // curve points
908            let start_offset = i * srs_size;
909            let num_terms = min((i + 1) * srs_size, n) - start_offset;
910            for j in 0..num_terms {
911                lg[start_offset + j] = self.g[j].into_group()
912            }
913            // Apply the IFFT
914            domain.ifft_in_place(&mut lg);
915            // Append the 'partial Langrange polynomials' to the vector of elems
916            // chunks
917            chunks.push(<G as AffineRepr>::Group::normalize_batch(lg.as_mut_slice()));
918        }
919
920        (0..n)
921            .map(|i| PolyComm {
922                chunks: chunks.iter().map(|v| v[i]).collect(),
923            })
924            .collect()
925    }
926}
927
928#[serde_as]
929#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq)]
930#[serde(bound = "G: ark_serialize::CanonicalDeserialize + ark_serialize::CanonicalSerialize")]
931pub struct OpeningProof<G: AffineRepr, const FULL_ROUNDS: usize> {
932    /// Vector of rounds of L & R commitments
933    #[serde_as(as = "Vec<(o1_utils::serialization::SerdeAs, o1_utils::serialization::SerdeAs)>")]
934    pub lr: Vec<(G, G)>,
935    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
936    pub delta: G,
937    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
938    pub z1: G::ScalarField,
939    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
940    pub z2: G::ScalarField,
941    /// A final folded commitment base
942    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
943    pub sg: G,
944}
945
946impl<
947        BaseField: PrimeField,
948        G: AffineRepr<BaseField = BaseField> + CommitmentCurve + EndoCurve,
949        const FULL_ROUNDS: usize,
950    > crate::OpenProof<G, FULL_ROUNDS> for OpeningProof<G, FULL_ROUNDS>
951{
952    type SRS = SRS<G>;
953
954    fn open<EFqSponge, RNG, D: EvaluationDomain<<G as AffineRepr>::ScalarField>>(
955        srs: &Self::SRS,
956        group_map: &<G as CommitmentCurve>::Map,
957        plnms: PolynomialsToCombine<G, D>,
958        elm: &[<G as AffineRepr>::ScalarField],
959        polyscale: <G as AffineRepr>::ScalarField,
960        evalscale: <G as AffineRepr>::ScalarField,
961        sponge: EFqSponge,
962        rng: &mut RNG,
963    ) -> Self
964    where
965        EFqSponge: Clone
966            + FqSponge<<G as AffineRepr>::BaseField, G, <G as AffineRepr>::ScalarField, FULL_ROUNDS>,
967        RNG: RngCore + CryptoRng,
968    {
969        srs.open(group_map, plnms, elm, polyscale, evalscale, sponge, rng)
970    }
971
972    fn verify<EFqSponge, RNG>(
973        srs: &Self::SRS,
974        group_map: &G::Map,
975        batch: &mut [BatchEvaluationProof<G, EFqSponge, Self, FULL_ROUNDS>],
976        rng: &mut RNG,
977    ) -> bool
978    where
979        EFqSponge:
980            FqSponge<<G as AffineRepr>::BaseField, G, <G as AffineRepr>::ScalarField, FULL_ROUNDS>,
981        RNG: RngCore + CryptoRng,
982    {
983        srs.verify(group_map, batch, rng)
984    }
985}
986
987/// Commitment round challenges (endo mapped) and their inverses.
988pub struct Challenges<F> {
989    pub chal: Vec<F>,
990    pub chal_inv: Vec<F>,
991}
992
993impl<G: AffineRepr, const FULL_ROUNDS: usize> OpeningProof<G, FULL_ROUNDS> {
994    /// Computes a log-sized vector of scalar challenges for
995    /// recombining elements inside the IPA.
996    pub fn prechallenges<EFqSponge: FqSponge<G::BaseField, G, G::ScalarField, FULL_ROUNDS>>(
997        &self,
998        sponge: &mut EFqSponge,
999    ) -> Vec<ScalarChallenge<G::ScalarField>> {
1000        let _t = sponge.challenge_fq();
1001        self.lr
1002            .iter()
1003            .map(|(l, r)| {
1004                sponge.absorb_g(&[*l]);
1005                sponge.absorb_g(&[*r]);
1006                squeeze_prechallenge(sponge)
1007            })
1008            .collect()
1009    }
1010
1011    /// Same as `prechallenges`, but maps scalar challenges using the provided
1012    /// endomorphism, and computes their inverses.
1013    pub fn challenges<EFqSponge: FqSponge<G::BaseField, G, G::ScalarField, FULL_ROUNDS>>(
1014        &self,
1015        endo_r: &G::ScalarField,
1016        sponge: &mut EFqSponge,
1017    ) -> Challenges<G::ScalarField> {
1018        let chal: Vec<_> = self
1019            .lr
1020            .iter()
1021            .map(|(l, r)| {
1022                sponge.absorb_g(&[*l]);
1023                sponge.absorb_g(&[*r]);
1024                squeeze_challenge(endo_r, sponge)
1025            })
1026            .collect();
1027
1028        let chal_inv = {
1029            let mut cs = chal.clone();
1030            ark_ff::batch_inversion(&mut cs);
1031            cs
1032        };
1033
1034        Challenges { chal, chal_inv }
1035    }
1036}
1037
1038#[cfg(feature = "ocaml_types")]
1039#[allow(non_local_definitions)]
1040pub mod caml {
1041    use super::OpeningProof;
1042    use ark_ec::AffineRepr;
1043    use ocaml;
1044
1045    #[derive(ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)]
1046    pub struct CamlOpeningProof<G, F> {
1047        /// vector of rounds of L & R commitments
1048        pub lr: Vec<(G, G)>,
1049        pub delta: G,
1050        pub z1: F,
1051        pub z2: F,
1052        pub sg: G,
1053    }
1054
1055    impl<G, CamlF, CamlG, const FULL_ROUNDS: usize> From<OpeningProof<G, FULL_ROUNDS>>
1056        for CamlOpeningProof<CamlG, CamlF>
1057    where
1058        G: AffineRepr,
1059        CamlG: From<G>,
1060        CamlF: From<G::ScalarField>,
1061    {
1062        fn from(opening_proof: OpeningProof<G, FULL_ROUNDS>) -> Self {
1063            Self {
1064                lr: opening_proof
1065                    .lr
1066                    .into_iter()
1067                    .map(|(g1, g2)| (CamlG::from(g1), CamlG::from(g2)))
1068                    .collect(),
1069                delta: CamlG::from(opening_proof.delta),
1070                z1: opening_proof.z1.into(),
1071                z2: opening_proof.z2.into(),
1072                sg: CamlG::from(opening_proof.sg),
1073            }
1074        }
1075    }
1076
1077    impl<G, CamlF, CamlG, const FULL_ROUNDS: usize> From<CamlOpeningProof<CamlG, CamlF>>
1078        for OpeningProof<G, FULL_ROUNDS>
1079    where
1080        G: AffineRepr,
1081        CamlG: Into<G>,
1082        CamlF: Into<G::ScalarField>,
1083    {
1084        fn from(caml: CamlOpeningProof<CamlG, CamlF>) -> Self {
1085            Self {
1086                lr: caml
1087                    .lr
1088                    .into_iter()
1089                    .map(|(g1, g2)| (g1.into(), g2.into()))
1090                    .collect(),
1091                delta: caml.delta.into(),
1092                z1: caml.z1.into(),
1093                z2: caml.z2.into(),
1094                sg: caml.sg.into(),
1095            }
1096        }
1097    }
1098}