Skip to main content

poly_commitment/
kzg.rs

1//! KZG polynomial commitment scheme (KZG10).
2//!
3//! This module implements the KZG protocol described in the paper
4//! [Constant-Size Commitments to Polynomials and Their
5//! Applications](https://www.iacr.org/archive/asiacrypt2010/6477178/6477178.pdf)
6//! by Kate, Zaverucha and Goldberg, often referred to as the KZG10 paper.
7//!
8//! The protocol requires a structured reference string (SRS) that contains
9//! powers of a generator of a group, and a pairing friendly curve.
10//!
11//! The pairing friendly curve requirement is hidden in the Pairing trait
12//! parameter.
13
14use crate::{
15    commitment::{
16        combine_commitments, BatchEvaluationProof, BlindedCommitment, CommitmentCurve, Evaluation,
17        PolyComm,
18    },
19    ipa::SRS,
20    utils::combine_polys,
21    CommitmentError, PolynomialsToCombine, SRS as SRSTrait,
22};
23
24use ark_ec::{pairing::Pairing, AffineRepr, VariableBaseMSM};
25use ark_ff::{One, PrimeField, Zero};
26use ark_poly::{
27    univariate::{DenseOrSparsePolynomial, DensePolynomial},
28    DenseUVPolynomial, EvaluationDomain, Evaluations, Polynomial, Radix2EvaluationDomain as D,
29};
30use mina_poseidon::FqSponge;
31use rand::thread_rng;
32use rand_core::{CryptoRng, RngCore};
33use serde::{Deserialize, Serialize};
34use serde_with::serde_as;
35use std::ops::Neg;
36
37/// Combine the (chunked) evaluations of multiple polynomials.
38///
39/// This function returns the accumulation of the evaluations, scaled by
40/// `polyscale`.
41/// If no evaluation is given, the function returns an empty vector.
42/// It does also suppose that for each evaluation, the number of evaluations is
43/// the same. It is not constrained yet in the interface, but it should be. If
44/// one list has not the same size, it will be shrunk to the size of the first
45/// element of the list.
46/// For instance, if we have 3 polynomials P1, P2, P3 evaluated at the points
47/// ζ and ζω (like in vanilla `PlonK`), and for each polynomial, we have two
48/// chunks, i.e. we have
49/// ```text
50///         2 chunks of P1
51///        /---------------\
52/// E1 = [(P1_1(ζ), P1_2(ζ)), (P1_1(ζω), P1_2(ζω))]
53/// E2 = [(P2_1(ζ), P2_2(ζ)), (P2_1(ζω), P2_2(ζω))]
54/// E3 = [(P3_1(ζ), P3_2(ζ)), (P3_1(ζω), P3_2(ζω))]
55/// ```
56/// The output will be a list of 3 elements, equal to:
57/// ```text
58/// P1_1(ζ) + P1_2(ζ) * polyscale + P1_1(ζω) polyscale^2 + P1_2(ζω) * polyscale^3
59/// P2_1(ζ) + P2_2(ζ) * polyscale + P2_1(ζω) polyscale^2 + P2_2(ζω) * polyscale^3
60/// ```
61pub fn combine_evaluations<G: CommitmentCurve>(
62    evaluations: &[Evaluation<G>],
63    polyscale: G::ScalarField,
64) -> Vec<G::ScalarField> {
65    let mut polyscale_i = G::ScalarField::one();
66    let mut acc = {
67        let num_evals = evaluations.first().map_or(0, |e| e.evaluations.len());
68        vec![G::ScalarField::zero(); num_evals]
69    };
70
71    for Evaluation { evaluations, .. } in evaluations.iter().filter(|x| !x.commitment.is_empty()) {
72        // IMPROVEME: we could have a flat array that would contain all the
73        // evaluations and all the chunks. It would avoid fetching the memory
74        // and avoid indirection into RAM.
75        // We could have a single flat array.
76        // iterating over the polynomial segments
77        for chunk_idx in 0..evaluations[0].len() {
78            // supposes that all evaluations are of the same size
79            for eval_pt_idx in 0..evaluations.len() {
80                acc[eval_pt_idx] += evaluations[eval_pt_idx][chunk_idx] * polyscale_i;
81            }
82            polyscale_i *= polyscale;
83        }
84    }
85
86    acc
87}
88
89#[serde_as]
90#[derive(Debug, Serialize, Deserialize)]
91#[serde(
92    bound = "Pair::G1Affine: ark_serialize::CanonicalDeserialize + ark_serialize::CanonicalSerialize"
93)]
94pub struct KZGProof<Pair: Pairing> {
95    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
96    pub quotient: Pair::G1Affine,
97    #[serde_as(as = "o1_utils::serialization::SerdeAs")]
98    /// A blinding factor used to hide the polynomial, if necessary
99    pub blinding: <Pair::G1Affine as AffineRepr>::ScalarField,
100}
101
102impl<Pair: Pairing> Default for KZGProof<Pair> {
103    fn default() -> Self {
104        Self {
105            quotient: Pair::G1Affine::generator(),
106            blinding: <Pair::G1Affine as AffineRepr>::ScalarField::zero(),
107        }
108    }
109}
110
111impl<Pair: Pairing> Clone for KZGProof<Pair> {
112    fn clone(&self) -> Self {
113        Self {
114            quotient: self.quotient,
115            blinding: self.blinding,
116        }
117    }
118}
119
120/// Define a structured reference string for the KZG protocol.
121///
122/// The SRS (i.e. [`PairingSRS`]) consists of powers of an element `g^x`
123/// for some toxic waste `x`.
124///
125/// The SRS is formed using what we call a "trusted setup". For now, the setup
126/// is created using the method `create_trusted_setup_with_toxic_waste`.
127#[allow(clippy::unsafe_derive_deserialize)]
128#[derive(Debug, PartialEq, Serialize, Deserialize)]
129pub struct PairingSRS<Pair: Pairing> {
130    /// The full SRS is the one used by the prover. Can be seen as the "proving
131    /// key"/"secret key"
132    pub full_srs: SRS<Pair::G1Affine>,
133    /// SRS to be used by the verifier. Can be seen as the "verification
134    /// key"/"public key".
135    pub verifier_srs: SRS<Pair::G2Affine>,
136}
137
138impl<
139        F: PrimeField,
140        G: CommitmentCurve<ScalarField = F>,
141        G2: CommitmentCurve<ScalarField = F>,
142        Pair: Pairing<G1Affine = G, G2Affine = G2>,
143    > PairingSRS<Pair>
144{
145    /// Create a trusted setup for the KZG protocol.
146    ///
147    /// # Security
148    ///
149    /// The caller must ensure that `toxic_waste` is securely zeroized
150    /// after use. Leaking it compromises the soundness of the proof system.
151    pub fn create_trusted_setup_with_toxic_waste(toxic_waste: F, depth: usize) -> Self {
152        let full_srs = SRS::create_trusted_setup_with_toxic_waste(toxic_waste, depth);
153        let verifier_srs = SRS::create_trusted_setup_with_toxic_waste(toxic_waste, 3);
154        Self {
155            full_srs,
156            verifier_srs,
157        }
158    }
159}
160
161impl<Pair: Pairing> Default for PairingSRS<Pair> {
162    fn default() -> Self {
163        Self {
164            full_srs: SRS::default(),
165            verifier_srs: SRS::default(),
166        }
167    }
168}
169
170impl<Pair: Pairing> Clone for PairingSRS<Pair> {
171    fn clone(&self) -> Self {
172        Self {
173            full_srs: self.full_srs.clone(),
174            verifier_srs: self.verifier_srs.clone(),
175        }
176    }
177}
178
179impl<
180        F: PrimeField,
181        G: CommitmentCurve<ScalarField = F>,
182        G2: CommitmentCurve<ScalarField = F>,
183        Pair: Pairing<G1Affine = G, G2Affine = G2>,
184        const FULL_ROUNDS: usize,
185    > crate::OpenProof<G, FULL_ROUNDS> for KZGProof<Pair>
186{
187    type SRS = PairingSRS<Pair>;
188
189    /// Parameters:
190    /// - `srs`: the structured reference string
191    /// - `plnms`: vector of polynomials with optional degree bound and
192    ///   commitment randomness
193    /// - `elm`: vector of evaluation points
194    /// - `polyscale`: scaling factor for polynoms
195    ///   `group_maps`, `sponge`, `rng` and `evalscale` are not used. The
196    ///   parameters are kept to fit the trait and to be used generically.
197    fn open<EFqSponge, RNG, D: EvaluationDomain<F>>(
198        srs: &Self::SRS,
199        _group_map: &<G as CommitmentCurve>::Map,
200        plnms: PolynomialsToCombine<G, D>,
201        elm: &[<G as AffineRepr>::ScalarField],
202        polyscale: <G as AffineRepr>::ScalarField,
203        _evalscale: <G as AffineRepr>::ScalarField,
204        _sponge: EFqSponge,
205        _rng: &mut RNG,
206    ) -> Self
207    where
208        EFqSponge: Clone + FqSponge<<G as AffineRepr>::BaseField, G, F, FULL_ROUNDS>,
209        RNG: RngCore + CryptoRng,
210    {
211        Self::create(srs, plnms, elm, polyscale).unwrap()
212    }
213
214    fn verify<EFqSponge, RNG>(
215        srs: &Self::SRS,
216        _group_map: &G::Map,
217        batch: &mut [BatchEvaluationProof<G, EFqSponge, Self, FULL_ROUNDS>],
218        _rng: &mut RNG,
219    ) -> bool
220    where
221        EFqSponge: FqSponge<G::BaseField, G, F, FULL_ROUNDS>,
222        RNG: RngCore + CryptoRng,
223    {
224        for BatchEvaluationProof {
225            sponge: _,
226            evaluations,
227            evaluation_points,
228            polyscale,
229            evalscale: _,
230            opening,
231            combined_inner_product: _,
232        } in batch.iter()
233        {
234            if !opening.verify(srs, evaluations, *polyscale, evaluation_points) {
235                return false;
236            }
237        }
238        true
239    }
240}
241
242impl<
243        F: PrimeField,
244        G: CommitmentCurve<ScalarField = F>,
245        G2: CommitmentCurve<ScalarField = F>,
246        Pair: Pairing<G1Affine = G, G2Affine = G2>,
247    > SRSTrait<G> for PairingSRS<Pair>
248{
249    fn max_poly_size(&self) -> usize {
250        self.full_srs.max_poly_size()
251    }
252
253    fn get_lagrange_basis(&self, domain: D<G::ScalarField>) -> &Vec<PolyComm<G>> {
254        self.full_srs.get_lagrange_basis(domain)
255    }
256
257    fn get_lagrange_basis_from_domain_size(&self, domain_size: usize) -> &Vec<PolyComm<G>> {
258        self.full_srs
259            .get_lagrange_basis_from_domain_size(domain_size)
260    }
261
262    fn blinding_commitment(&self) -> G {
263        self.full_srs.blinding_commitment()
264    }
265
266    fn mask_custom(
267        &self,
268        com: PolyComm<G>,
269        blinders: &PolyComm<G::ScalarField>,
270    ) -> Result<BlindedCommitment<G>, CommitmentError> {
271        self.full_srs.mask_custom(com, blinders)
272    }
273
274    fn mask(
275        &self,
276        comm: PolyComm<G>,
277        rng: &mut (impl RngCore + CryptoRng),
278    ) -> BlindedCommitment<G> {
279        self.full_srs.mask(comm, rng)
280    }
281
282    fn commit(
283        &self,
284        plnm: &DensePolynomial<F>,
285        num_chunks: usize,
286        rng: &mut (impl RngCore + CryptoRng),
287    ) -> BlindedCommitment<G> {
288        self.full_srs.commit(plnm, num_chunks, rng)
289    }
290
291    fn commit_non_hiding(
292        &self,
293        plnm: &DensePolynomial<G::ScalarField>,
294        num_chunks: usize,
295    ) -> PolyComm<G> {
296        self.full_srs.commit_non_hiding(plnm, num_chunks)
297    }
298
299    fn commit_custom(
300        &self,
301        plnm: &DensePolynomial<<G>::ScalarField>,
302        num_chunks: usize,
303        blinders: &PolyComm<<G>::ScalarField>,
304    ) -> Result<BlindedCommitment<G>, CommitmentError> {
305        self.full_srs.commit_custom(plnm, num_chunks, blinders)
306    }
307
308    fn commit_evaluations_non_hiding(
309        &self,
310        domain: D<G::ScalarField>,
311        plnm: &Evaluations<G::ScalarField, D<G::ScalarField>>,
312    ) -> PolyComm<G> {
313        self.full_srs.commit_evaluations_non_hiding(domain, plnm)
314    }
315
316    fn commit_evaluations(
317        &self,
318        domain: D<G::ScalarField>,
319        plnm: &Evaluations<G::ScalarField, D<G::ScalarField>>,
320        rng: &mut (impl RngCore + CryptoRng),
321    ) -> BlindedCommitment<G> {
322        self.full_srs.commit_evaluations(domain, plnm, rng)
323    }
324
325    fn commit_evaluations_custom(
326        &self,
327        domain: D<<G>::ScalarField>,
328        plnm: &Evaluations<<G>::ScalarField, D<<G>::ScalarField>>,
329        blinders: &PolyComm<<G>::ScalarField>,
330    ) -> Result<BlindedCommitment<G>, CommitmentError> {
331        self.full_srs
332            .commit_evaluations_custom(domain, plnm, blinders)
333    }
334
335    fn create(depth: usize) -> Self {
336        let mut rng = thread_rng();
337        let mut toxic_waste = G::ScalarField::rand(&mut rng);
338        let result = Self::create_trusted_setup_with_toxic_waste(toxic_waste, depth);
339        toxic_waste.zeroize();
340        result
341    }
342
343    fn size(&self) -> usize {
344        self.full_srs.g.len()
345    }
346}
347
348/// The polynomial that evaluates to each of `evals` for the respective `elm`s.
349/// For now, only works for 2 evaluations points.
350/// `elm` is the vector of evaluation points and `evals` is the vector of
351/// evaluations at those points.
352fn eval_polynomial<F: PrimeField>(elm: &[F], evals: &[F]) -> DensePolynomial<F> {
353    assert_eq!(elm.len(), evals.len());
354    let (zeta, zeta_omega) = if elm.len() == 2 {
355        (elm[0], elm[1])
356    } else {
357        todo!()
358    };
359    let (eval_zeta, eval_zeta_omega) = if evals.len() == 2 {
360        (evals[0], evals[1])
361    } else {
362        todo!()
363    };
364
365    // The polynomial that evaluates to `p(ζ)` at `ζ` and `p(ζω)` at
366    // `ζω`.
367    // We write `p(x) = a + bx`, which gives
368    // ```text
369    // p(ζ) = a + b * ζ
370    // p(ζω) = a + b * ζω
371    // ```
372    // and so
373    // ```text
374    // b = (p(ζω) - p(ζ)) / (ζω - ζ)
375    // a = p(ζ) - b * ζ
376    // ```
377    let b = (eval_zeta_omega - eval_zeta) / (zeta_omega - zeta);
378    let a = eval_zeta - b * zeta;
379    DensePolynomial::from_coefficients_slice(&[a, b])
380}
381
382/// The polynomial that evaluates to `0` at the evaluation points.
383fn divisor_polynomial<F: PrimeField>(elm: &[F]) -> DensePolynomial<F> {
384    elm.iter()
385        .map(|value| DensePolynomial::from_coefficients_slice(&[-(*value), F::one()]))
386        .reduce(|poly1, poly2| &poly1 * &poly2)
387        .unwrap()
388}
389
390impl<
391        F: PrimeField,
392        G: CommitmentCurve<ScalarField = F>,
393        G2: CommitmentCurve<ScalarField = F>,
394        Pair: Pairing<G1Affine = G, G2Affine = G2>,
395    > KZGProof<Pair>
396{
397    /// Create a KZG proof.
398    /// Parameters:
399    /// - `srs`: the structured reference string used to commit
400    ///   to the polynomials
401    /// - `plnms`: the list of polynomials to open.
402    ///   The type is simply an alias to handle the polynomials in evaluations
403    ///   or coefficients forms.
404    /// - `elm`: vector of evaluation points. Note that it only works for two
405    ///   elements for now.
406    /// - `polyscale`: a challenge to batch the polynomials.
407    pub fn create<D: EvaluationDomain<F>>(
408        srs: &PairingSRS<Pair>,
409        plnms: PolynomialsToCombine<G, D>,
410        elm: &[F],
411        polyscale: F,
412    ) -> Option<Self> {
413        let (p, blinding_factor) = combine_polys::<G, D>(plnms, polyscale, srs.full_srs.g.len());
414        let evals: Vec<_> = elm.iter().map(|pt| p.evaluate(pt)).collect();
415
416        let quotient_poly = {
417            // This is where the condition on two points is enforced.
418            let eval_polynomial = eval_polynomial(elm, &evals);
419            let divisor_polynomial = divisor_polynomial(elm);
420            let numerator_polynomial = &p - &eval_polynomial;
421            let (quotient, remainder) = DenseOrSparsePolynomial::divide_with_q_and_r(
422                &numerator_polynomial.into(),
423                &divisor_polynomial.into(),
424            )?;
425            if !remainder.is_zero() {
426                return None;
427            }
428            quotient
429        };
430
431        let quotient = srs
432            .full_srs
433            .commit_non_hiding(&quotient_poly, 1)
434            .get_first_chunk();
435
436        Some(Self {
437            quotient,
438            blinding: blinding_factor,
439        })
440    }
441
442    /// Verify a proof. Note that it only works for two elements for now, i.e.
443    /// elm must be of size 2.
444    /// Also, chunking is not supported.
445    pub fn verify(
446        &self,
447        srs: &PairingSRS<Pair>,
448        evaluations: &[Evaluation<G>], // commitments to the polynomials
449        polyscale: F,                  // scaling factor for polynoms
450        elm: &[F],                     // vector of evaluation points
451    ) -> bool {
452        let poly_commitment: G::Group = {
453            let mut scalars: Vec<F> = Vec::new();
454            let mut points = Vec::new();
455            combine_commitments(
456                evaluations,
457                &mut scalars,
458                &mut points,
459                polyscale,
460                F::one(), /* TODO: This is inefficient */
461            );
462            let scalars: Vec<_> = scalars.iter().map(|x| x.into_bigint()).collect();
463
464            G::Group::msm_bigint(&points, &scalars)
465        };
466
467        // IMPROVEME: we could have a single flat array for all evaluations, see
468        // same comment in combine_evaluations
469        let evals = combine_evaluations(evaluations, polyscale);
470        let blinding_commitment = srs.full_srs.h.mul(self.blinding);
471        // Taking the first element of the commitment, i.e. no support for
472        // chunking.
473        let divisor_commitment = srs
474            .verifier_srs
475            .commit_non_hiding(&divisor_polynomial(elm), 1)
476            .get_first_chunk();
477        // Taking the first element of the commitment, i.e. no support for
478        // chunking.
479        let eval_commitment = srs
480            .full_srs
481            .commit_non_hiding(&eval_polynomial(elm, &evals), 1)
482            .get_first_chunk()
483            .into_group();
484        let numerator_commitment = { poly_commitment - eval_commitment - blinding_commitment };
485        // We compute the result of the multiplication of two miller loop, to
486        // apply only one final exponentiation
487        let to_loop_left = [
488            ark_ec::pairing::prepare_g1::<Pair>(numerator_commitment),
489            // Note that we do a neagtion here, to put everything on the same
490            // side
491            ark_ec::pairing::prepare_g1::<Pair>(self.quotient.into_group().neg()),
492        ];
493        let to_loop_right = [
494            ark_ec::pairing::prepare_g2::<Pair>(Pair::G2Affine::generator()),
495            ark_ec::pairing::prepare_g2::<Pair>(divisor_commitment),
496        ];
497        // the result here is
498        // `numerator_commitment * 1 - quotient * divisor_commitment`.
499        // Note that the unwrap cannot fail as the output of a miller loop is
500        // non zero
501        let res = Pair::multi_pairing(to_loop_left, to_loop_right);
502
503        res.is_zero()
504    }
505}