arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/frontend/circom/mod.rs
folding-schemes/src/frontend/circom/mod.rsRaw
  1use crate::frontend::FCircuit;
  2use crate::frontend::FpVar::Var;
  3use crate::Error;
  4use ark_circom::circom::{CircomCircuit, R1CS as CircomR1CS};
  5use ark_ff::PrimeField;
  6use ark_r1cs_std::alloc::AllocVar;
  7use ark_r1cs_std::fields::fp::FpVar;
  8use ark_r1cs_std::R1CSVar;
  9use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
 10use ark_std::fmt::Debug;
 11use num_bigint::BigInt;
 12use std::path::PathBuf;
 13use std::rc::Rc;
 14use std::{fmt, usize};
 15
 16pub mod utils;
 17use utils::CircomWrapper;
 18
 19type ClosurePointer<F> = Rc<dyn Fn(usize, Vec<F>, Vec<F>) -> Result<Vec<F>, Error>>;
 20
 21#[derive(Clone)]
 22struct CustomStepNative<F: PrimeField> {
 23    func: ClosurePointer<F>,
 24}
 25
 26impl<F: PrimeField> fmt::Debug for CustomStepNative<F> {
 27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 28        write!(
 29            f,
 30            "Function pointer: {:?}",
 31            std::any::type_name::<fn(usize, Vec<F>, Vec<F>) -> Result<Vec<F>, Error>>()
 32        )
 33    }
 34}
 35
 36/// Define CircomFCircuit
 37#[derive(Clone, Debug)]
 38pub struct CircomFCircuit<F: PrimeField> {
 39    circom_wrapper: CircomWrapper<F>,
 40    pub state_len: usize,
 41    pub external_inputs_len: usize,
 42    r1cs: CircomR1CS<F>,
 43    custom_step_native_code: Option<CustomStepNative<F>>,
 44}
 45
 46impl<F: PrimeField> CircomFCircuit<F> {
 47    pub fn set_custom_step_native(&mut self, func: ClosurePointer<F>) {
 48        self.custom_step_native_code = Some(CustomStepNative::<F> { func });
 49    }
 50
 51    pub fn execute_custom_step_native(
 52        &self,
 53        _i: usize,
 54        z_i: Vec<F>,
 55        external_inputs: Vec<F>,
 56    ) -> Result<Vec<F>, Error> {
 57        if let Some(code) = &self.custom_step_native_code {
 58            (code.func)(_i, z_i, external_inputs)
 59        } else {
 60            #[cfg(test)]
 61            assert_eq!(z_i.len(), self.state_len());
 62            #[cfg(test)]
 63            assert_eq!(external_inputs.len(), self.external_inputs_len());
 64
 65            let inputs_bi = z_i
 66                .iter()
 67                .map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val))
 68                .collect::<Vec<BigInt>>();
 69            let mut inputs_map = vec![("ivc_input".to_string(), inputs_bi)];
 70
 71            if self.external_inputs_len() > 0 {
 72                let external_inputs_bi = external_inputs
 73                    .iter()
 74                    .map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val))
 75                    .collect::<Vec<BigInt>>();
 76                inputs_map.push(("external_inputs".to_string(), external_inputs_bi));
 77            }
 78
 79            // Computes witness
 80            let witness = self
 81                .circom_wrapper
 82                .extract_witness(&inputs_map)
 83                .map_err(|e| {
 84                    Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e))
 85                })?;
 86
 87            // Extracts the z_i1(next state) from the witness vector.
 88            let z_i1 = witness[1..1 + self.state_len()].to_vec();
 89            Ok(z_i1)
 90        }
 91    }
 92}
 93
 94impl<F: PrimeField> FCircuit<F> for CircomFCircuit<F> {
 95    /// (r1cs_path, wasm_path, state_len, external_inputs_len)
 96    type Params = (PathBuf, PathBuf, usize, usize);
 97
 98    fn new(params: Self::Params) -> Result<Self, Error> {
 99        let (r1cs_path, wasm_path, state_len, external_inputs_len) = params;
100        let circom_wrapper = CircomWrapper::new(r1cs_path, wasm_path);
101
102        let r1cs = circom_wrapper.extract_r1cs()?;
103        Ok(Self {
104            circom_wrapper,
105            state_len,
106            external_inputs_len,
107            r1cs,
108            custom_step_native_code: None,
109        })
110    }
111
112    fn state_len(&self) -> usize {
113        self.state_len
114    }
115    fn external_inputs_len(&self) -> usize {
116        self.external_inputs_len
117    }
118
119    fn step_native(
120        &self,
121        _i: usize,
122        z_i: Vec<F>,
123        external_inputs: Vec<F>,
124    ) -> Result<Vec<F>, Error> {
125        self.execute_custom_step_native(_i, z_i, external_inputs)
126    }
127
128    fn generate_step_constraints(
129        &self,
130        cs: ConstraintSystemRef<F>,
131        _i: usize,
132        z_i: Vec<FpVar<F>>,
133        external_inputs: Vec<FpVar<F>>,
134    ) -> Result<Vec<FpVar<F>>, SynthesisError> {
135        #[cfg(test)]
136        assert_eq!(z_i.len(), self.state_len());
137        #[cfg(test)]
138        assert_eq!(external_inputs.len(), self.external_inputs_len());
139
140        let input_values = self.fpvars_to_bigints(&z_i)?;
141        let mut inputs_map = vec![("ivc_input".to_string(), input_values)];
142
143        if self.external_inputs_len() > 0 {
144            let external_inputs_bi = self.fpvars_to_bigints(&external_inputs)?;
145            inputs_map.push(("external_inputs".to_string(), external_inputs_bi));
146        }
147
148        let witness = self
149            .circom_wrapper
150            .extract_witness(&inputs_map)
151            .map_err(|_| SynthesisError::AssignmentMissing)?;
152
153        // Since public inputs are already allocated variables, we will tell `circom-compat` to not re-allocate those
154        let mut already_allocated_public_inputs = vec![];
155        for var in z_i.iter() {
156            match var {
157                Var(var) => already_allocated_public_inputs.push(var.variable),
158                _ => return Err(SynthesisError::Unsatisfiable), // allocated z_i should be Var
159            }
160        }
161
162        // Initializes the CircomCircuit.
163        let circom_circuit = CircomCircuit {
164            r1cs: self.r1cs.clone(),
165            witness: Some(witness.clone()),
166            public_inputs_indexes: already_allocated_public_inputs,
167            allocate_inputs_as_witnesses: true,
168        };
169
170        // Generates the constraints for the circom_circuit.
171        circom_circuit.generate_constraints(cs.clone())?;
172
173        // TODO: https://github.com/privacy-scaling-explorations/sonobe/issues/104
174        // We disable checking constraints for now
175        // Checks for constraint satisfaction.
176        // if !cs.is_satisfied().unwrap() {
177        //     return Err(SynthesisError::Unsatisfiable);
178        // }
179
180        // Extracts the z_i1(next state) from the witness vector.
181        let z_i1: Vec<FpVar<F>> = Vec::<FpVar<F>>::new_witness(cs.clone(), || {
182            Ok(witness[1..1 + self.state_len()].to_vec())
183        })?;
184
185        Ok(z_i1)
186    }
187}
188
189impl<F: PrimeField> CircomFCircuit<F> {
190    fn fpvars_to_bigints(&self, fpVars: &[FpVar<F>]) -> Result<Vec<BigInt>, SynthesisError> {
191        let mut input_values = Vec::new();
192        // converts each FpVar to PrimeField value, then to num_bigint::BigInt.
193        for fp_var in fpVars.iter() {
194            // extracts the PrimeField value from FpVar.
195            let primefield_value = fp_var.value()?;
196            // converts the PrimeField value to num_bigint::BigInt.
197            let num_bigint_value = self
198                .circom_wrapper
199                .ark_primefield_to_num_bigint(primefield_value);
200            input_values.push(num_bigint_value);
201        }
202        Ok(input_values)
203    }
204}
205
206#[cfg(test)]
207pub mod tests {
208    use super::*;
209    use ark_bn254::Fr;
210    use ark_r1cs_std::alloc::AllocVar;
211    use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
212
213    // Tests the step_native function of CircomFCircuit.
214    #[test]
215    fn test_circom_step_native() {
216        let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
217        let wasm_path =
218            PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
219
220        let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
221
222        let z_i = vec![Fr::from(3u32)];
223        let z_i1 = circom_fcircuit.step_native(1, z_i, vec![]).unwrap();
224        assert_eq!(z_i1, vec![Fr::from(35u32)]);
225    }
226
227    // Tests the generate_step_constraints function of CircomFCircuit.
228    #[test]
229    fn test_circom_step_constraints() {
230        let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
231        let wasm_path =
232            PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
233
234        let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
235
236        let cs = ConstraintSystem::<Fr>::new_ref();
237
238        let z_i = vec![Fr::from(3u32)];
239
240        let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
241        let z_i1_var = circom_fcircuit
242            .generate_step_constraints(cs.clone(), 1, z_i_var, vec![])
243            .unwrap();
244        assert_eq!(z_i1_var.value().unwrap(), vec![Fr::from(35u32)]);
245    }
246
247    // Tests the WrapperCircuit with CircomFCircuit.
248    #[test]
249    fn test_wrapper_circomtofcircuit() {
250        let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
251        let wasm_path =
252            PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
253
254        let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
255
256        // Allocates z_i1 by using step_native function.
257        let z_i = vec![Fr::from(3_u32)];
258        let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
259            FC: circom_fcircuit.clone(),
260            z_i: Some(z_i.clone()),
261            z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
262        };
263
264        let cs = ConstraintSystem::<Fr>::new_ref();
265
266        wrapper_circuit.generate_constraints(cs.clone()).unwrap();
267        assert!(
268            cs.is_satisfied().unwrap(),
269            "Constraint system is not satisfied"
270        );
271    }
272
273    #[test]
274    fn test_circom_external_inputs() {
275        let r1cs_path =
276            PathBuf::from("./src/frontend/circom/test_folder/with_external_inputs.r1cs");
277        let wasm_path = PathBuf::from(
278            "./src/frontend/circom/test_folder/with_external_inputs_js/with_external_inputs.wasm",
279        );
280        let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 2)).unwrap(); // state_len:1, external_inputs_len:2
281        let cs = ConstraintSystem::<Fr>::new_ref();
282        let z_i = vec![Fr::from(3u32)];
283        let external_inputs = vec![Fr::from(6u32), Fr::from(7u32)];
284
285        // run native step
286        let z_i1_native = circom_fcircuit
287            .step_native(1, z_i.clone(), external_inputs.clone())
288            .unwrap();
289
290        // run gadget step
291        let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
292        let external_inputs_var =
293            Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(external_inputs.clone())).unwrap();
294        let z_i1_var = circom_fcircuit
295            .generate_step_constraints(cs.clone(), 1, z_i_var, external_inputs_var)
296            .unwrap();
297
298        assert_eq!(z_i1_var.value().unwrap(), z_i1_native);
299
300        // re-init cs and run gadget step with wrong ivc inputs (first ivc should not be zero)
301        let cs = ConstraintSystem::<Fr>::new_ref();
302        let wrong_z_i = vec![Fr::from(0)];
303        let wrong_z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(wrong_z_i)).unwrap();
304        let external_inputs_var =
305            Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(external_inputs)).unwrap();
306        let _z_i1_var = circom_fcircuit.generate_step_constraints(
307            cs.clone(),
308            1,
309            wrong_z_i_var,
310            external_inputs_var,
311        );
312        // TODO:: https://github.com/privacy-scaling-explorations/sonobe/issues/104
313        // Disable check for now
314        // assert!(z_i1_var.is_err());
315    }
316
317    #[test]
318    fn test_circom_no_external_inputs() {
319        let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/no_external_inputs.r1cs");
320        let wasm_path = PathBuf::from(
321            "./src/frontend/circom/test_folder/no_external_inputs_js/no_external_inputs.wasm",
322        );
323        let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 3, 0)).unwrap();
324        let cs = ConstraintSystem::<Fr>::new_ref();
325        let z_i = vec![Fr::from(3u32), Fr::from(4u32), Fr::from(5u32)];
326        let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();
327
328        // run native step
329        let z_i1_native = circom_fcircuit.step_native(1, z_i.clone(), vec![]).unwrap();
330
331        // run gadget step
332        let z_i1_var = circom_fcircuit
333            .generate_step_constraints(cs.clone(), 1, z_i_var, vec![])
334            .unwrap();
335
336        assert_eq!(z_i1_var.value().unwrap(), z_i1_native);
337
338        // re-init cs and run gadget step with wrong ivc inputs (first ivc input should not be zero)
339        let cs = ConstraintSystem::<Fr>::new_ref();
340        let wrong_z_i = vec![Fr::from(0u32), Fr::from(4u32), Fr::from(5u32)];
341        let wrong_z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(wrong_z_i)).unwrap();
342        let _z_i1_var =
343            circom_fcircuit.generate_step_constraints(cs.clone(), 1, wrong_z_i_var, vec![]);
344        // TODO:: https://github.com/privacy-scaling-explorations/sonobe/issues/104
345        // Disable check for now
346        // assert!(z_i1_var.is_err())
347    }
348
349    #[test]
350    fn test_custom_code() {
351        let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
352        let wasm_path =
353            PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
354
355        let mut circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
356
357        circom_fcircuit.set_custom_step_native(Rc::new(|_i, z_i, _external| {
358            let z = z_i[0];
359            Ok(vec![z * z * z + z + Fr::from(5)])
360        }));
361
362        // Allocates z_i1 by using step_native function.
363        let z_i = vec![Fr::from(3_u32)];
364        let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
365            FC: circom_fcircuit.clone(),
366            z_i: Some(z_i.clone()),
367            z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
368        };
369
370        let cs = ConstraintSystem::<Fr>::new_ref();
371
372        wrapper_circuit.generate_constraints(cs.clone()).unwrap();
373        assert!(
374            cs.is_satisfied().unwrap(),
375            "Constraint system is not satisfied"
376        );
377    }
378}