Browse Source

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 <root@arnaucube.com>

* chore: clippy

* fix: remove `unwrap()` within `verify()`

Co-authored-by: arnaucube <root@arnaucube.com>

* chore: add comment on `unwrap()`

* refactor: move `compute_lagrange_interpolated_poly` tests to `lagrange_poly.rs`

---------

Co-authored-by: arnaucube <root@arnaucube.com>
main
Pierre 10 months ago
committed by GitHub
parent
commit
05f49918ac
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 440 additions and 95 deletions
  1. +1
    -0
      src/folding/circuits/mod.rs
  2. +273
    -0
      src/folding/circuits/sum_check.rs
  3. +5
    -6
      src/folding/hypernova/nimfs.rs
  4. +12
    -15
      src/utils/espresso/sum_check/mod.rs
  5. +6
    -3
      src/utils/espresso/sum_check/prover.rs
  6. +2
    -3
      src/utils/espresso/sum_check/structs.rs
  7. +17
    -68
      src/utils/espresso/sum_check/verifier.rs
  8. +123
    -0
      src/utils/lagrange_poly.rs
  9. +1
    -0
      src/utils/mod.rs

+ 1
- 0
src/folding/circuits/mod.rs

@ -3,6 +3,7 @@ use ark_ec::CurveGroup;
use ark_ff::Field; use ark_ff::Field;
pub mod nonnative; pub mod nonnative;
pub mod sum_check;
// CF represents the constraints field // CF represents the constraints field
pub type CF<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField; pub type CF<C> = <<C as CurveGroup>::BaseField as Field>::BasePrimeField;

+ 273
- 0
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<F: PrimeField> {
pub coeffs: Vec<FpVar<F>>,
}
impl<F: PrimeField> AllocVar<DensePolynomial<F>, F> for DensePolynomialVar<F> {
fn new_variable<T: Borrow<DensePolynomial<F>>>(
cs: impl Into<Namespace<F>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|c| {
let cs = cs.into();
let cp: &DensePolynomial<F> = c.borrow();
let mut coeffs_var = Vec::<FpVar<F>>::with_capacity(cp.coeffs.len());
for coeff in cp.coeffs.iter() {
let coeff_var = FpVar::<F>::new_variable(cs.clone(), || Ok(coeff), mode)?;
coeffs_var.push(coeff_var);
}
Ok(Self { coeffs: coeffs_var })
})
}
}
impl<F: PrimeField> DensePolynomialVar<F> {
pub fn eval_at_zero(&self) -> FpVar<F> {
self.coeffs[0].clone()
}
pub fn eval_at_one(&self) -> FpVar<F> {
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<F>) -> FpVar<F> {
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<C: CurveGroup> {
// We have to be generic over a CurveGroup because instantiating a IOPProofVar will call IOPSumCheck which requires a CurveGroup
pub proofs: Vec<DensePolynomialVar<C::ScalarField>>,
pub claim: FpVar<C::ScalarField>,
}
impl<C: CurveGroup> AllocVar<IOPProof<C::ScalarField>, C::ScalarField> for IOPProofVar<C>
where
<C as Group>::ScalarField: Absorb,
{
fn new_variable<T: Borrow<IOPProof<C::ScalarField>>>(
cs: impl Into<Namespace<C::ScalarField>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|c| {
let cs = cs.into();
let cp: &IOPProof<C::ScalarField> = c.borrow();
let claim = IOPSumCheck::<C, PoseidonTranscript<C>>::extract_sum(cp);
let claim = FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(claim), mode)?;
let mut proofs =
Vec::<DensePolynomialVar<C::ScalarField>>::with_capacity(cp.proofs.len());
for proof in cp.proofs.iter() {
let poly = DensePolynomial::from_coefficients_slice(&proof.coeffs);
let proof = DensePolynomialVar::<C::ScalarField>::new_variable(
cs.clone(),
|| Ok(poly),
mode,
)?;
proofs.push(proof);
}
Ok(Self { proofs, claim })
})
}
}
#[derive(Clone, Debug)]
pub struct VPAuxInfoVar<F: PrimeField> {
pub num_variables: FpVar<F>,
pub max_degree: FpVar<F>,
}
impl<F: PrimeField> AllocVar<VPAuxInfo<F>, F> for VPAuxInfoVar<F> {
fn new_variable<T: Borrow<VPAuxInfo<F>>>(
cs: impl Into<Namespace<F>>,
f: impl FnOnce() -> Result<T, SynthesisError>,
mode: AllocationMode,
) -> Result<Self, SynthesisError> {
f().and_then(|c| {
let cs = cs.into();
let cp: &VPAuxInfo<F> = c.borrow();
let num_variables = FpVar::<F>::new_variable(
cs.clone(),
|| Ok(F::from(cp.num_variables as u64)),
mode,
)?;
let max_degree =
FpVar::<F>::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<C: CurveGroup> {
_f: PhantomData<C>,
}
impl<C: CurveGroup> SumCheckVerifierGadget<C> {
#[allow(clippy::type_complexity)]
pub fn verify(
iop_proof_var: &IOPProofVar<C>,
poly_aux_info_var: &VPAuxInfoVar<C::ScalarField>,
transcript_var: &mut PoseidonTranscriptVar<C::ScalarField>,
) -> Result<(Vec<FpVar<C::ScalarField>>, Vec<FpVar<C::ScalarField>>), SynthesisError> {
let mut e_vars = vec![iop_proof_var.claim.clone()];
let mut r_vars: Vec<FpVar<C::ScalarField>> = 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<F> = (VirtualPolynomial<F>, PoseidonConfig<F>, IOPProof<F>);
/// 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<C: CurveGroup>(
num_vars: usize,
) -> TestSumCheckProof<C::ScalarField>
where
<C as ark_ec::Group>::ScalarField: Absorb,
{
let mut rng = ark_std::test_rng();
let poseidon_config: PoseidonConfig<C::ScalarField> =
poseidon_test_config::<C::ScalarField>();
let mut poseidon_transcript_prove = PoseidonTranscript::<C>::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<C::ScalarField> = IOPSumCheck::<C, PoseidonTranscript<C>>::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::<Fr>::new_ref();
let (virtual_poly, poseidon_config, sum_check) =
get_test_sumcheck_proof::<Projective>(num_vars);
let mut poseidon_var: PoseidonTranscriptVar<Fr> =
PoseidonTranscriptVar::new(cs.clone(), &poseidon_config);
let iop_proof_var =
IOPProofVar::<Projective>::new_witness(cs.clone(), || Ok(&sum_check)).unwrap();
let poly_aux_info_var =
VPAuxInfoVar::<Fr>::new_witness(cs.clone(), || Ok(virtual_poly.aux_info)).unwrap();
let res = SumCheckVerifierGadget::<Projective>::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::<Projective, PoseidonTranscript<Projective>>::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());
}
}
}

+ 5
- 6
src/folding/hypernova/nimfs.rs

@ -1,6 +1,8 @@
use ark_crypto_primitives::sponge::Absorb; use ark_crypto_primitives::sponge::Absorb;
use ark_ec::{CurveGroup, Group}; use ark_ec::{CurveGroup, Group};
use ark_ff::{Field, PrimeField}; use ark_ff::{Field, PrimeField};
use ark_poly::univariate::DensePolynomial;
use ark_poly::{DenseUVPolynomial, Polynomial};
use ark_std::{One, Zero}; use ark_std::{One, Zero};
use super::cccs::{Witness, CCCS}; use super::cccs::{Witness, CCCS};
@ -10,7 +12,6 @@ use crate::ccs::CCS;
use crate::transcript::Transcript; use crate::transcript::Transcript;
use crate::utils::hypercube::BooleanHypercube; use crate::utils::hypercube::BooleanHypercube;
use crate::utils::sum_check::structs::IOPProof as SumCheckProof; 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::sum_check::{IOPSumCheck, SumCheck};
use crate::utils::virtual_polynomial::VPAuxInfo; use crate::utils::virtual_polynomial::VPAuxInfo;
use crate::Error; use crate::Error;
@ -342,11 +343,9 @@ where
// Sanity check: we can also compute g(r_x') from the proof last evaluation value, and // Sanity check: we can also compute g(r_x') from the proof last evaluation value, and
// should be equal to the previously obtained values. // should be equal to the previously obtained values.
let g_on_rxprime_from_sumcheck_last_eval = interpolate_uni_poly::<C::ScalarField>(
&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 { if g_on_rxprime_from_sumcheck_last_eval != c {
return Err(Error::NotEqual); return Err(Error::NotEqual);
} }

+ 12
- 15
src/utils/espresso/sum_check/mod.rs

@ -13,14 +13,16 @@ use crate::{
transcript::Transcript, transcript::Transcript,
utils::virtual_polynomial::{VPAuxInfo, VirtualPolynomial}, utils::virtual_polynomial::{VPAuxInfo, VirtualPolynomial},
}; };
use ark_ec::{CurveGroup, Group};
use ark_ec::CurveGroup;
use ark_ff::PrimeField; 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 ark_std::{end_timer, start_timer};
use std::{fmt::Debug, marker::PhantomData, sync::Arc}; use std::{fmt::Debug, marker::PhantomData, sync::Arc};
use crate::utils::sum_check::structs::IOPProverMessage; use crate::utils::sum_check::structs::IOPProverMessage;
use crate::utils::sum_check::structs::IOPVerifierState; use crate::utils::sum_check::structs::IOPVerifierState;
use ark_ff::Field;
use espresso_subroutines::poly_iop::prelude::PolyIOPErrors; use espresso_subroutines::poly_iop::prelude::PolyIOPErrors;
use structs::{IOPProof, IOPProverState}; use structs::{IOPProof, IOPProverState};
@ -143,7 +145,8 @@ impl> SumCheck for IOPSumCheck {
fn extract_sum(proof: &Self::SumCheckProof) -> C::ScalarField { fn extract_sum(proof: &Self::SumCheckProof) -> C::ScalarField {
let start = start_timer!(|| "extract sum"); 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); end_timer!(start);
res res
} }
@ -152,12 +155,8 @@ impl> SumCheck for IOPSumCheck {
poly: &VirtualPolynomial<C::ScalarField>, poly: &VirtualPolynomial<C::ScalarField>,
transcript: &mut impl Transcript<C>, transcript: &mut impl Transcript<C>,
) -> Result<IOPProof<C::ScalarField>, PolyIOPErrors> { ) -> Result<IOPProof<C::ScalarField>, PolyIOPErrors> {
transcript.absorb(&<C as Group>::ScalarField::from(
poly.aux_info.num_variables as u64,
));
transcript.absorb(&<C as Group>::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<C> = IOPProverState::prover_init(poly)?; let mut prover_state: IOPProverState<C> = IOPProverState::prover_init(poly)?;
let mut challenge: Option<C::ScalarField> = None; let mut challenge: Option<C::ScalarField> = None;
let mut prover_msgs: Vec<IOPProverMessage<C::ScalarField>> = let mut prover_msgs: Vec<IOPProverMessage<C::ScalarField>> =
@ -165,7 +164,7 @@ impl> SumCheck for IOPSumCheck {
for _ in 0..poly.aux_info.num_variables { for _ in 0..poly.aux_info.num_variables {
let prover_msg: IOPProverMessage<C::ScalarField> = let prover_msg: IOPProverMessage<C::ScalarField> =
IOPProverState::prove_round_and_update_state(&mut prover_state, &challenge)?; 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); prover_msgs.push(prover_msg);
challenge = Some(transcript.get_challenge()); challenge = Some(transcript.get_challenge());
} }
@ -184,14 +183,12 @@ impl> SumCheck for IOPSumCheck {
aux_info: &VPAuxInfo<C::ScalarField>, aux_info: &VPAuxInfo<C::ScalarField>,
transcript: &mut impl Transcript<C>, transcript: &mut impl Transcript<C>,
) -> Result<SumCheckSubClaim<C::ScalarField>, PolyIOPErrors> { ) -> Result<SumCheckSubClaim<C::ScalarField>, PolyIOPErrors> {
transcript.absorb(&<C as Group>::ScalarField::from(
aux_info.num_variables as u64,
));
transcript.absorb(&<C as Group>::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); let mut verifier_state = IOPVerifierState::verifier_init(aux_info);
for i in 0..aux_info.num_variables { for i in 0..aux_info.num_variables {
let prover_msg = proof.proofs.get(i).expect("proof is incomplete"); 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( IOPVerifierState::verify_round_and_update_state(
&mut verifier_state, &mut verifier_state,
prover_msg, prover_msg,

+ 6
- 3
src/utils/espresso/sum_check/prover.rs

@ -10,8 +10,10 @@
//! Prover subroutines for a SumCheck protocol. //! Prover subroutines for a SumCheck protocol.
use super::SumCheckProver; 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_ec::CurveGroup;
use ark_ff::Field; use ark_ff::Field;
use ark_ff::{batch_inversion, PrimeField}; use ark_ff::{batch_inversion, PrimeField};
@ -182,8 +184,9 @@ impl SumCheckProver for IOPProverState {
.map(|x| Arc::new(x.clone())) .map(|x| Arc::new(x.clone()))
.collect(); .collect();
let prover_poly = compute_lagrange_interpolated_poly::<C::ScalarField>(&products_sum);
Ok(IOPProverMessage { Ok(IOPProverMessage {
evaluations: products_sum,
coeffs: prover_poly.coeffs,
}) })
} }
} }

+ 2
- 3
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 /// 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)] #[derive(Clone, Debug, Default, PartialEq, Eq, CanonicalSerialize)]
pub struct IOPProverMessage<F: PrimeField> { pub struct IOPProverMessage<F: PrimeField> {
pub(crate) evaluations: Vec<F>,
pub(crate) coeffs: Vec<F>,
} }
/// Prover State of a PolyIOP. /// Prover State of a PolyIOP.
@ -51,7 +51,6 @@ pub struct IOPProverState {
pub struct IOPVerifierState<C: CurveGroup> { pub struct IOPVerifierState<C: CurveGroup> {
pub(crate) round: usize, pub(crate) round: usize,
pub(crate) num_vars: usize, pub(crate) num_vars: usize,
pub(crate) max_degree: usize,
pub(crate) finished: bool, pub(crate) finished: bool,
/// a list storing the univariate polynomial in evaluation form sent by the /// a list storing the univariate polynomial in evaluation form sent by the
/// prover at each round /// prover at each round

+ 17
- 68
src/utils/espresso/sum_check/verifier.rs

@ -16,8 +16,9 @@ use super::{
use crate::{transcript::Transcript, utils::virtual_polynomial::VPAuxInfo}; use crate::{transcript::Transcript, utils::virtual_polynomial::VPAuxInfo};
use ark_ec::CurveGroup; use ark_ec::CurveGroup;
use ark_ff::PrimeField; use ark_ff::PrimeField;
use ark_poly::Polynomial;
use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial};
use ark_std::{end_timer, start_timer}; use ark_std::{end_timer, start_timer};
use espresso_subroutines::poly_iop::prelude::PolyIOPErrors; use espresso_subroutines::poly_iop::prelude::PolyIOPErrors;
#[cfg(feature = "parallel")] #[cfg(feature = "parallel")]
@ -35,7 +36,6 @@ impl SumCheckVerifier for IOPVerifierState {
let res = Self { let res = Self {
round: 1, round: 1,
num_vars: index_info.num_variables, num_vars: index_info.num_variables,
max_degree: index_info.max_degree,
finished: false, finished: false,
polynomials_received: Vec::with_capacity(index_info.num_variables), polynomials_received: Vec::with_capacity(index_info.num_variables),
challenges: 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. // such checks to `check_and_generate_subclaim` after the last round.
let challenge = transcript.get_challenge(); let challenge = transcript.get_challenge();
self.challenges.push(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 { if self.round == self.num_vars {
// accept and close // accept and close
@ -107,15 +106,10 @@ impl SumCheckVerifier for IOPVerifierState {
.clone() .clone()
.into_par_iter() .into_par_iter()
.zip(self.challenges.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::<C::ScalarField>(&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::<Result<Vec<_>, PolyIOPErrors>>()?; .collect::<Result<Vec<_>, PolyIOPErrors>>()?;
@ -126,32 +120,30 @@ impl SumCheckVerifier for IOPVerifierState {
.into_iter() .into_iter()
.zip(self.challenges.clone().into_iter()) .zip(self.challenges.clone().into_iter())
.map(|(evaluations, challenge)| { .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::<F>(&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::<Result<Vec<_>, PolyIOPErrors>>()?; .collect::<Result<Vec<_>, PolyIOPErrors>>()?;
// insert the asserted_sum to the first position of the expected vector // insert the asserted_sum to the first position of the expected vector
expected_vec.insert(0, *asserted_sum); expected_vec.insert(0, *asserted_sum);
for (evaluations, &expected) in self
for (coeffs, &expected) in self
.polynomials_received .polynomials_received
.iter() .iter()
.zip(expected_vec.iter()) .zip(expected_vec.iter())
.take(self.num_vars) .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: // the deferred check during the interactive phase:
// 1. check if the received 'P(0) + P(1) = expected`. // 1. check if the received 'P(0) + P(1) = expected`.
if evaluations[0] + evaluations[1] != expected {
if eval != expected {
return Err(PolyIOPErrors::InvalidProof( return Err(PolyIOPErrors::InvalidProof(
"Prover message is not consistent with the claim.".to_string(), "Prover message is not consistent with the claim.".to_string(),
)); ));
@ -306,46 +298,3 @@ fn u64_factorial(a: usize) -> u64 {
} }
res 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::<Fr>::rand(20 - 1, &mut prng);
let evals = (0..20)
.map(|i| poly.evaluate(&Fr::from(i)))
.collect::<Vec<Fr>>();
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::<Fr>::rand(33 - 1, &mut prng);
let evals = (0..33)
.map(|i| poly.evaluate(&Fr::from(i)))
.collect::<Vec<Fr>>();
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::<Fr>::rand(64 - 1, &mut prng);
let evals = (0..64)
.map(|i| poly.evaluate(&Fr::from(i)))
.collect::<Vec<Fr>>();
let query = Fr::rand(&mut prng);
assert_eq!(poly.evaluate(&query), interpolate_uni_poly(&evals, query)?);
Ok(())
}
}

+ 123
- 0
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<F: PrimeField>(p_i: &[F]) -> DensePolynomial<F> {
// domain is 0..p_i.len(), to fit `interpolate_uni_poly` from hyperplonk
let domain: Vec<usize> = (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<F> = 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::<Fr>::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::<Vec<Fr>>();
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::<Fr>::rand(20 - 1, &mut prng);
let evals = (0..20)
.map(|i| poly.evaluate(&Fr::from(i)))
.collect::<Vec<Fr>>();
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::<Fr>::rand(33 - 1, &mut prng);
let evals = (0..33)
.map(|i| poly.evaluate(&Fr::from(i)))
.collect::<Vec<Fr>>();
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::<Fr>::rand(64 - 1, &mut prng);
let evals = (0..64)
.map(|i| poly.evaluate(&Fr::from(i)))
.collect::<Vec<Fr>>();
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(())
}
}

+ 1
- 0
src/utils/mod.rs

@ -1,5 +1,6 @@
pub mod bit; pub mod bit;
pub mod hypercube; pub mod hypercube;
pub mod lagrange_poly;
pub mod mle; pub mod mle;
pub mod vec; pub mod vec;

Loading…
Cancel
Save