arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/folding/circuits/sum_check.rs
folding-schemes/src/folding/circuits/sum_check.rsRaw
  1use crate::utils::espresso::sum_check::SumCheck;
  2use crate::utils::virtual_polynomial::VPAuxInfo;
  3use crate::{
  4    transcript::{
  5        poseidon::{PoseidonTranscript, PoseidonTranscriptVar},
  6        TranscriptVar,
  7    },
  8    utils::sum_check::{structs::IOPProof, IOPSumCheck},
  9};
 10use ark_crypto_primitives::sponge::Absorb;
 11use ark_ec::{CurveGroup, Group};
 12/// Heavily inspired from testudo: https://github.com/cryptonetlab/testudo/tree/master
 13/// Some changes:
 14/// - Typings to better stick to ark_poly's API
 15/// - Uses `folding-schemes`' own `TranscriptVar` trait and `PoseidonTranscriptVar` struct
 16/// - API made closer to gadgets found in `folding-schemes`
 17use ark_ff::PrimeField;
 18use ark_poly::{univariate::DensePolynomial, DenseUVPolynomial};
 19use ark_r1cs_std::{
 20    alloc::{AllocVar, AllocationMode},
 21    eq::EqGadget,
 22    fields::fp::FpVar,
 23};
 24use ark_relations::r1cs::{Namespace, SynthesisError};
 25use std::{borrow::Borrow, marker::PhantomData};
 26
 27#[derive(Clone, Debug)]
 28pub struct DensePolynomialVar<F: PrimeField> {
 29    pub coeffs: Vec<FpVar<F>>,
 30}
 31
 32impl<F: PrimeField> AllocVar<DensePolynomial<F>, F> for DensePolynomialVar<F> {
 33    fn new_variable<T: Borrow<DensePolynomial<F>>>(
 34        cs: impl Into<Namespace<F>>,
 35        f: impl FnOnce() -> Result<T, SynthesisError>,
 36        mode: AllocationMode,
 37    ) -> Result<Self, SynthesisError> {
 38        f().and_then(|c| {
 39            let cs = cs.into();
 40            let cp: &DensePolynomial<F> = c.borrow();
 41            let mut coeffs_var = Vec::<FpVar<F>>::with_capacity(cp.coeffs.len());
 42            for coeff in cp.coeffs.iter() {
 43                let coeff_var = FpVar::<F>::new_variable(cs.clone(), || Ok(coeff), mode)?;
 44                coeffs_var.push(coeff_var);
 45            }
 46            Ok(Self { coeffs: coeffs_var })
 47        })
 48    }
 49}
 50
 51impl<F: PrimeField> DensePolynomialVar<F> {
 52    pub fn eval_at_zero(&self) -> FpVar<F> {
 53        self.coeffs[0].clone()
 54    }
 55
 56    pub fn eval_at_one(&self) -> FpVar<F> {
 57        let mut res = self.coeffs[0].clone();
 58        for i in 1..self.coeffs.len() {
 59            res = &res + &self.coeffs[i];
 60        }
 61        res
 62    }
 63
 64    pub fn evaluate(&self, r: &FpVar<F>) -> FpVar<F> {
 65        let mut eval = self.coeffs[0].clone();
 66        let mut power = r.clone();
 67
 68        for i in 1..self.coeffs.len() {
 69            eval += &power * &self.coeffs[i];
 70            power *= r;
 71        }
 72        eval
 73    }
 74}
 75
 76#[derive(Clone, Debug)]
 77pub struct IOPProofVar<C: CurveGroup> {
 78    // We have to be generic over a CurveGroup because instantiating a IOPProofVar will call IOPSumCheck which requires a CurveGroup
 79    pub proofs: Vec<DensePolynomialVar<C::ScalarField>>,
 80    pub claim: FpVar<C::ScalarField>,
 81}
 82
 83impl<C: CurveGroup> AllocVar<IOPProof<C::ScalarField>, C::ScalarField> for IOPProofVar<C>
 84where
 85    <C as Group>::ScalarField: Absorb,
 86{
 87    fn new_variable<T: Borrow<IOPProof<C::ScalarField>>>(
 88        cs: impl Into<Namespace<C::ScalarField>>,
 89        f: impl FnOnce() -> Result<T, SynthesisError>,
 90        mode: AllocationMode,
 91    ) -> Result<Self, SynthesisError> {
 92        f().and_then(|c| {
 93            let cs = cs.into();
 94            let cp: &IOPProof<C::ScalarField> = c.borrow();
 95            let claim = IOPSumCheck::<C, PoseidonTranscript<C>>::extract_sum(cp);
 96            let claim = FpVar::<C::ScalarField>::new_variable(cs.clone(), || Ok(claim), mode)?;
 97            let mut proofs =
 98                Vec::<DensePolynomialVar<C::ScalarField>>::with_capacity(cp.proofs.len());
 99            for proof in cp.proofs.iter() {
100                let poly = DensePolynomial::from_coefficients_slice(&proof.coeffs);
101                let proof = DensePolynomialVar::<C::ScalarField>::new_variable(
102                    cs.clone(),
103                    || Ok(poly),
104                    mode,
105                )?;
106                proofs.push(proof);
107            }
108            Ok(Self { proofs, claim })
109        })
110    }
111}
112
113#[derive(Clone, Debug)]
114pub struct VPAuxInfoVar<F: PrimeField> {
115    pub num_variables: FpVar<F>,
116    pub max_degree: FpVar<F>,
117}
118
119impl<F: PrimeField> AllocVar<VPAuxInfo<F>, F> for VPAuxInfoVar<F> {
120    fn new_variable<T: Borrow<VPAuxInfo<F>>>(
121        cs: impl Into<Namespace<F>>,
122        f: impl FnOnce() -> Result<T, SynthesisError>,
123        mode: AllocationMode,
124    ) -> Result<Self, SynthesisError> {
125        f().and_then(|c| {
126            let cs = cs.into();
127            let cp: &VPAuxInfo<F> = c.borrow();
128            let num_variables = FpVar::<F>::new_variable(
129                cs.clone(),
130                || Ok(F::from(cp.num_variables as u64)),
131                mode,
132            )?;
133            let max_degree =
134                FpVar::<F>::new_variable(cs.clone(), || Ok(F::from(cp.max_degree as u64)), mode)?;
135            Ok(Self {
136                num_variables,
137                max_degree,
138            })
139        })
140    }
141}
142
143#[derive(Debug, Clone)]
144pub struct SumCheckVerifierGadget<C: CurveGroup> {
145    _f: PhantomData<C>,
146}
147
148impl<C: CurveGroup> SumCheckVerifierGadget<C> {
149    #[allow(clippy::type_complexity)]
150    pub fn verify(
151        iop_proof_var: &IOPProofVar<C>,
152        poly_aux_info_var: &VPAuxInfoVar<C::ScalarField>,
153        transcript_var: &mut PoseidonTranscriptVar<C::ScalarField>,
154    ) -> Result<(Vec<FpVar<C::ScalarField>>, Vec<FpVar<C::ScalarField>>), SynthesisError> {
155        let mut e_vars = vec![iop_proof_var.claim.clone()];
156        let mut r_vars: Vec<FpVar<C::ScalarField>> = Vec::new();
157        transcript_var.absorb(poly_aux_info_var.num_variables.clone())?;
158        transcript_var.absorb(poly_aux_info_var.max_degree.clone())?;
159
160        for poly_var in iop_proof_var.proofs.iter() {
161            let res = poly_var.eval_at_one() + poly_var.eval_at_zero();
162            let e_var = e_vars.last().ok_or(SynthesisError::Unsatisfiable)?;
163            res.enforce_equal(e_var)?;
164            transcript_var.absorb_vec(&poly_var.coeffs)?;
165            let r_i_var = transcript_var.get_challenge()?;
166            e_vars.push(poly_var.evaluate(&r_i_var));
167            r_vars.push(r_i_var);
168        }
169
170        Ok((e_vars, r_vars))
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use crate::{
177        folding::circuits::sum_check::{IOPProofVar, VPAuxInfoVar},
178        transcript::{
179            poseidon::{poseidon_test_config, PoseidonTranscript, PoseidonTranscriptVar},
180            Transcript, TranscriptVar,
181        },
182        utils::{
183            sum_check::{structs::IOPProof, IOPSumCheck, SumCheck},
184            virtual_polynomial::VirtualPolynomial,
185        },
186    };
187    use ark_crypto_primitives::sponge::{poseidon::PoseidonConfig, Absorb};
188    use ark_ec::CurveGroup;
189    use ark_ff::Field;
190    use ark_pallas::{Fr, Projective};
191    use ark_poly::{
192        univariate::DensePolynomial, DenseMultilinearExtension, DenseUVPolynomial,
193        MultilinearExtension, Polynomial,
194    };
195    use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
196    use ark_relations::r1cs::ConstraintSystem;
197    use std::sync::Arc;
198
199    use super::SumCheckVerifierGadget;
200
201    pub type TestSumCheckProof<F> = (VirtualPolynomial<F>, PoseidonConfig<F>, IOPProof<F>);
202
203    /// Primarily used for testing the sumcheck gadget
204    /// Returns a random virtual polynomial, the poseidon config used and the associated sumcheck proof
205    pub fn get_test_sumcheck_proof<C: CurveGroup>(
206        num_vars: usize,
207    ) -> TestSumCheckProof<C::ScalarField>
208    where
209        <C as ark_ec::Group>::ScalarField: Absorb,
210    {
211        let mut rng = ark_std::test_rng();
212        let poseidon_config: PoseidonConfig<C::ScalarField> =
213            poseidon_test_config::<C::ScalarField>();
214        let mut poseidon_transcript_prove = PoseidonTranscript::<C>::new(&poseidon_config);
215        let poly_mle = DenseMultilinearExtension::rand(num_vars, &mut rng);
216        let virtual_poly =
217            VirtualPolynomial::new_from_mle(&Arc::new(poly_mle), C::ScalarField::ONE);
218        let sum_check: IOPProof<C::ScalarField> = IOPSumCheck::<C, PoseidonTranscript<C>>::prove(
219            &virtual_poly,
220            &mut poseidon_transcript_prove,
221        )
222        .unwrap();
223        (virtual_poly, poseidon_config, sum_check)
224    }
225
226    #[test]
227    fn test_sum_check_circuit() {
228        for num_vars in 1..15 {
229            let cs = ConstraintSystem::<Fr>::new_ref();
230            let (virtual_poly, poseidon_config, sum_check) =
231                get_test_sumcheck_proof::<Projective>(num_vars);
232            let mut poseidon_var: PoseidonTranscriptVar<Fr> =
233                PoseidonTranscriptVar::new(cs.clone(), &poseidon_config);
234            let iop_proof_var =
235                IOPProofVar::<Projective>::new_witness(cs.clone(), || Ok(&sum_check)).unwrap();
236            let poly_aux_info_var =
237                VPAuxInfoVar::<Fr>::new_witness(cs.clone(), || Ok(virtual_poly.aux_info)).unwrap();
238            let res = SumCheckVerifierGadget::<Projective>::verify(
239                &iop_proof_var,
240                &poly_aux_info_var,
241                &mut poseidon_var,
242            );
243
244            assert!(res.is_ok());
245            let (circuit_evals, r_challenges) = res.unwrap();
246
247            // 1. assert claim from circuit is equal to the one from the sum-check
248            let claim: Fr =
249                IOPSumCheck::<Projective, PoseidonTranscript<Projective>>::extract_sum(&sum_check);
250            assert_eq!(circuit_evals[0].value().unwrap(), claim);
251
252            // 2. assert that all in-circuit evaluations are equal to the ones from the sum-check
253            for ((proof, point), circuit_eval) in sum_check
254                .proofs
255                .iter()
256                .zip(sum_check.point.iter())
257                .zip(circuit_evals.iter().skip(1))
258            // we skip the first one since it's the above checked claim
259            {
260                let poly = DensePolynomial::from_coefficients_slice(&proof.coeffs);
261                let eval = poly.evaluate(point);
262                assert_eq!(eval, circuit_eval.value().unwrap());
263            }
264
265            // 3. assert that all challenges are equal to the ones from the sum-check
266            for (point, r_challenge) in sum_check.point.iter().zip(r_challenges.iter()) {
267                assert_eq!(*point, r_challenge.value().unwrap());
268            }
269
270            assert!(cs.is_satisfied().unwrap());
271        }
272    }
273}