use acir::AcirField;
use acvm_blackbox_solver::BlackBoxFunctionSolver;
use num_bigint::BigUint;
use crate::{
BinaryFieldOp, BinaryIntOp, MemoryValue, NextOpcodePositionOrState, OpcodePosition, VM,
};
use std::collections::HashMap;
const FUZZING_COMPARISON_TRUE_STATE: usize = usize::MAX - 1;
const FUZZING_COMPARISON_FALSE_STATE: usize = usize::MAX;
const FUZZING_COMPARISON_LOG_RANGE_START_STATE: usize = 0;
pub type Branch = (OpcodePosition, NextOpcodePositionOrState);
pub type UniqueFeatureIndex = usize;
pub type BranchToFeatureMap = HashMap<Branch, UniqueFeatureIndex>;
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub(super) struct FuzzingTrace {
trace: Vec<u32>,
branch_to_feature_map: HashMap<(usize, usize), usize>,
}
impl FuzzingTrace {
fn branch_state(cond: bool) -> usize {
if cond { FUZZING_COMPARISON_TRUE_STATE } else { FUZZING_COMPARISON_FALSE_STATE }
}
fn log_range_state(log: usize) -> usize {
FUZZING_COMPARISON_LOG_RANGE_START_STATE + log
}
fn field_diff_log<F: AcirField>(a: F, b: F) -> u64 {
let d = if a > b { a - b } else { b - a };
BigUint::from_bytes_be(&d.to_be_bytes()).bits()
}
fn int_diff_log(a: u128, b: u128) -> u32 {
a.abs_diff(b).checked_ilog2().map_or(0, |x| x + 1)
}
pub(super) fn new(branch_to_feature_map: HashMap<(usize, usize), usize>) -> Self {
let len = branch_to_feature_map.len();
Self { trace: vec![0; len], branch_to_feature_map }
}
fn record_branch(&mut self, pc: usize, destination: usize) {
let index = self.branch_to_feature_map[&(pc, destination)];
self.trace[index] += 1;
}
fn record_conditional_mov(&mut self, pc: usize, branch: bool) {
let index = self.branch_to_feature_map[&(pc, Self::branch_state(branch))];
self.trace[index] += 1;
}
fn record_binary_field_op_comparison<F: AcirField>(
&mut self,
pc: usize,
op: &BinaryFieldOp,
lhs: MemoryValue<F>,
rhs: MemoryValue<F>,
result: MemoryValue<F>,
) {
match op {
BinaryFieldOp::Equals | BinaryFieldOp::LessThan | BinaryFieldOp::LessThanEquals => {
let MemoryValue::Field(a) = lhs else {
return;
};
let MemoryValue::Field(b) = rhs else {
return;
};
let MemoryValue::U1(c) = result else {
return;
};
let diff_log = Self::field_diff_log(a, b);
let approach_index =
self.branch_to_feature_map[&(pc, Self::log_range_state(diff_log as usize))];
let condition_index = self.branch_to_feature_map[&(pc, Self::branch_state(c))];
self.trace[approach_index] += 1;
self.trace[condition_index] += 1;
}
BinaryFieldOp::Add
| BinaryFieldOp::Sub
| BinaryFieldOp::Mul
| BinaryFieldOp::Div
| BinaryFieldOp::IntegerDiv => {}
}
}
fn record_binary_int_op_comparison<F: AcirField>(
&mut self,
pc: usize,
op: &BinaryIntOp,
lhs: MemoryValue<F>,
rhs: MemoryValue<F>,
result: MemoryValue<F>,
) {
match op {
BinaryIntOp::Equals | BinaryIntOp::LessThan | BinaryIntOp::LessThanEquals => {
let lhs_val = lhs.to_u128().expect("lhs is not an integer");
let rhs_val = rhs.to_u128().expect("rhs is not an integer");
let MemoryValue::U1(c) = result else {
return;
};
let diff_log = Self::int_diff_log(lhs_val, rhs_val);
let approach_index =
self.branch_to_feature_map[&(pc, Self::log_range_state(diff_log as usize))];
let condition_index = self.branch_to_feature_map[&(pc, Self::branch_state(c))];
self.trace[approach_index] += 1;
self.trace[condition_index] += 1;
}
BinaryIntOp::Add
| BinaryIntOp::Sub
| BinaryIntOp::Mul
| BinaryIntOp::Div
| BinaryIntOp::And
| BinaryIntOp::Or
| BinaryIntOp::Xor
| BinaryIntOp::Shl
| BinaryIntOp::Shr => {}
}
}
pub(super) fn get_trace(&self) -> Vec<u32> {
self.trace.clone()
}
}
impl<F: AcirField, B: BlackBoxFunctionSolver<F>> VM<'_, F, B> {
pub(super) fn fuzzing_trace_binary_field_op_comparison(
&mut self,
op: &BinaryFieldOp,
lhs: MemoryValue<F>,
rhs: MemoryValue<F>,
result: MemoryValue<F>,
) {
if let Some(ref mut trace) = self.fuzzing_trace {
trace.record_binary_field_op_comparison(self.program_counter, op, lhs, rhs, result);
}
}
pub(super) fn fuzzing_trace_binary_int_op_comparison(
&mut self,
op: &BinaryIntOp,
lhs: MemoryValue<F>,
rhs: MemoryValue<F>,
result: MemoryValue<F>,
) {
if let Some(ref mut trace) = self.fuzzing_trace {
trace.record_binary_int_op_comparison(self.program_counter, op, lhs, rhs, result);
}
}
pub(super) fn fuzzing_trace_branching(&mut self, destination: NextOpcodePositionOrState) {
if let Some(ref mut trace) = self.fuzzing_trace {
trace.record_branch(self.program_counter, destination);
}
}
pub(super) fn fuzzing_trace_conditional_mov(&mut self, branch: bool) {
if let Some(ref mut trace) = self.fuzzing_trace {
trace.record_conditional_mov(self.program_counter, branch);
}
}
}
#[cfg(test)]
mod tests {
use acir::FieldElement;
use proptest::proptest;
use crate::FuzzingTrace;
proptest! {
#[test]
fn int_diff_log_is_symmetric(a: u128, b: u128) {
let ab = FuzzingTrace::int_diff_log(a, b);
let ba = FuzzingTrace::int_diff_log(b, a);
assert_eq!(ab, ba);
}
}
proptest! {
#[test]
fn field_diff_log_is_symmetric(a: u128, b: u128) {
let a = FieldElement::from(a);
let b = FieldElement::from(b);
let ab = FuzzingTrace::field_diff_log(a, b);
let ba = FuzzingTrace::field_diff_log(b, a);
assert_eq!(ab, ba);
}
}
#[test]
fn field_diff_log_with_1_diff() {
let a = FieldElement::from(1);
let b = FieldElement::from(2);
let ab = FuzzingTrace::field_diff_log(a, b);
let ba = FuzzingTrace::field_diff_log(b, a);
assert_eq!(ab, 1);
assert_eq!(ab, ba);
}
}