sonobe/folding-schemes/src/frontend/circom/utils.rs
folding-schemes/src/frontend/circom/utils.rsRaw
1use ark_circom::{
2 circom::{r1cs_reader, R1CS},
3 WitnessCalculator,
4};
5use ark_ff::{BigInteger, PrimeField};
6use color_eyre::Result;
7use num_bigint::{BigInt, Sign};
8use std::{fs::File, io::BufReader, marker::PhantomData, path::PathBuf};
9
10use crate::Error;
11
12// A struct that wraps Circom functionalities, allowing for extraction of R1CS and witnesses
13// based on file paths to Circom's .r1cs and .wasm.
14#[derive(Clone, Debug)]
15pub struct CircomWrapper<F: PrimeField> {
16 r1cs_filepath: PathBuf,
17 wasm_filepath: PathBuf,
18 _marker: PhantomData<F>,
19}
20
21impl<F: PrimeField> CircomWrapper<F> {
22 // Creates a new instance of the CircomWrapper with the file paths.
23 pub fn new(r1cs_filepath: PathBuf, wasm_filepath: PathBuf) -> Self {
24 CircomWrapper {
25 r1cs_filepath,
26 wasm_filepath,
27 _marker: PhantomData,
28 }
29 }
30
31 // Aggregated function to obtain R1CS and witness from Circom.
32 pub fn extract_r1cs_and_witness(
33 &self,
34 inputs: &[(String, Vec<BigInt>)],
35 ) -> Result<(R1CS<F>, Option<Vec<F>>), Error> {
36 // Extracts the R1CS
37 let file = File::open(&self.r1cs_filepath)?;
38 let reader = BufReader::new(file);
39 let r1cs_file = r1cs_reader::R1CSFile::<F>::new(reader)?;
40 let r1cs = r1cs_reader::R1CS::<F>::from(r1cs_file);
41
42 // Extracts the witness vector
43 let witness_vec = self.extract_witness(inputs)?;
44
45 Ok((r1cs, Some(witness_vec)))
46 }
47
48 pub fn extract_r1cs(&self) -> Result<R1CS<F>, Error> {
49 let file = File::open(&self.r1cs_filepath)?;
50 let reader = BufReader::new(file);
51 let r1cs_file = r1cs_reader::R1CSFile::<F>::new(reader)?;
52 Ok(r1cs_reader::R1CS::<F>::from(r1cs_file))
53 }
54
55 // Extracts the witness vector as a vector of PrimeField elements.
56 pub fn extract_witness(&self, inputs: &[(String, Vec<BigInt>)]) -> Result<Vec<F>, Error> {
57 let witness_bigint = self.calculate_witness(inputs)?;
58
59 witness_bigint
60 .iter()
61 .map(|big_int| {
62 self.num_bigint_to_ark_bigint(big_int)
63 .and_then(|ark_big_int| {
64 F::from_bigint(ark_big_int)
65 .ok_or_else(|| Error::Other("could not get F from bigint".to_string()))
66 })
67 })
68 .collect()
69 }
70
71 // Calculates the witness given the Wasm filepath and inputs.
72 pub fn calculate_witness(
73 &self,
74 inputs: &[(String, Vec<BigInt>)],
75 ) -> Result<Vec<BigInt>, Error> {
76 let mut calculator = WitnessCalculator::new(&self.wasm_filepath).map_err(|e| {
77 Error::WitnessCalculationError(format!("Failed to create WitnessCalculator: {}", e))
78 })?;
79 calculator
80 .calculate_witness(inputs.iter().cloned(), true)
81 .map_err(|e| {
82 Error::WitnessCalculationError(format!("Failed to calculate witness: {}", e))
83 })
84 }
85
86 // Converts a num_bigint::BigInt to a PrimeField::BigInt.
87 pub fn num_bigint_to_ark_bigint(&self, value: &BigInt) -> Result<F::BigInt, Error> {
88 let big_uint = value
89 .to_biguint()
90 .ok_or_else(|| Error::BigIntConversionError("BigInt is negative".to_string()))?;
91 F::BigInt::try_from(big_uint).map_err(|_| {
92 Error::BigIntConversionError("Failed to convert to PrimeField::BigInt".to_string())
93 })
94 }
95
96 // Converts a PrimeField element to a num_bigint::BigInt representation.
97 pub fn ark_primefield_to_num_bigint(&self, value: F) -> BigInt {
98 let primefield_bigint: F::BigInt = value.into_bigint();
99 let bytes = primefield_bigint.to_bytes_be();
100 BigInt::from_bytes_be(Sign::Plus, &bytes)
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use ark_bn254::Fr;
108 use ark_circom::circom::{CircomBuilder, CircomConfig};
109 use ark_circom::CircomCircuit;
110 use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem};
111 use std::path::PathBuf;
112
113 //To generate .r1cs and .wasm files, run the below command in the terminal.
114 //bash ./folding-schemes/src/frontend/circom/test_folder/compile.sh
115
116 // Test the satisfication by using the CircomBuilder of circom-compat
117 #[test]
118 fn test_circombuilder_satisfied() {
119 let cfg = CircomConfig::<Fr>::new(
120 "./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm",
121 "./src/frontend/circom/test_folder/cubic_circuit.r1cs",
122 )
123 .unwrap();
124 let mut builder = CircomBuilder::new(cfg);
125 builder.push_input("ivc_input", 3);
126
127 let circom = builder.build().unwrap();
128 let cs = ConstraintSystem::<Fr>::new_ref();
129 circom.generate_constraints(cs.clone()).unwrap();
130 assert!(cs.is_satisfied().unwrap());
131 }
132
133 // Test the satisfication by using the CircomWrapper
134 #[test]
135 fn test_extract_r1cs_and_witness() {
136 let r1cs_path = PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit.r1cs");
137 let wasm_path =
138 PathBuf::from("./src/frontend/circom/test_folder/cubic_circuit_js/cubic_circuit.wasm");
139
140 let inputs = vec![("ivc_input".to_string(), vec![BigInt::from(3)])];
141 let wrapper = CircomWrapper::<Fr>::new(r1cs_path, wasm_path);
142
143 let (r1cs, witness) = wrapper.extract_r1cs_and_witness(&inputs).unwrap();
144
145 let cs = ConstraintSystem::<Fr>::new_ref();
146
147 let circom_circuit = CircomCircuit {
148 r1cs,
149 witness,
150 inputs_already_allocated: false,
151 };
152
153 circom_circuit.generate_constraints(cs.clone()).unwrap();
154 assert!(cs.is_satisfied().unwrap());
155 }
156}