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