ivc/
prover.rs

1#![allow(clippy::type_complexity)]
2#![allow(clippy::boxed_local)]
3
4use crate::{
5    expr_eval::SimpleEvalEnv,
6    plonkish_lang::{PlonkishChallenge, PlonkishInstance, PlonkishWitness},
7};
8use ark_ff::{Field, One, Zero};
9use ark_poly::{
10    univariate::DensePolynomial, EvaluationDomain, Evaluations, Polynomial,
11    Radix2EvaluationDomain as R2D,
12};
13use folding::{
14    eval_leaf::EvalLeaf,
15    instance_witness::{ExtendedWitness, RelaxedInstance, RelaxedWitness},
16    Alphas, FoldingCompatibleExpr, FoldingConfig,
17};
18use kimchi::{
19    self,
20    circuits::{
21        domains::EvaluationDomains,
22        expr::{ColumnEvaluations, ExprError},
23    },
24    curve::KimchiCurve,
25    groupmap::GroupMap,
26    plonk_sponge::FrSponge,
27    proof::PointEvaluations,
28};
29use kimchi_msm::{columns::Column as GenericColumn, witness::Witness};
30use mina_poseidon::{sponge::ScalarChallenge, FqSponge};
31use o1_utils::ExtendedDensePolynomial;
32use poly_commitment::{
33    commitment::{absorb_commitment, CommitmentCurve, PolyComm},
34    kzg::{KZGProof, PairingSRS},
35    utils::DensePolynomialOrEvaluations,
36    OpenProof, SRS,
37};
38use rand::{CryptoRng, RngCore};
39use rayon::iter::{IntoParallelIterator, ParallelIterator};
40use std::collections::BTreeMap;
41use thiserror::Error;
42
43/// Errors that can arise when creating a proof
44#[derive(Error, Debug, Clone)]
45pub enum ProverError {
46    #[error("the proof could not be constructed: {0}")]
47    Generic(&'static str),
48
49    #[error("the provided (witness) constraints was not satisfied: {0}")]
50    ConstraintNotSatisfied(String),
51
52    #[error("the provided (witness) constraint has degree {0} > allowed {1}; expr: {2}")]
53    ConstraintDegreeTooHigh(u64, u64, String),
54}
55
56pub type Pairing = kimchi_msm::BN254;
57/// The curve we commit into
58pub type G = kimchi_msm::BN254G1Affine;
59/// Scalar field of the curve.
60pub type Fp = kimchi_msm::Fp;
61/// The base field of the curve
62/// Used to encode the polynomial commitments
63pub type Fq = ark_bn254::Fq;
64
65#[derive(Debug, Clone)]
66// TODO Should public input and fixed selectors evaluations be here?
67pub struct ProofEvaluations<
68    const N_WIT: usize,
69    const N_REL: usize,
70    const N_DSEL: usize,
71    const N_FSEL: usize,
72    F,
73> {
74    /// Witness evaluations, including public inputs
75    pub witness_evals: Witness<N_WIT, PointEvaluations<F>>,
76    /// Evaluations of fixed selectors.
77    pub fixed_selectors_evals: Box<[PointEvaluations<F>; N_FSEL]>,
78    pub error_vec: PointEvaluations<F>,
79    /// Evaluation of Z_H(ζ) (t_0(X) + ζ^n t_1(X) + ...) at ζω.
80    pub ft_eval1: F,
81}
82
83/// The trait ColumnEvaluations is used by the verifier.
84/// It will return the evaluation of the corresponding column at the
85/// evaluation points coined by the verifier during the protocol.
86impl<
87        const N_WIT: usize,
88        const N_REL: usize,
89        const N_DSEL: usize,
90        const N_FSEL: usize,
91        F: Clone,
92    > ColumnEvaluations<F> for ProofEvaluations<N_WIT, N_REL, N_DSEL, N_FSEL, F>
93{
94    type Column = kimchi_msm::columns::Column<usize>;
95
96    fn evaluate(&self, col: Self::Column) -> Result<PointEvaluations<F>, ExprError<Self::Column>> {
97        // TODO: substitute when non-literal generic constants are available
98        assert!(N_WIT == N_REL + N_DSEL);
99        let res = match col {
100            Self::Column::Relation(i) => {
101                assert!(i < N_REL, "Index out of bounds");
102                self.witness_evals[i].clone()
103            }
104            Self::Column::DynamicSelector(i) => {
105                assert!(i < N_DSEL, "Index out of bounds");
106                self.witness_evals[N_REL + i].clone()
107            }
108            Self::Column::FixedSelector(i) => {
109                assert!(i < N_FSEL, "Index out of bounds");
110                self.fixed_selectors_evals[i].clone()
111            }
112            _ => panic!("lookup columns not supported"),
113        };
114        Ok(res)
115    }
116}
117
118#[derive(Debug, Clone)]
119pub struct ProofCommitments<const N_WIT: usize, G: KimchiCurve> {
120    /// Commitments to the N columns of the circuits, also called the 'witnesses'.
121    /// If some columns are considered as public inputs, it is counted in the witness.
122    pub witness_comms: Witness<N_WIT, PolyComm<G>>,
123    /// Commitments to the quotient polynomial.
124    /// The value contains the chunked polynomials.
125    pub t_comm: PolyComm<G>,
126}
127
128#[derive(Debug, Clone)]
129pub struct Proof<
130    const N_WIT: usize,
131    const N_REL: usize,
132    const N_DSEL: usize,
133    const N_FSEL: usize,
134    G: KimchiCurve,
135    OpeningProof: OpenProof<G>,
136> {
137    pub proof_comms: ProofCommitments<N_WIT, G>,
138    pub proof_evals: ProofEvaluations<N_WIT, N_REL, N_DSEL, N_FSEL, G::ScalarField>,
139    pub opening_proof: OpeningProof,
140
141    // Unsure whether this is necessary.
142    pub alphas: Alphas<G::ScalarField>,
143    pub challenges: [G::ScalarField; 3],
144    pub u: G::ScalarField,
145}
146
147pub fn prove<
148    EFqSponge: Clone + FqSponge<Fq, G, Fp>,
149    EFrSponge: FrSponge<Fp>,
150    FC: FoldingConfig<Column = GenericColumn<usize>, Curve = G, Challenge = PlonkishChallenge>,
151    RNG,
152    const N_WIT: usize,
153    const N_WIT_QUAD: usize, // witness columns + quad columns
154    const N_REL: usize,
155    const N_DSEL: usize,
156    const N_FSEL: usize,
157    const N_ALPHAS: usize,
158>(
159    domain: EvaluationDomains<Fp>,
160    srs: &PairingSRS<Pairing>,
161    combined_expr: &FoldingCompatibleExpr<FC>,
162    folded_instance: RelaxedInstance<G, PlonkishInstance<G, N_WIT, 3, N_ALPHAS>>,
163    folded_witness: RelaxedWitness<G, PlonkishWitness<N_WIT, N_FSEL, Fp>>,
164    rng: &mut RNG,
165) -> Result<Proof<N_WIT_QUAD, N_WIT_QUAD, N_DSEL, N_FSEL, G, KZGProof<Pairing>>, ProverError>
166where
167    RNG: RngCore + CryptoRng,
168{
169    assert_eq!(
170        folded_witness.extended_witness.extended.values().len(),
171        N_WIT_QUAD - N_WIT
172    );
173    assert!(N_WIT == N_REL + N_DSEL);
174
175    ////////////////////////////////////////////////////////////////////////////
176    // Setting up the protocol
177    ////////////////////////////////////////////////////////////////////////////
178
179    let group_map = <G as CommitmentCurve>::Map::setup();
180
181    ////////////////////////////////////////////////////////////////////////////
182    // Round 1: Creating and absorbing column commitments
183    ////////////////////////////////////////////////////////////////////////////
184
185    let mut fq_sponge = EFqSponge::new(G::other_curve_sponge_params());
186
187    let fixed_selectors_evals_d1: Box<[Evaluations<Fp, R2D<Fp>>; N_FSEL]> =
188        folded_witness.extended_witness.witness.fixed_selectors.cols;
189
190    let fixed_selectors_polys: Box<[DensePolynomial<Fp>; N_FSEL]> =
191        o1_utils::array::vec_to_boxed_array(
192            fixed_selectors_evals_d1
193                .clone()
194                .into_par_iter()
195                .map(|evals| evals.interpolate())
196                .collect(),
197        );
198
199    let fixed_selectors_comms: Box<[PolyComm<G>; N_FSEL]> = {
200        let comm = |poly: &DensePolynomial<Fp>| srs.commit_non_hiding(poly, 1);
201        o1_utils::array::vec_to_boxed_array(
202            fixed_selectors_polys
203                .as_ref()
204                .into_par_iter()
205                .map(comm)
206                .collect(),
207        )
208    };
209
210    // Do not use parallelism
211    (fixed_selectors_comms)
212        .into_iter()
213        .for_each(|comm| absorb_commitment(&mut fq_sponge, &comm));
214
215    let witness_main: Witness<N_WIT, _> = folded_witness.extended_witness.witness.witness;
216    let witness_ext: BTreeMap<usize, Evaluations<Fp, R2D<Fp>>> =
217        folded_witness.extended_witness.extended;
218
219    // Joint main + ext
220    let witness_evals_d1: Witness<N_WIT_QUAD, Evaluations<_, _>> = {
221        let mut acc = witness_main.cols.to_vec();
222        acc.extend(witness_ext.values().cloned());
223        acc.try_into().unwrap()
224    };
225
226    let witness_polys: Witness<N_WIT_QUAD, DensePolynomial<Fp>> = {
227        witness_evals_d1
228            .into_par_iter()
229            .map(|e| e.interpolate())
230            .collect::<Vec<_>>()
231            .try_into()
232            .unwrap()
233    };
234
235    let witness_comms: Witness<N_WIT_QUAD, PolyComm<G>> = {
236        let blinders = PolyComm {
237            chunks: vec![Fp::one()],
238        };
239        let comm = {
240            |poly: &DensePolynomial<Fp>| {
241                // In case the column polynomial is all zeroes, we want to mask the commitment
242                let comm = srs.commit_custom(poly, 1, &blinders).unwrap();
243                comm.commitment
244            }
245        };
246        (&witness_polys)
247            .into_par_iter()
248            .map(comm)
249            .collect::<Witness<N_WIT_QUAD, PolyComm<G>>>()
250    };
251
252    // Do not use parallelism
253    (&witness_comms)
254        .into_iter()
255        .for_each(|comm| absorb_commitment(&mut fq_sponge, comm));
256
257    ////////////////////////////////////////////////////////////////////////////
258    // Round 2: Creating and committing to the quotient polynomial
259    ////////////////////////////////////////////////////////////////////////////
260
261    let (_, endo_r) = G::endos();
262
263    let quotient_poly = {
264        let evaluation_domain = domain.d4;
265
266        let enlarge_to_domain_generic =
267            |evaluations: &Evaluations<Fp, R2D<Fp>>, new_domain: R2D<Fp>| {
268                assert!(evaluations.domain() == domain.d1);
269                evaluations
270                    .interpolate_by_ref()
271                    .evaluate_over_domain_by_ref(new_domain)
272            };
273
274        let enlarge_to_domain = |evaluations: &Evaluations<Fp, R2D<Fp>>| {
275            enlarge_to_domain_generic(evaluations, evaluation_domain)
276        };
277
278        let simple_eval_env: SimpleEvalEnv<G, N_WIT, N_FSEL> = {
279            let ext_witness = ExtendedWitness {
280                witness: PlonkishWitness {
281                    witness: (&witness_main)
282                        .into_par_iter()
283                        .map(enlarge_to_domain)
284                        .collect(),
285                    fixed_selectors: (&fixed_selectors_evals_d1.to_vec())
286                        .into_par_iter()
287                        .map(enlarge_to_domain)
288                        .collect(),
289                    phantom: core::marker::PhantomData,
290                },
291                extended: (&witness_ext)
292                    .into_par_iter()
293                    .map(|(ix, evals)| (*ix, enlarge_to_domain(evals)))
294                    .collect(),
295            };
296
297            SimpleEvalEnv {
298                ext_witness,
299                alphas: folded_instance.extended_instance.instance.alphas.clone(),
300                challenges: folded_instance.extended_instance.instance.challenges,
301                error_vec: enlarge_to_domain(&folded_witness.error_vec),
302                u: folded_instance.u,
303            }
304        };
305
306        {
307            let eval_leaf = simple_eval_env.eval_naive_fcompat(combined_expr);
308
309            let evaluations_big = match eval_leaf {
310                EvalLeaf::Result(evaluations) => evaluations,
311                EvalLeaf::Col(evaluations) => evaluations.to_vec().clone(),
312                _ => panic!("eval_leaf is not Result"),
313            };
314
315            let interpolated =
316                Evaluations::from_vec_and_domain(evaluations_big, evaluation_domain).interpolate();
317            if interpolated.is_zero() {
318                println!("Interpolated expression is zero");
319            }
320
321            let (quotient, remainder) = interpolated
322                .divide_by_vanishing_poly(domain.d1)
323                .unwrap_or_else(|| panic!("ERROR: Cannot divide by vanishing polynomial"));
324            if !remainder.is_zero() {
325                panic!("ERROR: Remainder is not zero for joint folding expression",);
326            }
327
328            quotient
329        }
330    };
331
332    // we interpolate over d4, so number of chunks should be 3
333    let num_chunks: usize = 3;
334
335    //~ 1. commit to the quotient polynomial $t$.
336    let t_comm = srs.commit_non_hiding(&quotient_poly, num_chunks);
337
338    ////////////////////////////////////////////////////////////////////////////
339    // Round 3: Evaluations at ζ and ζω
340    ////////////////////////////////////////////////////////////////////////////
341
342    //~ 1. Absorb the commitment of the quotient polynomial with the Fq-Sponge.
343    absorb_commitment(&mut fq_sponge, &t_comm);
344
345    //~ 1. Sample ζ with the Fq-Sponge.
346    let zeta_chal = ScalarChallenge(fq_sponge.challenge());
347
348    let zeta = zeta_chal.to_field(endo_r);
349
350    let omega = domain.d1.group_gen;
351    // We will also evaluate at ζω as lookups do require to go to the next row.
352    let zeta_omega = zeta * omega;
353
354    let eval_at_challenge = |p: &DensePolynomial<_>| PointEvaluations {
355        zeta: p.evaluate(&zeta),
356        zeta_omega: p.evaluate(&zeta_omega),
357    };
358
359    // Evaluate the polynomials at ζ and ζω -- Columns
360    let witness_point_evals: Witness<N_WIT_QUAD, PointEvaluations<_>> = {
361        (&witness_polys)
362            .into_par_iter()
363            .map(eval_at_challenge)
364            .collect::<Witness<N_WIT_QUAD, PointEvaluations<_>>>()
365    };
366
367    let fixed_selectors_point_evals: Box<[PointEvaluations<_>; N_FSEL]> = {
368        o1_utils::array::vec_to_boxed_array(
369            fixed_selectors_polys
370                .as_ref()
371                .into_par_iter()
372                .map(eval_at_challenge)
373                .collect::<_>(),
374        )
375    };
376
377    let error_vec_point_eval = eval_at_challenge(&folded_witness.error_vec.interpolate());
378
379    ////////////////////////////////////////////////////////////////////////////
380    // Round 4: Opening proof w/o linearization polynomial
381    ////////////////////////////////////////////////////////////////////////////
382
383    // Fiat Shamir - absorbing evaluations
384    let fq_sponge_before_evaluations = fq_sponge.clone();
385    let mut fr_sponge = EFrSponge::new(G::sponge_params());
386    fr_sponge.absorb(&fq_sponge.digest());
387
388    for PointEvaluations { zeta, zeta_omega } in (&witness_point_evals).into_iter() {
389        fr_sponge.absorb(zeta);
390        fr_sponge.absorb(zeta_omega);
391    }
392
393    for PointEvaluations { zeta, zeta_omega } in fixed_selectors_point_evals.as_ref().iter() {
394        fr_sponge.absorb(zeta);
395        fr_sponge.absorb(zeta_omega);
396    }
397
398    // Compute ft(X) = \
399    //   (1 - ζ^n) \
400    //    (t_0(X) + ζ^n t_1(X) + ... + ζ^{kn} t_{k}(X))
401    // where \sum_i t_i(X) X^{i n} = t(X), and t(X) is the quotient polynomial.
402    // At the end, we get the (partial) evaluation of the constraint polynomial
403    // in ζ.
404    //
405    // Note: both (ζ^n - 1) and (1 - ζ^n) (and C * (1 - ζ^n)) are
406    // vanishing polynomial, but we have to be consistent with respect
407    // to just one everywhere.
408    let ft: DensePolynomial<Fp> = {
409        let evaluation_point_to_domain_size = zeta.pow([domain.d1.size]);
410        // Compute \sum_i t_i(X) ζ^{i n}
411        // First we split t in t_i, and we reduce to degree (n - 1) after using `linearize`
412        let t_chunked: DensePolynomial<Fp> = quotient_poly
413            .to_chunked_polynomial(num_chunks, domain.d1.size as usize)
414            .linearize(evaluation_point_to_domain_size);
415
416        // -Z_H = (1 - ζ^n)
417        let minus_vanishing_poly_at_zeta: Fp = -domain.d1.vanishing_polynomial().evaluate(&zeta);
418        // Multiply the polynomial \sum_i t_i(X) ζ^{i n} by -Z_H(ζ)
419        // (the evaluation in ζ of the vanishing polynomial)
420        t_chunked.scale(minus_vanishing_poly_at_zeta)
421    };
422
423    // We only evaluate at ζω as the verifier can compute the
424    // evaluation at ζ from the independent evaluations at ζ of the
425    // witness columns because ft(X) is the constraint polynomial, built from
426    // the public constraints.
427    // We evaluate at ζω because the lookup argument requires to compute
428    // \phi(Xω) - \phi(X).
429    let ft_eval1 = ft.evaluate(&zeta_omega);
430
431    // Absorb ft(ζω)
432    fr_sponge.absorb(&ft_eval1);
433
434    let v_chal = fr_sponge.challenge();
435    let v = v_chal.to_field(endo_r);
436    let u_chal = fr_sponge.challenge();
437    let u = u_chal.to_field(endo_r);
438
439    let coefficients_form = DensePolynomialOrEvaluations::DensePolynomial;
440    let non_hiding = |n_chunks| PolyComm {
441        chunks: vec![Fp::zero(); n_chunks],
442    };
443    let hiding = |n_chunks| PolyComm {
444        chunks: vec![Fp::one(); n_chunks],
445    };
446
447    // Gathering all polynomials_to_open to use in the opening proof
448    let mut polynomials_to_open: Vec<_> = vec![];
449
450    polynomials_to_open.extend(
451        (&witness_polys)
452            .into_par_iter()
453            .map(|poly| (coefficients_form(poly), hiding(1)))
454            .collect::<Vec<_>>(),
455    );
456
457    // @volhovm: I'm not sure we need to prove opening of fixed
458    // selectors in the commitment.
459    polynomials_to_open.extend(
460        fixed_selectors_polys
461            .as_ref()
462            .into_par_iter()
463            .map(|poly| (coefficients_form(poly), non_hiding(1)))
464            .collect::<Vec<_>>(),
465    );
466
467    polynomials_to_open.push((coefficients_form(&ft), non_hiding(1)));
468
469    let opening_proof = OpenProof::open::<_, _, R2D<Fp>>(
470        srs,
471        &group_map,
472        polynomials_to_open.as_slice(),
473        &[zeta, zeta_omega],
474        v,
475        u,
476        fq_sponge_before_evaluations,
477        rng,
478    );
479
480    let proof_evals: ProofEvaluations<N_WIT_QUAD, N_WIT_QUAD, N_DSEL, N_FSEL, Fp> = {
481        ProofEvaluations {
482            witness_evals: witness_point_evals,
483            fixed_selectors_evals: fixed_selectors_point_evals,
484            error_vec: error_vec_point_eval,
485            ft_eval1,
486        }
487    };
488
489    Ok(Proof {
490        proof_comms: ProofCommitments {
491            witness_comms,
492            t_comm,
493        },
494        proof_evals,
495        opening_proof,
496        alphas: folded_instance.extended_instance.instance.alphas,
497        challenges: folded_instance.extended_instance.instance.challenges,
498        u: folded_instance.u,
499    })
500}