Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
invert_differential.fuzzer.cpp
Go to the documentation of this file.
1
33#include <cassert>
34#include <cstddef>
35#include <cstdio>
36#include <cstdlib>
37#include <cstring>
38#include <vector>
39
40using namespace bb;
42
43// ---------------------------------------------------------------
44// Phase header — same 2-byte layout as multi_field.fuzzer.cpp but restricted
45// to the two 254-bit fields that actually use BY.
46// ---------------------------------------------------------------
47enum class FieldType : uint8_t {
48 BN254_FQ = 0,
49 BN254_FR = 1,
50};
51
52static constexpr size_t NUM_FIELD_TYPES = 2;
53static constexpr size_t MAX_STEPS = 64;
54static constexpr size_t PHASE_HEADER_SIZE = 2;
55
57 uint8_t field_type;
58 uint8_t steps;
59};
60static_assert(sizeof(VMPhaseHeader) == 2, "VMPhaseHeader must be 2 bytes");
61
62template <typename Field> static uint256_t reduce_to_modulus(const uint256_t& value)
63{
64 return (value < Field::modulus) ? value : (value % Field::modulus);
65}
66
67template <typename Field>
68static void import_state_with_reduction(FieldVM<Field>& vm, const std::vector<uint256_t>& state)
69{
70 for (size_t i = 0; i < INTERNAL_STATE_SIZE && i < state.size(); i++) {
71 vm.uint_internal_state[i] = reduce_to_modulus<Field>(state[i]);
72 vm.field_internal_state[i] = Field(vm.uint_internal_state[i]);
73 }
74}
75
76// ---------------------------------------------------------------
77// Differential oracle.
78//
79// Fetches `a_raw` (the non-Montgomery integer) from the VM's uint state and
80// computes a^{-1} two ways; aborts on mismatch.
81// ---------------------------------------------------------------
82template <typename Field> static Field raw_to_montgomery(const uint256_t& raw)
83{
84 Field f{ raw.data[0], raw.data[1], raw.data[2], raw.data[3] };
85 f.self_to_montgomery_form();
86 return f;
87}
88
89static void print_limbs(const char* label, const uint256_t& v)
90{
91 std::fprintf(stderr,
92 " %s = 0x%016lx%016lx%016lx%016lx\n",
93 label,
94 (unsigned long)v.data[3],
95 (unsigned long)v.data[2],
96 (unsigned long)v.data[1],
97 (unsigned long)v.data[0]);
98}
99
100template <typename Field> static void differential_check_inverse(const Field& a_mont, const uint256_t& a_raw)
101{
102 if (a_raw == 0) {
103 return; // 0 has no inverse — skip.
104 }
105
106 // A: Fermat via pow. We bypass field::invert() (which now dispatches into
107 // BY) by calling pow(modulus_minus_two) directly, so the paths are
108 // genuinely independent implementations.
109 Field fermat_inv = a_mont.pow(Field::modulus_minus_two);
110
111 // B, C: Bernstein-Yang safegcd, called with the raw (non-Montgomery) value
112 // on both the Native5x64 (BATCH=62) and Wasm9x29 (BATCH=58) kernels.
113 // Each kernel needs its own p_inv_mod_2^BATCH constant.
114 constexpr uint256_t p_uint = Field::modulus;
115 constexpr uint64_t p_inv_native =
117 constexpr uint64_t p_inv_wasm = bernstein_yang::Wasm9x29::p_inv_mod_2k_from_montgomery_r_inv(Field::Params::r_inv);
118
119 uint256_t native_inv_raw = bernstein_yang::invert_vartime<bernstein_yang::Native5x64>(a_raw, p_uint, p_inv_native);
120 uint256_t wasm_inv_raw = bernstein_yang::invert_vartime<bernstein_yang::Wasm9x29>(a_raw, p_uint, p_inv_wasm);
121
122 Field native_inv = raw_to_montgomery<Field>(native_inv_raw);
123 Field wasm_inv = raw_to_montgomery<Field>(wasm_inv_raw);
124
125 const bool native_ok = (fermat_inv == native_inv);
126 const bool wasm_ok = (fermat_inv == wasm_inv);
127 if (native_ok && wasm_ok) {
128 return;
129 }
130
131 std::fprintf(stderr, "\n[invert_differential.fuzzer] MISMATCH\n");
132 std::fprintf(stderr, " field: %s\n", typeid(Field).name());
133 std::fprintf(stderr, " native_ok: %s\n", native_ok ? "yes" : "NO");
134 std::fprintf(stderr, " wasm_ok: %s\n", wasm_ok ? "yes" : "NO");
135 print_limbs("a_raw ", a_raw);
136 print_limbs("fermat ", static_cast<uint256_t>(fermat_inv));
137 print_limbs("BY native ", static_cast<uint256_t>(native_inv));
138 print_limbs("BY wasm ", static_cast<uint256_t>(wasm_inv));
139 print_limbs("a*fermat ", static_cast<uint256_t>(a_mont * fermat_inv));
140 print_limbs("a*native ", static_cast<uint256_t>(a_mont * native_inv));
141 print_limbs("a*wasm ", static_cast<uint256_t>(a_mont * wasm_inv));
142 std::fflush(stderr);
143 std::abort();
144}
145
146// Pick the last element produced: highest-indexed non-zero slot of the VM's
147// uint state, with a fallback to slot 0 if all slots are zero.
148static size_t last_element_index(const std::vector<uint256_t>& state)
149{
150 for (size_t i = state.size(); i > 0; --i) {
151 if (state[i - 1] != uint256_t(0)) {
152 return i - 1;
153 }
154 }
155 return 0;
156}
157
158template <typename Field>
159static int run_phase_and_diff(const VMPhaseHeader& header,
160 const unsigned char* data,
161 size_t size,
162 size_t& data_offset,
163 std::vector<uint256_t>& current_state)
164{
165 FieldVM<Field> vm(false, header.steps);
166 if (!current_state.empty()) {
167 import_state_with_reduction<Field>(vm, current_state);
168 }
169 vm.set_max_steps(header.steps);
170 size_t bytes_consumed = vm.run(data + data_offset, size - data_offset, true);
171 if (bytes_consumed == 0) {
172 return 0;
173 }
174
175 if (!vm.check_internal_state()) {
176 // Internal VM invariant violation — not the inverse bug we're looking
177 // for, but still a failure of the driver. Report and stop.
178 std::fprintf(stderr, "[invert_differential.fuzzer] VM internal state check failed\n");
179 return -1;
180 }
181
182 // Differential inverse check on the last element produced this phase,
183 // plus every non-zero slot in the final state for extra coverage.
184 auto uint_state = vm.export_uint_state();
185 size_t last_idx = last_element_index(uint_state);
186 differential_check_inverse<Field>(vm.field_internal_state[last_idx], uint_state[last_idx]);
187
188 // Extra coverage: also diff every other non-zero slot. Same check on
189 // many more values per phase, virtually free CPU-wise.
190 for (size_t i = 0; i < uint_state.size(); ++i) {
191 if (i != last_idx && uint_state[i] != uint256_t(0)) {
192 differential_check_inverse<Field>(vm.field_internal_state[i], uint_state[i]);
193 }
194 }
195
196 current_state = uint_state;
197 data_offset += bytes_consumed;
198 return 1;
199}
200
201extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size)
202{
203 if (size < PHASE_HEADER_SIZE) {
204 return 0;
205 }
206
207 std::vector<uint256_t> current_state;
208 size_t data_offset = 0;
209
210 while (data_offset + PHASE_HEADER_SIZE <= size) {
211 const VMPhaseHeader* header_ptr = reinterpret_cast<const VMPhaseHeader*>(data + data_offset);
212 VMPhaseHeader header = *header_ptr;
213
214 FieldType selected_field_type = static_cast<FieldType>(header.field_type % NUM_FIELD_TYPES);
215 uint8_t selected_steps = header.steps % MAX_STEPS;
216 if (selected_steps == 0) {
217 selected_steps = 1;
218 }
219 header.field_type = static_cast<uint8_t>(selected_field_type);
220 header.steps = selected_steps;
221
222 int r = 0;
223 switch (selected_field_type) {
225 r = run_phase_and_diff<fq>(header, data, size, data_offset, current_state);
226 break;
228 r = run_phase_and_diff<fr>(header, data, size, data_offset, current_state);
229 break;
230 }
231
232 if (r < 0) {
233 return 1;
234 }
235 if (r == 0) {
236 break;
237 }
238 }
239 return 0;
240}
static constexpr u64 p_inv_mod_2k_from_montgomery_r_inv(u64 r_inv) noexcept
static constexpr u64 p_inv_mod_2k_from_montgomery_r_inv(u64 r_inv) noexcept
Field arithmetic fuzzer for testing cryptographic field operations.
int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
const size_t INTERNAL_STATE_SIZE
Constant defining the number of elements in the VM's internal state.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::byte * data
Header structure for each VM execution phase.
uint8_t field_type
Field type identifier (0-5)
uint8_t steps
Number of VM steps to execute (0-63)
Virtual machine for field arithmetic operations.
size_t run(const unsigned char *Data, size_t Size, bool reset_steps=true)
Run the VM on input data.
std::vector< numeric::uint256_t > export_uint_state() const
Export the final uint state as a vector of uint256_t values.
std::array< numeric::uint256_t, INTERNAL_STATE_SIZE > uint_internal_state
Internal state array of uint256_t values.
void set_max_steps(size_t new_max_steps)
Set a new step limit for the VM.
std::array< Field, INTERNAL_STATE_SIZE > field_internal_state
Internal state array of field elements.
bool check_internal_state() const
Check internal state consistency between field and uint256_t representations.