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 (_, res) = expr
270 .evaluations(&column_env)
271 .interpolate_by_ref()
272 .divide_by_vanishing_poly(domain.d1);
273 if !res.is_zero() {
274 eprintln!("Unsatisfied expression: {}", expr);
275 last_constraint_failed = Some(expr.clone());
277 }
278 }
279 if let Some(expr) = last_constraint_failed {
280 return Err(ProverError::ConstraintNotSatisfied(format!(
281 "Unsatisfied expression: {:}",
282 expr
283 )));
284 }
285
286 let combined_expr =
288 Expr::combine_constraints(0..(constraints.len() as u32), constraints.to_vec());
289
290 let expr_evaluation: Evaluations<G::ScalarField, R2D<G::ScalarField>> =
303 combined_expr.evaluations(&column_env);
304
305 let expr_evaluation_interpolated = expr_evaluation.interpolate();
307
308 let fail_final_q_division = || {
309 panic!("Division by vanishing poly must not fail at this point, we checked it before")
310 };
311 let (quotient, res) = expr_evaluation_interpolated.divide_by_vanishing_poly(domain.d1);
314 if !res.is_zero() {
318 fail_final_q_division();
319 }
320
321 quotient
322 };
323
324 let num_chunks: usize = if max_degree == 1 {
325 1
326 } else {
327 (max_degree - 1) as usize
328 };
329
330 let t_comm = srs.commit_non_hiding("ient_poly, num_chunks);
332
333 absorb_commitment(&mut fq_sponge, &t_comm);
339
340 let zeta_chal = ScalarChallenge(fq_sponge.challenge());
342
343 let zeta = zeta_chal.to_field(endo_r);
344
345 let omega = domain.d1.group_gen;
346 let zeta_omega = zeta * omega;
348
349 let witness_evals: Witness<N_WIT, PointEvaluations<_>> = {
351 let eval = |p: &DensePolynomial<_>| PointEvaluations {
352 zeta: p.evaluate(&zeta),
353 zeta_omega: p.evaluate(&zeta_omega),
354 };
355 (&witness_polys)
356 .into_par_iter()
357 .map(eval)
358 .collect::<Witness<N_WIT, PointEvaluations<_>>>()
359 };
360
361 let fixed_selectors_evals: Box<[PointEvaluations<_>; N_FSEL]> = {
362 let eval = |p: &DensePolynomial<_>| PointEvaluations {
363 zeta: p.evaluate(&zeta),
364 zeta_omega: p.evaluate(&zeta_omega),
365 };
366 o1_utils::array::vec_to_boxed_array(
367 fixed_selectors_polys
368 .as_ref()
369 .into_par_iter()
370 .map(eval)
371 .collect::<_>(),
372 )
373 };
374
375 let logup_evals = lookup_env.as_ref().map(|lookup_env| LookupProof {
377 m: lookup_env
378 .lookup_counters_poly_d1
379 .iter()
380 .map(|(id, polys)| {
381 (
382 *id,
383 polys
384 .iter()
385 .map(|poly| {
386 let zeta = poly.evaluate(&zeta);
387 let zeta_omega = poly.evaluate(&zeta_omega);
388 PointEvaluations { zeta, zeta_omega }
389 })
390 .collect(),
391 )
392 })
393 .collect(),
394 h: lookup_env
395 .lookup_terms_poly_d1
396 .iter()
397 .map(|(id, polys)| {
398 let polys_evals: Vec<_> = polys
399 .iter()
400 .map(|poly| PointEvaluations {
401 zeta: poly.evaluate(&zeta),
402 zeta_omega: poly.evaluate(&zeta_omega),
403 })
404 .collect();
405 (*id, polys_evals)
406 })
407 .collect(),
408 sum: PointEvaluations {
409 zeta: lookup_env.lookup_aggregation_poly_d1.evaluate(&zeta),
410 zeta_omega: lookup_env.lookup_aggregation_poly_d1.evaluate(&zeta_omega),
411 },
412 fixed_tables: {
413 lookup_env
414 .fixed_lookup_tables_poly_d1
415 .iter()
416 .map(|(id, poly)| {
417 let zeta = poly.evaluate(&zeta);
418 let zeta_omega = poly.evaluate(&zeta_omega);
419 (*id, PointEvaluations { zeta, zeta_omega })
420 })
421 .collect()
422 },
423 });
424
425 let fq_sponge_before_evaluations = fq_sponge.clone();
431 let mut fr_sponge = EFrSponge::new(G::sponge_params());
432 fr_sponge.absorb(&fq_sponge.digest());
433
434 for PointEvaluations { zeta, zeta_omega } in (&witness_evals).into_iter() {
435 fr_sponge.absorb(zeta);
436 fr_sponge.absorb(zeta_omega);
437 }
438
439 for PointEvaluations { zeta, zeta_omega } in fixed_selectors_evals.as_ref().iter() {
440 fr_sponge.absorb(zeta);
441 fr_sponge.absorb(zeta_omega);
442 }
443
444 if lookup_env.is_some() {
445 for PointEvaluations { zeta, zeta_omega } in logup_evals.as_ref().unwrap().into_iter() {
446 fr_sponge.absorb(zeta);
447 fr_sponge.absorb(zeta_omega);
448 }
449 }
450
451 let ft: DensePolynomial<G::ScalarField> = {
458 let evaluation_point_to_domain_size = zeta.pow([domain.d1.size]);
459 let t_chunked: DensePolynomial<G::ScalarField> = quotient_poly
462 .to_chunked_polynomial(num_chunks, domain.d1.size as usize)
463 .linearize(evaluation_point_to_domain_size);
464 let minus_vanishing_poly_at_zeta = -domain.d1.vanishing_polynomial().evaluate(&zeta);
466 t_chunked.scale(minus_vanishing_poly_at_zeta)
469 };
470
471 let ft_eval1 = ft.evaluate(&zeta_omega);
478
479 fr_sponge.absorb(&ft_eval1);
481
482 let v_chal = fr_sponge.challenge();
483 let v = v_chal.to_field(endo_r);
484 let u_chal = fr_sponge.challenge();
485 let u = u_chal.to_field(endo_r);
486
487 let coefficients_form = DensePolynomialOrEvaluations::DensePolynomial;
488 let non_hiding = |n_chunks| PolyComm {
489 chunks: vec![G::ScalarField::zero(); n_chunks],
490 };
491 let hiding = |n_chunks| PolyComm {
492 chunks: vec![G::ScalarField::one(); n_chunks],
493 };
494
495 let mut polynomials: Vec<_> = (&witness_polys)
497 .into_par_iter()
498 .map(|poly| (coefficients_form(poly), hiding(1)))
499 .collect();
500
501 polynomials.extend(
504 fixed_selectors_polys
505 .as_ref()
506 .into_par_iter()
507 .map(|poly| (coefficients_form(poly), non_hiding(1)))
508 .collect::<Vec<_>>(),
509 );
510
511 if let Some(ref lookup_env) = lookup_env {
513 polynomials.extend(
515 lookup_env
516 .lookup_counters_poly_d1
517 .values()
518 .flat_map(|polys| {
519 polys
520 .iter()
521 .map(|poly| (coefficients_form(poly), non_hiding(1)))
522 .collect::<Vec<_>>()
523 })
524 .collect::<Vec<_>>(),
525 );
526 polynomials.extend({
528 let polys = lookup_env.lookup_terms_poly_d1.values().map(|polys| {
529 polys
530 .iter()
531 .map(|poly| (coefficients_form(poly), non_hiding(1)))
532 .collect::<Vec<_>>()
533 });
534 let polys: Vec<_> = polys.flatten().collect();
535 polys
536 });
537 polynomials.push((
539 coefficients_form(&lookup_env.lookup_aggregation_poly_d1),
540 non_hiding(1),
541 ));
542 polynomials.extend(
544 lookup_env
545 .fixed_lookup_tables_poly_d1
546 .values()
547 .map(|poly| (coefficients_form(poly), non_hiding(1)))
548 .collect::<Vec<_>>(),
549 );
550 }
551 polynomials.push((coefficients_form(&ft), non_hiding(1)));
552
553 let opening_proof = OpenProof::open::<_, _, R2D<G::ScalarField>>(
554 srs,
555 &group_map,
556 polynomials.as_slice(),
557 &[zeta, zeta_omega],
558 v,
559 u,
560 fq_sponge_before_evaluations,
561 rng,
562 );
563
564 let proof_evals: ProofEvaluations<N_WIT, N_REL, N_DSEL, N_FSEL, G::ScalarField, ID> = {
565 ProofEvaluations {
566 witness_evals,
567 fixed_selectors_evals,
568 logup_evals,
569 ft_eval1,
570 }
571 };
572
573 Ok(Proof {
574 proof_comms: ProofCommitments {
575 witness_comms,
576 logup_comms,
577 t_comm,
578 },
579 proof_evals,
580 opening_proof,
581 })
582}