sonobe/folding-schemes/src/frontend/noir/mod.rs
folding-schemes/src/frontend/noir/mod.rsRaw
1use std::collections::HashMap;
2
3use crate::Error;
4
5use super::FCircuit;
6use acvm::{
7 acir::{
8 acir_field::GenericFieldElement,
9 circuit::{Circuit, Program},
10 native_types::{Witness as AcvmWitness, WitnessMap},
11 },
12 blackbox_solver::StubbedBlackBoxSolver,
13 pwg::ACVM,
14};
15use ark_ff::PrimeField;
16use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar, R1CSVar};
17use ark_relations::r1cs::ConstraintSynthesizer;
18use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
19use arkworks_backend::{read_program_from_file, sonobe_bridge::AcirCircuitSonobe};
20
21#[derive(Clone, Debug)]
22pub struct NoirFCircuit<F: PrimeField> {
23 pub circuit: Circuit<GenericFieldElement<F>>,
24 pub state_len: usize,
25 pub external_inputs_len: usize,
26}
27
28impl<F: PrimeField> FCircuit<F> for NoirFCircuit<F> {
29 type Params = (String, usize, usize);
30
31 fn new(params: Self::Params) -> Result<Self, crate::Error> {
32 let (path, state_len, external_inputs_len) = params;
33 let program =
34 read_program_from_file(path).map_err(|ee| Error::Other(format!("{:?}", ee)))?;
35 let circuit: Circuit<GenericFieldElement<F>> = program.functions[0].clone();
36 let ivc_input_length = circuit.public_parameters.0.len();
37 let ivc_return_length = circuit.return_values.0.len();
38
39 if ivc_input_length != ivc_return_length {
40 return Err(Error::NotSameLength(
41 "IVC input: ".to_string(),
42 ivc_input_length,
43 "IVC output: ".to_string(),
44 ivc_return_length,
45 ));
46 }
47
48 Ok(NoirFCircuit {
49 circuit,
50 state_len,
51 external_inputs_len,
52 })
53 }
54
55 fn state_len(&self) -> usize {
56 self.state_len
57 }
58
59 fn external_inputs_len(&self) -> usize {
60 self.external_inputs_len
61 }
62
63 fn step_native(
64 &self,
65 _i: usize,
66 z_i: Vec<F>,
67 external_inputs: Vec<F>, // inputs that are not part of the state
68 ) -> Result<Vec<F>, crate::Error> {
69 let mut acvm = ACVM::new(
70 &StubbedBlackBoxSolver,
71 &self.circuit.opcodes,
72 WitnessMap::new(),
73 &[],
74 &[],
75 );
76
77 self.circuit
78 .public_parameters
79 .0
80 .iter()
81 .map(|witness| {
82 let idx: usize = witness.as_usize();
83 let value = z_i[idx].to_string();
84 let witness = AcvmWitness(witness.witness_index());
85 let f = GenericFieldElement::<F>::try_from_str(&value)
86 .ok_or(SynthesisError::Unsatisfiable)?;
87 acvm.overwrite_witness(witness, f);
88 Ok(())
89 })
90 .collect::<Result<Vec<()>, SynthesisError>>()?;
91
92 // write witness values for external_inputs
93 self.circuit
94 .private_parameters
95 .iter()
96 .map(|witness| {
97 let idx = witness.as_usize() - z_i.len();
98 let value = external_inputs[idx].to_string();
99 let f = GenericFieldElement::<F>::try_from_str(&value)
100 .ok_or(SynthesisError::Unsatisfiable)?;
101 acvm.overwrite_witness(AcvmWitness(witness.witness_index()), f);
102 Ok(())
103 })
104 .collect::<Result<Vec<()>, SynthesisError>>()?;
105 let _ = acvm.solve();
106
107 let witness_map = acvm.finalize();
108
109 // get the z_{i+1} output state
110 let assigned_z_i1 = self
111 .circuit
112 .return_values
113 .0
114 .iter()
115 .map(|witness| {
116 let noir_field_element = witness_map
117 .get(witness)
118 .ok_or(SynthesisError::AssignmentMissing)?;
119 Ok(noir_field_element.into_repr())
120 })
121 .collect::<Result<Vec<F>, SynthesisError>>()?;
122
123 Ok(assigned_z_i1)
124 }
125
126 fn generate_step_constraints(
127 &self,
128 cs: ConstraintSystemRef<F>,
129 _i: usize,
130 z_i: Vec<FpVar<F>>,
131 external_inputs: Vec<FpVar<F>>, // inputs that are not part of the state
132 ) -> Result<Vec<FpVar<F>>, SynthesisError> {
133 let mut acvm = ACVM::new(
134 &StubbedBlackBoxSolver,
135 &self.circuit.opcodes,
136 WitnessMap::new(),
137 &[],
138 &[],
139 );
140
141 let mut already_assigned_witness_values = HashMap::new();
142
143 self.circuit
144 .public_parameters
145 .0
146 .iter()
147 .map(|witness| {
148 let idx: usize = witness.as_usize();
149 let witness = AcvmWitness(witness.witness_index());
150 already_assigned_witness_values.insert(witness, &z_i[idx]);
151 let val = z_i[idx].value()?;
152 let value = if val == F::zero() {
153 "0".to_string()
154 } else {
155 val.to_string()
156 };
157
158 let f = GenericFieldElement::<F>::try_from_str(&value)
159 .ok_or(SynthesisError::Unsatisfiable)?;
160 acvm.overwrite_witness(witness, f);
161 Ok(())
162 })
163 .collect::<Result<Vec<()>, SynthesisError>>()?;
164
165 // write witness values for external_inputs
166 self.circuit
167 .private_parameters
168 .iter()
169 .map(|witness| {
170 let idx = witness.as_usize() - z_i.len();
171 let witness = AcvmWitness(witness.witness_index());
172 already_assigned_witness_values.insert(witness, &external_inputs[idx]);
173
174 let val = external_inputs[idx].value()?;
175 let value = if val == F::zero() {
176 "0".to_string()
177 } else {
178 val.to_string()
179 };
180
181 let f = GenericFieldElement::<F>::try_from_str(&value)
182 .ok_or(SynthesisError::Unsatisfiable)?;
183 acvm.overwrite_witness(witness, f);
184 Ok(())
185 })
186 .collect::<Result<Vec<()>, SynthesisError>>()?;
187
188 // computes the witness
189 let _ = acvm.solve();
190 let witness_map = acvm.finalize();
191
192 // get the z_{i+1} output state
193 let assigned_z_i1 = self
194 .circuit
195 .return_values
196 .0
197 .iter()
198 .map(|witness| {
199 let noir_field_element = witness_map
200 .get(witness)
201 .ok_or(SynthesisError::AssignmentMissing)?;
202 FpVar::<F>::new_witness(cs.clone(), || Ok(noir_field_element.into_repr()))
203 })
204 .collect::<Result<Vec<FpVar<F>>, SynthesisError>>()?;
205
206 // initialize circuit and set already assigned values
207 let mut acir_circuit = AcirCircuitSonobe::from((&self.circuit, witness_map));
208 acir_circuit.already_assigned_witnesses = already_assigned_witness_values;
209
210 acir_circuit.generate_constraints(cs.clone())?;
211
212 Ok(assigned_z_i1)
213 }
214}
215
216pub fn load_noir_circuit<F: PrimeField>(path: String) -> Circuit<GenericFieldElement<F>> {
217 let program: Program<GenericFieldElement<F>> = read_program_from_file(path).unwrap();
218 let circuit: Circuit<GenericFieldElement<F>> = program.functions[0].clone();
219 circuit
220}
221
222#[cfg(test)]
223mod tests {
224 use crate::frontend::{noir::load_noir_circuit, FCircuit};
225 use ark_bn254::Fr;
226 use ark_r1cs_std::R1CSVar;
227 use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar};
228 use ark_relations::r1cs::ConstraintSystem;
229 use std::env;
230
231 use crate::frontend::noir::NoirFCircuit;
232
233 #[test]
234 fn test_step_native() {
235 let cur_path = env::current_dir().unwrap();
236 let circuit_path = format!(
237 "{}/src/frontend/noir/test_folder/test_circuit/target/test_circuit.json",
238 cur_path.to_str().unwrap()
239 );
240 let circuit = load_noir_circuit(circuit_path);
241 let noirfcircuit = NoirFCircuit {
242 circuit,
243 state_len: 2,
244 external_inputs_len: 2,
245 };
246 let inputs = vec![Fr::from(2), Fr::from(5)];
247 let res = noirfcircuit.step_native(0, inputs.clone(), inputs);
248 assert!(res.is_ok());
249 assert_eq!(res.unwrap(), vec![Fr::from(4), Fr::from(25)]);
250 }
251
252 #[test]
253 fn test_step_constraints() {
254 let cs = ConstraintSystem::<Fr>::new_ref();
255 let cur_path = env::current_dir().unwrap();
256 let circuit_path = format!(
257 "{}/src/frontend/noir/test_folder/test_circuit/target/test_circuit.json",
258 cur_path.to_str().unwrap()
259 );
260 let circuit = load_noir_circuit(circuit_path);
261 let noirfcircuit = NoirFCircuit {
262 circuit,
263 state_len: 2,
264 external_inputs_len: 2,
265 };
266 let inputs = vec![Fr::from(2), Fr::from(5)];
267 let z_i = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs.clone())).unwrap();
268 let external_inputs = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs)).unwrap();
269 let output = noirfcircuit
270 .generate_step_constraints(cs.clone(), 0, z_i, external_inputs)
271 .unwrap();
272 assert_eq!(output[0].value().unwrap(), Fr::from(4));
273 assert_eq!(output[1].value().unwrap(), Fr::from(25));
274 }
275
276 #[test]
277 fn test_step_constraints_no_external_inputs() {
278 let cs = ConstraintSystem::<Fr>::new_ref();
279 let cur_path = env::current_dir().unwrap();
280 let circuit_path = format!(
281 "{}/src/frontend/noir/test_folder/test_no_external_inputs/target/test_no_external_inputs.json",
282 cur_path.to_str().unwrap()
283 );
284 let circuit = load_noir_circuit(circuit_path);
285 let noirfcircuit = NoirFCircuit {
286 circuit,
287 state_len: 2,
288 external_inputs_len: 0,
289 };
290 let inputs = vec![Fr::from(2), Fr::from(5)];
291 let z_i = Vec::<FpVar<Fr>>::new_witness(cs.clone(), || Ok(inputs.clone())).unwrap();
292 let external_inputs = vec![];
293 let output = noirfcircuit
294 .generate_step_constraints(cs.clone(), 0, z_i, external_inputs)
295 .unwrap();
296 assert_eq!(output[0].value().unwrap(), Fr::from(4));
297 assert_eq!(output[1].value().unwrap(), Fr::from(25));
298 }
299}