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_relations::r1cs::ConstraintSystem;
211
212 // Tests the step_native function of CircomFCircuit.
213 #[test]
214 fn test_circom_step_native() {
215 let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
216 let wasm_path =
217 PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
218
219 let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 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/frontend/circom/test_folder/cubic_circuit.r1cs");
230 let wasm_path =
231 PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
232
233 let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
234
235 let cs = ConstraintSystem::<Fr>::new_ref();
236
237 let z_i = vec![Fr::from(3u32)];
238
239 let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
240 let z_i1_var = circom_fcircuit
241 .generate_step_constraints(cs.clone(), 1, z_i_var, vec![])
242 .unwrap();
243 assert_eq!(z_i1_var.value().unwrap(), vec![Fr::from(35u32)]);
244 }
245
246 // Tests the WrapperCircuit with CircomFCircuit.
247 #[test]
248 fn test_wrapper_circomtofcircuit() {
249 let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
250 let wasm_path =
251 PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
252
253 let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
254
255 // Allocates z_i1 by using step_native function.
256 let z_i = vec![Fr::from(3_u32)];
257 let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
258 FC: circom_fcircuit.clone(),
259 z_i: Some(z_i.clone()),
260 z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
261 };
262
263 let cs = ConstraintSystem::<Fr>::new_ref();
264
265 wrapper_circuit.generate_constraints(cs.clone()).unwrap();
266 assert!(
267 cs.is_satisfied().unwrap(),
268 "Constraint system is not satisfied"
269 );
270 }
271
272 #[test]
273 fn test_circom_external_inputs() {
274 let r1cs_path =
275 PathBuf::from("./src/frontend/circom/test_folder/with_external_inputs.r1cs");
276 let wasm_path = PathBuf::from(
277 "./src/frontend/circom/test_folder/with_external_inputs_js/with_external_inputs.wasm",
278 );
279 let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 2)).unwrap(); // state_len:1, external_inputs_len:2
280 let cs = ConstraintSystem::<Fr>::new_ref();
281 let z_i = vec![Fr::from(3u32)];
282 let external_inputs = vec![Fr::from(6u32), Fr::from(7u32)];
283
284 // run native step
285 let z_i1_native = circom_fcircuit
286 .step_native(1, z_i.clone(), external_inputs.clone())
287 .unwrap();
288
289 // run gadget step
290 let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
291 let external_inputs_var =
292 Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(external_inputs.clone())).unwrap();
293 let z_i1_var = circom_fcircuit
294 .generate_step_constraints(cs.clone(), 1, z_i_var, external_inputs_var)
295 .unwrap();
296
297 assert_eq!(z_i1_var.value().unwrap(), z_i1_native);
298
299 // re-init cs and run gadget step with wrong ivc inputs (first ivc should not be zero)
300 let cs = ConstraintSystem::<Fr>::new_ref();
301 let wrong_z_i = vec![Fr::from(0)];
302 let wrong_z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(wrong_z_i)).unwrap();
303 let external_inputs_var =
304 Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(external_inputs)).unwrap();
305 let _z_i1_var = circom_fcircuit.generate_step_constraints(
306 cs.clone(),
307 1,
308 wrong_z_i_var,
309 external_inputs_var,
310 );
311 // TODO:: https://github.com/privacy-scaling-explorations/sonobe/issues/104
312 // Disable check for now
313 // assert!(z_i1_var.is_err());
314 }
315
316 #[test]
317 fn test_circom_no_external_inputs() {
318 let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/no_external_inputs.r1cs");
319 let wasm_path = PathBuf::from(
320 "./src/frontend/circom/test_folder/no_external_inputs_js/no_external_inputs.wasm",
321 );
322 let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 3, 0)).unwrap();
323 let cs = ConstraintSystem::<Fr>::new_ref();
324 let z_i = vec![Fr::from(3u32), Fr::from(4u32), Fr::from(5u32)];
325 let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i.clone())).unwrap();
326
327 // run native step
328 let z_i1_native = circom_fcircuit.step_native(1, z_i.clone(), vec![]).unwrap();
329
330 // run gadget step
331 let z_i1_var = circom_fcircuit
332 .generate_step_constraints(cs.clone(), 1, z_i_var, vec![])
333 .unwrap();
334
335 assert_eq!(z_i1_var.value().unwrap(), z_i1_native);
336
337 // re-init cs and run gadget step with wrong ivc inputs (first ivc input should not be zero)
338 let cs = ConstraintSystem::<Fr>::new_ref();
339 let wrong_z_i = vec![Fr::from(0u32), Fr::from(4u32), Fr::from(5u32)];
340 let wrong_z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(wrong_z_i)).unwrap();
341 let _z_i1_var =
342 circom_fcircuit.generate_step_constraints(cs.clone(), 1, wrong_z_i_var, vec![]);
343 // TODO:: https://github.com/privacy-scaling-explorations/sonobe/issues/104
344 // Disable check for now
345 // assert!(z_i1_var.is_err())
346 }
347
348 #[test]
349 fn test_custom_code() {
350 let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
351 let wasm_path =
352 PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
353
354 let mut circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path, 1, 0)).unwrap(); // state_len:1, external_inputs_len:0
355
356 circom_fcircuit.set_custom_step_native(Rc::new(|_i, z_i, _external| {
357 let z = z_i[0];
358 Ok(vec![z * z * z + z + Fr::from(5)])
359 }));
360
361 // Allocates z_i1 by using step_native function.
362 let z_i = vec![Fr::from(3_u32)];
363 let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
364 FC: circom_fcircuit.clone(),
365 z_i: Some(z_i.clone()),
366 z_i1: Some(circom_fcircuit.step_native(0, z_i.clone(), vec![]).unwrap()),
367 };
368
369 let cs = ConstraintSystem::<Fr>::new_ref();
370
371 wrapper_circuit.generate_constraints(cs.clone()).unwrap();
372 assert!(
373 cs.is_satisfied().unwrap(),
374 "Constraint system is not satisfied"
375 );
376 }
377}