1use crate::{Curve, CurveFqSponge, CurveFrSponge, ScalarField, SRS_SIZE};
34use ark_ec::AffineRepr;
35use ark_ff::{Field, One, Zero};
36use ark_poly::{
37 univariate::DensePolynomial, EvaluationDomain, Evaluations, Polynomial,
38 Radix2EvaluationDomain as R2D,
39};
40use kimchi::{circuits::domains::EvaluationDomains, curve::KimchiCurve, plonk_sponge::FrSponge};
41use mina_poseidon::FqSponge;
42use poly_commitment::{
43 commitment::{combined_inner_product, BatchEvaluationProof, CommitmentCurve, Evaluation},
44 ipa::{OpeningProof, SRS},
45 utils::DensePolynomialOrEvaluations,
46 PolyComm, SRS as _,
47};
48use rand::{CryptoRng, RngCore};
49
50#[derive(PartialEq, Eq)]
51pub struct CoreInstance {
53 pub comm_d: Curve,
55 pub comm_q: Curve,
57 pub comm_a: Curve,
59}
60
61#[derive(PartialEq, Eq)]
62pub struct RelaxedInstance {
64 pub core: CoreInstance,
66 pub u: ScalarField,
68 pub comm_e: Curve,
70}
71
72impl CoreInstance {
73 pub fn relax(self) -> RelaxedInstance {
74 RelaxedInstance {
75 core: self,
76 u: ScalarField::one(),
77 comm_e: Curve::zero(),
78 }
79 }
80}
81
82#[derive(PartialEq, Eq)]
83pub struct CoreWitness {
86 pub d: Evaluations<ScalarField, R2D<ScalarField>>,
87 pub q: Evaluations<ScalarField, R2D<ScalarField>>,
88 pub a: Evaluations<ScalarField, R2D<ScalarField>>,
89}
90
91#[derive(PartialEq, Eq)]
92pub struct RelaxedWitness {
95 pub core: CoreWitness,
96 pub e: Evaluations<ScalarField, R2D<ScalarField>>,
97}
98
99impl CoreWitness {
100 pub fn relax(self, domain: R2D<ScalarField>) -> RelaxedWitness {
101 RelaxedWitness {
102 core: self,
103 e: Evaluations::from_vec_and_domain(vec![ScalarField::zero(); domain.size()], domain),
104 }
105 }
106}
107
108impl RelaxedInstance {
109 pub fn check_in_language(
113 &self,
114 srs: &SRS<Curve>,
115 domain: R2D<ScalarField>,
116 wit: &RelaxedWitness,
117 ) -> bool {
118 for i in 0..SRS_SIZE {
119 if self.u * wit.core.a[i] - wit.core.q[i] * wit.core.d[i] + wit.e[i]
121 != ScalarField::zero()
122 {
123 return false;
124 }
125 }
126 if self.core.comm_a
127 != srs
128 .commit_evaluations_non_hiding(domain, &wit.core.a)
129 .chunks[0]
130 {
131 return false;
132 }
133 if self.core.comm_d
134 != srs
135 .commit_evaluations_non_hiding(domain, &wit.core.d)
136 .chunks[0]
137 {
138 return false;
139 }
140 if self.core.comm_q
141 != srs
142 .commit_evaluations_non_hiding(domain, &wit.core.q)
143 .chunks[0]
144 {
145 return false;
146 }
147 if self.comm_e != srs.commit_evaluations_non_hiding(domain, &wit.e).chunks[0] {
148 return false;
149 }
150 true
151 }
152}
153
154pub fn folding_prover(
155 srs: &SRS<Curve>,
156 domain: R2D<ScalarField>,
157 inst1: &CoreInstance,
158 wit1: &CoreWitness,
159 inst2: &RelaxedInstance,
160 wit2: &RelaxedWitness,
161) -> (RelaxedInstance, RelaxedWitness, Curve) {
162 let mut fq_sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
163
164 let cross_term: Evaluations<ScalarField, R2D<ScalarField>> =
165 &(&(&wit2.core.a + &(&wit1.a * inst2.u)) - &(&wit2.core.q * &wit1.d))
166 - &(&wit1.q * &wit2.core.d);
167
168 let cross_term_comm: Curve = srs
169 .commit_evaluations_non_hiding(domain, &cross_term.clone())
170 .chunks[0];
171
172 fq_sponge.absorb_g(&[
173 inst1.comm_d,
174 inst1.comm_q,
175 inst1.comm_a,
176 inst2.core.comm_d,
177 inst2.core.comm_q,
178 inst2.core.comm_a,
179 inst2.comm_e,
180 cross_term_comm,
181 ]);
182
183 let recombination_chal = fq_sponge.challenge();
184
185 let a3 = &wit1.a + &(&wit2.core.a * recombination_chal);
186 let q3 = &wit1.q + &(&wit2.core.q * recombination_chal);
187 let d3 = &wit1.d + &(&wit2.core.d * recombination_chal);
188
189 let comm_a3 = inst1.comm_a + inst2.core.comm_a * recombination_chal;
190 let comm_q3 = inst1.comm_q + inst2.core.comm_q * recombination_chal;
191 let comm_d3 = inst1.comm_d + inst2.core.comm_d * recombination_chal;
192
193 let new_u = ScalarField::one() + recombination_chal * inst2.u;
194
195 let e3 = &(&wit2.e * (recombination_chal * recombination_chal))
196 - &(&cross_term * recombination_chal);
197 let comm_e3 = inst2.comm_e * (recombination_chal * recombination_chal)
198 - cross_term_comm * recombination_chal;
199
200 let new_inst = RelaxedInstance {
201 u: new_u,
202 comm_e: comm_e3.into(),
203 core: CoreInstance {
204 comm_d: comm_d3.into(),
205 comm_q: comm_q3.into(),
206 comm_a: comm_a3.into(),
207 },
208 };
209
210 let new_wit = RelaxedWitness {
211 e: e3,
212 core: CoreWitness {
213 d: d3,
214 q: q3,
215 a: a3,
216 },
217 };
218
219 (new_inst, new_wit, cross_term_comm)
220}
221
222pub fn folding_verifier(
223 inst1: &CoreInstance,
224 inst2: &RelaxedInstance,
225 cross_term_comm: Curve,
226) -> RelaxedInstance {
227 let mut fq_sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
228 fq_sponge.absorb_g(&[
229 inst1.comm_d,
230 inst1.comm_q,
231 inst1.comm_a,
232 inst2.core.comm_d,
233 inst2.core.comm_q,
234 inst2.core.comm_a,
235 inst2.comm_e,
236 cross_term_comm,
237 ]);
238
239 let recombination_chal = fq_sponge.challenge();
240
241 let comm_a3 = inst1.comm_a + inst2.core.comm_a * recombination_chal;
242 let comm_q3 = inst1.comm_q + inst2.core.comm_q * recombination_chal;
243 let comm_d3 = inst1.comm_d + inst2.core.comm_d * recombination_chal;
244
245 let new_u = ScalarField::one() + recombination_chal * inst2.u;
246
247 let comm_e3 = inst2.comm_e * (recombination_chal * recombination_chal)
248 - cross_term_comm * recombination_chal;
249
250 RelaxedInstance {
251 u: new_u,
252 comm_e: comm_e3.into(),
253 core: CoreInstance {
254 comm_d: comm_d3.into(),
255 comm_q: comm_q3.into(),
256 comm_a: comm_a3.into(),
257 },
258 }
259}
260
261#[derive(Debug, Clone)]
262pub struct ReadProof {
264 pub quotient_comm: Curve,
266
267 pub data_eval: ScalarField,
269 pub query_eval: ScalarField,
271 pub answer_eval: ScalarField,
273 pub error_eval: ScalarField,
275
276 pub opening_proof: OpeningProof<Curve>,
278}
279
280pub fn prove_relaxed<RNG>(
281 srs: &SRS<Curve>,
282 domain: EvaluationDomains<ScalarField>,
283 group_map: &<Curve as CommitmentCurve>::Map,
284 rng: &mut RNG,
285 inst: &RelaxedInstance,
286 wit: &RelaxedWitness,
287) -> ReadProof
288where
289 RNG: RngCore + CryptoRng,
290{
291 let mut fq_sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
292
293 let data_poly: DensePolynomial<ScalarField> = wit.core.d.interpolate_by_ref();
294 let query_poly: DensePolynomial<ScalarField> = wit.core.q.interpolate_by_ref();
295 let answer_poly: DensePolynomial<ScalarField> = wit.core.a.interpolate_by_ref();
296 let error_poly: DensePolynomial<ScalarField> = wit.e.interpolate_by_ref();
297
298 fq_sponge.absorb_g(&[
299 inst.core.comm_d,
300 inst.core.comm_q,
301 inst.core.comm_a,
302 inst.comm_e,
303 ]);
304
305 let quotient_poly: DensePolynomial<ScalarField> = {
307 let data_d2 = data_poly.evaluate_over_domain_by_ref(domain.d2);
308 let query_d2 = query_poly.evaluate_over_domain_by_ref(domain.d2);
309 let answer_d2 = answer_poly.evaluate_over_domain_by_ref(domain.d2);
310 let error_d2 = error_poly.evaluate_over_domain_by_ref(domain.d2);
311
312 let numerator_eval: Evaluations<ScalarField, R2D<ScalarField>> =
314 &(&(&answer_d2 * inst.u) - &(&data_d2 * &query_d2)) + &error_d2;
315
316 let numerator_eval_interpolated = numerator_eval.interpolate();
317
318 let fail_final_q_division = || panic!("Division by vanishing poly must not fail");
319 let (quotient, res) = numerator_eval_interpolated
322 .divide_by_vanishing_poly(domain.d1)
323 .unwrap_or_else(fail_final_q_division);
324 if !res.is_zero() {
328 fail_final_q_division();
329 }
330
331 quotient
332 };
333
334 let quotient_comm = srs.commit_non_hiding("ient_poly, 1).chunks[0];
337 fq_sponge.absorb_g(&[quotient_comm]);
338
339 let evaluation_point = fq_sponge.challenge();
341
342 let mut fr_sponge = CurveFrSponge::new(Curve::sponge_params());
344 fr_sponge.absorb(&fq_sponge.clone().digest());
345
346 let data_eval = data_poly.evaluate(&evaluation_point);
347 let query_eval = query_poly.evaluate(&evaluation_point);
348 let answer_eval = answer_poly.evaluate(&evaluation_point);
349 let error_eval = error_poly.evaluate(&evaluation_point);
350 let quotient_eval = quotient_poly.evaluate(&evaluation_point);
351
352 for eval in [
353 data_eval,
354 query_eval,
355 answer_eval,
356 error_eval,
357 quotient_eval,
358 ]
359 .into_iter()
360 {
361 fr_sponge.absorb(&eval);
362 }
363
364 let (_, endo_r) = Curve::endos();
365 let polyscale = fr_sponge.challenge().to_field(endo_r);
367 let evalscale = fr_sponge.challenge().to_field(endo_r);
368
369 let opening_proof_inputs: Vec<_> = {
372 let coefficients_form =
373 DensePolynomialOrEvaluations::<_, R2D<ScalarField>>::DensePolynomial;
374 let non_hiding = |n_chunks| PolyComm {
375 chunks: vec![ScalarField::zero(); n_chunks],
376 };
377
378 vec![
379 (coefficients_form(&data_poly), non_hiding(1)),
380 (coefficients_form(&query_poly), non_hiding(1)),
381 (coefficients_form(&answer_poly), non_hiding(1)),
382 (coefficients_form(&error_poly), non_hiding(1)),
383 (coefficients_form("ient_poly), non_hiding(1)),
384 ]
385 };
386
387 let opening_proof = srs.open(
388 group_map,
389 opening_proof_inputs.as_slice(),
390 &[evaluation_point],
391 polyscale,
392 evalscale,
393 fq_sponge,
394 rng,
395 );
396
397 ReadProof {
398 quotient_comm,
399 data_eval,
400 query_eval,
401 answer_eval,
402 error_eval,
403 opening_proof,
404 }
405}
406
407pub fn verify_relaxed<RNG>(
408 srs: &SRS<Curve>,
409 domain: EvaluationDomains<ScalarField>,
410 group_map: &<Curve as CommitmentCurve>::Map,
411 rng: &mut RNG,
412 inst: &RelaxedInstance,
413 proof: &ReadProof,
414) -> bool
415where
416 RNG: RngCore + CryptoRng,
417{
418 let mut fq_sponge = CurveFqSponge::new(Curve::other_curve_sponge_params());
419 fq_sponge.absorb_g(&[
420 inst.core.comm_d,
421 inst.core.comm_q,
422 inst.core.comm_a,
423 inst.comm_e,
424 ]);
425 fq_sponge.absorb_g(&[proof.quotient_comm]);
426
427 let evaluation_point = fq_sponge.challenge();
428
429 let mut fr_sponge = CurveFrSponge::new(Curve::sponge_params());
430 fr_sponge.absorb(&fq_sponge.clone().digest());
431
432 let vanishing_poly_at_zeta = domain.d1.vanishing_polynomial().evaluate(&evaluation_point);
433 let quotient_eval = {
434 (inst.u * proof.answer_eval - proof.data_eval * proof.query_eval + proof.error_eval)
435 * vanishing_poly_at_zeta
436 .inverse()
437 .unwrap_or_else(|| panic!("Inverse fails only with negligible probability"))
438 };
439
440 for eval in [
441 proof.data_eval,
442 proof.query_eval,
443 proof.answer_eval,
444 proof.error_eval,
445 quotient_eval,
446 ]
447 .into_iter()
448 {
449 fr_sponge.absorb(&eval);
450 }
451
452 let (_, endo_r) = Curve::endos();
453 let polyscale = fr_sponge.challenge().to_field(endo_r);
455 let evalscale = fr_sponge.challenge().to_field(endo_r);
456
457 let coms_and_evaluations = vec![
458 Evaluation {
459 commitment: PolyComm {
460 chunks: vec![inst.core.comm_d],
461 },
462 evaluations: vec![vec![proof.data_eval]],
463 },
464 Evaluation {
465 commitment: PolyComm {
466 chunks: vec![inst.core.comm_q],
467 },
468 evaluations: vec![vec![proof.query_eval]],
469 },
470 Evaluation {
471 commitment: PolyComm {
472 chunks: vec![inst.core.comm_a],
473 },
474 evaluations: vec![vec![proof.answer_eval]],
475 },
476 Evaluation {
477 commitment: PolyComm {
478 chunks: vec![inst.comm_e],
479 },
480 evaluations: vec![vec![proof.error_eval]],
481 },
482 Evaluation {
483 commitment: PolyComm {
484 chunks: vec![proof.quotient_comm],
485 },
486 evaluations: vec![vec![quotient_eval]],
487 },
488 ];
489 let combined_inner_product = {
490 let evaluations: Vec<_> = coms_and_evaluations
491 .iter()
492 .map(|Evaluation { evaluations, .. }| evaluations.clone())
493 .collect();
494
495 combined_inner_product(&polyscale, &evalscale, evaluations.as_slice())
496 };
497
498 srs.verify(
499 group_map,
500 &mut [BatchEvaluationProof {
501 sponge: fq_sponge,
502 evaluation_points: vec![evaluation_point],
503 polyscale,
504 evalscale,
505 evaluations: coms_and_evaluations,
506 opening: &proof.opening_proof,
507 combined_inner_product,
508 }],
509 rng,
510 )
511}
512
513pub mod testing {
514 use super::*;
515 use crate::{Curve, ScalarField};
516 use ark_ff::UniformRand;
517 use ark_poly::Evaluations;
518 use kimchi::circuits::domains::EvaluationDomains;
519 use mina_curves::pasta::{Fp, Vesta};
520 use poly_commitment::ipa::SRS;
521 use rand::Rng;
522
523 pub fn generate_random_inst_wit_core<RNG>(
525 srs: &SRS<Vesta>,
526 domain: EvaluationDomains<ScalarField>,
527 rng: &mut RNG,
528 ) -> (CoreInstance, CoreWitness)
529 where
530 RNG: RngCore + CryptoRng,
531 {
532 let data: Vec<ScalarField> = {
533 let mut data = vec![];
534 (0..domain.d1.size).for_each(|_| data.push(Fp::rand(rng)));
535 data
536 };
537
538 let data_comm: Curve = srs
539 .commit_evaluations_non_hiding(
540 domain.d1,
541 &Evaluations::from_vec_and_domain(data.clone(), domain.d1),
542 )
543 .chunks[0];
544
545 let query: Vec<ScalarField> = {
546 let mut query = vec![];
547 (0..domain.d1.size)
548 .for_each(|_| query.push(Fp::from(rand::thread_rng().gen::<f64>() < 0.1)));
549 query
550 };
551
552 let answer: Vec<ScalarField> = data
553 .clone()
554 .iter()
555 .zip(query.iter())
556 .map(|(d, q)| *d * q)
557 .collect();
558
559 let comm_q = srs
560 .commit_evaluations_non_hiding(
561 domain.d1,
562 &Evaluations::from_vec_and_domain(query.clone(), domain.d1),
563 )
564 .chunks[0];
565
566 let comm_a = srs
567 .commit_evaluations_non_hiding(
568 domain.d1,
569 &Evaluations::from_vec_and_domain(answer.clone(), domain.d1),
570 )
571 .chunks[0];
572
573 let core_instance = CoreInstance {
574 comm_d: data_comm,
575 comm_q,
576 comm_a,
577 };
578
579 let core_witness = CoreWitness {
580 d: Evaluations::from_vec_and_domain(data, domain.d1),
581 q: Evaluations::from_vec_and_domain(query, domain.d1),
582 a: Evaluations::from_vec_and_domain(answer, domain.d1),
583 };
584
585 (core_instance, core_witness)
586 }
587
588 pub fn generate_random_inst_wit_relaxed<RNG>(
592 srs: &SRS<Vesta>,
593 domain: EvaluationDomains<ScalarField>,
594 rng: &mut RNG,
595 ) -> (RelaxedInstance, RelaxedWitness)
596 where
597 RNG: RngCore + CryptoRng,
598 {
599 let (inst, wit) = generate_random_inst_wit_core(srs, domain, rng);
600 let u = Fp::rand(rng);
601 let e = &(&wit.d * &wit.q) - &(&wit.a * u);
602 let comm_e = srs.commit_evaluations_non_hiding(domain.d1, &e).chunks[0];
603
604 let relaxed_instance = RelaxedInstance {
605 core: inst,
606 u,
607 comm_e,
608 };
609
610 let relaxed_witness = RelaxedWitness { core: wit, e };
611
612 assert!(relaxed_instance.check_in_language(srs, domain.d1, &relaxed_witness));
613
614 (relaxed_instance, relaxed_witness)
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::{testing::generate_random_inst_wit_core, *};
621 use crate::{Curve, ScalarField};
622 use ark_ec::AffineRepr;
623 use ark_ff::One;
624 use kimchi::{circuits::domains::EvaluationDomains, groupmap::GroupMap};
625 use mina_curves::pasta::Vesta;
626 use poly_commitment::commitment::CommitmentCurve;
627
628 #[test]
629 fn test_folding_read_proof_completeness_soundness() {
630 let mut rng = o1_utils::tests::make_test_rng(None);
631
632 let srs = poly_commitment::precomputed_srs::get_srs_test();
633 let domain: EvaluationDomains<ScalarField> =
634 EvaluationDomains::<ScalarField>::create(srs.size()).unwrap();
635 let group_map = <Vesta as CommitmentCurve>::Map::setup();
636
637 let (core_instance_1, core_witness_1) =
638 generate_random_inst_wit_core(&srs, domain, &mut rng);
639 let (core_instance_2, core_witness_2) =
640 generate_random_inst_wit_core(&srs, domain, &mut rng);
641 let relaxed_instance_2 = core_instance_2.relax();
642 let relaxed_witness_2 = core_witness_2.relax(domain.d1);
643
644 assert!(relaxed_instance_2.check_in_language(&srs, domain.d1, &relaxed_witness_2));
645
646 let (relaxed_instance_3, relaxed_witness_3, cross_term_1) = folding_prover(
647 &srs,
648 domain.d1,
649 &core_instance_1,
650 &core_witness_1,
651 &relaxed_instance_2,
652 &relaxed_witness_2,
653 );
654
655 assert!(relaxed_instance_3.check_in_language(&srs, domain.d1, &relaxed_witness_3));
656
657 assert!(
658 folding_verifier(&core_instance_1, &relaxed_instance_2, cross_term_1)
659 == relaxed_instance_3
660 );
661
662 let (core_instance_4, core_witness_4) =
663 generate_random_inst_wit_core(&srs, domain, &mut rng);
664 let (relaxed_instance_5, relaxed_witness_5, cross_term_2) = folding_prover(
665 &srs,
666 domain.d1,
667 &core_instance_4,
668 &core_witness_4,
669 &relaxed_instance_3,
670 &relaxed_witness_3,
671 );
672
673 assert!(relaxed_instance_5.check_in_language(&srs, domain.d1, &relaxed_witness_5));
674
675 assert!(
676 folding_verifier(&core_instance_4, &relaxed_instance_3, cross_term_2)
677 == relaxed_instance_5
678 );
679
680 let proof = prove_relaxed(
681 &srs,
682 domain,
683 &group_map,
684 &mut rng,
685 &relaxed_instance_5,
686 &relaxed_witness_5,
687 );
688 let res = verify_relaxed(
689 &srs,
690 domain,
691 &group_map,
692 &mut rng,
693 &relaxed_instance_5,
694 &proof,
695 );
696
697 assert!(res, "Completeness: Proof must verify");
698
699 let proof_malformed_1 = ReadProof {
700 quotient_comm: Curve::zero(),
701 ..proof.clone()
702 };
703
704 let res_1 = verify_relaxed(
705 &srs,
706 domain,
707 &group_map,
708 &mut rng,
709 &relaxed_instance_5,
710 &proof_malformed_1,
711 );
712
713 assert!(!res_1, "Soundness: Malformed proof #1 must NOT verify");
714
715 let proof_malformed_2 = ReadProof {
716 query_eval: ScalarField::one(),
717 ..proof.clone()
718 };
719
720 let res_2 = verify_relaxed(
721 &srs,
722 domain,
723 &group_map,
724 &mut rng,
725 &relaxed_instance_5,
726 &proof_malformed_2,
727 );
728
729 assert!(!res_2, "Soundness: Malformed proof #2 must NOT verify");
730 }
731}