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