sonobe/folding-schemes/src/frontend/circom/mod.rs
folding-schemes/src/frontend/circom/mod.rsRaw
1use ark_circom::circom::CircomCircuit;
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}
22
23impl<F: PrimeField> FCircuit<F> for CircomFCircuit<F> {
24 type Params = (PathBuf, PathBuf);
25
26 fn new(params: Self::Params) -> Self {
27 let (r1cs_path, wasm_path) = params;
28 let circom_wrapper = CircomWrapper::new(r1cs_path, wasm_path);
29 Self { circom_wrapper }
30 }
31
32 fn state_len(&self) -> usize {
33 1
34 }
35
36 fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
37 // Converts PrimeField values to BigInt for computing witness.
38 let input_num_bigint = z_i
39 .iter()
40 .map(|val| self.circom_wrapper.ark_primefield_to_num_bigint(*val))
41 .collect::<Vec<BigInt>>();
42
43 // Computes witness
44 let witness = self
45 .circom_wrapper
46 .extract_witness(&[("ivc_input".to_string(), input_num_bigint)])
47 .map_err(|e| {
48 Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e))
49 })?;
50
51 // Extracts the z_i1(next state) from the witness vector.
52 let z_i1 = witness[1..1 + self.state_len()].to_vec();
53 Ok(z_i1)
54 }
55
56 fn generate_step_constraints(
57 &self,
58 cs: ConstraintSystemRef<F>,
59 _i: usize,
60 z_i: Vec<FpVar<F>>,
61 ) -> Result<Vec<FpVar<F>>, SynthesisError> {
62 let mut input_values = Vec::new();
63 // Converts each FpVar to PrimeField value, then to num_bigint::BigInt.
64 for fp_var in z_i.iter() {
65 // Extracts the PrimeField value from FpVar.
66 let primefield_value = fp_var.value()?;
67 // Converts the PrimeField value to num_bigint::BigInt.
68 let num_bigint_value = self
69 .circom_wrapper
70 .ark_primefield_to_num_bigint(primefield_value);
71 input_values.push(num_bigint_value);
72 }
73
74 let num_bigint_inputs = vec![("ivc_input".to_string(), input_values)];
75
76 // Extracts R1CS and witness.
77 let (r1cs, witness) = self
78 .circom_wrapper
79 .extract_r1cs_and_witness(&num_bigint_inputs)
80 .map_err(|_| SynthesisError::AssignmentMissing)?;
81
82 // Initializes the CircomCircuit.
83 let circom_circuit = CircomCircuit {
84 r1cs,
85 witness: witness.clone(),
86 inputs_already_computed: true,
87 };
88
89 // Generates the constraints for the circom_circuit.
90 circom_circuit
91 .generate_constraints(cs.clone())
92 .map_err(|_| SynthesisError::AssignmentMissing)?;
93
94 // Checks for constraint satisfaction.
95 if !cs.is_satisfied().unwrap() {
96 return Err(SynthesisError::Unsatisfiable);
97 }
98
99 let w = witness.ok_or(SynthesisError::Unsatisfiable)?;
100
101 // Extracts the z_i1(next state) from the witness vector.
102 let z_i1: Vec<FpVar<F>> =
103 Vec::<FpVar<F>>::new_witness(cs.clone(), || Ok(w[1..1 + self.state_len()].to_vec()))?;
104
105 Ok(z_i1)
106 }
107}
108
109#[cfg(test)]
110pub mod tests {
111 use super::*;
112 use ark_pallas::Fr;
113 use ark_r1cs_std::alloc::AllocVar;
114 use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
115
116 // Tests the step_native function of CircomFCircuit.
117 #[test]
118 fn test_circom_step_native() {
119 let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
120 let wasm_path =
121 PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
122
123 let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path));
124
125 let z_i = vec![Fr::from(3u32)];
126 let z_i1 = circom_fcircuit.step_native(1, z_i).unwrap();
127 assert_eq!(z_i1, vec![Fr::from(35u32)]);
128 }
129
130 // Tests the generate_step_constraints function of CircomFCircuit.
131 #[test]
132 fn test_circom_step_constraints() {
133 let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
134 let wasm_path =
135 PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
136
137 let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path));
138
139 let cs = ConstraintSystem::<Fr>::new_ref();
140
141 let z_i = vec![Fr::from(3u32)];
142
143 let z_i_var = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(z_i)).unwrap();
144
145 let cs = ConstraintSystem::<Fr>::new_ref();
146 let z_i1_var = circom_fcircuit
147 .generate_step_constraints(cs.clone(), 1, z_i_var)
148 .unwrap();
149 assert_eq!(z_i1_var.value().unwrap(), vec![Fr::from(35u32)]);
150 }
151
152 // Tests the WrapperCircuit with CircomFCircuit.
153 #[test]
154 fn test_wrapper_circomtofcircuit() {
155 let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
156 let wasm_path =
157 PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
158
159 let circom_fcircuit = CircomFCircuit::<Fr>::new((r1cs_path, wasm_path));
160
161 // Allocates z_i1 by using step_native function.
162 let z_i = vec![Fr::from(3_u32)];
163 let wrapper_circuit = crate::frontend::tests::WrapperCircuit {
164 FC: circom_fcircuit.clone(),
165 z_i: Some(z_i.clone()),
166 z_i1: Some(circom_fcircuit.step_native(0, z_i.clone()).unwrap()),
167 };
168
169 let cs = ConstraintSystem::<Fr>::new_ref();
170
171 wrapper_circuit.generate_constraints(cs.clone()).unwrap();
172 assert!(
173 cs.is_satisfied().unwrap(),
174 "Constraint system is not satisfied"
175 );
176 }
177}