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}