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