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