acvm/compiler/
simulator.rs1use acir::{
2 AcirField,
3 circuit::{
4 Circuit, Opcode,
5 brillig::{BrilligInputs, BrilligOutputs},
6 opcodes::{BlockId, FunctionInput, MemOpKind},
7 },
8 native_types::{Expression, Witness},
9};
10use std::collections::HashSet;
11
12use crate::pwg::arithmetic::ExpressionSolver;
13
14#[derive(Default)]
22pub struct CircuitSimulator {
23 solvable_witnesses: HashSet<Witness>,
25
26 initialized_blocks: HashSet<BlockId>,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum SimulationFailure {
33 UnsolvableOpcode(usize),
35 UnsolvableOutput(Witness),
37}
38
39impl CircuitSimulator {
40 pub fn check_circuit<F: AcirField>(circuit: &Circuit<F>) -> Option<SimulationFailure> {
47 Self::default().run_check_circuit(circuit)
48 }
49
50 #[tracing::instrument(level = "trace", skip_all)]
53 fn run_check_circuit<F: AcirField>(
54 &mut self,
55 circuit: &Circuit<F>,
56 ) -> Option<SimulationFailure> {
57 let circuit_inputs = circuit.circuit_arguments();
58 self.solvable_witnesses.extend(circuit_inputs.iter());
59 for (i, op) in circuit.opcodes.iter().enumerate() {
60 if !self.try_solve(op) {
61 return Some(SimulationFailure::UnsolvableOpcode(i));
62 }
63 }
64 for ret in &circuit.return_values.0 {
67 if !self.solvable_witnesses.contains(ret) {
68 return Some(SimulationFailure::UnsolvableOutput(*ret));
69 }
70 }
71 None
72 }
73
74 fn try_solve<F: AcirField>(&mut self, opcode: &Opcode<F>) -> bool {
76 match opcode {
77 Opcode::AssertZero(expr) => {
78 let Some(unresolved) = unresolved_witnesses(expr, &self.solvable_witnesses) else {
79 return false;
80 };
81 if unresolved.len() == 1 {
82 self.mark_solvable(*unresolved.iter().next().expect("len == 1"));
83 return true;
84 }
85 unresolved.is_empty()
86 }
87 Opcode::BlackBoxFuncCall(black_box_func_call) => {
88 let inputs = black_box_func_call.get_inputs_vec();
89 for input in inputs {
90 if !self.can_solve_function_input(&input) {
91 return false;
92 }
93 }
94 let outputs = black_box_func_call.get_outputs_vec();
95 for output in outputs {
96 self.mark_solvable(output);
97 }
98 true
99 }
100 Opcode::MemoryOp { block_id, op } => {
101 if !self.initialized_blocks.contains(block_id) {
102 return false;
104 }
105 if !self.solvable_witnesses.contains(&op.index) {
106 return false;
107 }
108 match op.operation {
109 MemOpKind::Read => {
110 self.mark_solvable(op.value);
111 true
112 }
113 MemOpKind::Write => self.solvable_witnesses.contains(&op.value),
114 }
115 }
116 Opcode::MemoryInit { block_id, init, .. } => {
117 for w in init {
118 if !self.solvable_witnesses.contains(w) {
119 return false;
120 }
121 }
122 self.initialized_blocks.insert(*block_id)
123 }
124 Opcode::BrilligCall { id: _, inputs, outputs, predicate } => {
125 for input in inputs {
126 if !self.can_solve_brillig_input(input) {
127 return false;
128 }
129 }
130 if !self.can_solve_expression(predicate) {
131 return false;
132 }
133 for output in outputs {
134 match output {
135 BrilligOutputs::Simple(w) => self.mark_solvable(*w),
136 BrilligOutputs::Array(arr) => {
137 for w in arr {
138 self.mark_solvable(*w);
139 }
140 }
141 }
142 }
143 true
144 }
145 Opcode::Call { id: _, inputs, outputs, predicate } => {
146 for w in inputs {
147 if !self.solvable_witnesses.contains(w) {
148 return false;
149 }
150 }
151 if !self.can_solve_expression(predicate) {
152 return false;
153 }
154 for w in outputs {
155 self.mark_solvable(*w);
156 }
157 true
158 }
159 }
160 }
161
162 pub(crate) fn mark_solvable(&mut self, witness: Witness) {
164 self.solvable_witnesses.insert(witness);
165 }
166
167 pub fn can_solve_function_input<F: AcirField>(&self, input: &FunctionInput<F>) -> bool {
168 if let FunctionInput::Witness(w) = input {
169 return self.solvable_witnesses.contains(w);
170 }
171 true
172 }
173
174 fn can_solve_expression<F>(&self, expr: &Expression<F>) -> bool {
175 for w in Self::expr_witness(expr) {
176 if !self.solvable_witnesses.contains(&w) {
177 return false;
178 }
179 }
180 true
181 }
182
183 fn can_solve_brillig_input<F>(&self, input: &BrilligInputs<F>) -> bool {
184 match input {
185 BrilligInputs::Single(expr) => self.can_solve_expression(expr),
186 BrilligInputs::Array(exprs) => {
187 for expr in exprs {
188 if !self.can_solve_expression(expr) {
189 return false;
190 }
191 }
192 true
193 }
194
195 BrilligInputs::MemoryArray(block_id) => self.initialized_blocks.contains(block_id),
196 }
197 }
198
199 pub(crate) fn expr_witness<F>(expr: &Expression<F>) -> impl Iterator<Item = Witness> {
200 expr.mul_terms
201 .iter()
202 .flat_map(|i| [i.1, i.2])
203 .chain(expr.linear_combinations.iter().map(|i| i.1))
204 }
205}
206
207pub(crate) fn unresolved_witnesses<F: AcirField>(
216 expr: &Expression<F>,
217 solvable: &HashSet<Witness>,
218) -> Option<HashSet<Witness>> {
219 let combined_mul_terms = ExpressionSolver::combine_mul_terms(&expr.mul_terms);
220 let combined_linear_terms = ExpressionSolver::combine_linear_terms(&expr.linear_combinations);
221 let mut unresolved = HashSet::new();
222 for (_, w1, w2) in &combined_mul_terms {
223 if !solvable.contains(w1) {
224 unresolved.insert(*w1);
225 }
226 if !solvable.contains(w2) {
227 if w2 == w1 {
228 return None;
231 }
232 unresolved.insert(*w2);
233 }
234 }
235 for (_, w) in &combined_linear_terms {
236 if !solvable.contains(w) {
237 unresolved.insert(*w);
238 }
239 }
240 Some(unresolved)
241}
242
243#[cfg(test)]
244mod tests {
245 use crate::compiler::{CircuitSimulator, simulator::SimulationFailure};
246 use acir::{circuit::Circuit, native_types::Witness};
247
248 #[test]
249 fn reports_none_for_empty_circuit() {
250 let src = "
251 private parameters: []
252 public parameters: []
253 return values: []
254 ";
255 let empty_circuit = Circuit::from_str(src).unwrap();
256 assert!(CircuitSimulator::check_circuit(&empty_circuit).is_none());
257 }
258
259 #[test]
260 fn reports_none_for_connected_circuit() {
261 let src = "
262 private parameters: [w1]
263 public parameters: []
264 return values: []
265 ASSERT w2 = w1
266 ";
267 let connected_circuit = Circuit::from_str(src).unwrap();
268 assert!(CircuitSimulator::check_circuit(&connected_circuit).is_none());
269 }
270
271 #[test]
272 fn reports_true_for_connected_circuit_with_range() {
273 let src = "
274 private parameters: [w1, w3]
275 public parameters: []
276 return values: []
277 ASSERT w2 = w1
278 BLACKBOX::RANGE input: w3, bits: 8
279 ";
280 let connected_circuit = Circuit::from_str(src).unwrap();
281
282 assert!(CircuitSimulator::check_circuit(&connected_circuit).is_none());
283 }
284
285 #[test]
286 fn reports_false_for_disconnected_circuit() {
287 let src = "
288 private parameters: [w1]
289 public parameters: []
290 return values: []
291 ASSERT w2 = w1
292 ASSERT w4 = w3
293 ";
294 let disconnected_circuit = Circuit::from_str(src).unwrap();
295
296 assert!(CircuitSimulator::check_circuit(&disconnected_circuit).is_some());
297 }
298
299 #[test]
300 fn reports_none_for_blackbox_output() {
301 let src = "
302 private parameters: [w0, w1]
303 public parameters: []
304 return values: []
305 BLACKBOX::AND lhs: w0, rhs: w1, output: w2, bits: 32
306 ASSERT w3 = w2
307 ";
308 let circuit = Circuit::from_str(src).unwrap();
309 assert!(CircuitSimulator::check_circuit(&circuit).is_none());
310 }
311
312 #[test]
313 fn reports_none_for_read_memory() {
314 let src = "
315 private parameters: [w0]
316 public parameters: []
317 return values: []
318 INIT b0 = [w0]
319 READ w1 = b0[w0]
320 ASSERT w2 = w1
321 ";
322 let circuit = Circuit::from_str(src).unwrap();
323 assert!(CircuitSimulator::check_circuit(&circuit).is_none());
324 }
325
326 #[test]
327 fn reports_none_for_call_output() {
328 let src = "
329 private parameters: [w0]
330 public parameters: []
331 return values: []
332 CALL func: 0, predicate: 1, inputs: [w0], outputs: [w1]
333 ASSERT w2 = w1
334 ";
335 let circuit = Circuit::from_str(src).unwrap();
336 assert!(CircuitSimulator::check_circuit(&circuit).is_none());
337 }
338
339 #[test]
340 fn reports_none_for_brillig_call_output() {
341 let src = "
342 private parameters: [w0]
343 public parameters: []
344 return values: []
345 BRILLIG CALL func: 0, predicate: 1, inputs: [w0], outputs: [w1]
346 ASSERT w2 = w1
347 ";
348 let circuit = Circuit::from_str(src).unwrap();
349 assert!(CircuitSimulator::check_circuit(&circuit).is_none());
350 }
351
352 #[test]
353 fn reports_some_for_disconnected_circuit() {
354 let src = "
355 private parameters: [w1]
356 public parameters: []
357 return values: []
358 ASSERT w2 = w1
359 ASSERT w4 = w3
360 ";
361 let disconnected_circuit = Circuit::from_str(src).unwrap();
362 assert_eq!(
363 CircuitSimulator::check_circuit(&disconnected_circuit),
364 Some(SimulationFailure::UnsolvableOpcode(1))
365 );
366 }
367
368 #[test]
369 fn reports_some_when_memory_block_is_passed_an_unknown_witness() {
370 let src = "
371 private parameters: [w1]
372 public parameters: []
373 return values: []
374 ASSERT w1 = 0
375 INIT b0 = [w0]
376 ";
377 let circuit = Circuit::from_str(src).unwrap();
378 assert_eq!(
379 CircuitSimulator::check_circuit(&circuit),
380 Some(SimulationFailure::UnsolvableOpcode(1))
381 );
382 }
383
384 #[test]
385 fn reports_some_when_attempting_to_reinitialize_memory_block() {
386 let src = "
387 private parameters: [w0]
388 public parameters: []
389 return values: []
390 INIT b0 = [w0]
391 INIT b0 = [w0]
392 ";
393 let circuit = Circuit::from_str(src).unwrap();
394 assert_eq!(
395 CircuitSimulator::check_circuit(&circuit),
396 Some(SimulationFailure::UnsolvableOpcode(1))
397 );
398 }
399
400 #[test]
401 fn reports_some_when_unknown_witness_is_multiplied_by_itself() {
402 let src = "
405 private parameters: [w0]
406 public parameters: []
407 return values: []
408 ASSERT w0 = w1*w1
409 ";
410 let circuit = Circuit::from_str(src).unwrap();
411 assert_eq!(
412 CircuitSimulator::check_circuit(&circuit),
413 Some(SimulationFailure::UnsolvableOpcode(0))
414 );
415 }
416
417 #[test]
418 fn reports_some_when_write_has_a_single_unknown_witness_in_its_value() {
419 let src = "
420 private parameters: [w0, w1]
421 public parameters: []
422 return values: []
423 INIT b0 = [w0]
424 WRITE b0[w0] = w2
425 ";
426 let circuit = Circuit::from_str(src).unwrap();
427 assert_eq!(
428 CircuitSimulator::check_circuit(&circuit),
429 Some(SimulationFailure::UnsolvableOpcode(1))
430 );
431 }
432
433 #[test]
434 fn reports_none_when_write_has_known_witnesses_in_its_value() {
435 let src = "
436 private parameters: [w0, w1, w2]
437 public parameters: []
438 return values: []
439 INIT b0 = [w0]
440 WRITE b0[w0] = w1 + w2
441 ";
442 let circuit = Circuit::from_str(src).unwrap();
443 assert_eq!(CircuitSimulator::check_circuit(&circuit), None);
444 }
445
446 #[test]
447 fn reports_failure_for_uncomputed_return_value() {
448 let src = "
449 private parameters: [w0]
450 public parameters: []
451 return values: [w2]
452 ASSERT w1 = w0
453 ";
454 let circuit = Circuit::from_str(src).unwrap();
455 assert_eq!(
456 CircuitSimulator::check_circuit(&circuit),
457 Some(SimulationFailure::UnsolvableOutput(Witness(2)))
458 );
459 }
460
461 #[test]
462 fn reports_none_for_computed_return_value() {
463 let src = "
464 private parameters: [w0]
465 public parameters: []
466 return values: [w1]
467 ASSERT w1 = w0
468 ";
469 let circuit = Circuit::from_str(src).unwrap();
470 assert!(CircuitSimulator::check_circuit(&circuit).is_none());
471 }
472
473 #[test]
474 fn reports_none_for_return_value_that_is_also_a_parameter() {
475 let src = "
476 private parameters: [w0]
477 public parameters: []
478 return values: [w0]
479 ";
480 let circuit = Circuit::from_str(src).unwrap();
481 assert!(CircuitSimulator::check_circuit(&circuit).is_none());
482 }
483
484 #[test]
485 fn reports_some_when_expression_can_simplify() {
486 let src = "
487 private parameters: []
488 public parameters: []
489 return values: []
490 ASSERT w1 = w1
491 ASSERT w2 = w1
492 ";
493 let empty_circuit = Circuit::from_str(src).unwrap();
494 assert_eq!(
495 CircuitSimulator::check_circuit(&empty_circuit),
496 Some(SimulationFailure::UnsolvableOpcode(1))
497 );
498 }
499}