-
Notifications
You must be signed in to change notification settings - Fork 204
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: build simple dictionary from inspecting ACIR program (#5264)
# Description ## Problem\* Resolves #5262 ## Summary\* This PR pulls out a bunch of relevant constants from the ACIR/brillig bytecode so we can feed these into the fuzzer's strategy to ensure proper coverage of these values. ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings.
- Loading branch information
1 parent
318314d
commit 508e677
Showing
6 changed files
with
193 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
[package] | ||
name = "fuzzer_checks" | ||
type = "bin" | ||
authors = [""] | ||
[dependencies] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
|
||
#[test(should_fail_with = "42 is not allowed")] | ||
fn finds_magic_value(x: u32) { | ||
let x = x as u64; | ||
assert(2 * x != 42, "42 is not allowed"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
//! This module defines how to build a dictionary of values which are likely to be correspond | ||
//! to significant inputs during fuzzing by inspecting the [Program] being fuzzed. | ||
//! | ||
//! This dictionary can be fed into the fuzzer's [strategy][proptest::strategy::Strategy] in order to bias it towards | ||
//! generating these values to ensure they get proper coverage. | ||
use std::collections::HashSet; | ||
|
||
use acvm::{ | ||
acir::{ | ||
circuit::{ | ||
brillig::{BrilligBytecode, BrilligInputs}, | ||
directives::Directive, | ||
opcodes::{BlackBoxFuncCall, FunctionInput}, | ||
Circuit, Opcode, Program, | ||
}, | ||
native_types::Expression, | ||
}, | ||
brillig_vm::brillig::Opcode as BrilligOpcode, | ||
AcirField, | ||
}; | ||
|
||
/// Constructs a [HashSet<F>] of values pulled from a [Program<F>] which are likely to be correspond | ||
/// to significant inputs during fuzzing. | ||
pub(super) fn build_dictionary_from_program<F: AcirField>(program: &Program<F>) -> HashSet<F> { | ||
let constrained_dictionaries = program.functions.iter().map(build_dictionary_from_circuit); | ||
let unconstrained_dictionaries = | ||
program.unconstrained_functions.iter().map(build_dictionary_from_unconstrained_function); | ||
let dictionaries = constrained_dictionaries.chain(unconstrained_dictionaries); | ||
|
||
let mut constants: HashSet<F> = HashSet::new(); | ||
for dictionary in dictionaries { | ||
constants.extend(dictionary); | ||
} | ||
constants | ||
} | ||
|
||
fn build_dictionary_from_circuit<F: AcirField>(circuit: &Circuit<F>) -> HashSet<F> { | ||
let mut constants: HashSet<F> = HashSet::new(); | ||
|
||
fn insert_expr<F: AcirField>(dictionary: &mut HashSet<F>, expr: &Expression<F>) { | ||
let quad_coefficients = expr.mul_terms.iter().map(|(k, _, _)| *k); | ||
let linear_coefficients = expr.linear_combinations.iter().map(|(k, _)| *k); | ||
let coefficients = linear_coefficients.chain(quad_coefficients); | ||
|
||
dictionary.extend(coefficients.clone()); | ||
dictionary.insert(expr.q_c); | ||
|
||
// We divide the constant term by any coefficients in the expression to aid solving constraints such as `2 * x - 4 == 0`. | ||
let scaled_constants = coefficients.map(|coefficient| expr.q_c / coefficient); | ||
dictionary.extend(scaled_constants); | ||
} | ||
|
||
fn insert_array_len<F: AcirField, T>(dictionary: &mut HashSet<F>, array: &[T]) { | ||
let array_length = array.len() as u128; | ||
dictionary.insert(F::from(array_length)); | ||
dictionary.insert(F::from(array_length - 1)); | ||
} | ||
|
||
for opcode in &circuit.opcodes { | ||
match opcode { | ||
Opcode::AssertZero(expr) | ||
| Opcode::Call { predicate: Some(expr), .. } | ||
| Opcode::MemoryOp { predicate: Some(expr), .. } | ||
| Opcode::Directive(Directive::ToLeRadix { a: expr, .. }) => { | ||
insert_expr(&mut constants, expr) | ||
} | ||
|
||
Opcode::MemoryInit { init, .. } => insert_array_len(&mut constants, init), | ||
|
||
Opcode::BrilligCall { inputs, predicate, .. } => { | ||
for input in inputs { | ||
match input { | ||
BrilligInputs::Single(expr) => insert_expr(&mut constants, expr), | ||
BrilligInputs::Array(exprs) => { | ||
exprs.iter().for_each(|expr| insert_expr(&mut constants, expr)); | ||
insert_array_len(&mut constants, exprs); | ||
} | ||
BrilligInputs::MemoryArray(_) => (), | ||
} | ||
} | ||
if let Some(predicate) = predicate { | ||
insert_expr(&mut constants, predicate) | ||
} | ||
} | ||
|
||
Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { | ||
input: FunctionInput { num_bits, .. }, | ||
}) => { | ||
let field = 1u128.wrapping_shl(*num_bits); | ||
constants.insert(F::from(field)); | ||
constants.insert(F::from(field - 1)); | ||
} | ||
_ => (), | ||
} | ||
} | ||
|
||
constants | ||
} | ||
|
||
fn build_dictionary_from_unconstrained_function<F: AcirField>( | ||
function: &BrilligBytecode<F>, | ||
) -> HashSet<F> { | ||
let mut constants: HashSet<F> = HashSet::new(); | ||
|
||
for opcode in &function.bytecode { | ||
match opcode { | ||
BrilligOpcode::Cast { bit_size, .. } => { | ||
let field = 1u128.wrapping_shl(*bit_size); | ||
constants.insert(F::from(field)); | ||
constants.insert(F::from(field - 1)); | ||
} | ||
BrilligOpcode::Const { bit_size, value, .. } => { | ||
constants.insert(*value); | ||
|
||
let field = 1u128.wrapping_shl(*bit_size); | ||
constants.insert(F::from(field)); | ||
constants.insert(F::from(field - 1)); | ||
} | ||
_ => (), | ||
} | ||
} | ||
|
||
constants | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters