Skip to main content

poly_commitment/
commitment.rs

1//! This module implements Dlog-based polynomial commitment schema.
2//! The following functionality is implemented
3//!
4//! 1. Commit to polynomial with its max degree
5//! 2. Open polynomial commitment batch at the given evaluation point and
6//!    scaling factor scalar producing the batched opening proof
7//! 3. Verify batch of batched opening proofs
8
9use alloc::{vec, vec::Vec};
10use ark_ec::{
11    models::short_weierstrass::Affine as SWJAffine, short_weierstrass::SWCurveConfig, AffineRepr,
12    CurveGroup, VariableBaseMSM,
13};
14use ark_ff::{BigInteger, Field, One, PrimeField, Zero};
15use ark_poly::univariate::DensePolynomial;
16use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
17use core::{
18    iter::Iterator,
19    marker::PhantomData,
20    ops::{Add, AddAssign, Sub},
21};
22use groupmap::{BWParameters, GroupMap};
23use mina_poseidon::{sponge::ScalarChallenge, FqSponge};
24use o1_utils::{field_helpers::product, ExtendedDensePolynomial as _};
25#[cfg(feature = "parallel")]
26use rayon::prelude::*;
27use serde::{de::Visitor, Deserialize, Serialize};
28use serde_with::{
29    de::DeserializeAsWrap, ser::SerializeAsWrap, serde_as, DeserializeAs, SerializeAs,
30};
31
32/// Represent a polynomial commitment when the type is instantiated with a
33/// curve.
34///
35/// The structure also handles chunking, i.e. when we aim to handle polynomials
36/// whose degree is higher than the SRS size. For this reason, we do use a
37/// vector for the field `chunks`.
38///
39/// Note that the parameter `C` is not constrained to be a curve, therefore in
40/// some places in the code, `C` can refer to a scalar field element. For
41/// instance, `PolyComm<G::ScalarField>` is used to represent the evaluation of
42/// the polynomial bound by a specific commitment, at a particular evaluation
43/// point.
44#[serde_as]
45#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
46#[serde(bound = "C: CanonicalDeserialize + CanonicalSerialize")]
47pub struct PolyComm<C> {
48    #[serde_as(as = "Vec<o1_utils::serialization::SerdeAs>")]
49    pub chunks: Vec<C>,
50}
51
52impl<C> PolyComm<C>
53where
54    C: CommitmentCurve,
55{
56    /// Multiplies each commitment chunk of f with powers of zeta^n
57    #[must_use]
58    pub fn chunk_commitment(&self, zeta_n: C::ScalarField) -> Self {
59        let mut res = C::Group::zero();
60        // use Horner's to compute chunk[0] + z^n chunk[1] + z^2n chunk[2] + ...
61        // as ( chunk[-1] * z^n + chunk[-2] ) * z^n + chunk[-3]
62        // (https://en.wikipedia.org/wiki/Horner%27s_method)
63        for chunk in self.chunks.iter().rev() {
64            res *= zeta_n;
65            res.add_assign(chunk);
66        }
67
68        Self {
69            chunks: vec![res.into_affine()],
70        }
71    }
72}
73
74impl<F> PolyComm<F>
75where
76    F: Field,
77{
78    /// Multiplies each blinding chunk of f with powers of zeta^n
79    pub fn chunk_blinding(&self, zeta_n: F) -> F {
80        let mut res = F::zero();
81        // use Horner's to compute chunk[0] + z^n chunk[1] + z^2n chunk[2] + ...
82        // as ( chunk[-1] * z^n + chunk[-2] ) * z^n + chunk[-3]
83        // (https://en.wikipedia.org/wiki/Horner%27s_method)
84        for chunk in self.chunks.iter().rev() {
85            res *= zeta_n;
86            res += chunk;
87        }
88        res
89    }
90}
91
92impl<G> PolyComm<G> {
93    /// Returns an iterator over the chunks.
94    pub fn iter(&self) -> core::slice::Iter<'_, G> {
95        self.chunks.iter()
96    }
97}
98
99impl<'a, G> IntoIterator for &'a PolyComm<G> {
100    type Item = &'a G;
101    type IntoIter = core::slice::Iter<'a, G>;
102
103    fn into_iter(self) -> Self::IntoIter {
104        self.chunks.iter()
105    }
106}
107
108/// A commitment to a polynomial with some blinding factors.
109#[derive(Clone, Debug, Serialize, Deserialize)]
110pub struct BlindedCommitment<G>
111where
112    G: CommitmentCurve,
113{
114    pub commitment: PolyComm<G>,
115    pub blinders: PolyComm<G::ScalarField>,
116}
117
118impl<T> PolyComm<T> {
119    #[must_use]
120    pub const fn new(chunks: Vec<T>) -> Self {
121        Self { chunks }
122    }
123}
124
125impl<T, U> SerializeAs<PolyComm<T>> for PolyComm<U>
126where
127    U: SerializeAs<T>,
128{
129    fn serialize_as<S>(source: &PolyComm<T>, serializer: S) -> Result<S::Ok, S::Error>
130    where
131        S: serde::Serializer,
132    {
133        serializer.collect_seq(
134            source
135                .chunks
136                .iter()
137                .map(|e| SerializeAsWrap::<T, U>::new(e)),
138        )
139    }
140}
141
142impl<'de, T, U> DeserializeAs<'de, PolyComm<T>> for PolyComm<U>
143where
144    U: DeserializeAs<'de, T>,
145{
146    fn deserialize_as<D>(deserializer: D) -> Result<PolyComm<T>, D::Error>
147    where
148        D: serde::Deserializer<'de>,
149    {
150        struct SeqVisitor<T, U> {
151            marker: PhantomData<(T, U)>,
152        }
153
154        impl<'de, T, U> Visitor<'de> for SeqVisitor<T, U>
155        where
156            U: DeserializeAs<'de, T>,
157        {
158            type Value = PolyComm<T>;
159
160            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
161                formatter.write_str("a sequence")
162            }
163
164            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
165            where
166                A: serde::de::SeqAccess<'de>,
167            {
168                #[allow(clippy::redundant_closure_call)]
169                let mut chunks = vec![];
170
171                while let Some(value) = seq
172                    .next_element()?
173                    .map(|v: DeserializeAsWrap<T, U>| v.into_inner())
174                {
175                    chunks.push(value);
176                }
177
178                Ok(PolyComm::new(chunks))
179            }
180        }
181
182        let visitor = SeqVisitor::<T, U> {
183            marker: PhantomData,
184        };
185        deserializer.deserialize_seq(visitor)
186    }
187}
188
189impl<A: Copy + Clone + CanonicalDeserialize + CanonicalSerialize> PolyComm<A> {
190    pub fn map<B, F>(&self, mut f: F) -> PolyComm<B>
191    where
192        F: FnMut(A) -> B,
193        B: CanonicalDeserialize + CanonicalSerialize,
194    {
195        let chunks = self.chunks.iter().map(|x| f(*x)).collect();
196        PolyComm::new(chunks)
197    }
198
199    /// Returns the number of chunks.
200    #[must_use]
201    #[allow(clippy::missing_const_for_fn)]
202    pub fn len(&self) -> usize {
203        self.chunks.len()
204    }
205
206    /// Returns `true` if the commitment is empty.
207    #[must_use]
208    #[allow(clippy::missing_const_for_fn)]
209    pub fn is_empty(&self) -> bool {
210        self.chunks.is_empty()
211    }
212
213    // TODO: if all callers end up calling unwrap, just call this zip_eq and
214    // panic here (and document the panic)
215    #[must_use]
216    pub fn zip<B: Copy + CanonicalDeserialize + CanonicalSerialize>(
217        &self,
218        other: &PolyComm<B>,
219    ) -> Option<PolyComm<(A, B)>> {
220        if self.chunks.len() != other.chunks.len() {
221            return None;
222        }
223        let chunks = self
224            .chunks
225            .iter()
226            .zip(other.chunks.iter())
227            .map(|(x, y)| (*x, *y))
228            .collect();
229        Some(PolyComm::new(chunks))
230    }
231
232    /// Return only the first chunk.
233    ///
234    /// Getting this single value is relatively common in the codebase, even
235    /// though we should not do this, and abstract the chunks in the structure.
236    #[must_use]
237    pub fn get_first_chunk(&self) -> A {
238        self.chunks[0]
239    }
240}
241
242/// Inside the circuit, we have a specialized scalar multiplication which
243/// computes either
244///
245/// ```ignore
246/// |g: G, x: G::ScalarField| g.scale(x + 2^n)
247/// ```
248///
249/// if the scalar field of G is greater than the size of the base field
250/// and
251///
252/// ```ignore
253/// |g: G, x: G::ScalarField| g.scale(2*x + 2^n)
254/// ```
255///
256/// otherwise. So, if we want to actually scale by `x`, we need to apply the
257/// inverse function of `|x| x + 2^n` (or of `|x| 2*x + 2^n` in the other case),
258/// before supplying the scalar to our in-circuit scalar-multiplication
259/// function. This computes that inverse function.
260/// Namely,
261///
262/// ```ignore
263/// |x: G::ScalarField| x - 2^n
264/// ```
265///
266/// when the scalar field is larger than the base field and
267///
268/// ```ignore
269/// |x: G::ScalarField| (x - 2^n) / 2
270/// ```
271///
272/// in the other case.
273pub fn shift_scalar<G: AffineRepr>(x: G::ScalarField) -> G::ScalarField
274where
275    G::BaseField: PrimeField,
276{
277    let n1 = <G::ScalarField as PrimeField>::MODULUS;
278    let n2 = <G::ScalarField as PrimeField>::BigInt::from_bits_le(
279        &<G::BaseField as PrimeField>::MODULUS.to_bits_le()[..],
280    );
281    let two: G::ScalarField = (2u64).into();
282    let two_pow = two.pow([u64::from(<G::ScalarField as PrimeField>::MODULUS_BIT_SIZE)]);
283    if n1 < n2 {
284        (x - (two_pow + G::ScalarField::one())) / two
285    } else {
286        x - two_pow
287    }
288}
289
290impl<'a, C: AffineRepr> Add<&'a PolyComm<C>> for &PolyComm<C> {
291    type Output = PolyComm<C>;
292
293    fn add(self, other: &'a PolyComm<C>) -> PolyComm<C> {
294        let mut chunks = vec![];
295        let n1 = self.chunks.len();
296        let n2 = other.chunks.len();
297        for i in 0..core::cmp::max(n1, n2) {
298            let pt = if i < n1 && i < n2 {
299                (self.chunks[i] + other.chunks[i]).into_affine()
300            } else if i < n1 {
301                self.chunks[i]
302            } else {
303                other.chunks[i]
304            };
305            chunks.push(pt);
306        }
307        PolyComm::new(chunks)
308    }
309}
310
311impl<'a, C: AffineRepr + Sub<Output = C::Group>> Sub<&'a PolyComm<C>> for &PolyComm<C> {
312    type Output = PolyComm<C>;
313
314    fn sub(self, other: &'a PolyComm<C>) -> PolyComm<C> {
315        let mut chunks = vec![];
316        let n1 = self.chunks.len();
317        let n2 = other.chunks.len();
318        for i in 0..core::cmp::max(n1, n2) {
319            let pt = if i < n1 && i < n2 {
320                (self.chunks[i] - other.chunks[i]).into_affine()
321            } else if i < n1 {
322                self.chunks[i]
323            } else {
324                other.chunks[i]
325            };
326            chunks.push(pt);
327        }
328        PolyComm::new(chunks)
329    }
330}
331
332impl<C: AffineRepr> PolyComm<C> {
333    #[must_use]
334    pub fn scale(&self, c: C::ScalarField) -> Self {
335        Self {
336            chunks: self.chunks.iter().map(|g| g.mul(c).into_affine()).collect(),
337        }
338    }
339
340    /// Performs a multi-scalar multiplication between scalars `elm` and
341    /// commitments `com`.
342    ///
343    /// If both are empty, returns a commitment of length 1
344    /// containing the point at infinity.
345    ///
346    /// ## Panics
347    ///
348    /// Panics if `com` and `elm` are not of the same size.
349    #[must_use]
350    pub fn multi_scalar_mul(com: &[&Self], elm: &[C::ScalarField]) -> Self {
351        assert_eq!(com.len(), elm.len());
352
353        if com.is_empty() || elm.is_empty() {
354            return Self::new(vec![C::zero()]);
355        }
356
357        let all_scalars: Vec<_> = elm.iter().map(|s| s.into_bigint()).collect();
358
359        let elems_size = Iterator::max(com.iter().map(|c| c.chunks.len())).unwrap();
360
361        let chunks = (0..elems_size)
362            .map(|chunk| {
363                let (points, scalars): (Vec<_>, Vec<_>) = com
364                    .iter()
365                    .zip(&all_scalars)
366                    // get rid of scalars that don't have an associated chunk
367                    .filter_map(|(com, scalar)| com.chunks.get(chunk).map(|c| (c, scalar)))
368                    .unzip();
369
370                // Splitting into 2 chunks seems optimal; but in
371                // practice elems_size is almost always 1
372                //
373                // (see the comment to the `benchmark_msm_parallel_vesta` MSM benchmark)
374                {
375                    #[cfg(feature = "parallel")]
376                    {
377                        let subchunk_size = core::cmp::max(points.len() / 2, 1);
378                        points
379                            .into_par_iter()
380                            .chunks(subchunk_size)
381                            .zip(scalars.into_par_iter().chunks(subchunk_size))
382                            .map(|(psc, ssc)| C::Group::msm_bigint(&psc, &ssc).into_affine())
383                            .reduce(C::zero, |x, y| (x + y).into())
384                    }
385                    #[cfg(not(feature = "parallel"))]
386                    {
387                        C::Group::msm_bigint(&points, &scalars).into_affine()
388                    }
389                }
390            })
391            .collect();
392
393        Self::new(chunks)
394    }
395}
396
397/// Evaluates the challenge polynomial `b(X)` at point `x`.
398///
399/// The challenge polynomial is defined as:
400/// ```text
401/// b(X) = prod_{i=0}^{k-1} (1 + u_{k-i} * X^{2^i})
402/// ```
403/// where `chals = [u_1, ..., u_k]` are the IPA challenges.
404///
405/// This polynomial was introduced in Section 3.2 of the
406/// [Halo paper](https://eprint.iacr.org/2019/1021.pdf) as part of the Inner Product
407/// Argument. It efficiently encodes the folded evaluation point: after `k` rounds
408/// of IPA, the evaluation point vector is reduced to a single value `b(x)`.
409///
410/// The polynomial has degree `2^k - 1` and can be evaluated in `O(k)` field
411/// operations using the product form above. Its coefficients can be computed
412/// using [`b_poly_coefficients`].
413///
414/// # Usage
415///
416/// - In the verifier (`ipa.rs`), `b_poly` is called to compute evaluations at
417///   the challenge points (e.g., `zeta`, `zeta * omega`).
418/// - In `RecursionChallenge::evals`, it computes evaluations for the batched
419///   polynomial commitment check.
420///
421/// # References
422/// - [Halo paper, Section 3.2](https://eprint.iacr.org/2019/1021.pdf) - original
423///   definition of the challenge polynomial
424/// - [PCD paper, Appendix A.2, Step 8](https://eprint.iacr.org/2020/499) - use in
425///   accumulation context
426pub fn b_poly<F: Field>(chals: &[F], x: F) -> F {
427    let k = chals.len();
428
429    let mut pow_twos = vec![x];
430
431    for i in 1..k {
432        pow_twos.push(pow_twos[i - 1].square());
433    }
434
435    product((0..k).map(|i| F::one() + (chals[i] * pow_twos[k - 1 - i])))
436}
437
438/// Computes the coefficients of the challenge polynomial `b(X)`.
439///
440/// Given IPA challenges `chals = [u_1, ..., u_k]`, returns a vector
441/// `[s_0, s_1, ..., s_{2^k - 1}]` such that:
442/// ```text
443/// b(X) = sum_{i=0}^{2^k - 1} s_i * X^i
444/// ```
445///
446/// The coefficients have a closed form: for each index `i` with bit decomposition
447/// `i = sum_j b_j * 2^j`, the coefficient is `s_i = prod_{j: b_j = 1} u_{k-j}`.
448///
449/// # Usage
450///
451/// This function is used when the full coefficient vector is needed:
452/// - In `SRS::verify` (`ipa.rs`): the coefficients are computed and included in
453///   the batched MSM to verify the accumulated commitment `<s, G>`.
454/// - In `RecursionChallenge::evals`: for chunked polynomial evaluation when the
455///   degree exceeds `max_poly_size`.
456///
457/// # Complexity
458///
459/// Returns `2^k` coefficients. The MSM `<s, G>` using these coefficients is the
460/// expensive operation that gets deferred in recursive proof composition.
461///
462/// # References
463/// - [Halo paper, Section 3.2](https://eprint.iacr.org/2019/1021.pdf)
464pub fn b_poly_coefficients<F: Field>(chals: &[F]) -> Vec<F> {
465    let rounds = chals.len();
466    let s_length = 1 << rounds;
467    let mut s = vec![F::one(); s_length];
468    let mut k: usize = 0;
469    let mut pow: usize = 1;
470    for i in 1..s_length {
471        k += usize::from(i == pow);
472        pow <<= u32::from(i == pow);
473        s[i] = s[i - (pow >> 1)] * chals[rounds - 1 - (k - 1)];
474    }
475    s
476}
477
478pub fn squeeze_prechallenge<
479    const FULL_ROUNDS: usize,
480    Fq: Field,
481    G,
482    Fr: Field,
483    EFqSponge: FqSponge<Fq, G, Fr, FULL_ROUNDS>,
484>(
485    sponge: &mut EFqSponge,
486) -> ScalarChallenge<Fr> {
487    ScalarChallenge::new(sponge.challenge())
488}
489
490pub fn squeeze_challenge<
491    const FULL_ROUNDS: usize,
492    Fq: Field,
493    G,
494    Fr: PrimeField,
495    EFqSponge: FqSponge<Fq, G, Fr, FULL_ROUNDS>,
496>(
497    endo_r: &Fr,
498    sponge: &mut EFqSponge,
499) -> Fr {
500    squeeze_prechallenge(sponge).to_field(endo_r)
501}
502
503pub fn absorb_commitment<
504    const FULL_ROUNDS: usize,
505    Fq: Field,
506    G: Clone,
507    Fr: PrimeField,
508    EFqSponge: FqSponge<Fq, G, Fr, FULL_ROUNDS>,
509>(
510    sponge: &mut EFqSponge,
511    commitment: &PolyComm<G>,
512) {
513    sponge.absorb_g(&commitment.chunks);
514}
515
516/// A useful trait extending [`AffineRepr`] for commitments.
517///
518/// Unfortunately, we can't specify that `AffineRepr<BaseField : PrimeField>`,
519/// so usage of this traits must manually bind `G::BaseField: PrimeField`.
520pub trait CommitmentCurve: AffineRepr + Sub<Output = Self::Group> {
521    type Params: SWCurveConfig;
522    type Map: GroupMap<Self::BaseField>;
523
524    fn to_coordinates(&self) -> Option<(Self::BaseField, Self::BaseField)>;
525    fn of_coordinates(x: Self::BaseField, y: Self::BaseField) -> Self;
526}
527
528/// A trait extending [`CommitmentCurve`] for endomorphisms.
529///
530/// Unfortunately, we can't specify that `AffineRepr<BaseField : PrimeField>`,
531/// so usage of this traits must manually bind `G::BaseField: PrimeField`.
532pub trait EndoCurve: CommitmentCurve {
533    /// Combine where x1 = one
534    fn combine_one(g1: &[Self], g2: &[Self], x2: Self::ScalarField) -> Vec<Self> {
535        crate::combine::window_combine(g1, g2, Self::ScalarField::one(), x2)
536    }
537
538    /// Combine where x1 = one
539    fn combine_one_endo(
540        endo_r: Self::ScalarField,
541        _endo_q: Self::BaseField,
542        g1: &[Self],
543        g2: &[Self],
544        x2: &ScalarChallenge<Self::ScalarField>,
545    ) -> Vec<Self> {
546        crate::combine::window_combine(g1, g2, Self::ScalarField::one(), x2.to_field(&endo_r))
547    }
548
549    fn combine(
550        g1: &[Self],
551        g2: &[Self],
552        x1: Self::ScalarField,
553        x2: Self::ScalarField,
554    ) -> Vec<Self> {
555        crate::combine::window_combine(g1, g2, x1, x2)
556    }
557}
558
559impl<P: SWCurveConfig + Clone> CommitmentCurve for SWJAffine<P> {
560    type Params = P;
561    type Map = BWParameters<P>;
562
563    fn to_coordinates(&self) -> Option<(Self::BaseField, Self::BaseField)> {
564        if self.infinity {
565            None
566        } else {
567            Some((self.x, self.y))
568        }
569    }
570
571    fn of_coordinates(x: P::BaseField, y: P::BaseField) -> Self {
572        Self::new_unchecked(x, y)
573    }
574}
575
576impl<P: SWCurveConfig + Clone> EndoCurve for SWJAffine<P> {
577    fn combine_one(g1: &[Self], g2: &[Self], x2: Self::ScalarField) -> Vec<Self> {
578        crate::combine::affine_window_combine_one(g1, g2, x2)
579    }
580
581    fn combine_one_endo(
582        _endo_r: Self::ScalarField,
583        endo_q: Self::BaseField,
584        g1: &[Self],
585        g2: &[Self],
586        x2: &ScalarChallenge<Self::ScalarField>,
587    ) -> Vec<Self> {
588        crate::combine::affine_window_combine_one_endo(endo_q, g1, g2, x2)
589    }
590
591    fn combine(
592        g1: &[Self],
593        g2: &[Self],
594        x1: Self::ScalarField,
595        x2: Self::ScalarField,
596    ) -> Vec<Self> {
597        crate::combine::affine_window_combine(g1, g2, x1, x2)
598    }
599}
600
601/// Computes the linearization of evaluations.
602///
603/// Handles a (potentially split) polynomial.
604/// Each polynomial in `polys` is represented by a matrix where the
605/// rows correspond to evaluated points, and the columns represent
606/// potential segments (if a polynomial was split in several parts).
607///
608/// Elements in `evaluation_points` are several discrete points on which
609/// we evaluate polynomials, e.g. `[zeta,zeta*w]`. See `PointEvaluations`.
610///
611/// Note that if one of the polynomial comes specified with a degree
612/// bound, the evaluation for the last segment is potentially shifted
613/// to meet the proof.
614///
615/// Returns
616/// ```text
617/// |polys| |segments[k]|
618///    Σ         Σ         polyscale^{k*n+i} (Σ polys[k][j][i] * evalscale^j)
619///  k = 1     i = 1                          j
620/// ```
621#[allow(clippy::type_complexity)]
622pub fn combined_inner_product<F: PrimeField>(
623    polyscale: &F,
624    evalscale: &F,
625    // TODO(mimoo): needs a type that can get you evaluations or segments
626    polys: &[Vec<Vec<F>>],
627) -> F {
628    // final combined evaluation result
629    let mut res = F::zero();
630    // polyscale^i
631    let mut polyscale_i = F::one();
632
633    for evals_tr in polys.iter().filter(|evals_tr| !evals_tr[0].is_empty()) {
634        // Transpose the evaluations.
635        // evals[i] = {evals_tr[j][i]}_j now corresponds to a column in
636        // evals_tr, representing a segment.
637        let evals: Vec<_> = (0..evals_tr[0].len())
638            .map(|i| evals_tr.iter().map(|v| v[i]).collect::<Vec<_>>())
639            .collect();
640
641        // Iterating over the polynomial segments.
642        // Each segment gets its own polyscale^i, each segment element j is
643        // multiplied by evalscale^j. Given that polyscale_i = polyscale^i0 at
644        // this point, after this loop we have:
645        //
646        //    res += Σ polyscale^{i0+i} ( Σ evals_tr[j][i] * evalscale^j )
647        //           i                    j
648        //
649        for eval in &evals {
650            // p_i(evalscale)
651            let term = DensePolynomial::<F>::eval_polynomial(eval, *evalscale);
652            res += &(polyscale_i * term);
653            polyscale_i *= polyscale;
654        }
655    }
656    res
657}
658
659/// Contains the evaluation of a polynomial commitment at a set of points.
660pub struct Evaluation<G>
661where
662    G: AffineRepr,
663{
664    /// The commitment of the polynomial being evaluated.
665    ///
666    /// Note that [`PolyComm`] contains a vector of commitments, which handles
667    /// the case when chunking is used, i.e. when the polynomial degree is
668    /// higher than the SRS size.
669    pub commitment: PolyComm<G>,
670
671    /// Contains an evaluation table.
672    ///
673    /// For instance, for vanilla `PlonK`, it would be a vector of (chunked)
674    /// evaluations at zeta and zeta*omega. The outer vector would be the
675    /// evaluations at the different points (e.g. zeta and zeta*omega for
676    /// vanilla `PlonK`) and the inner vector would be the chunks of the
677    /// polynomial.
678    pub evaluations: Vec<Vec<G::ScalarField>>,
679}
680
681/// Contains the batch evaluation
682pub struct BatchEvaluationProof<'a, G, EFqSponge, OpeningProof, const FULL_ROUNDS: usize>
683where
684    G: AffineRepr,
685    EFqSponge: FqSponge<G::BaseField, G, G::ScalarField, FULL_ROUNDS>,
686{
687    /// Sponge used to coin and absorb values and simulate
688    /// non-interactivity using the Fiat-Shamir transformation.
689    pub sponge: EFqSponge,
690    /// A list of evaluations, each supposed to correspond to a different
691    /// polynomial.
692    pub evaluations: Vec<Evaluation<G>>,
693    /// The actual evaluation points. Each field `evaluations` of each structure
694    /// of `Evaluation` should have the same (outer) length.
695    pub evaluation_points: Vec<G::ScalarField>,
696    /// A challenge to combine polynomials. Powers of this point will be used,
697    /// hence the name.
698    pub polyscale: G::ScalarField,
699    /// A challenge to aggregate multiple evaluation points.
700    pub evalscale: G::ScalarField,
701    /// The opening proof.
702    pub opening: &'a OpeningProof,
703    pub combined_inner_product: G::ScalarField,
704}
705
706/// Populates the parameters `scalars` and `points`.
707///
708/// It iterates over the evaluations and adds each commitment to the
709/// vector `points`.
710/// The parameter `scalars` is populated with the values:
711/// `rand_base * polyscale^i` for each commitment.
712/// For instance, if we have 3 commitments, the `scalars` vector will
713/// contain the values
714/// ```text
715/// [rand_base, rand_base * polyscale, rand_base * polyscale^2]
716/// ```
717/// and the vector `points` will contain the commitments.
718///
719/// Note that the function skips the commitments that are empty.
720///
721/// If more than one commitment is present in a single evaluation (i.e. if
722/// `elems` is larger than one), it means that probably chunking was used (i.e.
723/// it is a commitment to a polynomial larger than the SRS).
724pub fn combine_commitments<G: CommitmentCurve>(
725    evaluations: &[Evaluation<G>],
726    scalars: &mut Vec<G::ScalarField>,
727    points: &mut Vec<G>,
728    polyscale: G::ScalarField,
729    rand_base: G::ScalarField,
730) {
731    // will contain the power of polyscale
732    let mut polyscale_i = G::ScalarField::one();
733
734    for Evaluation { commitment, .. } in evaluations.iter().filter(|x| !x.commitment.is_empty()) {
735        // iterating over the polynomial segments
736        for comm_ch in &commitment.chunks {
737            scalars.push(rand_base * polyscale_i);
738            points.push(*comm_ch);
739
740            // compute next power of polyscale
741            polyscale_i *= polyscale;
742        }
743    }
744}
745
746#[cfg(feature = "ocaml_types")]
747#[allow(non_local_definitions)]
748pub mod caml {
749    // polynomial commitment
750    use super::PolyComm;
751    use ark_ec::AffineRepr;
752
753    #[derive(Clone, Debug, ocaml::IntoValue, ocaml::FromValue, ocaml_gen::Struct)]
754    pub struct CamlPolyComm<CamlG> {
755        pub unshifted: Vec<CamlG>,
756        pub shifted: Option<CamlG>,
757    }
758
759    // handy conversions
760
761    impl<G, CamlG> From<PolyComm<G>> for CamlPolyComm<CamlG>
762    where
763        G: AffineRepr,
764        CamlG: From<G>,
765    {
766        fn from(polycomm: PolyComm<G>) -> Self {
767            Self {
768                unshifted: polycomm.chunks.into_iter().map(CamlG::from).collect(),
769                shifted: None,
770            }
771        }
772    }
773
774    impl<'a, G, CamlG> From<&'a PolyComm<G>> for CamlPolyComm<CamlG>
775    where
776        G: AffineRepr,
777        CamlG: From<G> + From<&'a G>,
778    {
779        fn from(polycomm: &'a PolyComm<G>) -> Self {
780            Self {
781                unshifted: polycomm.chunks.iter().map(Into::<CamlG>::into).collect(),
782                shifted: None,
783            }
784        }
785    }
786
787    impl<G, CamlG> From<CamlPolyComm<CamlG>> for PolyComm<G>
788    where
789        G: AffineRepr + From<CamlG>,
790    {
791        fn from(camlpolycomm: CamlPolyComm<CamlG>) -> Self {
792            assert!(
793                camlpolycomm.shifted.is_none(),
794                "mina#14628: Shifted commitments are deprecated and must not be used"
795            );
796            Self {
797                chunks: camlpolycomm
798                    .unshifted
799                    .into_iter()
800                    .map(Into::<G>::into)
801                    .collect(),
802            }
803        }
804    }
805
806    impl<'a, G, CamlG> From<&'a CamlPolyComm<CamlG>> for PolyComm<G>
807    where
808        G: AffineRepr + From<&'a CamlG> + From<CamlG>,
809    {
810        fn from(camlpolycomm: &'a CamlPolyComm<CamlG>) -> Self {
811            assert!(
812                camlpolycomm.shifted.is_none(),
813                "mina#14628: Shifted commitments are deprecated and must not be used"
814            );
815            Self {
816                //FIXME something with as_ref()
817                chunks: camlpolycomm.unshifted.iter().map(Into::into).collect(),
818            }
819        }
820    }
821}
822
823#[cfg(test)]
824mod tests {
825    use super::*;
826    use core::str::FromStr;
827    use mina_curves::pasta::Fp;
828
829    /// Regression test for `b_poly` evaluation.
830    ///
831    /// The challenge polynomial b(X) is defined as:
832    /// ```text
833    /// b(X) = prod_{i=0}^{k-1} (1 + u_{k-i} * X^{2^i})
834    /// ```
835    /// See Section 3.2 of the [Halo paper](https://eprint.iacr.org/2019/1021.pdf).
836    #[test]
837    fn test_b_poly_regression() {
838        // 15 challenges (typical for domain size 2^15)
839        let chals: Vec<Fp> = (2u64..=16).map(Fp::from).collect();
840
841        let zeta = Fp::from(17u64);
842        let zeta_omega = Fp::from(19u64);
843
844        let b_at_zeta = b_poly(&chals, zeta);
845        let b_at_zeta_omega = b_poly(&chals, zeta_omega);
846
847        // Expected values (computed and verified)
848        let expected_at_zeta = Fp::from_str(
849            "21115683812642620361045381629886583866877919362491419134086003378733605776328",
850        )
851        .unwrap();
852        let expected_at_zeta_omega = Fp::from_str(
853            "2298325069360593860729719174291433577456794311517767070156020442825391962511",
854        )
855        .unwrap();
856
857        assert_eq!(b_at_zeta, expected_at_zeta, "b(zeta) mismatch");
858        assert_eq!(
859            b_at_zeta_omega, expected_at_zeta_omega,
860            "b(zeta*omega) mismatch"
861        );
862    }
863
864    /// Regression test for `b_poly_coefficients`.
865    ///
866    /// Verifies that the coefficients satisfy: `b(x) = sum_i s_i * x^i`
867    /// where `s_i = prod_{j: bit_j(i) = 1} u_{k-j}`
868    #[test]
869    fn test_b_poly_coefficients_regression() {
870        // Use 4 challenges for manageable output (2^4 = 16 coefficients)
871        let chals: Vec<Fp> = vec![
872            Fp::from(2u64),
873            Fp::from(3u64),
874            Fp::from(5u64),
875            Fp::from(7u64),
876        ];
877
878        let coeffs = b_poly_coefficients(&chals);
879
880        assert_eq!(coeffs.len(), 16, "Should have 2^4 = 16 coefficients");
881
882        // Expected coefficients (computed and verified)
883        // s_i = prod_{j: bit_j(i) = 1} u_{k-j}
884        // chals = [2, 3, 5, 7] means u_1=2, u_2=3, u_3=5, u_4=7
885        // Index 0 (0000): 1
886        // Index 1 (0001): u_4 = 7
887        // Index 2 (0010): u_3 = 5
888        // Index 3 (0011): u_3 * u_4 = 35
889        // etc.
890        let expected: Vec<Fp> = vec![
891            Fp::from(1u64),   // 0: 1
892            Fp::from(7u64),   // 1: u_4
893            Fp::from(5u64),   // 2: u_3
894            Fp::from(35u64),  // 3: u_3 * u_4
895            Fp::from(3u64),   // 4: u_2
896            Fp::from(21u64),  // 5: u_2 * u_4
897            Fp::from(15u64),  // 6: u_2 * u_3
898            Fp::from(105u64), // 7: u_2 * u_3 * u_4
899            Fp::from(2u64),   // 8: u_1
900            Fp::from(14u64),  // 9: u_1 * u_4
901            Fp::from(10u64),  // 10: u_1 * u_3
902            Fp::from(70u64),  // 11: u_1 * u_3 * u_4
903            Fp::from(6u64),   // 12: u_1 * u_2
904            Fp::from(42u64),  // 13: u_1 * u_2 * u_4
905            Fp::from(30u64),  // 14: u_1 * u_2 * u_3
906            Fp::from(210u64), // 15: u_1 * u_2 * u_3 * u_4
907        ];
908
909        assert_eq!(coeffs, expected, "Coefficients mismatch");
910    }
911
912    /// Test that `b_poly` and `b_poly_coefficients` are consistent:
913    /// evaluating b(x) via the product form should equal evaluating via coefficients.
914    #[test]
915    fn test_b_poly_consistency() {
916        let chals: Vec<Fp> = (2u64..=10).map(Fp::from).collect();
917        let coeffs = b_poly_coefficients(&chals);
918
919        // Test at several points
920        for x_val in [1u64, 7, 13, 42, 100] {
921            let x = Fp::from(x_val);
922
923            // Evaluate via product form
924            let b_product = b_poly(&chals, x);
925
926            // Evaluate via coefficients: sum_i s_i * x^i
927            let mut b_coeffs = Fp::zero();
928            let mut x_pow = Fp::one();
929            for coeff in &coeffs {
930                b_coeffs += *coeff * x_pow;
931                x_pow *= x;
932            }
933
934            assert_eq!(
935                b_product, b_coeffs,
936                "b_poly and b_poly_coefficients inconsistent at x={x_val}"
937            );
938        }
939    }
940}