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}