1use acir::AcirField;
2use acvm_blackbox_solver::BlackBoxFunctionSolver;
3use num_bigint::BigUint;
4
5use crate::{
6 BinaryFieldOp, BinaryIntOp, MemoryValue, NextOpcodePositionOrState, OpcodePosition, VM,
7};
8use std::collections::HashMap;
9
10const FUZZING_COMPARISON_TRUE_STATE: usize = usize::MAX - 1;
12const FUZZING_COMPARISON_FALSE_STATE: usize = usize::MAX;
14
15const FUZZING_COMPARISON_LOG_RANGE_START_STATE: usize = 0;
17
18pub type Branch = (OpcodePosition, NextOpcodePositionOrState);
20
21pub type UniqueFeatureIndex = usize;
23
24pub type BranchToFeatureMap = HashMap<Branch, UniqueFeatureIndex>;
26
27#[derive(Debug, PartialEq, Eq, Clone, Default)]
29pub(super) struct FuzzingTrace {
30 trace: Vec<u32>,
35 branch_to_feature_map: HashMap<(usize, usize), usize>,
38}
39
40impl FuzzingTrace {
41 fn branch_state(cond: bool) -> usize {
42 if cond { FUZZING_COMPARISON_TRUE_STATE } else { FUZZING_COMPARISON_FALSE_STATE }
43 }
44
45 fn log_range_state(log: usize) -> usize {
46 FUZZING_COMPARISON_LOG_RANGE_START_STATE + log
47 }
48
49 fn field_diff_log<F: AcirField>(a: F, b: F) -> u64 {
52 let d = if a > b { a - b } else { b - a };
59 BigUint::from_bytes_be(&d.to_be_bytes()).bits()
60 }
61
62 fn int_diff_log(a: u128, b: u128) -> u32 {
65 a.abs_diff(b).checked_ilog2().map_or(0, |x| x + 1)
66 }
67
68 pub(super) fn new(branch_to_feature_map: HashMap<(usize, usize), usize>) -> Self {
69 let len = branch_to_feature_map.len();
70 Self { trace: vec![0; len], branch_to_feature_map }
71 }
72
73 fn record_branch(&mut self, pc: usize, destination: usize) {
74 let index = self.branch_to_feature_map[&(pc, destination)];
75 self.trace[index] += 1;
76 }
77
78 fn record_conditional_mov(&mut self, pc: usize, branch: bool) {
79 let index = self.branch_to_feature_map[&(pc, Self::branch_state(branch))];
80 self.trace[index] += 1;
81 }
82
83 fn record_binary_field_op_comparison<F: AcirField>(
84 &mut self,
85 pc: usize,
86 op: &BinaryFieldOp,
87 lhs: MemoryValue<F>,
88 rhs: MemoryValue<F>,
89 result: MemoryValue<F>,
90 ) {
91 match op {
92 BinaryFieldOp::Equals | BinaryFieldOp::LessThan | BinaryFieldOp::LessThanEquals => {
93 let MemoryValue::Field(a) = lhs else {
94 return;
95 };
96 let MemoryValue::Field(b) = rhs else {
97 return;
98 };
99 let MemoryValue::U1(c) = result else {
100 return;
101 };
102
103 let diff_log = Self::field_diff_log(a, b);
105
106 let approach_index =
107 self.branch_to_feature_map[&(pc, Self::log_range_state(diff_log as usize))];
108
109 let condition_index = self.branch_to_feature_map[&(pc, Self::branch_state(c))];
110
111 self.trace[approach_index] += 1;
112 self.trace[condition_index] += 1;
113 }
114 BinaryFieldOp::Add
115 | BinaryFieldOp::Sub
116 | BinaryFieldOp::Mul
117 | BinaryFieldOp::Div
118 | BinaryFieldOp::IntegerDiv => {}
119 }
120 }
121
122 fn record_binary_int_op_comparison<F: AcirField>(
123 &mut self,
124 pc: usize,
125 op: &BinaryIntOp,
126 lhs: MemoryValue<F>,
127 rhs: MemoryValue<F>,
128 result: MemoryValue<F>,
129 ) {
130 match op {
131 BinaryIntOp::Equals | BinaryIntOp::LessThan | BinaryIntOp::LessThanEquals => {
132 let lhs_val = lhs.to_u128().expect("lhs is not an integer");
133 let rhs_val = rhs.to_u128().expect("rhs is not an integer");
134 let MemoryValue::U1(c) = result else {
135 return;
136 };
137
138 let diff_log = Self::int_diff_log(lhs_val, rhs_val);
139
140 let approach_index =
141 self.branch_to_feature_map[&(pc, Self::log_range_state(diff_log as usize))];
142
143 let condition_index = self.branch_to_feature_map[&(pc, Self::branch_state(c))];
144
145 self.trace[approach_index] += 1;
146 self.trace[condition_index] += 1;
147 }
148 BinaryIntOp::Add
149 | BinaryIntOp::Sub
150 | BinaryIntOp::Mul
151 | BinaryIntOp::Div
152 | BinaryIntOp::And
153 | BinaryIntOp::Or
154 | BinaryIntOp::Xor
155 | BinaryIntOp::Shl
156 | BinaryIntOp::Shr => {}
157 }
158 }
159
160 pub(super) fn get_trace(&self) -> Vec<u32> {
161 self.trace.clone()
162 }
163}
164
165impl<F: AcirField, B: BlackBoxFunctionSolver<F>> VM<'_, F, B> {
166 pub(super) fn fuzzing_trace_binary_field_op_comparison(
168 &mut self,
169 op: &BinaryFieldOp,
170 lhs: MemoryValue<F>,
171 rhs: MemoryValue<F>,
172 result: MemoryValue<F>,
173 ) {
174 if let Some(ref mut trace) = self.fuzzing_trace {
175 trace.record_binary_field_op_comparison(self.program_counter, op, lhs, rhs, result);
176 }
177 }
178
179 pub(super) fn fuzzing_trace_binary_int_op_comparison(
181 &mut self,
182 op: &BinaryIntOp,
183 lhs: MemoryValue<F>,
184 rhs: MemoryValue<F>,
185 result: MemoryValue<F>,
186 ) {
187 if let Some(ref mut trace) = self.fuzzing_trace {
188 trace.record_binary_int_op_comparison(self.program_counter, op, lhs, rhs, result);
189 }
190 }
191
192 pub(super) fn fuzzing_trace_branching(&mut self, destination: NextOpcodePositionOrState) {
194 if let Some(ref mut trace) = self.fuzzing_trace {
195 trace.record_branch(self.program_counter, destination);
196 }
197 }
198
199 pub(super) fn fuzzing_trace_conditional_mov(&mut self, branch: bool) {
201 if let Some(ref mut trace) = self.fuzzing_trace {
202 trace.record_conditional_mov(self.program_counter, branch);
203 }
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use acir::FieldElement;
210 use proptest::proptest;
211
212 use crate::FuzzingTrace;
213
214 proptest! {
215 #[test]
216 fn int_diff_log_is_symmetric(a: u128, b: u128) {
217 let ab = FuzzingTrace::int_diff_log(a, b);
218 let ba = FuzzingTrace::int_diff_log(b, a);
219 assert_eq!(ab, ba);
220 }
221 }
222
223 proptest! {
224 #[test]
225 fn field_diff_log_is_symmetric(a: u128, b: u128) {
226 let a = FieldElement::from(a);
227 let b = FieldElement::from(b);
228 let ab = FuzzingTrace::field_diff_log(a, b);
229 let ba = FuzzingTrace::field_diff_log(b, a);
230 assert_eq!(ab, ba);
231 }
232 }
233
234 #[test]
235 fn field_diff_log_with_1_diff() {
236 let a = FieldElement::from(1);
237 let b = FieldElement::from(2);
238 let ab = FuzzingTrace::field_diff_log(a, b);
239 let ba = FuzzingTrace::field_diff_log(b, a);
240 assert_eq!(ab, 1);
241 assert_eq!(ab, ba);
242 }
243}