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