|
| 1 | +//! This module defines how to build a dictionary of values which are likely to be correspond |
| 2 | +//! to significant inputs during fuzzing by inspecting the [Program] being fuzzed. |
| 3 | +//! |
| 4 | +//! This dictionary can be fed into the fuzzer's [strategy][proptest::strategy::Strategy] in order to bias it towards |
| 5 | +//! generating these values to ensure they get proper coverage. |
| 6 | +use std::collections::HashSet; |
| 7 | + |
| 8 | +use acvm::{ |
| 9 | + acir::{ |
| 10 | + circuit::{ |
| 11 | + brillig::{BrilligBytecode, BrilligInputs}, |
| 12 | + directives::Directive, |
| 13 | + opcodes::{BlackBoxFuncCall, FunctionInput}, |
| 14 | + Circuit, Opcode, Program, |
| 15 | + }, |
| 16 | + native_types::Expression, |
| 17 | + }, |
| 18 | + brillig_vm::brillig::Opcode as BrilligOpcode, |
| 19 | + AcirField, |
| 20 | +}; |
| 21 | + |
| 22 | +/// Constructs a [HashSet<F>] of values pulled from a [Program<F>] which are likely to be correspond |
| 23 | +/// to significant inputs during fuzzing. |
| 24 | +pub(super) fn build_dictionary_from_program<F: AcirField>(program: &Program<F>) -> HashSet<F> { |
| 25 | + let constrained_dictionaries = program.functions.iter().map(build_dictionary_from_circuit); |
| 26 | + let unconstrained_dictionaries = |
| 27 | + program.unconstrained_functions.iter().map(build_dictionary_from_unconstrained_function); |
| 28 | + let dictionaries = constrained_dictionaries.chain(unconstrained_dictionaries); |
| 29 | + |
| 30 | + let mut constants: HashSet<F> = HashSet::new(); |
| 31 | + for dictionary in dictionaries { |
| 32 | + constants.extend(dictionary); |
| 33 | + } |
| 34 | + constants |
| 35 | +} |
| 36 | + |
| 37 | +fn build_dictionary_from_circuit<F: AcirField>(circuit: &Circuit<F>) -> HashSet<F> { |
| 38 | + let mut constants: HashSet<F> = HashSet::new(); |
| 39 | + |
| 40 | + fn insert_expr<F: AcirField>(dictionary: &mut HashSet<F>, expr: &Expression<F>) { |
| 41 | + let quad_coefficients = expr.mul_terms.iter().map(|(k, _, _)| *k); |
| 42 | + let linear_coefficients = expr.linear_combinations.iter().map(|(k, _)| *k); |
| 43 | + let coefficients = linear_coefficients.chain(quad_coefficients); |
| 44 | + |
| 45 | + dictionary.extend(coefficients.clone()); |
| 46 | + dictionary.insert(expr.q_c); |
| 47 | + |
| 48 | + // We divide the constant term by any coefficients in the expression to aid solving constraints such as `2 * x - 4 == 0`. |
| 49 | + let scaled_constants = coefficients.map(|coefficient| expr.q_c / coefficient); |
| 50 | + dictionary.extend(scaled_constants); |
| 51 | + } |
| 52 | + |
| 53 | + fn insert_array_len<F: AcirField, T>(dictionary: &mut HashSet<F>, array: &[T]) { |
| 54 | + let array_length = array.len() as u128; |
| 55 | + dictionary.insert(F::from(array_length)); |
| 56 | + dictionary.insert(F::from(array_length - 1)); |
| 57 | + } |
| 58 | + |
| 59 | + for opcode in &circuit.opcodes { |
| 60 | + match opcode { |
| 61 | + Opcode::AssertZero(expr) |
| 62 | + | Opcode::Call { predicate: Some(expr), .. } |
| 63 | + | Opcode::MemoryOp { predicate: Some(expr), .. } |
| 64 | + | Opcode::Directive(Directive::ToLeRadix { a: expr, .. }) => { |
| 65 | + insert_expr(&mut constants, expr) |
| 66 | + } |
| 67 | + |
| 68 | + Opcode::MemoryInit { init, .. } => insert_array_len(&mut constants, init), |
| 69 | + |
| 70 | + Opcode::BrilligCall { inputs, predicate, .. } => { |
| 71 | + for input in inputs { |
| 72 | + match input { |
| 73 | + BrilligInputs::Single(expr) => insert_expr(&mut constants, expr), |
| 74 | + BrilligInputs::Array(exprs) => { |
| 75 | + exprs.iter().for_each(|expr| insert_expr(&mut constants, expr)); |
| 76 | + insert_array_len(&mut constants, exprs); |
| 77 | + } |
| 78 | + BrilligInputs::MemoryArray(_) => (), |
| 79 | + } |
| 80 | + } |
| 81 | + if let Some(predicate) = predicate { |
| 82 | + insert_expr(&mut constants, predicate) |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { |
| 87 | + input: FunctionInput { num_bits, .. }, |
| 88 | + }) => { |
| 89 | + let field = 1u128.wrapping_shl(*num_bits); |
| 90 | + constants.insert(F::from(field)); |
| 91 | + constants.insert(F::from(field - 1)); |
| 92 | + } |
| 93 | + _ => (), |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + constants |
| 98 | +} |
| 99 | + |
| 100 | +fn build_dictionary_from_unconstrained_function<F: AcirField>( |
| 101 | + function: &BrilligBytecode<F>, |
| 102 | +) -> HashSet<F> { |
| 103 | + let mut constants: HashSet<F> = HashSet::new(); |
| 104 | + |
| 105 | + for opcode in &function.bytecode { |
| 106 | + match opcode { |
| 107 | + BrilligOpcode::Cast { bit_size, .. } => { |
| 108 | + let field = 1u128.wrapping_shl(*bit_size); |
| 109 | + constants.insert(F::from(field)); |
| 110 | + constants.insert(F::from(field - 1)); |
| 111 | + } |
| 112 | + BrilligOpcode::Const { bit_size, value, .. } => { |
| 113 | + constants.insert(*value); |
| 114 | + |
| 115 | + let field = 1u128.wrapping_shl(*bit_size); |
| 116 | + constants.insert(F::from(field)); |
| 117 | + constants.insert(F::from(field - 1)); |
| 118 | + } |
| 119 | + _ => (), |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + constants |
| 124 | +} |
0 commit comments