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