diff --git a/Cargo.lock b/Cargo.lock index 124a72ffd2..f2958269c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1907,6 +1907,7 @@ dependencies = [ "log", "mina-curves", "mina-poseidon", + "o1-utils", "os_pipe", "poly-commitment", "rand", diff --git a/o1vm/Cargo.toml b/o1vm/Cargo.toml index 2109281914..1c14712ece 100644 --- a/o1vm/Cargo.toml +++ b/o1vm/Cargo.toml @@ -25,6 +25,7 @@ name = "pickles_o1vm" path = "src/pickles/main.rs" [dependencies] +o1-utils.workspace = true # FIXME: Only activate this when legacy_o1vm is built ark-bn254.workspace = true # FIXME: Only activate this when legacy_o1vm is built diff --git a/o1vm/run-code.sh b/o1vm/run-code.sh index 8753acf2c5..fba71d8d0c 100755 --- a/o1vm/run-code.sh +++ b/o1vm/run-code.sh @@ -16,4 +16,5 @@ else ./run-op-program.sh ./run-cannon.sh fi + ./run-vm.sh diff --git a/o1vm/src/pickles/column_env.rs b/o1vm/src/pickles/column_env.rs index 801965689e..c66f68160f 100644 --- a/o1vm/src/pickles/column_env.rs +++ b/o1vm/src/pickles/column_env.rs @@ -1,5 +1,6 @@ use ark_ff::FftField; use ark_poly::{Evaluations, Radix2EvaluationDomain}; +use kimchi_msm::columns::Column; use crate::{ interpreters::mips::{column::N_MIPS_SEL_COLS, witness::SCRATCH_SIZE}, @@ -34,35 +35,40 @@ pub struct ColumnEnvironment<'a, F: FftField> { pub domain: EvaluationDomains, } -impl<'a, F: FftField> TColumnEnvironment<'a, F, BerkeleyChallengeTerm, BerkeleyChallenges> - for ColumnEnvironment<'a, F> -{ - // FIXME: do we change to the MIPS column type? - // We do not want to keep kimchi_msm/generic prover - type Column = kimchi_msm::columns::Column; +pub fn get_all_columns() -> Vec { + let mut cols = Vec::::with_capacity(SCRATCH_SIZE + 2 + N_MIPS_SEL_COLS); + for i in 0..SCRATCH_SIZE + 2 { + cols.push(Column::Relation(i)); + } + for i in 0..N_MIPS_SEL_COLS { + cols.push(Column::DynamicSelector(i)); + } + cols +} - fn get_column(&self, col: &Self::Column) -> Option<&'a Evals> { +impl WitnessColumns { + pub fn get_column(&self, col: &Column) -> Option<&G> { match *col { - Self::Column::Relation(i) => { + Column::Relation(i) => { if i < SCRATCH_SIZE { - let res = &self.witness.scratch[i]; + let res = &self.scratch[i]; Some(res) } else if i == SCRATCH_SIZE { - let res = &self.witness.instruction_counter; + let res = &self.instruction_counter; Some(res) } else if i == SCRATCH_SIZE + 1 { - let res = &self.witness.error; + let res = &self.error; Some(res) } else { panic!("We should not have that many relation columns"); } } - Self::Column::DynamicSelector(i) => { + Column::DynamicSelector(i) => { assert!( i < N_MIPS_SEL_COLS, "We do not have that many dynamic selector columns" ); - let res = &self.witness.selector[i]; + let res = &self.selector[i]; Some(res) } _ => { @@ -70,6 +76,18 @@ impl<'a, F: FftField> TColumnEnvironment<'a, F, BerkeleyChallengeTerm, BerkeleyC } } } +} + +impl<'a, F: FftField> TColumnEnvironment<'a, F, BerkeleyChallengeTerm, BerkeleyChallenges> + for ColumnEnvironment<'a, F> +{ + // FIXME: do we change to the MIPS column type? + // We do not want to keep kimchi_msm/generic prover + type Column = Column; + + fn get_column(&self, col: &Self::Column) -> Option<&'a Evals> { + self.witness.get_column(col) + } fn get_domain(&self, d: Domain) -> Radix2EvaluationDomain { match d { diff --git a/o1vm/src/pickles/main.rs b/o1vm/src/pickles/main.rs index f5ea5fa72e..0ea1bf6bc0 100644 --- a/o1vm/src/pickles/main.rs +++ b/o1vm/src/pickles/main.rs @@ -17,10 +17,7 @@ use o1vm::{ witness::{self as mips_witness}, Instruction, }, - pickles::{ - proof::{Proof, ProofInputs}, - prover, - }, + pickles::{proof::ProofInputs, prover, verifier}, preimage_oracle::PreImageOracle, }; use poly_commitment::{ipa::SRS, SRS as _}; @@ -124,19 +121,26 @@ pub fn main() -> ExitCode { // FIXME let start_iteration = Instant::now(); debug!("Limit of {DOMAIN_SIZE} reached. We make a proof, verify it (for testing) and start with a new chunk"); - let _proof: Result, prover::ProverError> = - prover::prove::< - Vesta, - DefaultFqSponge, - DefaultFrSponge, - _, - >(domain_fp, &srs, curr_proof_inputs, &constraints, &mut rng); + let proof = prover::prove::< + Vesta, + DefaultFqSponge, + DefaultFrSponge, + _, + >(domain_fp, &srs, curr_proof_inputs, &constraints, &mut rng) + .unwrap(); // FIXME: check that the proof is correct. This is for testing purposes. // Leaving like this for now. debug!( "Proof generated in {elapsed} μs", elapsed = start_iteration.elapsed().as_micros() ); + let verif = verifier::verify::< + Vesta, + DefaultFqSponge, + DefaultFrSponge, + >(domain_fp, &srs, &constraints, &proof); + assert!(verif); + curr_proof_inputs = ProofInputs::new(DOMAIN_SIZE); } } diff --git a/o1vm/src/pickles/mod.rs b/o1vm/src/pickles/mod.rs index 328d7d368a..193162e90b 100644 --- a/o1vm/src/pickles/mod.rs +++ b/o1vm/src/pickles/mod.rs @@ -15,6 +15,7 @@ pub mod column_env; pub mod proof; pub mod prover; +pub mod verifier; /// Maximum degree of the constraints. /// It does include the additional degree induced by the multiplication of the diff --git a/o1vm/src/pickles/proof.rs b/o1vm/src/pickles/proof.rs index 1c5526f1cc..23c02dbb45 100644 --- a/o1vm/src/pickles/proof.rs +++ b/o1vm/src/pickles/proof.rs @@ -1,8 +1,9 @@ -use kimchi::curve::KimchiCurve; +use kimchi::{curve::KimchiCurve, proof::PointEvaluations}; use poly_commitment::{ipa::OpeningProof, PolyComm}; use crate::interpreters::mips::column::N_MIPS_SEL_COLS; +#[derive(Debug)] pub struct WitnessColumns { pub scratch: [G; crate::interpreters::mips::witness::SCRATCH_SIZE], pub instruction_counter: G, @@ -32,6 +33,8 @@ pub struct Proof { pub commitments: WitnessColumns, [PolyComm; N_MIPS_SEL_COLS]>, pub zeta_evaluations: WitnessColumns, pub zeta_omega_evaluations: WitnessColumns, + pub quotient_commitment: PolyComm, + pub quotient_evaluations: PointEvaluations>, /// IPA opening proof pub opening_proof: OpeningProof, } diff --git a/o1vm/src/pickles/prover.rs b/o1vm/src/pickles/prover.rs index b4788dda46..82c34896bd 100644 --- a/o1vm/src/pickles/prover.rs +++ b/o1vm/src/pickles/prover.rs @@ -12,9 +12,11 @@ use kimchi::{ curve::KimchiCurve, groupmap::GroupMap, plonk_sponge::FrSponge, + proof::PointEvaluations, }; use log::debug; use mina_poseidon::{sponge::ScalarChallenge, FqSponge}; +use o1_utils::ExtendedDensePolynomial; use poly_commitment::{ commitment::{absorb_commitment, PolyComm}, ipa::{DensePolynomialOrEvaluations, OpeningProof, SRS}, @@ -126,9 +128,16 @@ where error, selector, } = &polys; - // Note: we do not blind. We might want in the near future in case we - // have a column with only zeroes. - let comm = |poly: &DensePolynomial| srs.commit_non_hiding(poly, num_chunks); + + let comm = |poly: &DensePolynomial| { + srs.commit_custom( + poly, + num_chunks, + &PolyComm::new(vec![G::ScalarField::one()]), + ) + .unwrap() + .commitment + }; // Doing in parallel let scratch = scratch.par_iter().map(comm).collect::>(); let selector = selector.par_iter().map(comm).collect::>(); @@ -240,27 +249,36 @@ where // And we interpolate using the evaluations let expr_evaluation_interpolated = expr_evaluation.interpolate(); - let fail_final_q_division = || { - panic!("Division by vanishing poly must not fail at this point, we checked it before") - }; + let fail_final_q_division = || panic!("Fail division by vanishing poly"); + let fail_remainder_not_zero = + || panic!("The constraints are not satisifed since the remainder is not zero"); // We compute the polynomial t(X) by dividing the constraints polynomial // by the vanishing polynomial, i.e. Z_H(X). - let (quotient, res) = expr_evaluation_interpolated + let (quotient, rem) = expr_evaluation_interpolated + // FIXME: Should this be d8? .divide_by_vanishing_poly(domain.d1) .unwrap_or_else(fail_final_q_division); // As the constraints must be verified on H, the rest of the division // must be equal to 0 as the constraints polynomial and Z_H(X) are both // equal on H. - if !res.is_zero() { - fail_final_q_division(); + if !rem.is_zero() { + fail_remainder_not_zero(); } quotient }; - let t_comm = srs.commit_non_hiding("ient_poly, DEGREE_QUOTIENT_POLYNOMIAL as usize); - - absorb_commitment(&mut fq_sponge, &t_comm); + let quotient_commitment = srs + .commit_custom( + "ient_poly, + DEGREE_QUOTIENT_POLYNOMIAL as usize, + &PolyComm::new(vec![ + G::ScalarField::one(); + DEGREE_QUOTIENT_POLYNOMIAL as usize + ]), + ) + .unwrap(); + absorb_commitment(&mut fq_sponge, "ient_commitment.commitment); //////////////////////////////////////////////////////////////////////////// // Round 3: Evaluations at ζ and ζω @@ -294,22 +312,34 @@ where <::Group as Group>::ScalarField, [<::Group as Group>::ScalarField; N_MIPS_SEL_COLS], > = evals(&zeta); + // All evaluations at ζω let zeta_omega_evaluations: WitnessColumns< <::Group as Group>::ScalarField, [<::Group as Group>::ScalarField; N_MIPS_SEL_COLS], > = evals(&zeta_omega); + let chunked_quotient = quotient_poly + .to_chunked_polynomial(DEGREE_QUOTIENT_POLYNOMIAL as usize, domain.d1.size as usize); + let quotient_evaluations = PointEvaluations { + zeta: chunked_quotient + .polys + .iter() + .map(|p| p.evaluate(&zeta)) + .collect::>(), + zeta_omega: chunked_quotient + .polys + .iter() + .map(|p| p.evaluate(&zeta_omega)) + .collect(), + }; + // Absorbing evaluations with a sponge for the other field // We initialize the state with the previous state of the fq_sponge let fq_sponge_before_evaluations = fq_sponge.clone(); let mut fr_sponge = EFrSponge::new(G::sponge_params()); fr_sponge.absorb(&fq_sponge.digest()); - // Quotient poly evals - let quotient_zeta_eval = quotient_poly.evaluate(&zeta); - let quotient_zeta_omega_eval = quotient_poly.evaluate(&zeta_omega); - for (zeta_eval, zeta_omega_eval) in zeta_evaluations .scratch .iter() @@ -330,30 +360,40 @@ where fr_sponge.absorb(zeta_eval); fr_sponge.absorb(zeta_omega_eval); } - fr_sponge.absorb("ient_zeta_eval); - fr_sponge.absorb("ient_zeta_omega_eval); - + for (quotient_zeta_eval, quotient_zeta_omega_eval) in quotient_evaluations + .zeta + .iter() + .zip(quotient_evaluations.zeta_omega.iter()) + { + fr_sponge.absorb(quotient_zeta_eval); + fr_sponge.absorb(quotient_zeta_omega_eval); + } //////////////////////////////////////////////////////////////////////////// // Round 4: Opening proof w/o linearization polynomial //////////////////////////////////////////////////////////////////////////// - // Preparing the polynomials for the opening proof let mut polynomials: Vec<_> = polys.scratch.into_iter().collect(); polynomials.push(polys.instruction_counter); polynomials.push(polys.error); polynomials.extend(polys.selector); - polynomials.push(quotient_poly); - let polynomials: Vec<_> = polynomials + // Preparing the polynomials for the opening proof + let mut polynomials: Vec<_> = polynomials .iter() .map(|poly| { ( DensePolynomialOrEvaluations::DensePolynomial(poly), - // We do not have any blinder, therefore we set to 0. - PolyComm::new(vec![G::ScalarField::zero()]), + // We do not have any blinder, therefore we set to 1. + PolyComm::new(vec![G::ScalarField::one()]), ) }) .collect(); + // we handle the quotient separately because the number of blinders = + // number of chunks, which is different for just the quotient polynomial. + polynomials.push(( + DensePolynomialOrEvaluations::DensePolynomial("ient_poly), + quotient_commitment.blinders, + )); // poly scale let v_chal = fr_sponge.challenge(); @@ -381,6 +421,8 @@ where commitments, zeta_evaluations, zeta_omega_evaluations, + quotient_commitment: quotient_commitment.commitment, + quotient_evaluations, opening_proof, }) } diff --git a/o1vm/src/pickles/tests.rs b/o1vm/src/pickles/tests.rs index b7e1bf1815..422f10bc13 100644 --- a/o1vm/src/pickles/tests.rs +++ b/o1vm/src/pickles/tests.rs @@ -1,14 +1,29 @@ +use std::time::Instant; + +use super::{ + super::interpreters::mips::witness::SCRATCH_SIZE, + proof::{ProofInputs, WitnessColumns}, + prover::prove, +}; use crate::{ interpreters::mips::{ constraints as mips_constraints, interpreter, interpreter::InterpreterEnv, Instruction, }, - pickles::{MAXIMUM_DEGREE_CONSTRAINTS, TOTAL_NUMBER_OF_CONSTRAINTS}, + pickles::{verifier::verify, MAXIMUM_DEGREE_CONSTRAINTS, TOTAL_NUMBER_OF_CONSTRAINTS}, }; +use ark_ff::{One, Zero}; use interpreter::{ITypeInstruction, JTypeInstruction, RTypeInstruction}; -use kimchi_msm::expr::E; -use mina_curves::pasta::Fp; +use kimchi::circuits::{domains::EvaluationDomains, expr::Expr, gate::CurrOrNext}; +use kimchi_msm::{columns::Column, expr::E}; +use log::debug; +use mina_curves::pasta::{Fp, Fq, Pallas, PallasParameters}; +use mina_poseidon::{ + constants::PlonkSpongeConstantsKimchi, + sponge::{DefaultFqSponge, DefaultFrSponge}, +}; +use o1_utils::tests::make_test_rng; +use poly_commitment::SRS; use strum::{EnumCount, IntoEnumIterator}; - #[test] fn test_regression_constraints_with_selectors() { let constraints = { @@ -56,3 +71,52 @@ fn test_regression_selectors_for_instructions() { .iter() .for_each(|c| assert!(c.degree(1, 0) == 2 || c.degree(1, 0) == 1)); } + +fn zero_to_n_minus_one(n: usize) -> Vec { + (0..n).map(|i| Fq::from((i) as u64)).collect() +} +#[test] +fn test_small_circuit() { + let domain = EvaluationDomains::::create(8).unwrap(); + let srs = SRS::create(8); + let proof_input = ProofInputs:: { + evaluations: WitnessColumns { + scratch: std::array::from_fn(|_| zero_to_n_minus_one(8)), + instruction_counter: zero_to_n_minus_one(8) + .into_iter() + .map(|x| x + Fq::one()) + .collect(), + error: (0..8) + .map(|i| -Fq::from((i * SCRATCH_SIZE + (i + 1)) as u64)) + .collect(), + selector: zero_to_n_minus_one(8), + }, + }; + let mut expr = Expr::zero(); + for i in 0..SCRATCH_SIZE + 2 { + expr += Expr::cell(Column::Relation(i), CurrOrNext::Curr); + } + let mut rng = make_test_rng(None); + + type BaseSponge = DefaultFqSponge; + type ScalarSponge = DefaultFrSponge; + + let proof = prove::( + domain, + &srs, + proof_input, + &[expr.clone()], + &mut rng, + ) + .unwrap(); + + let instant_before_verification = Instant::now(); + let verif = + verify::(domain, &srs, &vec![expr.clone()], &proof); + let instant_after_verification = Instant::now(); + debug!( + "Verification took: {} ms", + (instant_after_verification - instant_before_verification).as_millis() + ); + assert!(verif, "Verification fails"); +} diff --git a/o1vm/src/pickles/verifier.rs b/o1vm/src/pickles/verifier.rs new file mode 100644 index 0000000000..0895d0e673 --- /dev/null +++ b/o1vm/src/pickles/verifier.rs @@ -0,0 +1,268 @@ +#![allow(clippy::boxed_local)] + +use ark_ec::{AffineRepr, Group}; +use ark_ff::{Field, One, PrimeField, Zero}; +use rand::thread_rng; + +use kimchi::{ + circuits::{ + berkeley_columns::BerkeleyChallenges, + domains::EvaluationDomains, + expr::{ColumnEvaluations, Constants, Expr, ExprError, PolishToken}, + gate::CurrOrNext, + }, + curve::KimchiCurve, + groupmap::GroupMap, + plonk_sponge::FrSponge, + proof::PointEvaluations, +}; +use mina_poseidon::{sponge::ScalarChallenge, FqSponge}; +use poly_commitment::{ + commitment::{ + absorb_commitment, combined_inner_product, BatchEvaluationProof, Evaluation, PolyComm, + }, + ipa::OpeningProof, + OpenProof, +}; + +use super::{ + column_env::get_all_columns, + proof::{Proof, WitnessColumns}, +}; +use crate::{interpreters::mips::column::N_MIPS_SEL_COLS, E}; +use kimchi_msm::columns::Column; + +type CommitmentColumns = WitnessColumns, [PolyComm; N_MIPS_SEL_COLS]>; +type EvaluationColumns = WitnessColumns< + <::Group as Group>::ScalarField, + [<::Group as Group>::ScalarField; N_MIPS_SEL_COLS], +>; + +struct ColumnEval<'a, G: AffineRepr> { + commitment: &'a CommitmentColumns, + zeta_eval: &'a EvaluationColumns, + zeta_omega_eval: &'a EvaluationColumns, +} + +impl ColumnEvaluations<::ScalarField> for ColumnEval<'_, G> { + type Column = Column; + fn evaluate( + &self, + col: Self::Column, + ) -> Result::ScalarField>, ExprError> { + let ColumnEval { + commitment: _, + zeta_eval, + zeta_omega_eval, + } = self; + if let Some(&zeta) = zeta_eval.get_column(&col) { + if let Some(&zeta_omega) = zeta_omega_eval.get_column(&col) { + Ok(PointEvaluations { zeta, zeta_omega }) + } else { + Err(ExprError::MissingEvaluation(col, CurrOrNext::Next)) + } + } else { + Err(ExprError::MissingEvaluation(col, CurrOrNext::Curr)) + } + } +} + +pub fn verify< + G: KimchiCurve, + EFqSponge: Clone + FqSponge, + EFrSponge: FrSponge, +>( + domain: EvaluationDomains, + srs: & as OpenProof>::SRS, + //FIXME: change vec to array + constraints: &Vec>, + proof: &Proof, +) -> bool +where + ::BaseField: PrimeField, +{ + let Proof { + commitments, + zeta_evaluations, + zeta_omega_evaluations, + quotient_commitment, + quotient_evaluations, + opening_proof, + } = proof; + + //////////////////////////////////////////////////////////////////////////// + // TODO : public inputs + //////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////// + // Absorbing all the commitments to the columns + //////////////////////////////////////////////////////////////////////////// + + let mut fq_sponge = EFqSponge::new(G::other_curve_sponge_params()); + for comm in commitments.scratch.iter() { + absorb_commitment(&mut fq_sponge, comm) + } + absorb_commitment(&mut fq_sponge, &commitments.instruction_counter); + absorb_commitment(&mut fq_sponge, &commitments.error); + for comm in commitments.selector.iter() { + absorb_commitment(&mut fq_sponge, comm) + } + + // Sample α with the Fq-Sponge. + let alpha = fq_sponge.challenge(); + + //////////////////////////////////////////////////////////////////////////// + // Quotient polynomial + //////////////////////////////////////////////////////////////////////////// + + absorb_commitment(&mut fq_sponge, quotient_commitment); + + // -- Preparing for opening proof verification + let zeta_chal = ScalarChallenge(fq_sponge.challenge()); + let (_, endo_r) = G::endos(); + let zeta: G::ScalarField = zeta_chal.to_field(endo_r); + let omega = domain.d1.group_gen; + let zeta_omega = zeta * omega; + + let column_eval = ColumnEval { + commitment: commitments, + zeta_eval: zeta_evaluations, + zeta_omega_eval: zeta_omega_evaluations, + }; + + // -- Absorb all commitments_and_evaluations + let fq_sponge_before_commitments_and_evaluations = fq_sponge.clone(); + let mut fr_sponge = EFrSponge::new(G::sponge_params()); + fr_sponge.absorb(&fq_sponge.digest()); + + for (zeta_eval, zeta_omega_eval) in zeta_evaluations + .scratch + .iter() + .zip(zeta_omega_evaluations.scratch.iter()) + { + fr_sponge.absorb(zeta_eval); + fr_sponge.absorb(zeta_omega_eval); + } + fr_sponge.absorb(&zeta_evaluations.instruction_counter); + fr_sponge.absorb(&zeta_omega_evaluations.instruction_counter); + fr_sponge.absorb(&zeta_evaluations.error); + fr_sponge.absorb(&zeta_omega_evaluations.error); + for (zeta_eval, zeta_omega_eval) in zeta_evaluations + .selector + .iter() + .zip(zeta_omega_evaluations.selector.iter()) + { + fr_sponge.absorb(zeta_eval); + fr_sponge.absorb(zeta_omega_eval); + } + for (quotient_zeta_eval, quotient_zeta_omega_eval) in quotient_evaluations + .zeta + .iter() + .zip(quotient_evaluations.zeta_omega.iter()) + { + fr_sponge.absorb(quotient_zeta_eval); + fr_sponge.absorb(quotient_zeta_omega_eval); + } + + // FIXME: use a proper Challenge structure + let challenges = BerkeleyChallenges { + alpha, + // No permutation argument for the moment + beta: G::ScalarField::zero(), + gamma: G::ScalarField::zero(), + // No lookup for the moment + joint_combiner: G::ScalarField::zero(), + }; + let (_, endo_r) = G::endos(); + + let constants = Constants { + endo_coefficient: *endo_r, + mds: &G::sponge_params().mds, + zk_rows: 0, + }; + + let combined_expr = + Expr::combine_constraints(0..(constraints.len() as u32), constraints.clone()); + + let numerator_zeta = PolishToken::evaluate( + combined_expr.to_polish().as_slice(), + domain.d1, + zeta, + &column_eval, + &constants, + &challenges, + ) + .unwrap_or_else(|_| panic!("Could not evaluate quotient polynomial at zeta")); + + let v_chal = fr_sponge.challenge(); + let v = v_chal.to_field(endo_r); + let u_chal = fr_sponge.challenge(); + let u = u_chal.to_field(endo_r); + + let evaluations = { + let all_columns = get_all_columns(); + + let mut evaluations = Vec::with_capacity(all_columns.len() + 1); // +1 for the quotient + + all_columns.into_iter().for_each(|column| { + let point_evaluations = column_eval + .evaluate(column) + .unwrap_or_else(|_| panic!("Could not get `evaluations` for `Evaluation`")); + + let commitment = column_eval + .commitment + .get_column(&column) + .unwrap_or_else(|| panic!("Could not get `commitment` for `Evaluation`")) + .clone(); + + evaluations.push(Evaluation { + commitment, + evaluations: vec![ + vec![point_evaluations.zeta], + vec![point_evaluations.zeta_omega], + ], + }) + }); + evaluations.push(Evaluation { + commitment: proof.quotient_commitment.clone(), + evaluations: vec![ + quotient_evaluations.zeta.clone(), + quotient_evaluations.zeta_omega.clone(), + ], + }); + evaluations + }; + + let combined_inner_product = { + let es: Vec<_> = evaluations + .iter() + .map(|Evaluation { evaluations, .. }| evaluations.clone()) + .collect(); + + combined_inner_product(&v, &u, es.as_slice()) + }; + + let batch = BatchEvaluationProof { + sponge: fq_sponge_before_commitments_and_evaluations, + evaluations, + evaluation_points: vec![zeta, zeta_omega], + polyscale: v, + evalscale: u, + opening: opening_proof, + combined_inner_product, + }; + + let group_map = G::Map::setup(); + + // Check the actual quotient works. + let (quotient_zeta, _) = quotient_evaluations.zeta.iter().fold( + (G::ScalarField::zero(), G::ScalarField::one()), + |(res, zeta_i_n), chunk| { + let res = res + zeta_i_n * chunk; + let zeta_i_n = zeta_i_n * zeta.pow([domain.d1.size]); + (res, zeta_i_n) + }, + ); + (quotient_zeta == numerator_zeta / (zeta.pow([domain.d1.size]) - G::ScalarField::one())) + && OpeningProof::verify(srs, &group_map, &mut [batch], &mut thread_rng()) +}