arnaucube
arnaucube/sonobePublic Self-hosted repository
sonobe/folding-schemes/src/frontend/circom/mod.rs
folding-schemes/src/frontend/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::R1CSVar;
  6use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
  7use ark_std::fmt::Debug;
  8use num_bigint::BigInt;
  9use std::path::PathBuf;
 10
 11use crate::frontend::FCircuit;
 12use crate::Error;
 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        // Initializes the CircomCircuit.
114        let circom_circuit = CircomCircuit {
115            r1cs: self.r1cs.clone(),
116            witness: Some(witness.clone()),
117            inputs_already_allocated: true,
118        };
119
120        // Generates the constraints for the circom_circuit.
121        circom_circuit.generate_constraints(cs.clone())?;
122
123        // Checks for constraint satisfaction.
124        if !cs.is_satisfied().unwrap() {
125            return Err(SynthesisError::Unsatisfiable);
126        }
127
128        // Extracts the z_i1(next state) from the witness vector.
129        let z_i1: Vec<FpVar<F>> = Vec::<FpVar<F>>::new_witness(cs.clone(), || {
130            Ok(witness[1..1 + self.state_len()].to_vec())
131        })?;
132
133        Ok(z_i1)
134    }
135}
136
137impl<F: PrimeField> CircomFCircuit<F> {
138    fn fpvars_to_bigints(&self, fpVars: Vec<FpVar<F>>) -> Result<Vec<BigInt>, SynthesisError> {
139        let mut input_values = Vec::new();
140        // converts each FpVar to PrimeField value, then to num_bigint::BigInt.
141        for fp_var in fpVars.iter() {
142            // extracts the PrimeField value from FpVar.
143            let primefield_value = fp_var.value()?;
144            // converts the PrimeField value to num_bigint::BigInt.
145            let num_bigint_value = self
146                .circom_wrapper
147                .ark_primefield_to_num_bigint(primefield_value);
148            input_values.push(num_bigint_value);
149        }
150        Ok(input_values)
151    }
152}
153
154#[cfg(test)]
155pub mod tests {
156    use super::*;
157    use ark_pallas::Fr;
158    use ark_r1cs_std::alloc::AllocVar;
159    use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
160
161    // Tests the step_native function of CircomFCircuit.
162    #[test]
163    fn test_circom_step_native() {
164        let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
165        let wasm_path =
166            PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
167
168        let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
169
170        let z_i = vec![Fr::from(3u32)];
171        let z_i1 = circom_fcircuit.step_native(1, z_i, vec![]).unwrap();
172        assert_eq!(z_i1, vec![Fr::from(35u32)]);
173    }
174
175    // Tests the generate_step_constraints function of CircomFCircuit.
176    #[test]
177    fn test_circom_step_constraints() {
178        let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
179        let wasm_path =
180            PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
181
182        let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
183
184        let cs = ConstraintSystem::<Fr>::new_ref();
185
186        let z_i = vec![Fr::from(3u32)];
187
188        let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
189
190        let cs = ConstraintSystem::<Fr>::new_ref();
191        let z_i1_var = circom_fcircuit
192            .generate_step_constraints(cs.clone(), 1, z_i_var, vec![])
193            .unwrap();
194        assert_eq!(z_i1_var.value().unwrap(), vec![Fr::from(35u32)]);
195    }
196
197    // Tests the WrapperCircuit with CircomFCircuit.
198    #[test]
199    fn test_wrapper_circomtofcircuit() {
200        let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
201        let wasm_path =
202            PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
203
204        let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
205
206        // Allocates z_i1 by using step_native function.
207        let z_i = vec![Fr::from(3_u32)];
208        let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
209            FC: circom_fcircuit.clone(),
210            z_i: Some(z_i.clone()),
211            z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
212        };
213
214        let cs = ConstraintSystem::<Fr>::new_ref();
215
216        wrapper_circuit.generate_constraints(cs.clone()).unwrap();
217        assert!(
218            cs.is_satisfied().unwrap(),
219            "Constraint system is not satisfied"
220        );
221    }
222
223    #[test]
224    fn test_circom_external_inputs() {
225        let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/external_inputs.r1cs");
226        let wasm_path = PathBuf::from(
227            "./src/frontend/circom/test_folder/external_inputs_js/external_inputs.wasm",
228        );
229
230        let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 2)).unwrap(); // state_len:1, external_inputs_len:2
231
232        let cs = ConstraintSystem::<Fr>::new_ref();
233
234        let z_i = vec![Fr::from(3u32)];
235        let external_inputs = vec![Fr::from(6u32), Fr::from(7u32)];
236
237        // run native step
238        let z_i1 = circom_fcircuit
239            .step_native(1, z_i.clone(), external_inputs.clone())
240            .unwrap();
241        assert_eq!(z_i1, vec![Fr::from(52u32)]);
242
243        // run gadget step
244        let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
245        let external_inputs_var =
246            Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(external_inputs)).unwrap();
247
248        let z_i1_var = circom_fcircuit
249            .generate_step_constraints(cs.clone(), 1, z_i_var, external_inputs_var)
250            .unwrap();
251        assert_eq!(z_i1_var.value().unwrap(), vec![Fr::from(52u32)]);
252    }
253}