From 05f49918ac35fba62bd43389943f6f5d33e78cd7 Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 4 Jan 2024 12:15:28 +0100 Subject: [PATCH] Feature/sumcheck circuit (#47) * feat: init sumcheck.rs * chore: rename * feat: update lib and add trait for transcript with vec storing challenges * bugfix: mut self ref of transcript * feat: tentative sum-check using poseidon * refactor: remove extension trait and use initial trait * refactor: stop using extension trait, use initial Transcript trait * feat: generic over CurveGroup sum-check verifier and algorithm * feat: implement generic sum-check veriy * bugfix: cargo clippy --fix * chore: cargo fmt * feat: (unstable) sum-check implementation * feat: start benches * chore: run clippy * chore: run cargo fmt * feat: add sum-check tests + benches * chore: clippy + fmt * chore: remove unstable sumcheck * chore: delete duplicated sum-check code * chore: remove deleted sum-check code from lib.rs imports * feat: remove non generic traits, implement sum-check with generic trait and add test * chore: remove non-generic struct * chore: remove non generic verifier * feat: make nifms generic over transcript and update to use poseidon transcript * chore: cargo fmt * chore: remove tmp benches * chore: update cargo.toml * refactor: remove Generic suffix * feat: prover state generic over CurveGroup * chore: disable clippy type complexity warning * refactor: remove Transcript type and espresso transcript dependency * refactor: SumCheckProver generic over CurveGroup * feat: init sumcheck.rs * chore: rename * feat: update lib and add trait for transcript with vec storing challenges * bugfix: mut self ref of transcript * feat: tentative sum-check using poseidon * refactor: stop using extension trait, use initial Transcript trait * feat: generic over CurveGroup sum-check verifier and algorithm * feat: implement generic sum-check veriy * bugfix: cargo clippy --fix * chore: cargo fmt * feat: (unstable) sum-check implementation * feat: start benches * chore: run clippy * chore: run cargo fmt * feat: add sum-check tests + benches * chore: clippy + fmt * chore: remove unstable sumcheck * chore: delete duplicated sum-check code * chore: remove deleted sum-check code from lib.rs imports * feat: remove non generic traits, implement sum-check with generic trait and add test * chore: remove non-generic struct * chore: remove non generic verifier * feat: make nifms generic over transcript and update to use poseidon transcript * chore: cargo fmt * chore: remove tmp benches * chore: update cargo.toml * refactor: remove Generic suffix * feat: prover state generic over CurveGroup * chore: disable clippy type complexity warning * refactor: remove Transcript type and espresso transcript dependency * refactor: SumCheckProver generic over CurveGroup * feat: adds `compute_lagrange_poly`, returning a `DensePolynomial` to extract coeffs from * chore: add assert on interpolated poly degree vs initial poly degree * refactor: use `compute_lagrange_poly` in `SumCheckVerifier` instead of `interpolate_uni_poly` * refactor: have `TranscriptVar` be generic over `CurveGroup` for consistency * refactor: change back to being generic over field * feat: start to use `PoseidonTranscriptVar` struct * chore: add line to eof for `Cargo.toml` * chore: naming consistency with espresso/sum_check folder * bugfix: add error handling on sum-check prove and verify * chore: clippy fix * chore: add line at eof * feat: switch to using coeffs instead of evals in sum-check * bugfix: tmp remove sanity check in nimfs * refactor: update sanity check * refactor: update verifier evaluation form + add comment * chore: run clippy * fix: correct merge artifacts * feat: verify circuit passing * refactor: change naming to use the `Gadget` suffix, update `verify_sumcheck` to not have `&self` as first argument, update test * feat: testing on polynomials with various number of variables * refactor: update method name * fix: avoid rust-analyzer from complaining * fix: fix clippy complains * chore: cargo clippy * chore: udpate arg name for `SumCheckVerifierGadget` * refactor: remove unnecessary cloning in sumcheck circuit * refactor: impl `get_poly_vars_from_sumcheck_proof` for `IOPProof` * chore: group imports * refactor: update `P(0) + P(1)` and name it `eval` * chore: clippy + fmt * refactor: move `compute_lagrange_poly` to `utils` * fix: wrong import * chore: cargo fmt * refactor: absorb num vars and max degree within sumcheck circuit * refactor: update name to `compute_lagrange_interpolated_poly` and add comment * feat: create `IOPProofVar`, which implements the `AllocVar` trait * fix: clippy allow `type_complexity` on return type of `SumCheckVerifierGadget` * refactor: use `VPAuxInfo` instead of virtual type, remove `_` prefix in params name * feat: add tests on computed & returned values from `verify` * refactor: use `new_witness` instead of `new_variable` Co-authored-by: arnaucube * chore: clippy * fix: remove `unwrap()` within `verify()` Co-authored-by: arnaucube * chore: add comment on `unwrap()` * refactor: move `compute_lagrange_interpolated_poly` tests to `lagrange_poly.rs` --------- Co-authored-by: arnaucube --- src/folding/circuits/mod.rs | 1 + src/folding/circuits/sum_check.rs | 273 +++++++++++++++++++++++ src/folding/hypernova/nimfs.rs | 11 +- src/utils/espresso/sum_check/mod.rs | 27 +-- src/utils/espresso/sum_check/prover.rs | 9 +- src/utils/espresso/sum_check/structs.rs | 5 +- src/utils/espresso/sum_check/verifier.rs | 85 ++----- src/utils/lagrange_poly.rs | 123 ++++++++++ src/utils/mod.rs | 1 + 9 files changed, 440 insertions(+), 95 deletions(-) create mode 100644 src/folding/circuits/sum_check.rs create mode 100644 src/utils/lagrange_poly.rs diff --git a/src/folding/circuits/mod.rs b/src/folding/circuits/mod.rs index ab2aab7..d021b7d 100644 --- a/src/folding/circuits/mod.rs +++ b/src/folding/circuits/mod.rs @@ -3,6 +3,7 @@ use ark_ec::CurveGroup; use ark_ff::Field; pub mod nonnative; +pub mod sum_check; // CF represents the constraints field pub type CF = <::BaseField as Field>::BasePrimeField; diff --git a/src/folding/circuits/sum_check.rs b/src/folding/circuits/sum_check.rs new file mode 100644 index 0000000..b570198 --- /dev/null +++ b/src/folding/circuits/sum_check.rs @@ -0,0 +1,273 @@ +use crate::utils::espresso::sum_check::SumCheck; +use crate::utils::virtual_polynomial::VPAuxInfo; +use crate::{ + transcript::{ + poseidon::{PoseidonTranscript, PoseidonTranscriptVar}, + TranscriptVar, + }, + utils::sum_check::{structs::IOPProof, IOPSumCheck}, +}; +use ark_crypto_primitives::sponge::Absorb; +use ark_ec::{CurveGroup, Group}; +/// Heavily inspired from testudo: https://github.com/cryptonetlab/testudo/tree/master +/// Some changes: +/// - Typings to better stick to ark_poly's API +/// - Uses `folding-schemes`' own `TranscriptVar` trait and `PoseidonTranscriptVar` struct +/// - API made closer to gadgets found in `folding-schemes` +use ark_ff::PrimeField; +use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; +use ark_r1cs_std::{ + alloc::{AllocVar, AllocationMode}, + eq::EqGadget, + fields::fp::FpVar, +}; +use ark_relations::r1cs::{Namespace, SynthesisError}; +use std::{borrow::Borrow, marker::PhantomData}; + +#[derive(Clone, Debug)] +pub struct DensePolynomialVar { + pub coeffs: Vec>, +} + +impl AllocVar, F> for DensePolynomialVar { + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|c| { + let cs = cs.into(); + let cp: &DensePolynomial = c.borrow(); + let mut coeffs_var = Vec::>::with_capacity(cp.coeffs.len()); + for coeff in cp.coeffs.iter() { + let coeff_var = FpVar::::new_variable(cs.clone(), || Ok(coeff), mode)?; + coeffs_var.push(coeff_var); + } + Ok(Self { coeffs: coeffs_var }) + }) + } +} + +impl DensePolynomialVar { + pub fn eval_at_zero(&self) -> FpVar { + self.coeffs[0].clone() + } + + pub fn eval_at_one(&self) -> FpVar { + let mut res = self.coeffs[0].clone(); + for i in 1..self.coeffs.len() { + res = &res + &self.coeffs[i]; + } + res + } + + pub fn evaluate(&self, r: &FpVar) -> FpVar { + let mut eval = self.coeffs[0].clone(); + let mut power = r.clone(); + + for i in 1..self.coeffs.len() { + eval += &power * &self.coeffs[i]; + power *= r; + } + eval + } +} + +#[derive(Clone, Debug)] +pub struct IOPProofVar { + // We have to be generic over a CurveGroup because instantiating a IOPProofVar will call IOPSumCheck which requires a CurveGroup + pub proofs: Vec>, + pub claim: FpVar, +} + +impl AllocVar, C::ScalarField> for IOPProofVar +where + ::ScalarField: Absorb, +{ + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|c| { + let cs = cs.into(); + let cp: &IOPProof = c.borrow(); + let claim = IOPSumCheck::>::extract_sum(cp); + let claim = FpVar::::new_variable(cs.clone(), || Ok(claim), mode)?; + let mut proofs = + Vec::>::with_capacity(cp.proofs.len()); + for proof in cp.proofs.iter() { + let poly = DensePolynomial::from_coefficients_slice(&proof.coeffs); + let proof = DensePolynomialVar::::new_variable( + cs.clone(), + || Ok(poly), + mode, + )?; + proofs.push(proof); + } + Ok(Self { proofs, claim }) + }) + } +} + +#[derive(Clone, Debug)] +pub struct VPAuxInfoVar { + pub num_variables: FpVar, + pub max_degree: FpVar, +} + +impl AllocVar, F> for VPAuxInfoVar { + fn new_variable>>( + cs: impl Into>, + f: impl FnOnce() -> Result, + mode: AllocationMode, + ) -> Result { + f().and_then(|c| { + let cs = cs.into(); + let cp: &VPAuxInfo = c.borrow(); + let num_variables = FpVar::::new_variable( + cs.clone(), + || Ok(F::from(cp.num_variables as u64)), + mode, + )?; + let max_degree = + FpVar::::new_variable(cs.clone(), || Ok(F::from(cp.max_degree as u64)), mode)?; + Ok(Self { + num_variables, + max_degree, + }) + }) + } +} + +#[derive(Debug, Clone)] +pub struct SumCheckVerifierGadget { + _f: PhantomData, +} + +impl SumCheckVerifierGadget { + #[allow(clippy::type_complexity)] + pub fn verify( + iop_proof_var: &IOPProofVar, + poly_aux_info_var: &VPAuxInfoVar, + transcript_var: &mut PoseidonTranscriptVar, + ) -> Result<(Vec>, Vec>), SynthesisError> { + let mut e_vars = vec![iop_proof_var.claim.clone()]; + let mut r_vars: Vec> = Vec::new(); + transcript_var.absorb(poly_aux_info_var.num_variables.clone())?; + transcript_var.absorb(poly_aux_info_var.max_degree.clone())?; + + for poly_var in iop_proof_var.proofs.iter() { + let res = poly_var.eval_at_one() + poly_var.eval_at_zero(); + let e_var = e_vars.last().ok_or(SynthesisError::Unsatisfiable)?; + res.enforce_equal(e_var)?; + transcript_var.absorb_vec(&poly_var.coeffs)?; + let r_i_var = transcript_var.get_challenge()?; + e_vars.push(poly_var.evaluate(&r_i_var)); + r_vars.push(r_i_var); + } + + Ok((e_vars, r_vars)) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + folding::circuits::sum_check::{IOPProofVar, VPAuxInfoVar}, + transcript::{ + poseidon::{tests::poseidon_test_config, PoseidonTranscript, PoseidonTranscriptVar}, + Transcript, TranscriptVar, + }, + utils::{ + sum_check::{structs::IOPProof, IOPSumCheck, SumCheck}, + virtual_polynomial::VirtualPolynomial, + }, + }; + use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb}; + use ark_ec::CurveGroup; + use ark_ff::Field; + use ark_pallas::{Fr, Projective}; + use ark_poly::{ + univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial, + MultilinearExtension, Polynomial, + }; + use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; + use ark_relations::r1cs::ConstraintSystem; + use std::sync::Arc; + + use super::SumCheckVerifierGadget; + + pub type TestSumCheckProof = (VirtualPolynomial, PoseidonConfig, IOPProof); + + /// Primarily used for testing the sumcheck gadget + /// Returns a random virtual polynomial, the poseidon config used and the associated sumcheck proof + pub fn get_test_sumcheck_proof( + num_vars: usize, + ) -> TestSumCheckProof + where + ::ScalarField: Absorb, + { + let mut rng = ark_std::test_rng(); + let poseidon_config: PoseidonConfig = + poseidon_test_config::(); + let mut poseidon_transcript_prove = PoseidonTranscript::::new(&poseidon_config); + let poly_mle = DenseMultilinearExtension::rand(num_vars, &mut rng); + let virtual_poly = + VirtualPolynomial::new_from_mle(&Arc::new(poly_mle), C::ScalarField::ONE); + let sum_check: IOPProof = IOPSumCheck::>::prove( + &virtual_poly, + &mut poseidon_transcript_prove, + ) + .unwrap(); + (virtual_poly, poseidon_config, sum_check) + } + + #[test] + fn test_sum_check_circuit() { + for num_vars in 1..15 { + let cs = ConstraintSystem::::new_ref(); + let (virtual_poly, poseidon_config, sum_check) = + get_test_sumcheck_proof::(num_vars); + let mut poseidon_var: PoseidonTranscriptVar = + PoseidonTranscriptVar::new(cs.clone(), &poseidon_config); + let iop_proof_var = + IOPProofVar::::new_witness(cs.clone(), || Ok(&sum_check)).unwrap(); + let poly_aux_info_var = + VPAuxInfoVar::::new_witness(cs.clone(), || Ok(virtual_poly.aux_info)).unwrap(); + let res = SumCheckVerifierGadget::::verify( + &iop_proof_var, + &poly_aux_info_var, + &mut poseidon_var, + ); + + assert!(res.is_ok()); + let (circuit_evals, r_challenges) = res.unwrap(); + + // 1. assert claim from circuit is equal to the one from the sum-check + let claim: Fr = + IOPSumCheck::>::extract_sum(&sum_check); + assert_eq!(circuit_evals[0].value().unwrap(), claim); + + // 2. assert that all in-circuit evaluations are equal to the ones from the sum-check + for ((proof, point), circuit_eval) in sum_check + .proofs + .iter() + .zip(sum_check.point.iter()) + .zip(circuit_evals.iter().skip(1)) + // we skip the first one since it's the above checked claim + { + let poly = DensePolynomial::from_coefficients_slice(&proof.coeffs); + let eval = poly.evaluate(point); + assert_eq!(eval, circuit_eval.value().unwrap()); + } + + // 3. assert that all challenges are equal to the ones from the sum-check + for (point, r_challenge) in sum_check.point.iter().zip(r_challenges.iter()) { + assert_eq!(*point, r_challenge.value().unwrap()); + } + + assert!(cs.is_satisfied().unwrap()); + } + } +} diff --git a/src/folding/hypernova/nimfs.rs b/src/folding/hypernova/nimfs.rs index 5d39700..a8ee4dc 100644 --- a/src/folding/hypernova/nimfs.rs +++ b/src/folding/hypernova/nimfs.rs @@ -1,6 +1,8 @@ use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; use ark_ff::{Field, PrimeField}; +use ark_poly::univariate::DensePolynomial; +use ark_poly::{DenseUVPolynomial, Polynomial}; use ark_std::{One, Zero}; use super::cccs::{Witness, CCCS}; @@ -10,7 +12,6 @@ use crate::ccs::CCS; use crate::transcript::Transcript; use crate::utils::hypercube::BooleanHypercube; use crate::utils::sum_check::structs::IOPProof as SumCheckProof; -use crate::utils::sum_check::verifier::interpolate_uni_poly; use crate::utils::sum_check::{IOPSumCheck, SumCheck}; use crate::utils::virtual_polynomial::VPAuxInfo; use crate::Error; @@ -342,11 +343,9 @@ where // Sanity check: we can also compute g(r_x') from the proof last evaluation value, and // should be equal to the previously obtained values. - let g_on_rxprime_from_sumcheck_last_eval = interpolate_uni_poly::( - &proof.sc_proof.proofs.last().unwrap().evaluations, - *r_x_prime.last().unwrap(), - ) - .unwrap(); + let g_on_rxprime_from_sumcheck_last_eval = + DensePolynomial::from_coefficients_slice(&proof.sc_proof.proofs.last().unwrap().coeffs) + .evaluate(r_x_prime.last().unwrap()); if g_on_rxprime_from_sumcheck_last_eval != c { return Err(Error::NotEqual); } diff --git a/src/utils/espresso/sum_check/mod.rs b/src/utils/espresso/sum_check/mod.rs index c30c39b..08ca033 100644 --- a/src/utils/espresso/sum_check/mod.rs +++ b/src/utils/espresso/sum_check/mod.rs @@ -13,14 +13,16 @@ use crate::{ transcript::Transcript, utils::virtual_polynomial::{VPAuxInfo, VirtualPolynomial}, }; -use ark_ec::{CurveGroup, Group}; +use ark_ec::CurveGroup; use ark_ff::PrimeField; -use ark_poly::DenseMultilinearExtension; +use ark_poly::univariate::DensePolynomial; +use ark_poly::{DenseMultilinearExtension, DenseUVPolynomial, Polynomial}; use ark_std::{end_timer, start_timer}; use std::{fmt::Debug, marker::PhantomData, sync::Arc}; use crate::utils::sum_check::structs::IOPProverMessage; use crate::utils::sum_check::structs::IOPVerifierState; +use ark_ff::Field; use espresso_subroutines::poly_iop::prelude::PolyIOPErrors; use structs::{IOPProof, IOPProverState}; @@ -143,7 +145,8 @@ impl> SumCheck for IOPSumCheck { fn extract_sum(proof: &Self::SumCheckProof) -> C::ScalarField { let start = start_timer!(|| "extract sum"); - let res = proof.proofs[0].evaluations[0] + proof.proofs[0].evaluations[1]; + let poly = DensePolynomial::from_coefficients_vec(proof.proofs[0].coeffs.clone()); + let res = poly.evaluate(&C::ScalarField::ONE) + poly.evaluate(&C::ScalarField::ZERO); end_timer!(start); res } @@ -152,12 +155,8 @@ impl> SumCheck for IOPSumCheck { poly: &VirtualPolynomial, transcript: &mut impl Transcript, ) -> Result, PolyIOPErrors> { - transcript.absorb(&::ScalarField::from( - poly.aux_info.num_variables as u64, - )); - transcript.absorb(&::ScalarField::from( - poly.aux_info.max_degree as u64, - )); + transcript.absorb(&C::ScalarField::from(poly.aux_info.num_variables as u64)); + transcript.absorb(&C::ScalarField::from(poly.aux_info.max_degree as u64)); let mut prover_state: IOPProverState = IOPProverState::prover_init(poly)?; let mut challenge: Option = None; let mut prover_msgs: Vec> = @@ -165,7 +164,7 @@ impl> SumCheck for IOPSumCheck { for _ in 0..poly.aux_info.num_variables { let prover_msg: IOPProverMessage = IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)?; - transcript.absorb_vec(&prover_msg.evaluations); + transcript.absorb_vec(&prover_msg.coeffs); prover_msgs.push(prover_msg); challenge = Some(transcript.get_challenge()); } @@ -184,14 +183,12 @@ impl> SumCheck for IOPSumCheck { aux_info: &VPAuxInfo, transcript: &mut impl Transcript, ) -> Result, PolyIOPErrors> { - transcript.absorb(&::ScalarField::from( - aux_info.num_variables as u64, - )); - transcript.absorb(&::ScalarField::from(aux_info.max_degree as u64)); + transcript.absorb(&C::ScalarField::from(aux_info.num_variables as u64)); + transcript.absorb(&C::ScalarField::from(aux_info.max_degree as u64)); let mut verifier_state = IOPVerifierState::verifier_init(aux_info); for i in 0..aux_info.num_variables { let prover_msg = proof.proofs.get(i).expect("proof is incomplete"); - transcript.absorb_vec(&prover_msg.evaluations); + transcript.absorb_vec(&prover_msg.coeffs); IOPVerifierState::verify_round_and_update_state( &mut verifier_state, prover_msg, diff --git a/src/utils/espresso/sum_check/prover.rs b/src/utils/espresso/sum_check/prover.rs index 56fd829..46d2e0c 100644 --- a/src/utils/espresso/sum_check/prover.rs +++ b/src/utils/espresso/sum_check/prover.rs @@ -10,8 +10,10 @@ //! Prover subroutines for a SumCheck protocol. use super::SumCheckProver; -use crate::utils::multilinear_polynomial::fix_variables; -use crate::utils::virtual_polynomial::VirtualPolynomial; +use crate::utils::{ + lagrange_poly::compute_lagrange_interpolated_poly, multilinear_polynomial::fix_variables, + virtual_polynomial::VirtualPolynomial, +}; use ark_ec::CurveGroup; use ark_ff::Field; use ark_ff::{batch_inversion, PrimeField}; @@ -182,8 +184,9 @@ impl SumCheckProver for IOPProverState { .map(|x| Arc::new(x.clone())) .collect(); + let prover_poly = compute_lagrange_interpolated_poly::(&products_sum); Ok(IOPProverMessage { - evaluations: products_sum, + coeffs: prover_poly.coeffs, }) } } diff --git a/src/utils/espresso/sum_check/structs.rs b/src/utils/espresso/sum_check/structs.rs index 40dbeff..b34ed81 100644 --- a/src/utils/espresso/sum_check/structs.rs +++ b/src/utils/espresso/sum_check/structs.rs @@ -25,10 +25,10 @@ pub struct IOPProof { } /// A message from the prover to the verifier at a given round -/// is a list of evaluations. +/// is a list of coeffs. #[derive(Clone, Debug, Default, PartialEq, Eq, CanonicalSerialize)] pub struct IOPProverMessage { - pub(crate) evaluations: Vec, + pub(crate) coeffs: Vec, } /// Prover State of a PolyIOP. @@ -51,7 +51,6 @@ pub struct IOPProverState { pub struct IOPVerifierState { pub(crate) round: usize, pub(crate) num_vars: usize, - pub(crate) max_degree: usize, pub(crate) finished: bool, /// a list storing the univariate polynomial in evaluation form sent by the /// prover at each round diff --git a/src/utils/espresso/sum_check/verifier.rs b/src/utils/espresso/sum_check/verifier.rs index 21dca45..33989ac 100644 --- a/src/utils/espresso/sum_check/verifier.rs +++ b/src/utils/espresso/sum_check/verifier.rs @@ -16,8 +16,9 @@ use super::{ use crate::{transcript::Transcript, utils::virtual_polynomial::VPAuxInfo}; use ark_ec::CurveGroup; use ark_ff::PrimeField; +use ark_poly::Polynomial; +use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; use ark_std::{end_timer, start_timer}; - use espresso_subroutines::poly_iop::prelude::PolyIOPErrors; #[cfg(feature = "parallel")] @@ -35,7 +36,6 @@ impl SumCheckVerifier for IOPVerifierState { let res = Self { round: 1, num_vars: index_info.num_variables, - max_degree: index_info.max_degree, finished: false, polynomials_received: Vec::with_capacity(index_info.num_variables), challenges: Vec::with_capacity(index_info.num_variables), @@ -67,8 +67,7 @@ impl SumCheckVerifier for IOPVerifierState { // such checks to `check_and_generate_subclaim` after the last round. let challenge = transcript.get_challenge(); self.challenges.push(challenge); - self.polynomials_received - .push(prover_msg.evaluations.to_vec()); + self.polynomials_received.push(prover_msg.coeffs.to_vec()); if self.round == self.num_vars { // accept and close @@ -107,15 +106,10 @@ impl SumCheckVerifier for IOPVerifierState { .clone() .into_par_iter() .zip(self.challenges.clone().into_par_iter()) - .map(|(evaluations, challenge)| { - if evaluations.len() != self.max_degree + 1 { - return Err(PolyIOPErrors::InvalidVerifier(format!( - "incorrect number of evaluations: {} vs {}", - evaluations.len(), - self.max_degree + 1 - ))); - } - interpolate_uni_poly::(&evaluations, challenge) + .map(|(coeffs, challenge)| { + // Removed check on number of evaluations here since verifier receives polynomial in coeffs form + let prover_poly = DensePolynomial::from_coefficients_slice(&coeffs); + Ok(prover_poly.evaluate(&challenge)) }) .collect::, PolyIOPErrors>>()?; @@ -126,32 +120,30 @@ impl SumCheckVerifier for IOPVerifierState { .into_iter() .zip(self.challenges.clone().into_iter()) .map(|(evaluations, challenge)| { - if evaluations.len() != self.max_degree + 1 { - return Err(PolyIOPErrors::InvalidVerifier(format!( - "incorrect number of evaluations: {} vs {}", - evaluations.len(), - self.max_degree + 1 - ))); - } - interpolate_uni_poly::(&evaluations, challenge) + // Removed check on number of evaluations here since verifier receives polynomial in coeffs form + let prover_poly = DensePolynomial::from_coefficients_slice(&coeffs); + Ok(prover_poly.evaluate(&challenge)) }) .collect::, PolyIOPErrors>>()?; // insert the asserted_sum to the first position of the expected vector expected_vec.insert(0, *asserted_sum); - for (evaluations, &expected) in self + for (coeffs, &expected) in self .polynomials_received .iter() .zip(expected_vec.iter()) .take(self.num_vars) { - let eval_: C::ScalarField = evaluations[0] + evaluations[1]; + let poly = DensePolynomial::from_coefficients_slice(coeffs); + let eval_at_one: C::ScalarField = poly.iter().sum(); + let eval_at_zero: C::ScalarField = poly.coeffs[0]; + let eval = eval_at_one + eval_at_zero; - println!("evaluations: {:?}, expected: {:?}", eval_, expected); + println!("evaluations: {:?}, expected: {:?}", eval, expected); // the deferred check during the interactive phase: // 1. check if the received 'P(0) + P(1) = expected`. - if evaluations[0] + evaluations[1] != expected { + if eval != expected { return Err(PolyIOPErrors::InvalidProof( "Prover message is not consistent with the claim.".to_string(), )); @@ -306,46 +298,3 @@ fn u64_factorial(a: usize) -> u64 { } res } - -#[cfg(test)] -mod tests { - use super::interpolate_uni_poly; - use ark_pallas::Fr; - use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; - use ark_std::{vec::Vec, UniformRand}; - use espresso_subroutines::poly_iop::prelude::PolyIOPErrors; - - #[test] - fn test_interpolation() -> Result<(), PolyIOPErrors> { - let mut prng = ark_std::test_rng(); - - // test a polynomial with 20 known points, i.e., with degree 19 - let poly = DensePolynomial::::rand(20 - 1, &mut prng); - let evals = (0..20) - .map(|i| poly.evaluate(&Fr::from(i))) - .collect::>(); - let query = Fr::rand(&mut prng); - - assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)?); - - // test a polynomial with 33 known points, i.e., with degree 32 - let poly = DensePolynomial::::rand(33 - 1, &mut prng); - let evals = (0..33) - .map(|i| poly.evaluate(&Fr::from(i))) - .collect::>(); - let query = Fr::rand(&mut prng); - - assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)?); - - // test a polynomial with 64 known points, i.e., with degree 63 - let poly = DensePolynomial::::rand(64 - 1, &mut prng); - let evals = (0..64) - .map(|i| poly.evaluate(&Fr::from(i))) - .collect::>(); - let query = Fr::rand(&mut prng); - - assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)?); - - Ok(()) - } -} diff --git a/src/utils/lagrange_poly.rs b/src/utils/lagrange_poly.rs new file mode 100644 index 0000000..f3dfaee --- /dev/null +++ b/src/utils/lagrange_poly.rs @@ -0,0 +1,123 @@ +use ark_ff::PrimeField; +use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial}; + +/// Computes the lagrange interpolated polynomial from the given points `p_i` +pub fn compute_lagrange_interpolated_poly(p_i: &[F]) -> DensePolynomial { + // domain is 0..p_i.len(), to fit `interpolate_uni_poly` from hyperplonk + let domain: Vec = (0..p_i.len()).collect(); + + // compute l(x), common to every basis polynomial + let mut l_x = DensePolynomial::from_coefficients_vec(vec![F::ONE]); + for x_m in domain.clone() { + let prod_m = DensePolynomial::from_coefficients_vec(vec![-F::from(x_m as u64), F::ONE]); + l_x = &l_x * &prod_m; + } + + // compute each w_j - barycentric weights + let mut w_j_vector: Vec = vec![]; + for x_j in domain.clone() { + let mut w_j = F::ONE; + for x_m in domain.clone() { + if x_m != x_j { + let prod = (F::from(x_j as u64) - F::from(x_m as u64)) + .inverse() + .unwrap(); // an inverse always exists since x_j != x_m (!=0) + // hence, we call unwrap() here without checking the Option's content + w_j *= prod; + } + } + w_j_vector.push(w_j); + } + + // compute each polynomial within the sum L(x) + let mut lagrange_poly = DensePolynomial::from_coefficients_vec(vec![F::ZERO]); + for (j, w_j) in w_j_vector.iter().enumerate() { + let x_j = domain[j]; + let y_j = p_i[j]; + // we multiply by l(x) here, otherwise the below division will not work - deg(0)/deg(d) + let poly_numerator = &(&l_x * (*w_j)) * (y_j); + let poly_denominator = + DensePolynomial::from_coefficients_vec(vec![-F::from(x_j as u64), F::ONE]); + let poly = &poly_numerator / &poly_denominator; + lagrange_poly = &lagrange_poly + &poly; + } + + lagrange_poly +} + +#[cfg(test)] +mod tests { + + use crate::utils::espresso::sum_check::verifier::interpolate_uni_poly; + use crate::utils::lagrange_poly::compute_lagrange_interpolated_poly; + use ark_pallas::Fr; + use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial, Polynomial}; + use ark_std::{vec::Vec, UniformRand}; + use espresso_subroutines::poly_iop::prelude::PolyIOPErrors; + + #[test] + fn test_compute_lagrange_interpolated_poly() { + let mut prng = ark_std::test_rng(); + for degree in 1..30 { + let poly = DensePolynomial::::rand(degree, &mut prng); + // range (which is exclusive) is from 0 to degree + 1, since we need degree + 1 evaluations + let evals = (0..(degree + 1)) + .map(|i| poly.evaluate(&Fr::from(i as u64))) + .collect::>(); + let lagrange_poly = compute_lagrange_interpolated_poly(&evals); + for _ in 0..10 { + let query = Fr::rand(&mut prng); + let lagrange_eval = lagrange_poly.evaluate(&query); + let eval = poly.evaluate(&query); + assert_eq!(eval, lagrange_eval); + assert_eq!(lagrange_poly.degree(), poly.degree()); + } + } + } + + #[test] + fn test_interpolation() -> Result<(), PolyIOPErrors> { + let mut prng = ark_std::test_rng(); + + // test a polynomial with 20 known points, i.e., with degree 19 + let poly = DensePolynomial::::rand(20 - 1, &mut prng); + let evals = (0..20) + .map(|i| poly.evaluate(&Fr::from(i))) + .collect::>(); + let query = Fr::rand(&mut prng); + + assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)?); + assert_eq!( + compute_lagrange_interpolated_poly(&evals).evaluate(&query), + interpolate_uni_poly(&evals, query)? + ); + + // test a polynomial with 33 known points, i.e., with degree 32 + let poly = DensePolynomial::::rand(33 - 1, &mut prng); + let evals = (0..33) + .map(|i| poly.evaluate(&Fr::from(i))) + .collect::>(); + let query = Fr::rand(&mut prng); + + assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)?); + assert_eq!( + compute_lagrange_interpolated_poly(&evals).evaluate(&query), + interpolate_uni_poly(&evals, query)? + ); + + // test a polynomial with 64 known points, i.e., with degree 63 + let poly = DensePolynomial::::rand(64 - 1, &mut prng); + let evals = (0..64) + .map(|i| poly.evaluate(&Fr::from(i))) + .collect::>(); + let query = Fr::rand(&mut prng); + + assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)?); + assert_eq!( + compute_lagrange_interpolated_poly(&evals).evaluate(&query), + interpolate_uni_poly(&evals, query)? + ); + + Ok(()) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6e2ae1d..31e71dd 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,6 @@ pub mod bit; pub mod hypercube; +pub mod lagrange_poly; pub mod mle; pub mod vec;