Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecc.fuzzer.cpp
Go to the documentation of this file.
2#include <cassert>
3#include <cstdint>
4#include <fuzzer/FuzzedDataProvider.h>
5#include <random>
6
37
38using namespace bb::avm2::simulation;
39using namespace bb::avm2::tracegen;
40using namespace bb::avm2::constraining;
41
45using bb::avm2::FF;
46using bb::avm2::Fq;
50
53
54namespace {
55
56Fq random_fq_scalar(std::mt19937_64& rng)
57{
58 std::uniform_int_distribution<uint64_t> dist(0, std::numeric_limits<uint64_t>::max());
59
61 for (size_t i = 0; i < 4; ++i) {
62 limbs[i] = dist(rng);
63 }
64
65 return Fq(limbs[0], limbs[1], limbs[2], limbs[3]);
66}
67
68// Right now just mutate the address within the u32 range
69MemoryAddress mutate_memory_address(MemoryAddress addr, std::mt19937_64& rng)
70{
71 int choose_mutation = std::uniform_int_distribution<int>(0, 2)(rng);
72 switch (choose_mutation) {
73 case 0: {
74 // Mutate by fixed amount
75 std::uniform_int_distribution<int32_t> offset_dist(-1024, 1024);
76 uint32_t offset = static_cast<uint32_t>(offset_dist(rng));
77 return addr + offset;
78 } break;
79 case 1: {
80 // Random new address
81 std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max());
82 return dist(rng);
83 }
84 default:
85 // No mutation
86 return addr;
87 }
88}
89
90} // namespace
91
95 Fq scalar = Fq::zero();
96 // Addresses are organised as:
97 // p_x, p_y, p_inf, q_x, q_y, q_inf, output_addr
98 std::array<MemoryAddress, 7> addresses{};
99 EccFuzzerInput() = default;
100
101 // Serialize to buffer
102 void to_buffer(uint8_t* buffer) const
103 {
104 size_t offset = 0;
106 offset += sizeof(AffinePoint);
108 offset += sizeof(AffinePoint);
109 Fq::serialize_to_buffer(scalar, buffer + offset);
110 offset += sizeof(Fq);
111 // Serialize memory addresses
112 std::memcpy(buffer + offset, &addresses[0], sizeof(MemoryAddress) * 7);
113 }
114
115 static EccFuzzerInput from_buffer(const uint8_t* buffer)
116 {
117 // Note: we cannot use AffinePoint::serialize_from_buffer() because this now throws if the point is not on the
118 // curve. We want to test such points so have to deserialize manually:
119 auto read_point = [](const uint8_t* src) -> AffinePoint {
120 bool is_point_at_infinity =
121 std::all_of(src, src + (sizeof(Fq) * 2), [](uint8_t val) { return val == 255; });
122 if (is_point_at_infinity) {
123 return AffinePoint::infinity();
124 }
125 AffinePoint result;
126 read(src, result.x);
127 read(src, result.y);
128 return result;
129 };
130
131 EccFuzzerInput input;
132 size_t offset = 0;
133 input.p = read_point(buffer + offset);
134 offset += sizeof(AffinePoint);
135 input.q = read_point(buffer + offset);
136 offset += sizeof(AffinePoint);
137 input.scalar = Fq::serialize_from_buffer(buffer + offset);
138 offset += sizeof(Fq);
139 // Deserialize memory addresses
140 std::memcpy(&input.addresses[0], buffer + offset, sizeof(MemoryAddress) * 7);
141
142 return input;
143 }
144};
145
146extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
147{
148 if (size < sizeof(EccFuzzerInput)) {
149 // Initialize with default input
150 EccFuzzerInput input;
151 input.to_buffer(data);
152 return sizeof(EccFuzzerInput);
153 }
154
155 std::mt19937_64 rng(seed);
156
157 // Deserialize current input
159
160 // We want to define sensible mutation of points as random bits are unlikely to yield valid points.
161 // Lib Fuzzer will stack 5-6 mutations on top of each other by default
163 int choice = dist(rng);
164
165 switch (choice) {
166 case 0: {
167 // Set P to random valid point
168 Fq rand_scalar = random_fq_scalar(rng);
169 input.p = AffinePoint::one() * rand_scalar;
170 break;
171 }
172 case 1: {
173 // Set P to random invalid point
174 FF rand_x = FF(random_fq_scalar(rng));
175 FF rand_y = FF(random_fq_scalar(rng));
176 input.p = AffinePoint(FF(rand_x), FF(rand_y));
177 while (input.p.on_curve()) {
178 // Ensure it's invalid
179 input.p = AffinePoint(FF(rand_x + FF(1)), FF(rand_y));
180 }
181
182 break;
183 }
184 case 2: {
185 // Set P to point at infinity
186 // Note: using input.p.set_infinity() did not work here (input.p.is_point_at_infinity() == 0 afterwards)
187 input.p = AffinePoint::infinity();
188 break;
189 }
190 case 3: {
191 // Swap P and Q
192 std::swap(input.p, input.q);
193 break;
194 }
195 case 4: {
196 // Set P = -Q
197 input.p = input.q * Fq(-1);
198 break;
199 }
200 case 5: {
201 // Set scalar to random point
202 input.scalar = random_fq_scalar(rng);
203 break;
204 }
205 case 6: {
206 // Set scalar to one
207 input.scalar = Fq::one();
208 break;
209 }
210 case 7: {
211 // Mutate memory addresses
212 // Select a random address to mutate
214 size_t addr_index = addr_dist(rng);
215 input.addresses[addr_index] = mutate_memory_address(input.addresses[addr_index], rng);
216 break;
217 }
218 default:
219 break;
220 }
221
222 // Serialize mutated input back to buffer
223 input.to_buffer(data);
224
225 if (max_size > sizeof(EccFuzzerInput)) {
226 return sizeof(EccFuzzerInput);
227 }
228
229 return sizeof(EccFuzzerInput);
230}
231
232extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
233{
235
236 if (size < sizeof(EccFuzzerInput)) {
237 info("Input size too small");
238 return 0;
239 }
240
241 // Parse input
243 bool error = false;
244
245 EmbeddedCurvePoint point_p = EmbeddedCurvePoint(input.p);
246 EmbeddedCurvePoint point_q = EmbeddedCurvePoint(input.q);
247
248 // Set up gadgets and event emitters
252 EventEmitter<EccAddEvent> ecadd_emitter;
253 EventEmitter<ScalarMulEvent> scalar_mul_emitter;
254 EventEmitter<EccAddMemoryEvent> add_memory_emitter;
255 EventEmitter<ToRadixEvent> to_radix_emitter;
256 EventEmitter<ToRadixMemoryEvent> to_radix_memory_emitter;
257 EventEmitter<MemoryEvent> memory_emitter;
258
261 GreaterThan greater_than(field_gt, range_check, greater_than_emitter);
263 ToRadix to_radix(execution_id_manager, greater_than, to_radix_emitter, to_radix_memory_emitter);
264 Ecc ecc(execution_id_manager, greater_than, to_radix, ecadd_emitter, scalar_mul_emitter, add_memory_emitter);
265
266 MemoryProvider mem_provider(range_check, execution_id_manager, memory_emitter);
267 auto mem = mem_provider.make_memory(0);
268
269 mem->set(/*p_x_addr*/ input.addresses[0], MemoryValue::from_tag(MemoryTag::FF, point_p.x()));
270 mem->set(/*p_y_addr*/ input.addresses[1], MemoryValue::from_tag(MemoryTag::FF, point_p.y()));
271 mem->set(/*p_inf*/ input.addresses[2], MemoryValue::from_tag(MemoryTag::U1, point_p.is_infinity() ? FF(1) : FF(0)));
272 mem->set(/*q_x_addr*/ input.addresses[3], MemoryValue::from_tag(MemoryTag::FF, point_q.x()));
273 mem->set(/*q_y_addr*/ input.addresses[4], MemoryValue::from_tag(MemoryTag::FF, point_q.y()));
274 mem->set(/*q_inf*/ input.addresses[5], MemoryValue::from_tag(MemoryTag::U1, point_q.is_infinity() ? FF(1) : FF(0)));
275
276 EmbeddedCurvePoint scalar_mul_result;
277
278 try {
279 ecc.add(*mem, point_p, point_q, /* output_addr */ input.addresses[6]);
280 scalar_mul_result = ecc.scalar_mul(input.p, FF(uint256_t(input.scalar)));
281 } catch (std::exception& e) {
282 // info("Caught exception during ECC add: {}", e.what());
283 error = true;
284 }
285 if (!error) {
286 EmbeddedCurvePoint expected_result = point_p + point_q;
287
288 // Verify output in memory
289 MemoryValue res_x = mem->get(input.addresses[6]);
290 MemoryValue res_y = mem->get(input.addresses[6] + 1);
291 MemoryValue res_inf = mem->get(input.addresses[6] + 2);
292
293 EmbeddedCurvePoint result_point = EmbeddedCurvePoint(res_x.as_ff(), res_y.as_ff(), res_inf.as_ff() == FF(1));
294
295 BB_ASSERT(result_point.x() == expected_result.x(), "Result x-coordinate mismatch");
296 BB_ASSERT(result_point.y() == expected_result.y(), "Result y-coordinate mismatch");
297 BB_ASSERT(result_point.is_infinity() == expected_result.is_infinity(), "Result infinity flag mismatch");
298
299 // Non mem-aware ecmul result:
300 expected_result = point_p * input.scalar;
301
302 BB_ASSERT(scalar_mul_result.x() == expected_result.x(), "Mul result x-coordinate mismatch");
303 BB_ASSERT(scalar_mul_result.y() == expected_result.y(), "Mul result y-coordinate mismatch");
304 BB_ASSERT(scalar_mul_result.is_infinity() == expected_result.is_infinity(),
305 "Mul result infinity flag mismatch");
306 }
307
308 // Initialize trace container and execution trace columns
309 auto trace = TestTraceContainer({ {
310 { Column::execution_context_id, 0 },
311 // Point P
312 { Column::execution_register_0_, point_p.x() }, // = px
313 { Column::execution_register_1_, point_p.y() }, // = py
314 { Column::execution_register_2_, point_p.is_infinity() ? FF(1) : FF(0) }, // = p_inf
315 // Point Q
316 { Column::execution_register_3_, point_q.x() }, // = qx
317 { Column::execution_register_4_, point_q.y() }, // = qy
318 { Column::execution_register_5_, point_q.is_infinity() ? FF(1) : FF(0) }, // = q_inf
319 // Dst address
320 { Column::execution_rop_6_, input.addresses[6] }, // = dst_addr
321 { Column::execution_sel_exec_dispatch_ecc_add, 1 }, // = sel
322 { Column::execution_sel_opcode_error, error ? 1 : 0 }, // = sel_err
323 } });
324
329 ToRadixTraceBuilder to_radix_builder;
331
335 gt_builder.process(greater_than_emitter.dump_events(), trace);
336 to_radix_builder.process(to_radix_emitter.dump_events(), trace);
337 builder.process_add_with_memory(add_memory_emitter.dump_events(), trace);
338 builder.process_add(ecadd_emitter.dump_events(), trace);
339 builder.process_scalar_mul(scalar_mul_emitter.dump_events(), trace);
340
341 if (getenv("AVM_DEBUG") != nullptr) {
342 info("Debugging trace:");
344 debugger.run();
345 }
346
347 check_relation<ecc_rel>(trace);
348 check_relation<scalar_mul_rel>(trace);
349 check_all_interactions<EccTraceBuilder>(trace);
350 check_interaction<ExecutionTraceBuilder, bb::avm2::perm_execution_dispatch_to_ecc_add_settings>(trace);
351
352 return 0;
353}
GreaterThan greater_than
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
FieldGreaterThan field_gt
EventEmitter< simulation::FieldGreaterThanEvent > field_gt_emitter
EventEmitter< simulation::RangeCheckEvent > range_check_emitter
void run(uint32_t starting_row=0)
Definition debugger.cpp:76
constexpr bool is_infinity() const noexcept
constexpr const BaseField & x() const noexcept
constexpr const BaseField & y() const noexcept
static TaggedValue from_tag(ValueTag tag, FF value)
EventEmitter< Event >::Container dump_events()
std::unique_ptr< MemoryInterface > make_memory(uint16_t space_id) override
Definition memory.hpp:57
void set(MemoryAddress index, MemoryValue value) override
const MemoryValue & get(MemoryAddress index) const override
void process(const simulation::EventEmitterInterface< simulation::FieldGreaterThanEvent >::Container &events, TraceContainer &trace)
Processes FieldGreaterThanEvent events and generates trace rows for the ff_gt gadget.
void process(const simulation::EventEmitterInterface< simulation::GreaterThanEvent >::Container &events, TraceContainer &trace)
Process the greater-than events and populate the relevant columns in the trace.
Definition gt_trace.cpp:20
void process_misc(TraceContainer &trace, const uint32_t num_rows=PRECOMPUTED_TRACE_SIZE)
Populate miscellaneous precomputed columns: first_row selector and idx (row index).
void process(const simulation::EventEmitterInterface< simulation::RangeCheckEvent >::Container &events, TraceContainer &trace)
Processes range check events and populates the trace with decomposed value columns.
void process(const simulation::EventEmitterInterface< simulation::ToRadixEvent >::Container &events, TraceContainer &trace)
Processes the non-memory aware to_radix subtrace ingesting ToRadixEvent events. The populated number ...
static constexpr affine_element infinity()
constexpr bool on_curve() const noexcept
static constexpr affine_element one() noexcept
static void serialize_to_buffer(const affine_element &value, uint8_t *buffer, bool write_x_first=false)
Serialize the point to the given buffer.
#define info(...)
Definition log.hpp:93
RangeCheckTraceBuilder range_check_builder
Definition alu.test.cpp:121
PrecomputedTraceBuilder precomputed_builder
Definition alu.test.cpp:120
FieldGreaterThanTraceBuilder field_gt_builder
Definition alu.test.cpp:122
AluTraceBuilder builder
Definition alu.test.cpp:124
GreaterThanTraceBuilder gt_builder
Definition alu.test.cpp:123
ExecutionIdManager execution_id_manager
MemoryStore mem
const std::vector< MemoryValue > data
TestTraceContainer trace
bool expected_result
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size, size_t max_size, unsigned int seed)
ssize_t offset
Definition engine.cpp:62
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:60
AVM range check gadget for witness generation.
AvmFlavorSettings::FF FF
Definition field.hpp:10
StandardAffinePoint< AvmFlavorSettings::EmbeddedCurve::AffineElement > EmbeddedCurvePoint
Definition field.hpp:12
AvmFlavorSettings::G1::Fq Fq
Definition field.hpp:11
uint32_t MemoryAddress
grumpkin::g1::affine_element AffinePoint
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
grumpkin::fq Fq
AffinePoint p
void to_buffer(uint8_t *buffer) const
EccFuzzerInput()=default
static EccFuzzerInput from_buffer(const uint8_t *buffer)
AffinePoint q
std::array< MemoryAddress, 7 > addresses