saffron/
folding.rs

1//! Folding version of the read prover.
2//!
3//! The main gist of the protocol is to run a trivial sangria-like
4//! folding without recursive circuit (non-IVC variant),
5//! interactively, between the prover and the verifier.
6//!
7//! The original equation we want to prove is `d(X) * q(X) - a(X) = 0`.
8//!
9//! After homogenization it becomes `d(X) * q(X) - a(X) * u - e(X) =
10//! 0` where u is a homogenization factor (field element), and `e(X)`
11//! is an error polynomial.
12//!
13//! During the folding procedure we fold one non-relaxed instance with
14//! a relaxed instance (number 1 and 2 correspondingly), and the
15//! result of this is a relaxed instance. The recombination
16//! coefficient `r` is sampled as usual, using Fiat-Shamir, and then
17//! we recombine vectors and commitments as follows:
18//!
19//! `a_3(X) <- a_1(X) + r * a_2(X)` (same for `d, q`)
20//!
21//! To compute the new error term, we have to use the formula
22//!
23//! `e_3(X) <- r * cross_term(X) + r^2 * e_2(X)`
24//!
25//! where `cross_term(X) := a_2 + u_2 * a_1 - q_2 * d_1 - q_1 * d_2`.
26//!
27//! Finally, in this file we provide a prover and verifier attesting
28//! to the validity of a relaxed instance using plonk-like protocol
29//! with IPA.
30//!
31//! For mor details see the full version of the protocol in the whitepaper.
32
33use 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)]
51/// Non-relaxed instance attesting to `d * q - a = 0`
52pub struct CoreInstance {
53    /// Commitment to the data
54    pub comm_d: Curve,
55    /// Commitment to the query polynomial
56    pub comm_q: Curve,
57    /// Commitment to the answers
58    pub comm_a: Curve,
59}
60
61#[derive(PartialEq, Eq)]
62/// Relaxed instance variant, attesting to `d * q - a * u - e = 0`
63pub struct RelaxedInstance {
64    /// Non-relaxed part
65    pub core: CoreInstance,
66    /// Homogeneization term for folding
67    pub u: ScalarField,
68    /// Commitment to the error term for folding
69    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)]
83/// Non-relaxed witness contains evaluations (field vectors) for data,
84/// query, and answers.
85pub 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)]
92/// Relaxed witness extends the non-relaxed witness with evaluations
93/// of the error term.
94pub 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    /// This function checks if the provided instance is valid regarding the
110    /// provided witness, by checking both the validity of the witness and its
111    /// consistency with the instance's commitments.
112    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            // todo can be parallelized
120            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)]
262/// The proof attesting to the validity of the relaxed instance.
263pub struct ReadProof {
264    /// Commitment of quotient polynomial T (aka t_comm)
265    pub quotient_comm: Curve,
266
267    /// Evaluation of data polynomial at the required challenge point
268    pub data_eval: ScalarField,
269    /// Evaluation of query polynomial at the required challenge point
270    pub query_eval: ScalarField,
271    /// Evaluation of answer polynomial at the required challenge point
272    pub answer_eval: ScalarField,
273    /// Evaluation of error polynomial at the required challenge point
274    pub error_eval: ScalarField,
275
276    /// Polynomial commitment’s proof for the validity of returned evaluations
277    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    // quotient poly is (d * q - a * u + e) / (X^N-1)
306    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        // q×d - a
313        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        // We compute the polynomial t(X) by dividing the constraints polynomial
320        // by the vanishing polynomial, i.e. Z_H(X).
321        let (quotient, res) = numerator_eval_interpolated
322            .divide_by_vanishing_poly(domain.d1)
323            .unwrap_or_else(fail_final_q_division);
324        // As the constraints must be verified on H, the rest of the division
325        // must be equal to 0 as the constraints polynomial and Z_H(X) are both
326        // equal on H.
327        if !res.is_zero() {
328            fail_final_q_division();
329        }
330
331        quotient
332    };
333
334    // commit to the quotient polynomial $t$.
335    // num_chunks = 1 because our constraint is degree 2, which makes the quotient polynomial of degree d1
336    let quotient_comm = srs.commit_non_hiding(&quotient_poly, 1).chunks[0];
337    fq_sponge.absorb_g(&[quotient_comm]);
338
339    // aka zeta
340    let evaluation_point = fq_sponge.challenge();
341
342    // Fiat Shamir - absorbing evaluations
343    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    // Generate scalars used as combiners for sub-statements within our IPA opening proof.
366    let polyscale = fr_sponge.challenge().to_field(endo_r);
367    let evalscale = fr_sponge.challenge().to_field(endo_r);
368
369    // Creating the polynomials for the batch proof
370    // Gathering all polynomials to use in the opening proof
371    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(&quotient_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    // Generate scalars used as combiners for sub-statements within our IPA opening proof.
454    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    /// Generates a random core instance and witness
524    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    /// Generates a relaxed instance and witness. Note that the result
589    /// of this function is _not_ an instance-witness pair produced by
590    /// a valid folding procedure, but just a generic relaxed pair instead.
591    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}