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}