1#![allow(clippy::type_complexity)]
2#![allow(clippy::boxed_local)]
3
4use crate::{
5 column_env::ColumnEnvironment,
6 expr::E,
7 logup,
8 logup::{prover::Env, LookupProof, LookupTableID},
9 proof::{Proof, ProofCommitments, ProofEvaluations, ProofInputs},
10 witness::Witness,
11 MAX_SUPPORTED_DEGREE,
12};
13use ark_ff::{Field, One, Zero};
14use ark_poly::{
15 univariate::DensePolynomial, EvaluationDomain, Evaluations, Polynomial,
16 Radix2EvaluationDomain as R2D,
17};
18use kimchi::{
19 circuits::{
20 berkeley_columns::BerkeleyChallenges,
21 domains::EvaluationDomains,
22 expr::{l0_1, Constants, Expr},
23 },
24 curve::KimchiCurve,
25 groupmap::GroupMap,
26 plonk_sponge::FrSponge,
27 proof::PointEvaluations,
28};
29use mina_poseidon::{sponge::ScalarChallenge, FqSponge};
30use o1_utils::ExtendedDensePolynomial;
31use poly_commitment::{
32 commitment::{absorb_commitment, PolyComm},
33 utils::DensePolynomialOrEvaluations,
34 OpenProof, SRS,
35};
36use rand::{CryptoRng, RngCore};
37use rayon::iter::{IntoParallelIterator, ParallelIterator};
38use thiserror::Error;
39
40#[derive(Error, Debug, Clone)]
42pub enum ProverError {
43 #[error("the proof could not be constructed: {0}")]
44 Generic(&'static str),
45
46 #[error("the provided (witness) constraints was not satisfied: {0}")]
47 ConstraintNotSatisfied(String),
48
49 #[error("the provided (witness) constraint has degree {0} > allowed {1}; expr: {2}")]
50 ConstraintDegreeTooHigh(u64, u64, String),
51}
52
53pub fn prove<
54 G: KimchiCurve,
55 OpeningProof: OpenProof<G>,
56 EFqSponge: Clone + FqSponge<G::BaseField, G, G::ScalarField>,
57 EFrSponge: FrSponge<G::ScalarField>,
58 RNG,
59 const N_WIT: usize,
60 const N_REL: usize,
61 const N_DSEL: usize,
62 const N_FSEL: usize,
63 ID: LookupTableID,
64>(
65 domain: EvaluationDomains<G::ScalarField>,
66 srs: &OpeningProof::SRS,
67 constraints: &[E<G::ScalarField>],
68 fixed_selectors: Box<[Vec<G::ScalarField>; N_FSEL]>,
69 inputs: ProofInputs<N_WIT, G::ScalarField, ID>,
70 rng: &mut RNG,
71) -> Result<Proof<N_WIT, N_REL, N_DSEL, N_FSEL, G, OpeningProof, ID>, ProverError>
72where
73 OpeningProof::SRS: Sync,
74 RNG: RngCore + CryptoRng,
75{
76 let group_map = G::Map::setup();
81
82 let mut fq_sponge = EFqSponge::new(G::other_curve_sponge_params());
87
88 let fixed_selectors_evals_d1: Box<[Evaluations<G::ScalarField, R2D<G::ScalarField>>; N_FSEL]> =
89 o1_utils::array::vec_to_boxed_array(
90 fixed_selectors
91 .into_par_iter()
92 .map(|evals| Evaluations::from_vec_and_domain(evals, domain.d1))
93 .collect(),
94 );
95
96 let fixed_selectors_polys: Box<[DensePolynomial<G::ScalarField>; N_FSEL]> =
97 o1_utils::array::vec_to_boxed_array(
98 fixed_selectors_evals_d1
99 .into_par_iter()
100 .map(|evals| evals.interpolate())
101 .collect(),
102 );
103
104 let fixed_selectors_comms: Box<[PolyComm<G>; N_FSEL]> = {
105 let comm = |poly: &DensePolynomial<G::ScalarField>| srs.commit_non_hiding(poly, 1);
106 o1_utils::array::vec_to_boxed_array(
107 fixed_selectors_polys
108 .as_ref()
109 .into_par_iter()
110 .map(comm)
111 .collect(),
112 )
113 };
114
115 (fixed_selectors_comms)
117 .into_iter()
118 .for_each(|comm| absorb_commitment(&mut fq_sponge, &comm));
119
120 let witness_evals_d1: Witness<N_WIT, Evaluations<G::ScalarField, R2D<G::ScalarField>>> = inputs
122 .evaluations
123 .into_par_iter()
124 .map(|evals| {
125 Evaluations::<G::ScalarField, R2D<G::ScalarField>>::from_vec_and_domain(
126 evals, domain.d1,
127 )
128 })
129 .collect::<Witness<N_WIT, Evaluations<G::ScalarField, R2D<G::ScalarField>>>>();
130
131 let witness_polys: Witness<N_WIT, DensePolynomial<G::ScalarField>> = {
132 let interpolate =
133 |evals: Evaluations<G::ScalarField, R2D<G::ScalarField>>| evals.interpolate();
134 witness_evals_d1
135 .into_par_iter()
136 .map(interpolate)
137 .collect::<Witness<N_WIT, DensePolynomial<G::ScalarField>>>()
138 };
139
140 let witness_comms: Witness<N_WIT, PolyComm<G>> = {
141 let blinders = PolyComm {
142 chunks: vec![G::ScalarField::one()],
143 };
144 let comm = {
145 |poly: &DensePolynomial<G::ScalarField>| {
146 let comm = srs.commit_custom(poly, 1, &blinders).unwrap();
148 comm.commitment
149 }
150 };
151 (&witness_polys)
152 .into_par_iter()
153 .map(comm)
154 .collect::<Witness<N_WIT, PolyComm<G>>>()
155 };
156
157 (&witness_comms)
159 .into_iter()
160 .for_each(|comm| absorb_commitment(&mut fq_sponge, comm));
161
162 let lookup_env = if !inputs.logups.is_empty() {
164 Some(Env::create::<OpeningProof, EFqSponge>(
165 inputs.logups,
166 domain,
167 &mut fq_sponge,
168 srs,
169 ))
170 } else {
171 None
172 };
173
174 let max_degree = {
175 if lookup_env.is_none() {
176 constraints
177 .iter()
178 .map(|expr| expr.degree(1, 0))
179 .max()
180 .unwrap_or(0)
181 } else {
182 8
183 }
184 };
185
186 let logup_comms = Option::map(lookup_env.as_ref(), |lookup_env| LookupProof {
189 m: lookup_env.lookup_counters_comm_d1.clone(),
190 h: lookup_env.lookup_terms_comms_d1.clone(),
191 sum: lookup_env.lookup_aggregation_comm_d1.clone(),
192 fixed_tables: lookup_env.fixed_lookup_tables_comms_d1.clone(),
193 });
194
195 let domain_eval = if max_degree <= 4 {
199 domain.d4
200 } else if max_degree as usize <= MAX_SUPPORTED_DEGREE {
201 domain.d8
202 } else {
203 panic!("We do support constraints up to {:?}", MAX_SUPPORTED_DEGREE)
204 };
205
206 let witness_evals: Witness<N_WIT, Evaluations<G::ScalarField, R2D<G::ScalarField>>> = {
207 (&witness_polys)
208 .into_par_iter()
209 .map(|evals| evals.evaluate_over_domain_by_ref(domain_eval))
210 .collect::<Witness<N_WIT, Evaluations<G::ScalarField, R2D<G::ScalarField>>>>()
211 };
212
213 let fixed_selectors_evals: Box<[Evaluations<G::ScalarField, R2D<G::ScalarField>>; N_FSEL]> = {
214 o1_utils::array::vec_to_boxed_array(
215 (fixed_selectors_polys.as_ref())
216 .into_par_iter()
217 .map(|evals| evals.evaluate_over_domain_by_ref(domain_eval))
218 .collect(),
219 )
220 };
221
222 let (_, endo_r) = G::endos();
227
228 let alpha: G::ScalarField = fq_sponge.challenge();
230
231 let zk_rows = 0;
232 let column_env: ColumnEnvironment<'_, N_WIT, N_REL, N_DSEL, N_FSEL, _, _> = {
233 let challenges = BerkeleyChallenges {
234 alpha,
235 beta: Option::map(lookup_env.as_ref(), |x| x.beta).unwrap_or(G::ScalarField::zero()),
238 gamma: G::ScalarField::zero(),
239 joint_combiner: Option::map(lookup_env.as_ref(), |x| x.joint_combiner)
240 .unwrap_or(G::ScalarField::zero()),
241 };
242 ColumnEnvironment {
243 constants: Constants {
244 endo_coefficient: *endo_r,
245 mds: &G::sponge_params().mds,
246 zk_rows,
247 },
248 challenges,
249 witness: &witness_evals,
250 fixed_selectors: &fixed_selectors_evals,
251 l0_1: l0_1(domain.d1),
252 lookup: Option::map(lookup_env.as_ref(), |lookup_env| {
253 logup::prover::QuotientPolynomialEnvironment {
254 lookup_terms_evals_d8: &lookup_env.lookup_terms_evals_d8,
255 lookup_aggregation_evals_d8: &lookup_env.lookup_aggregation_evals_d8,
256 lookup_counters_evals_d8: &lookup_env.lookup_counters_evals_d8,
257 fixed_tables_evals_d8: &lookup_env.fixed_lookup_tables_evals_d8,
258 }
259 }),
260 domain,
261 }
262 };
263
264 let quotient_poly: DensePolynomial<G::ScalarField> = {
265 let mut last_constraint_failed = None;
266 for expr in constraints.iter() {
268 let fail_q_division =
269 ProverError::ConstraintNotSatisfied(format!("Unsatisfied expression: {:}", expr));
270 let (_, res) = expr
272 .evaluations(&column_env)
273 .interpolate_by_ref()
274 .divide_by_vanishing_poly(domain.d1)
275 .ok_or(fail_q_division.clone())?;
276 if !res.is_zero() {
277 eprintln!("Unsatisfied expression: {}", expr);
278 last_constraint_failed = Some(expr.clone());
280 }
281 }
282 if let Some(expr) = last_constraint_failed {
283 return Err(ProverError::ConstraintNotSatisfied(format!(
284 "Unsatisfied expression: {:}",
285 expr
286 )));
287 }
288
289 let combined_expr =
291 Expr::combine_constraints(0..(constraints.len() as u32), constraints.to_vec());
292
293 let expr_evaluation: Evaluations<G::ScalarField, R2D<G::ScalarField>> =
306 combined_expr.evaluations(&column_env);
307
308 let expr_evaluation_interpolated = expr_evaluation.interpolate();
310
311 let fail_final_q_division = || {
312 panic!("Division by vanishing poly must not fail at this point, we checked it before")
313 };
314 let (quotient, res) = expr_evaluation_interpolated
317 .divide_by_vanishing_poly(domain.d1)
318 .unwrap_or_else(fail_final_q_division);
319 if !res.is_zero() {
323 fail_final_q_division();
324 }
325
326 quotient
327 };
328
329 let num_chunks: usize = if max_degree == 1 {
330 1
331 } else {
332 (max_degree - 1) as usize
333 };
334
335 let t_comm = srs.commit_non_hiding("ient_poly, num_chunks);
337
338 absorb_commitment(&mut fq_sponge, &t_comm);
344
345 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 let zeta_omega = zeta * omega;
353
354 let witness_evals: Witness<N_WIT, PointEvaluations<_>> = {
356 let eval = |p: &DensePolynomial<_>| PointEvaluations {
357 zeta: p.evaluate(&zeta),
358 zeta_omega: p.evaluate(&zeta_omega),
359 };
360 (&witness_polys)
361 .into_par_iter()
362 .map(eval)
363 .collect::<Witness<N_WIT, PointEvaluations<_>>>()
364 };
365
366 let fixed_selectors_evals: Box<[PointEvaluations<_>; N_FSEL]> = {
367 let eval = |p: &DensePolynomial<_>| PointEvaluations {
368 zeta: p.evaluate(&zeta),
369 zeta_omega: p.evaluate(&zeta_omega),
370 };
371 o1_utils::array::vec_to_boxed_array(
372 fixed_selectors_polys
373 .as_ref()
374 .into_par_iter()
375 .map(eval)
376 .collect::<_>(),
377 )
378 };
379
380 let logup_evals = lookup_env.as_ref().map(|lookup_env| LookupProof {
382 m: lookup_env
383 .lookup_counters_poly_d1
384 .iter()
385 .map(|(id, polys)| {
386 (
387 *id,
388 polys
389 .iter()
390 .map(|poly| {
391 let zeta = poly.evaluate(&zeta);
392 let zeta_omega = poly.evaluate(&zeta_omega);
393 PointEvaluations { zeta, zeta_omega }
394 })
395 .collect(),
396 )
397 })
398 .collect(),
399 h: lookup_env
400 .lookup_terms_poly_d1
401 .iter()
402 .map(|(id, polys)| {
403 let polys_evals: Vec<_> = polys
404 .iter()
405 .map(|poly| PointEvaluations {
406 zeta: poly.evaluate(&zeta),
407 zeta_omega: poly.evaluate(&zeta_omega),
408 })
409 .collect();
410 (*id, polys_evals)
411 })
412 .collect(),
413 sum: PointEvaluations {
414 zeta: lookup_env.lookup_aggregation_poly_d1.evaluate(&zeta),
415 zeta_omega: lookup_env.lookup_aggregation_poly_d1.evaluate(&zeta_omega),
416 },
417 fixed_tables: {
418 lookup_env
419 .fixed_lookup_tables_poly_d1
420 .iter()
421 .map(|(id, poly)| {
422 let zeta = poly.evaluate(&zeta);
423 let zeta_omega = poly.evaluate(&zeta_omega);
424 (*id, PointEvaluations { zeta, zeta_omega })
425 })
426 .collect()
427 },
428 });
429
430 let fq_sponge_before_evaluations = fq_sponge.clone();
436 let mut fr_sponge = EFrSponge::new(G::sponge_params());
437 fr_sponge.absorb(&fq_sponge.digest());
438
439 for PointEvaluations { zeta, zeta_omega } in (&witness_evals).into_iter() {
440 fr_sponge.absorb(zeta);
441 fr_sponge.absorb(zeta_omega);
442 }
443
444 for PointEvaluations { zeta, zeta_omega } in fixed_selectors_evals.as_ref().iter() {
445 fr_sponge.absorb(zeta);
446 fr_sponge.absorb(zeta_omega);
447 }
448
449 if lookup_env.is_some() {
450 for PointEvaluations { zeta, zeta_omega } in logup_evals.as_ref().unwrap().into_iter() {
451 fr_sponge.absorb(zeta);
452 fr_sponge.absorb(zeta_omega);
453 }
454 }
455
456 let ft: DensePolynomial<G::ScalarField> = {
463 let evaluation_point_to_domain_size = zeta.pow([domain.d1.size]);
464 let t_chunked: DensePolynomial<G::ScalarField> = quotient_poly
467 .to_chunked_polynomial(num_chunks, domain.d1.size as usize)
468 .linearize(evaluation_point_to_domain_size);
469 let minus_vanishing_poly_at_zeta = -domain.d1.vanishing_polynomial().evaluate(&zeta);
471 t_chunked.scale(minus_vanishing_poly_at_zeta)
474 };
475
476 let ft_eval1 = ft.evaluate(&zeta_omega);
483
484 fr_sponge.absorb(&ft_eval1);
486
487 let v_chal = fr_sponge.challenge();
488 let v = v_chal.to_field(endo_r);
489 let u_chal = fr_sponge.challenge();
490 let u = u_chal.to_field(endo_r);
491
492 let coefficients_form = DensePolynomialOrEvaluations::DensePolynomial;
493 let non_hiding = |n_chunks| PolyComm {
494 chunks: vec![G::ScalarField::zero(); n_chunks],
495 };
496 let hiding = |n_chunks| PolyComm {
497 chunks: vec![G::ScalarField::one(); n_chunks],
498 };
499
500 let mut polynomials: Vec<_> = (&witness_polys)
502 .into_par_iter()
503 .map(|poly| (coefficients_form(poly), hiding(1)))
504 .collect();
505
506 polynomials.extend(
509 fixed_selectors_polys
510 .as_ref()
511 .into_par_iter()
512 .map(|poly| (coefficients_form(poly), non_hiding(1)))
513 .collect::<Vec<_>>(),
514 );
515
516 if let Some(ref lookup_env) = lookup_env {
518 polynomials.extend(
520 lookup_env
521 .lookup_counters_poly_d1
522 .values()
523 .flat_map(|polys| {
524 polys
525 .iter()
526 .map(|poly| (coefficients_form(poly), non_hiding(1)))
527 .collect::<Vec<_>>()
528 })
529 .collect::<Vec<_>>(),
530 );
531 polynomials.extend({
533 let polys = lookup_env.lookup_terms_poly_d1.values().map(|polys| {
534 polys
535 .iter()
536 .map(|poly| (coefficients_form(poly), non_hiding(1)))
537 .collect::<Vec<_>>()
538 });
539 let polys: Vec<_> = polys.flatten().collect();
540 polys
541 });
542 polynomials.push((
544 coefficients_form(&lookup_env.lookup_aggregation_poly_d1),
545 non_hiding(1),
546 ));
547 polynomials.extend(
549 lookup_env
550 .fixed_lookup_tables_poly_d1
551 .values()
552 .map(|poly| (coefficients_form(poly), non_hiding(1)))
553 .collect::<Vec<_>>(),
554 );
555 }
556 polynomials.push((coefficients_form(&ft), non_hiding(1)));
557
558 let opening_proof = OpenProof::open::<_, _, R2D<G::ScalarField>>(
559 srs,
560 &group_map,
561 polynomials.as_slice(),
562 &[zeta, zeta_omega],
563 v,
564 u,
565 fq_sponge_before_evaluations,
566 rng,
567 );
568
569 let proof_evals: ProofEvaluations<N_WIT, N_REL, N_DSEL, N_FSEL, G::ScalarField, ID> = {
570 ProofEvaluations {
571 witness_evals,
572 fixed_selectors_evals,
573 logup_evals,
574 ft_eval1,
575 }
576 };
577
578 Ok(Proof {
579 proof_comms: ProofCommitments {
580 witness_comms,
581 logup_comms,
582 t_comm,
583 },
584 proof_evals,
585 opening_proof,
586 })
587}