Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ecc.test.cpp
Go to the documentation of this file.
1#include <cstdio>
2#include <gmock/gmock.h>
3#include <gtest/gtest.h>
4
5#include <cstdint>
6
31
32namespace bb::avm2::constraining {
33namespace {
34
35using ::testing::Return;
36using ::testing::StrictMock;
37
38using tracegen::EccTraceBuilder;
39using tracegen::TestTraceContainer;
40using tracegen::ToRadixTraceBuilder;
41
43using C = Column;
44using ecc = bb::avm2::ecc<FF>;
45using scalar_mul = bb::avm2::scalar_mul<FF>;
46using mem_aware_ecc = bb::avm2::ecc_mem<FF>;
47using EccSimulator = simulation::Ecc;
48using ToRadixSimulator = simulation::ToRadix;
49
50using simulation::EccAddEvent;
51using simulation::EccAddMemoryEvent;
52using simulation::EventEmitter;
53using simulation::MemoryStore;
54using simulation::MockExecutionIdManager;
55using simulation::MockGreaterThan;
56using simulation::MockMemory;
57using simulation::NoopEventEmitter;
58using simulation::PureGreaterThan;
59using simulation::PureToRadix;
60using simulation::ScalarMulEvent;
61using simulation::ToRadixEvent;
62using simulation::ToRadixMemoryEvent;
63
64// Known good points for P and Q
65FF p_x("0x04c95d1b26d63d46918a156cae92db1bcbc4072a27ec81dc82ea959abdbcf16a");
66FF p_y("0x035b6dd9e63c1370462c74775765d07fc21fd1093cc988149d3aa763bb3dbb60");
67EmbeddedCurvePoint p(p_x, p_y);
68
69FF q_x("0x009242167ec31949c00cbe441cd36757607406e87844fa2c8c4364a4403e66d7");
70FF q_y("0x0fe3016d64cfa8045609f375284b6b739b5fa282e4cbb75cc7f1687ecc7420e3");
71EmbeddedCurvePoint q(q_x, q_y);
72
73TEST(EccAddConstrainingTest, EccEmptyRow)
74{
75 check_relation<ecc>(testing::empty_trace());
76}
77
78TEST(EccAddConstrainingTest, EccAdd)
79{
80 // R = P + Q;
81 FF r_x("0x2b01df0ef6d941a826bea23bece8243cbcdc159d5e97fbaa2171f028e05ba9b6");
82 FF r_y("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
83 EmbeddedCurvePoint r(r_x, r_y);
84
85 auto trace = TestTraceContainer({ {
86 { C::ecc_add_op, 1 },
87 { C::ecc_double_op, 0 },
88
89 { C::ecc_inv_2_p_y, FF::zero() },
90 { C::ecc_inv_x_diff, (q.x() - p.x()).invert() },
91 { C::ecc_inv_y_diff, (q.y() - p.y()).invert() },
92
93 { C::ecc_lambda, (q.y() - p.y()) / (q.x() - p.x()) },
94
95 // Point P
96 { C::ecc_p_is_inf, static_cast<int>(p.is_infinity()) },
97 { C::ecc_p_x, p.x() },
98 { C::ecc_p_y, p.y() },
99
100 // Point Q
101 { C::ecc_q_is_inf, static_cast<int>(q.is_infinity()) },
102 { C::ecc_q_x, q.x() },
103 { C::ecc_q_y, q.y() },
104
105 // Resulting Point
106 { C::ecc_r_x, r.x() },
107 { C::ecc_r_y, r.y() },
108
109 { C::ecc_result_infinity, 0 },
110
111 { C::ecc_sel, 1 },
112 { C::ecc_use_computed_result, 1 },
113 { C::ecc_x_match, 0 },
114 { C::ecc_y_match, 0 },
115
116 } });
117
118 check_relation<ecc>(trace);
119}
120
121TEST(EccAddConstrainingTest, EccDouble)
122{
123 // R = P + P;
124 FF r_x("0x088b996194bb5e6e8e5e49733bb671c3e660cf77254f743f366cc8e33534ee3b");
125 FF r_y("0x2807ffa01c0f522d0be1e1acfb6914ac8eabf1acf420c0629d37beee992e9a0e");
126 EmbeddedCurvePoint r(r_x, r_y);
127
128 auto trace = TestTraceContainer({ {
129 { C::ecc_add_op, 0 },
130 { C::ecc_double_op, 1 },
131
132 { C::ecc_inv_2_p_y, (p.y() * 2).invert() },
133 { C::ecc_inv_x_diff, FF::zero() },
134 { C::ecc_inv_y_diff, FF::zero() },
135
136 { C::ecc_lambda, (p.x() * p.x() * 3) / (p.y() * 2) },
137
138 // Point P
139 { C::ecc_p_is_inf, static_cast<int>(p.is_infinity()) },
140 { C::ecc_p_x, p.x() },
141 { C::ecc_p_y, p.y() },
142
143 // Point Q set to point p since this is doubling
144 { C::ecc_q_is_inf, static_cast<int>(p.is_infinity()) },
145 { C::ecc_q_x, p.x() },
146 { C::ecc_q_y, p.y() },
147
148 // Resulting Point
149 { C::ecc_r_x, r.x() },
150 { C::ecc_r_y, r.y() },
151
152 { C::ecc_result_infinity, 0 },
153
154 { C::ecc_sel, 1 },
155 { C::ecc_use_computed_result, 1 },
156 { C::ecc_x_match, 1 },
157 { C::ecc_y_match, 1 },
158
159 } });
160
161 check_relation<ecc>(trace);
162}
163
164// Test case for adding two points with different x-coordinates but the same y-coordinate.
165// This edge case exists because cube roots of unity in BN254 Fr allow multiple x values
166// to cube to the same result: if (x, y) is on Grumpkin (y² = x³ - 17), then (ω·x, y)
167// is also on the curve since ω³ = 1.
168//
169// This test uses simulation + tracegen to verify the full pipeline works.
170TEST(EccAddConstrainingTest, EccAddSameYDifferentX)
171{
172 // Point P - known valid point on Grumpkin
173 FF local_p_x("0x04c95d1b26d63d46918a156cae92db1bcbc4072a27ec81dc82ea959abdbcf16a");
174 FF local_p_y("0x035b6dd9e63c1370462c74775765d07fc21fd1093cc988149d3aa763bb3dbb60");
175 EmbeddedCurvePoint local_p(local_p_x, local_p_y);
176
177 // Point Q - p_x * omega (cube root of unity), same y-coordinate!
178 // omega = 0x0000000000000000b3c4d79d41a917585bfc41088d8daaa78b17ea66b99c90dd
179 FF local_q_x("0x14dd39aa19e1c8b29e0c530a28106a7d64d2213486baba3c86dce51bdddf75bb");
180 FF local_q_y("0x035b6dd9e63c1370462c74775765d07fc21fd1093cc988149d3aa763bb3dbb60");
181 EmbeddedCurvePoint local_q(local_q_x, local_q_y);
182
183 // Verify preconditions: same y, different x
184 ASSERT_NE(local_p.x(), local_q.x());
185 ASSERT_EQ(local_p.y(), local_q.y());
186
187 // Expected result R = P + Q (lambda = 0 since y's are equal)
188 FF local_r_x("0x16bdb7ada0799a3088b9dd3faade12c3f79dbfe9cb1234783a1a7add546398dc");
189 FF local_r_y("0x2d08e098faf58cb97223d13f2a1b87dd6614173f3cefe87ca6a74e3034c244a1");
190 EmbeddedCurvePoint local_r(local_r_x, local_r_y);
191
192 // Use simulation to generate events
193 EventEmitter<EccAddEvent> ecc_add_event_emitter;
194 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
195 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
196
197 StrictMock<MockExecutionIdManager> execution_id_manager;
198 PureGreaterThan gt;
199 PureToRadix to_radix_simulator;
200 EccSimulator ecc_simulator(execution_id_manager,
201 gt,
202 to_radix_simulator,
203 ecc_add_event_emitter,
204 scalar_mul_event_emitter,
205 ecc_add_memory_event_emitter);
206
207 // Perform the addition via simulation
208 EmbeddedCurvePoint result = ecc_simulator.add(local_p, local_q);
209 ASSERT_EQ(result, local_r) << "Simulation produced wrong result";
210
211 // Build trace from simulation events
212 TestTraceContainer trace;
213 EccTraceBuilder builder;
214 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
215
216 // Verify PIL constraints pass
217 check_relation<ecc>(trace);
218}
219
220TEST(EccAddConstrainingTest, EccAddResultingInInfinity)
221{
222 // R = P + (-P) = O; , where O is the point at infinity
223 EmbeddedCurvePoint q(p.x(), -p.y());
224 EmbeddedCurvePoint r(0, 0);
225
226 auto trace = TestTraceContainer({ {
227 { C::ecc_add_op, 0 },
228 { C::ecc_double_op, 0 },
229
230 { C::ecc_inv_2_p_y, FF::zero() },
231 { C::ecc_inv_x_diff, FF::zero() },
232 { C::ecc_inv_y_diff, (q.y() - p.y()).invert() },
233
234 { C::ecc_lambda, 0 },
235
236 // Point P
237 { C::ecc_p_is_inf, static_cast<int>(p.is_infinity()) },
238 { C::ecc_p_x, p.x() },
239 { C::ecc_p_y, p.y() },
240
241 // Point Q
242 { C::ecc_q_is_inf, static_cast<int>(q.is_infinity()) },
243 { C::ecc_q_x, q.x() },
244 { C::ecc_q_y, q.y() },
245
246 // Resulting Point
247 { C::ecc_r_x, r.x() },
248 { C::ecc_r_y, r.y() },
249
250 { C::ecc_result_infinity, 1 },
251
252 { C::ecc_sel, 1 },
253 { C::ecc_x_match, 1 },
254 { C::ecc_y_match, 0 },
255 } });
256
257 check_relation<ecc>(trace);
258}
259
260TEST(EccAddConstrainingTest, EccAddingToInfinity)
261{
262 EmbeddedCurvePoint p(0, 0);
263
264 // R = O + Q = Q; , where O is the point at infinity
265
266 EmbeddedCurvePoint r(q.x(), q.y());
267
268 auto trace = TestTraceContainer({ {
269 { C::ecc_add_op, 1 },
270 { C::ecc_double_op, 0 },
271
272 { C::ecc_inv_2_p_y, FF::zero() },
273 { C::ecc_inv_x_diff, (q.x() - p.x()).invert() },
274 { C::ecc_inv_y_diff, (q.y() - p.y()).invert() },
275
276 { C::ecc_lambda, (q.y() - p.y()) / (q.x() - p.x()) },
277
278 // Point P
279 { C::ecc_p_is_inf, static_cast<int>(p.is_infinity()) },
280 { C::ecc_p_x, p.x() },
281 { C::ecc_p_y, p.y() },
282
283 // Point Q
284 { C::ecc_q_is_inf, static_cast<int>(q.is_infinity()) },
285 { C::ecc_q_x, q.x() },
286 { C::ecc_q_y, q.y() },
287
288 // Resulting Point
289 { C::ecc_r_x, r.x() },
290 { C::ecc_r_y, r.y() },
291
292 { C::ecc_result_infinity, 0 },
293
294 { C::ecc_sel, 1 },
295 { C::ecc_x_match, 0 },
296 { C::ecc_y_match, 0 },
297 } });
298
299 check_relation<ecc>(trace);
300}
301
302TEST(EccAddConstrainingTest, EccAddingInfinity)
303{
304 EmbeddedCurvePoint q(0, 0);
305
306 // R = P + O = P; , where O is the point at infinity
307 EmbeddedCurvePoint r(p.x(), p.y());
308
309 auto trace = TestTraceContainer({ {
310 { C::ecc_add_op, 1 },
311 { C::ecc_double_op, 0 },
312
313 { C::ecc_inv_2_p_y, (p.y() * 2).invert() },
314 { C::ecc_inv_x_diff, (q.x() - p.x()).invert() },
315 { C::ecc_inv_y_diff, (q.y() - p.y()).invert() },
316
317 { C::ecc_lambda, (q.y() - p.y()) / (q.x() - p.x()) },
318
319 // Point P
320 { C::ecc_p_is_inf, static_cast<int>(p.is_infinity()) },
321 { C::ecc_p_x, p.x() },
322 { C::ecc_p_y, p.y() },
323
324 // Point Q
325 { C::ecc_q_is_inf, static_cast<int>(q.is_infinity()) },
326 { C::ecc_q_x, q.x() },
327 { C::ecc_q_y, q.y() },
328
329 // Resulting Point
330 { C::ecc_r_x, r.x() },
331 { C::ecc_r_y, r.y() },
332
333 { C::ecc_result_infinity, 0 },
334
335 { C::ecc_sel, 1 },
336 { C::ecc_x_match, 0 },
337 { C::ecc_y_match, 0 },
338
339 } });
340
341 check_relation<ecc>(trace);
342}
343
344TEST(EccAddConstrainingTest, EccDoublingInf)
345{
346 EmbeddedCurvePoint p(0, 0);
347
348 // r = O + O = O; , where O is the point at infinity
349 EmbeddedCurvePoint r(0, 0);
350
351 auto trace = TestTraceContainer({ {
352 { C::ecc_add_op, 0 },
353 { C::ecc_double_op, 1 },
354
355 { C::ecc_inv_2_p_y, FF::zero() },
356 { C::ecc_inv_x_diff, FF::zero() },
357 { C::ecc_inv_y_diff, FF::zero() },
358
359 { C::ecc_lambda, FF::zero() },
360
361 // Point P
362 { C::ecc_p_is_inf, static_cast<int>(p.is_infinity()) },
363 { C::ecc_p_x, p.x() },
364 { C::ecc_p_y, p.y() },
365
366 // Point Q
367 { C::ecc_q_is_inf, static_cast<int>(p.is_infinity()) },
368 { C::ecc_q_x, p.x() },
369 { C::ecc_q_y, p.y() },
370
371 // Resulting Point
372 { C::ecc_r_x, r.x() },
373 { C::ecc_r_y, r.y() },
374
375 { C::ecc_result_infinity, 1 },
376
377 { C::ecc_sel, 1 },
378 { C::ecc_x_match, 1 },
379 { C::ecc_y_match, 1 },
380
381 } });
382
383 check_relation<ecc>(trace);
384}
385
386TEST(EccAddConstrainingTest, EccTwoOps)
387{
388 EmbeddedCurvePoint r1 = p + q;
389 EmbeddedCurvePoint r2 = r1 + r1;
390
391 auto trace = TestTraceContainer({ {
392 { C::ecc_add_op, 1 },
393 { C::ecc_double_op, 0 },
394
395 { C::ecc_inv_2_p_y, FF::zero() },
396 { C::ecc_inv_x_diff, (q.x() - p.x()).invert() },
397 { C::ecc_inv_y_diff, (q.y() - p.y()).invert() },
398
399 { C::ecc_lambda, (q.y() - p.y()) / (q.x() - p.x()) },
400
401 // Point P
402 { C::ecc_p_is_inf, static_cast<int>(p.is_infinity()) },
403 { C::ecc_p_x, p.x() },
404 { C::ecc_p_y, p.y() },
405
406 // Point Q
407 { C::ecc_q_is_inf, static_cast<int>(q.is_infinity()) },
408 { C::ecc_q_x, q.x() },
409 { C::ecc_q_y, q.y() },
410
411 // Resulting Point
412 { C::ecc_r_x, r1.x() },
413 { C::ecc_r_y, r1.y() },
414
415 { C::ecc_result_infinity, 0 },
416
417 { C::ecc_sel, 1 },
418 { C::ecc_use_computed_result, 1 },
419 { C::ecc_x_match, 0 },
420 { C::ecc_y_match, 0 },
421
422 },
423 {
424 { C::ecc_add_op, 0 },
425 { C::ecc_double_op, 1 },
426
427 { C::ecc_inv_2_p_y, (r1.y() * 2).invert() },
428 { C::ecc_inv_x_diff, FF::zero() },
429 { C::ecc_inv_y_diff, FF::zero() },
430
431 { C::ecc_lambda, (r1.x() * r1.x() * 3) / (r1.y() * 2) },
432
433 // Point P
434 { C::ecc_p_is_inf, static_cast<int>(r1.is_infinity()) },
435 { C::ecc_p_x, r1.x() },
436 { C::ecc_p_y, r1.y() },
437
438 // Point Q set to point p since this is doubling
439 { C::ecc_q_is_inf, static_cast<int>(r1.is_infinity()) },
440 { C::ecc_q_x, r1.x() },
441 { C::ecc_q_y, r1.y() },
442
443 // Resulting Point
444 { C::ecc_r_x, r2.x() },
445 { C::ecc_r_y, r2.y() },
446
447 { C::ecc_result_infinity, 0 },
448
449 { C::ecc_sel, 1 },
450 { C::ecc_use_computed_result, 1 },
451 { C::ecc_x_match, 1 },
452 { C::ecc_y_match, 1 },
453
454 } });
455
456 check_relation<ecc>(trace);
457}
458
459TEST(EccAddConstrainingTest, EccNegativeBadAdd)
460{
461 // R != P + Q;
462
463 FF r_x("0x20f096ae3de9aea007e0b94a0274b2443d6682d1901f6909f284ec967bc169be");
464 FF r_y("0x27948713833bb314e828f2b6f45f408da6564a3ac03b9e430a9c6634bb849ef2");
465 EmbeddedCurvePoint r(r_x, r_y);
466
467 auto trace = TestTraceContainer({ {
468 { C::ecc_add_op, 1 },
469 { C::ecc_double_op, 0 },
470
471 { C::ecc_inv_2_p_y, FF::zero() },
472 { C::ecc_inv_x_diff, (q.x() - p.x()).invert() },
473 { C::ecc_inv_y_diff, (q.y() - p.y()).invert() },
474
475 { C::ecc_lambda, (q.y() - p.y()) / (q.x() - p.x()) },
476
477 // Point P
478 { C::ecc_p_is_inf, static_cast<int>(p.is_infinity()) },
479 { C::ecc_p_x, p.x() },
480 { C::ecc_p_y, p.y() },
481
482 // Point Q
483 { C::ecc_q_is_inf, static_cast<int>(q.is_infinity()) },
484 { C::ecc_q_x, q.x() },
485 { C::ecc_q_y, q.y() },
486
487 // Resulting Point
488 { C::ecc_r_x, r.x() },
489 { C::ecc_r_y, r.y() },
490
491 { C::ecc_result_infinity, 0 },
492
493 { C::ecc_sel, 1 },
494 { C::ecc_x_match, 0 },
495 { C::ecc_y_match, 0 },
496
497 } });
498
499 EXPECT_THROW_WITH_MESSAGE(check_relation<ecc>(trace, ecc::SR_OUTPUT_X_COORD),
501}
502
503TEST(EccAddConstrainingTest, EccNegativeBadDouble)
504{
505 // R != P + P;
506
507 FF r_x("0x2b01df0ef6d941a826bea23bece8243cbcdc159d5e97fbaa2171f028e05ba9b6");
508 FF r_y("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
509 EmbeddedCurvePoint r(r_x, r_y);
510
511 auto trace = TestTraceContainer({ {
512 { C::ecc_add_op, 0 },
513 { C::ecc_double_op, 1 },
514
515 { C::ecc_inv_2_p_y, (p.y() * 2).invert() },
516 { C::ecc_inv_x_diff, FF::zero() },
517 { C::ecc_inv_y_diff, FF::zero() },
518
519 { C::ecc_lambda, (p.x() * p.x() * 3) / (p.y() * 2) },
520
521 // Point P
522 { C::ecc_p_is_inf, static_cast<int>(p.is_infinity()) },
523 { C::ecc_p_x, p.x() },
524 { C::ecc_p_y, p.y() },
525
526 // Point Q set to point p since this is doubling
527 { C::ecc_q_is_inf, static_cast<int>(p.is_infinity()) },
528 { C::ecc_q_x, p.x() },
529 { C::ecc_q_y, p.y() },
530
531 // Resulting Point
532 { C::ecc_r_x, r.x() },
533 { C::ecc_r_y, r.y() },
534
535 { C::ecc_result_infinity, 0 },
536
537 { C::ecc_sel, 1 },
538 { C::ecc_x_match, 1 },
539 { C::ecc_y_match, 1 },
540
541 } });
542
543 EXPECT_THROW_WITH_MESSAGE(check_relation<ecc>(trace, ecc::SR_OUTPUT_X_COORD),
545}
546
547TEST(ScalarMulConstrainingTest, ScalarMulEmptyRow)
548{
549 check_relation<scalar_mul>(testing::empty_trace());
550}
551
552TEST(ScalarMulConstrainingTest, MulByOne)
553{
554 EccTraceBuilder builder;
555
556 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
557 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
558 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
559
560 StrictMock<MockExecutionIdManager> execution_id_manager;
561 StrictMock<MockGreaterThan> gt;
562 PureToRadix to_radix_simulator = PureToRadix();
563 EccSimulator ecc_simulator(execution_id_manager,
564 gt,
565 to_radix_simulator,
566 ecc_add_event_emitter,
567 scalar_mul_event_emitter,
568 ecc_add_memory_event_emitter);
569
570 FF scalar = FF(1);
571 ecc_simulator.scalar_mul(p, scalar);
572
573 TestTraceContainer trace({
574 { { C::precomputed_first_row, 1 } },
575 });
576
577 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
578 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 254);
579 check_relation<scalar_mul>(trace);
580}
581
582TEST(ScalarMulConstrainingTest, BasicMul)
583{
584 EccTraceBuilder builder;
585
586 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
587 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
588 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
589
590 StrictMock<MockExecutionIdManager> execution_id_manager;
591 StrictMock<MockGreaterThan> gt;
592 PureToRadix to_radix_simulator = PureToRadix();
593 EccSimulator ecc_simulator(execution_id_manager,
594 gt,
595 to_radix_simulator,
596 ecc_add_event_emitter,
597 scalar_mul_event_emitter,
598 ecc_add_memory_event_emitter);
599
600 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
601 ecc_simulator.scalar_mul(p, scalar);
602
603 TestTraceContainer trace({
604 { { C::precomputed_first_row, 1 } },
605 });
606
607 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
608 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 254);
609 check_relation<scalar_mul>(trace);
610}
611
612// Edge case: Verify that 0 * P = infinity (point at infinity)
613TEST(ScalarMulConstrainingTest, MulByZero)
614{
615 EccTraceBuilder builder;
616
617 EventEmitter<EccAddEvent> ecc_add_event_emitter;
618 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
619 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
620
621 StrictMock<MockExecutionIdManager> execution_id_manager;
622 StrictMock<MockGreaterThan> gt;
623 PureToRadix to_radix_simulator = PureToRadix();
624 EccSimulator ecc_simulator(execution_id_manager,
625 gt,
626 to_radix_simulator,
627 ecc_add_event_emitter,
628 scalar_mul_event_emitter,
629 ecc_add_memory_event_emitter);
630
631 // Multiply by zero - result should be point at infinity
632 FF scalar = FF(0);
633 EmbeddedCurvePoint result = ecc_simulator.scalar_mul(p, scalar);
634
635 // Verify result is infinity
636 ASSERT_TRUE(result.is_infinity());
637
638 TestTraceContainer trace({
639 { { C::precomputed_first_row, 1 } },
640 });
641
642 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
643 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
644
645 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 254);
646 check_relation<scalar_mul>(trace);
647 check_relation<ecc>(trace);
648}
649
650// Edge case: Verify scalar multiplication works with a large scalar near field modulus
651TEST(ScalarMulConstrainingTest, MulByLargeScalar)
652{
653 EccTraceBuilder builder;
654
655 EventEmitter<EccAddEvent> ecc_add_event_emitter;
656 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
657 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
658
659 StrictMock<MockExecutionIdManager> execution_id_manager;
660 StrictMock<MockGreaterThan> gt;
661 PureToRadix to_radix_simulator = PureToRadix();
662 EccSimulator ecc_simulator(execution_id_manager,
663 gt,
664 to_radix_simulator,
665 ecc_add_event_emitter,
666 scalar_mul_event_emitter,
667 ecc_add_memory_event_emitter);
668
669 // Use a large scalar (p - 1, where p is the field modulus)
670 // BN254 scalar field modulus - 1: 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000
671 FF scalar = FF("0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffffff");
672 EmbeddedCurvePoint result = ecc_simulator.scalar_mul(p, scalar);
673
674 // Verify result is a valid point (not infinity for non-zero scalar with non-infinity point)
675 // The exact result depends on the scalar and point, but it should be deterministic
677 EXPECT_EQ(result, expected_result);
678
679 TestTraceContainer trace({
680 { { C::precomputed_first_row, 1 } },
681 });
682
683 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
684 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
685
686 // Note: Row count varies based on number of ECC operations (depends on scalar bit pattern)
687 check_relation<scalar_mul>(trace);
688 check_relation<ecc>(trace);
689}
690
691TEST(ScalarMulConstrainingTest, MultipleInvocations)
692{
693 EccTraceBuilder builder;
694
695 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
696 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
697 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
698
699 StrictMock<MockExecutionIdManager> execution_id_manager;
700 StrictMock<MockGreaterThan> gt;
701 PureToRadix to_radix_simulator = PureToRadix();
702 EccSimulator ecc_simulator(execution_id_manager,
703 gt,
704 to_radix_simulator,
705 ecc_add_event_emitter,
706 scalar_mul_event_emitter,
707 ecc_add_memory_event_emitter);
708
709 ecc_simulator.scalar_mul(p, FF("0x2b01df0ef6d941a826bea23bece8243cbcdc159d5e97fbaa2171f028e05ba9b6"));
710 ecc_simulator.scalar_mul(q, FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09"));
711
712 TestTraceContainer trace({
713 { { C::precomputed_first_row, 1 } },
714 });
715
716 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
717 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + (254) * 2);
718 check_relation<scalar_mul>(trace);
719}
720
721TEST(ScalarMulConstrainingTest, MulInteractions)
722{
723 EccTraceBuilder builder;
724
725 EventEmitter<EccAddEvent> ecc_add_event_emitter;
726 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
727 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
728 EventEmitter<ToRadixEvent> to_radix_event_emitter;
729 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
730
731 StrictMock<MockExecutionIdManager> execution_id_manager;
732 StrictMock<MockGreaterThan> gt;
733 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
734 EccSimulator ecc_simulator(execution_id_manager,
735 gt,
736 to_radix_simulator,
737 ecc_add_event_emitter,
738 scalar_mul_event_emitter,
739 ecc_add_memory_event_emitter);
740
741 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
742 ecc_simulator.scalar_mul(p, scalar);
743
744 TestTraceContainer trace({
745 { { C::precomputed_first_row, 1 } },
746 });
747
748 ToRadixTraceBuilder to_radix_builder;
749 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
750 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
751 to_radix_builder.process(to_radix_event_emitter.dump_events(), trace);
752
753 check_interaction<EccTraceBuilder,
757}
758
759TEST(ScalarMulConstrainingTest, MulAddInteractionsInfinity)
760{
761 EccTraceBuilder builder;
762
763 EventEmitter<EccAddEvent> ecc_add_event_emitter;
764 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
765 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
766
767 StrictMock<MockExecutionIdManager> execution_id_manager;
768 StrictMock<MockGreaterThan> gt;
769 PureToRadix to_radix_simulator = PureToRadix();
770 EccSimulator ecc_simulator(execution_id_manager,
771 gt,
772 to_radix_simulator,
773 ecc_add_event_emitter,
774 scalar_mul_event_emitter,
775 ecc_add_memory_event_emitter);
776
778
780
781 EmbeddedCurvePoint result = ecc_simulator.scalar_mul(inf_bb, FF(10));
782 ASSERT_TRUE(result.is_infinity());
783 EXPECT_EQ(result.x(), inf.x());
784 EXPECT_EQ(result.y(), inf.y());
785
786 TestTraceContainer trace({
787 { { C::precomputed_first_row, 1 } },
788 });
789
790 auto scalar_mul_events = scalar_mul_event_emitter.dump_events();
791 // Infinity points should be normalised to (0, 0) for any lookups into ecc.pil
792 for (auto& event : scalar_mul_events) {
793 EXPECT_EQ(event.point.x(), inf.x());
794 EXPECT_EQ(event.point.y(), inf.y());
795 }
796
797 builder.process_scalar_mul(scalar_mul_events, trace);
798 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
799
800 check_interaction<EccTraceBuilder, lookup_scalar_mul_double_settings, lookup_scalar_mul_add_settings>(trace);
801
802 check_relation<scalar_mul>(trace);
803 check_relation<ecc>(trace);
804}
805
806TEST(ScalarMulConstrainingTest, NegativeMulAddInteractions)
807{
808 EccTraceBuilder builder;
809
810 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
811 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
812 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
813
814 StrictMock<MockExecutionIdManager> execution_id_manager;
815 StrictMock<MockGreaterThan> gt;
816 PureToRadix to_radix_simulator = PureToRadix();
817 EccSimulator ecc_simulator(execution_id_manager,
818 gt,
819 to_radix_simulator,
820 ecc_add_event_emitter,
821 scalar_mul_event_emitter,
822 ecc_add_memory_event_emitter);
823
824 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
825 ecc_simulator.scalar_mul(p, scalar);
826
827 TestTraceContainer trace({
828 { { C::precomputed_first_row, 1 } },
829 });
830
831 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
832
833 EXPECT_THROW_WITH_MESSAGE((check_interaction<EccTraceBuilder, lookup_scalar_mul_double_settings>(trace)),
834 "Failed.*SCALAR_MUL_DOUBLE. Could not find tuple in destination.");
835 EXPECT_THROW_WITH_MESSAGE((check_interaction<EccTraceBuilder, lookup_scalar_mul_add_settings>(trace)),
836 "Failed.*SCALAR_MUL_ADD. Could not find tuple in destination.");
837}
838
839TEST(ScalarMulConstrainingTest, NegativeMulRadixInteractions)
840{
841 EccTraceBuilder builder;
842
843 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
844 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
845 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
846
847 StrictMock<MockExecutionIdManager> execution_id_manager;
848 StrictMock<MockGreaterThan> gt;
849 PureToRadix to_radix_simulator = PureToRadix();
850 EccSimulator ecc_simulator(execution_id_manager,
851 gt,
852 to_radix_simulator,
853 ecc_add_event_emitter,
854 scalar_mul_event_emitter,
855 ecc_add_memory_event_emitter);
856
857 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
858 ecc_simulator.scalar_mul(p, scalar);
859
860 TestTraceContainer trace({
861 { { C::precomputed_first_row, 1 } },
862 });
863
864 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
865
866 EXPECT_THROW_WITH_MESSAGE((check_interaction<EccTraceBuilder, lookup_scalar_mul_to_radix_settings>(trace)),
867 "Failed.*SCALAR_MUL_TO_RADIX. Could not find tuple in destination.");
868
869 check_relation<scalar_mul>(trace);
870}
871
872TEST(ScalarMulConstrainingTest, NegativeDisableSel)
873{
874 EccTraceBuilder builder;
875
876 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
877 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
878 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
879
880 StrictMock<MockExecutionIdManager> execution_id_manager;
881 StrictMock<MockGreaterThan> gt;
882 PureToRadix to_radix_simulator = PureToRadix();
883 EccSimulator ecc_simulator(execution_id_manager,
884 gt,
885 to_radix_simulator,
886 ecc_add_event_emitter,
887 scalar_mul_event_emitter,
888 ecc_add_memory_event_emitter);
889
890 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
891 ecc_simulator.scalar_mul(p, scalar);
892
893 TestTraceContainer trace({
894 { { C::precomputed_first_row, 1 } },
895 });
896
897 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
898 // Disable the selector in one of the rows between start and end
899 trace.set(Column::scalar_mul_sel, 5, 0);
900 EXPECT_THROW_WITH_MESSAGE(check_relation<scalar_mul>(trace, scalar_mul::SR_TRACE_CONTINUITY),
902}
903
904TEST(ScalarMulConstrainingTest, NegativeEnableStartFirstRow)
905{
906 EccTraceBuilder builder;
907
908 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
909 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
910 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
911
912 StrictMock<MockExecutionIdManager> execution_id_manager;
913 StrictMock<MockGreaterThan> gt;
914 PureToRadix to_radix_simulator = PureToRadix();
915 EccSimulator ecc_simulator(execution_id_manager,
916 gt,
917 to_radix_simulator,
918 ecc_add_event_emitter,
919 scalar_mul_event_emitter,
920 ecc_add_memory_event_emitter);
921
922 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
923 ecc_simulator.scalar_mul(p, scalar);
924
925 TestTraceContainer trace({
926 { { C::precomputed_first_row, 1 } },
927 });
928
929 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
930 // Enable the start in the first row
931 trace.set(Column::scalar_mul_start, 0, 1);
932 EXPECT_THROW_WITH_MESSAGE(check_relation<scalar_mul>(trace, scalar_mul::SR_SEL_ON_START_OR_END),
934}
935
936TEST(ScalarMulConstrainingTest, NegativeMutateScalarOnEnd)
937{
938 EccTraceBuilder builder;
939
940 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
941 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
942 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
943
944 StrictMock<MockExecutionIdManager> execution_id_manager;
945 StrictMock<MockGreaterThan> gt;
946 PureToRadix to_radix_simulator = PureToRadix();
947 EccSimulator ecc_simulator(execution_id_manager,
948 gt,
949 to_radix_simulator,
950 ecc_add_event_emitter,
951 scalar_mul_event_emitter,
952 ecc_add_memory_event_emitter);
953
954 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
955 ecc_simulator.scalar_mul(p, scalar);
956
957 TestTraceContainer trace({
958 { { C::precomputed_first_row, 1 } },
959 });
960
961 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
962 // Mutate the scalar on the end row
963 trace.set(Column::scalar_mul_scalar, 254, 27);
966}
967
968TEST(ScalarMulConstrainingTest, NegativeMutatePointXOnEnd)
969{
970 EccTraceBuilder builder;
971
972 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
973 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
974 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
975
976 StrictMock<MockExecutionIdManager> execution_id_manager;
977 StrictMock<MockGreaterThan> gt;
978 PureToRadix to_radix_simulator = PureToRadix();
979 EccSimulator ecc_simulator(execution_id_manager,
980 gt,
981 to_radix_simulator,
982 ecc_add_event_emitter,
983 scalar_mul_event_emitter,
984 ecc_add_memory_event_emitter);
985
986 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
987 ecc_simulator.scalar_mul(p, scalar);
988
989 TestTraceContainer trace({
990 { { C::precomputed_first_row, 1 } },
991 });
992
993 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
994 // Mutate the point on the end row
995 trace.set(Column::scalar_mul_point_x, 254, q.x());
996
997 EXPECT_THROW_WITH_MESSAGE(check_relation<scalar_mul>(trace, scalar_mul::SR_INPUT_CONSISTENCY_X),
999}
1000
1001TEST(ScalarMulConstrainingTest, NegativeMutatePointYOnEnd)
1002{
1003 EccTraceBuilder builder;
1004
1005 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
1006 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1007 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1008
1009 StrictMock<MockExecutionIdManager> execution_id_manager;
1010 StrictMock<MockGreaterThan> gt;
1011 PureToRadix to_radix_simulator = PureToRadix();
1012 EccSimulator ecc_simulator(execution_id_manager,
1013 gt,
1014 to_radix_simulator,
1015 ecc_add_event_emitter,
1016 scalar_mul_event_emitter,
1017 ecc_add_memory_event_emitter);
1018
1019 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
1020 ecc_simulator.scalar_mul(p, scalar);
1021
1022 TestTraceContainer trace({
1023 { { C::precomputed_first_row, 1 } },
1024 });
1025
1026 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
1027 // Mutate the point on the end row
1028 trace.set(Column::scalar_mul_point_y, 254, q.y());
1029
1030 EXPECT_THROW_WITH_MESSAGE(check_relation<scalar_mul>(trace, scalar_mul::SR_INPUT_CONSISTENCY_Y),
1032}
1033
1034TEST(ScalarMulConstrainingTest, NegativeMutatePointInfOnEnd)
1035{
1036 EccTraceBuilder builder;
1037
1038 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
1039 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1040 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1041
1042 StrictMock<MockExecutionIdManager> execution_id_manager;
1043 StrictMock<MockGreaterThan> gt;
1044 PureToRadix to_radix_simulator = PureToRadix();
1045 EccSimulator ecc_simulator(execution_id_manager,
1046 gt,
1047 to_radix_simulator,
1048 ecc_add_event_emitter,
1049 scalar_mul_event_emitter,
1050 ecc_add_memory_event_emitter);
1051
1052 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
1053 ecc_simulator.scalar_mul(p, scalar);
1054
1055 TestTraceContainer trace({
1056 { { C::precomputed_first_row, 1 } },
1057 });
1058
1059 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
1060 // Mutate the point on the end row
1061 trace.set(Column::scalar_mul_point_inf, 254, 1);
1062
1063 EXPECT_THROW_WITH_MESSAGE(check_relation<scalar_mul>(trace, scalar_mul::SR_INPUT_CONSISTENCY_INF),
1065}
1066
1068// Memory Aware Ecc Add
1070
1071TEST(EccAddMemoryConstrainingTest, EccAddMemoryEmptyRow)
1072{
1073 check_relation<mem_aware_ecc>(testing::empty_trace());
1074}
1075
1076TEST(EccAddMemoryConstrainingTest, EccAddMemory)
1077{
1078 TestTraceContainer trace;
1079 EccTraceBuilder builder;
1080 MemoryStore memory;
1081
1082 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1083 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1084 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1085 NoopEventEmitter<ToRadixEvent> to_radix_event_emitter;
1086
1087 StrictMock<MockExecutionIdManager> execution_id_manager;
1088 EXPECT_CALL(execution_id_manager, get_execution_id)
1089 .WillRepeatedly(Return(0)); // Use a fixed execution IDfor the test
1090 PureGreaterThan gt;
1091 PureToRadix to_radix_simulator = PureToRadix();
1092 EccSimulator ecc_simulator(execution_id_manager,
1093 gt,
1094 to_radix_simulator,
1095 ecc_add_event_emitter,
1096 scalar_mul_event_emitter,
1097 ecc_add_memory_event_emitter);
1098
1100 ecc_simulator.add(memory, p, q, dst_address);
1101 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1102 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
1103
1104 check_relation<mem_aware_ecc>(trace);
1105}
1106
1107TEST(EccAddMemoryConstrainingTest, EccAddMemoryInteractions)
1108{
1109
1110 EccTraceBuilder builder;
1111 MemoryStore memory;
1112
1113 StrictMock<MockExecutionIdManager> execution_id_manager;
1114 EXPECT_CALL(execution_id_manager, get_execution_id)
1115 .WillRepeatedly(Return(0)); // Use a fixed execution IDfor the test
1116 PureGreaterThan gt;
1117 PureToRadix to_radix_simulator = PureToRadix();
1118
1119 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1120 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1121 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1122 NoopEventEmitter<ToRadixEvent> to_radix_event_emitter;
1123 EccSimulator ecc_simulator(execution_id_manager,
1124 gt,
1125 to_radix_simulator,
1126 ecc_add_event_emitter,
1127 scalar_mul_event_emitter,
1128 ecc_add_memory_event_emitter);
1129
1130 EmbeddedCurvePoint result = p + q;
1131
1132 uint32_t dst_address = 0x1000;
1133 // Set the execution and gt traces
1134 TestTraceContainer trace = TestTraceContainer({
1135 // Row 0
1136 {
1137 // Execution
1138 { C::execution_sel, 1 },
1139 { C::execution_sel_exec_dispatch_ecc_add, 1 },
1140 { C::execution_rop_4_, dst_address },
1141 { C::execution_register_0_, p.x() },
1142 { C::execution_register_1_, p.y() },
1143 { C::execution_register_2_, q.x() },
1144 { C::execution_register_3_, q.y() },
1145 // GT - dst out of range check
1146 { C::gt_sel, 1 },
1147 { C::gt_input_a, dst_address + 1 }, // highest write address is dst_address + 1
1148 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
1149 { C::gt_res, 0 },
1150 // Memory Writes
1151 { C::memory_address, dst_address },
1152 { C::memory_value, result.x() },
1153 { C::memory_sel, 1 },
1154 { C::memory_rw, 1 }, // write
1155 { C::memory_tag, static_cast<uint8_t>(MemoryTag::FF) },
1156 },
1157 {
1158 // Memory Writes
1159 { C::memory_address, dst_address + 1 },
1160 { C::memory_value, result.y() },
1161 { C::memory_sel, 1 },
1162 { C::memory_rw, 1 }, // write
1163 { C::memory_tag, static_cast<uint8_t>(MemoryTag::FF) },
1164 },
1165 });
1166
1167 ecc_simulator.add(memory, p, q, dst_address);
1168
1169 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1170 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
1171
1172 check_all_interactions<EccTraceBuilder>(trace);
1173 check_relation<mem_aware_ecc>(trace);
1174}
1175
1176TEST(EccAddMemoryConstrainingTest, EccAddMemoryInvalidDstRange)
1177{
1178
1179 EccTraceBuilder builder;
1180 MemoryStore memory;
1181
1182 NoopEventEmitter<ToRadixEvent> to_radix_event_emitter;
1183 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1184 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1185 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1186
1187 StrictMock<MockExecutionIdManager> execution_id_manager;
1188 EXPECT_CALL(execution_id_manager, get_execution_id)
1189 .WillRepeatedly(Return(0)); // Use a fixed execution ID for the test
1190 PureGreaterThan gt;
1191 PureToRadix to_radix_simulator = PureToRadix();
1192
1193 EccSimulator ecc_simulator(execution_id_manager,
1194 gt,
1195 to_radix_simulator,
1196 ecc_add_event_emitter,
1197 scalar_mul_event_emitter,
1198 ecc_add_memory_event_emitter);
1199
1200 uint32_t dst_address = AVM_HIGHEST_MEM_ADDRESS; // Invalid address, will result in out of range error
1201 // Set the execution and gt traces
1202 TestTraceContainer trace = TestTraceContainer({
1203 // Row 0
1204 {
1205 // Execution
1206 { C::execution_sel, 1 },
1207 { C::execution_sel_exec_dispatch_ecc_add, 1 },
1208 { C::execution_rop_4_, dst_address },
1209 { C::execution_register_0_, p.x() },
1210 { C::execution_register_1_, p.y() },
1211 { C::execution_register_2_, q.x() },
1212 { C::execution_register_3_, q.y() },
1213 { C::execution_sel_opcode_error, 1 },
1214 // GT - dst out of range check
1215 { C::gt_sel, 1 },
1216 { C::gt_input_a, static_cast<uint64_t>(dst_address) + 1 },
1217 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
1218 { C::gt_res, 1 },
1219 },
1220 });
1221
1222 EXPECT_THROW_WITH_MESSAGE(ecc_simulator.add(memory, p, q, dst_address), "EccException.* dst address out of range");
1223
1224 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1225 EXPECT_EQ(ecc_add_event_emitter.get_events().size(), 0); // Expect 0 add events since error in ecc_mem
1226
1227 check_all_interactions<EccTraceBuilder>(trace);
1228 check_relation<mem_aware_ecc>(trace);
1229}
1230
1231TEST(EccAddMemoryConstrainingTest, EccAddMemoryPointError)
1232{
1233
1234 EccTraceBuilder builder;
1235 MemoryStore memory;
1236 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1237 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1238 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1239
1240 StrictMock<MockExecutionIdManager> execution_id_manager;
1241 EXPECT_CALL(execution_id_manager, get_execution_id)
1242 .WillRepeatedly(Return(0)); // Use a fixed execution IDfor the test
1243 PureGreaterThan gt;
1244 PureToRadix to_radix_simulator = PureToRadix();
1245
1246 EccSimulator ecc_simulator(execution_id_manager,
1247 gt,
1248 to_radix_simulator,
1249 ecc_add_event_emitter,
1250 scalar_mul_event_emitter,
1251 ecc_add_memory_event_emitter);
1252
1253 // Point P is not on the curve
1254 FF p_x("0x0000000000063d46918a156cae92db1bcbc4072a27ec81dc82ea959abdbcf16a");
1255 FF p_y("0x00000000000c1370462c74775765d07fc21fd1093cc988149d3aa763bb3dbb60");
1256 EmbeddedCurvePoint p(p_x, p_y);
1257
1258 uint32_t dst_address = 0x1000;
1259
1260 EXPECT_CALL(execution_id_manager, get_execution_id()).WillOnce(::testing::Return(0));
1261 // Set the execution and gt traces
1262 TestTraceContainer trace = TestTraceContainer({
1263 // Row 0
1264 {
1265 // Execution
1266 { C::execution_sel, 1 },
1267 { C::execution_sel_exec_dispatch_ecc_add, 1 },
1268 { C::execution_rop_4_, dst_address },
1269 { C::execution_register_0_, p.x() },
1270 { C::execution_register_1_, p.y() },
1271 { C::execution_register_2_, q.x() },
1272 { C::execution_register_3_, q.y() },
1273 { C::execution_sel_opcode_error, 1 }, // Indicate an error in the operation
1274 // GT - dst out of range check
1275 { C::gt_sel, 1 },
1276 { C::gt_input_a, dst_address + 1 }, // highest write address is dst_address + 1
1277 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
1278 { C::gt_res, 0 },
1279 },
1280 });
1281
1282 EXPECT_THROW(ecc_simulator.add(memory, p, q, dst_address), simulation::EccException);
1283
1284 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1285 // Expect no events to be emitted since the operation failed
1286 EXPECT_EQ(ecc_add_event_emitter.get_events().size(), 0);
1287
1288 check_all_interactions<EccTraceBuilder>(trace);
1289 check_relation<mem_aware_ecc>(trace);
1290}
1291
1292TEST(EccAddMemoryConstrainingTest, InfinityRepresentations)
1293{
1294 EccTraceBuilder builder;
1295 MemoryStore memory;
1296
1297 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1298 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1299 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1300
1301 StrictMock<MockExecutionIdManager> execution_id_manager;
1302 EXPECT_CALL(execution_id_manager, get_execution_id)
1303 .WillRepeatedly(Return(0)); // Use a fixed execution ID for the test
1304 PureGreaterThan gt;
1305 PureToRadix to_radix_simulator = PureToRadix();
1306 EccSimulator ecc_simulator(execution_id_manager,
1307 gt,
1308 to_radix_simulator,
1309 ecc_add_event_emitter,
1310 scalar_mul_event_emitter,
1311 ecc_add_memory_event_emitter);
1313
1314 // Point P is infinity
1316 // EmbeddedCurvePoint always sets extractable coordinates as (0,0) and the underlying point as
1317 // AffinePoint::infinity() for input infinity points.
1319 EXPECT_EQ(inf_bb, inf);
1320 TestTraceContainer trace;
1321
1322 // The circuit correctly assigns double_op = true when doubling inf:
1323 ecc_simulator.add(memory, inf, inf_bb, dst_address);
1324
1325 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
1326 check_relation<ecc>(trace);
1327 EXPECT_EQ(trace.get(C::ecc_double_op, 0), 1);
1328
1329 ecc_simulator.add(memory, inf, inf_bb, dst_address);
1330
1331 // Set memory reads:
1332 trace.set(0,
1333 { { // Execution
1334 { C::execution_sel, 1 },
1335 { C::execution_sel_exec_dispatch_ecc_add, 1 },
1336 { C::execution_rop_4_, dst_address + 1 },
1337 { C::execution_register_0_, inf.x() },
1338 { C::execution_register_1_, inf.y() },
1339 { C::execution_register_2_, inf_bb.x() },
1340 { C::execution_register_3_, inf_bb.y() },
1341 // GT - dst out of range check
1342 { C::gt_sel, 1 },
1343 { C::gt_input_a, dst_address + 2 },
1344 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
1345 { C::gt_res, 0 } } });
1346
1347 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1348
1349 // The derived is_inf column must be true if the coordinates are (0, 0):
1350 trace.set(C::ecc_add_mem_p_is_inf, 0, 0);
1351 EXPECT_THROW_WITH_MESSAGE(check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_P_CURVE_EQN),
1352 mem_aware_ecc::get_subrelation_label(mem_aware_ecc::SR_P_CURVE_EQN));
1353
1354 // If is_inf is set, the coordinates must be (0, 0):
1355 trace.set(C::ecc_add_mem_q_x, 0, 1);
1356 trace.set(C::ecc_add_mem_q_y, 0, 2);
1357 EXPECT_THROW_WITH_MESSAGE(check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_Q_INF_X_CHECK),
1358 mem_aware_ecc::get_subrelation_label(mem_aware_ecc::SR_Q_INF_X_CHECK));
1359 EXPECT_THROW_WITH_MESSAGE(check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_Q_INF_Y_CHECK),
1360 mem_aware_ecc::get_subrelation_label(mem_aware_ecc::SR_Q_INF_Y_CHECK));
1361}
1362
1363// Exercises the new #[P/Q_NOT_INF_CHECK] relations which enforce
1364// (X, Y) == (INFINITY_X, INFINITY_Y) ==> is_inf. The relations have the form
1365// is_inf = (1 - (x - INFINITY_X) * x_inv) * (1 - (y - INFINITY_Y) * y_inv),
1366// so an honestly-built trace satisfies them for every is_inf state.
1367TEST(EccAddMemoryConstrainingTest, EccAddMemoryNotInfCheckHappyPath)
1368{
1369 EccTraceBuilder builder;
1370 MemoryStore memory;
1371
1372 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1373 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1374 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1375
1376 StrictMock<MockExecutionIdManager> execution_id_manager;
1377 EXPECT_CALL(execution_id_manager, get_execution_id).WillRepeatedly(Return(0));
1378 PureGreaterThan gt;
1379 PureToRadix to_radix_simulator = PureToRadix();
1380 EccSimulator ecc_simulator(execution_id_manager,
1381 gt,
1382 to_radix_simulator,
1383 ecc_add_event_emitter,
1384 scalar_mul_event_emitter,
1385 ecc_add_memory_event_emitter);
1386
1387 // Exercise all four (is_inf_p, is_inf_q) combinations across multiple rows.
1390 ecc_simulator.add(memory, p, q, dst_address); // non-inf, non-inf
1391 ecc_simulator.add(memory, p, inf, dst_address); // non-inf, inf
1392 ecc_simulator.add(memory, inf, q, dst_address); // inf, non-inf
1393 ecc_simulator.add(memory, inf, inf, dst_address); // inf, inf
1394
1395 TestTraceContainer trace;
1396 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1397
1398 check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_P_NOT_INF_CHECK);
1399 check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_Q_NOT_INF_CHECK);
1400}
1401
1402// Attack scenario: cheater provides P == (INFINITY_X, INFINITY_Y) but claims
1403// p_is_inf == 0 to force a spurious POINT_NOT_ON_CURVE error. With p_x - INFINITY_X
1404// and p_y - INFINITY_Y both zero, the per-coordinate "at infinity" indicators are
1405// forced to 1 regardless of how the cheater chooses the inverse witnesses, so the
1406// relation requires p_is_inf == 1 and rejects the spoof.
1407TEST(EccAddMemoryConstrainingTest, EccAddMemoryNegativeClaimInfPIsNotInf)
1408{
1409 EccTraceBuilder builder;
1410 MemoryStore memory;
1411
1412 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1413 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1414 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1415
1416 StrictMock<MockExecutionIdManager> execution_id_manager;
1417 EXPECT_CALL(execution_id_manager, get_execution_id).WillRepeatedly(Return(0));
1418 PureGreaterThan gt;
1419 PureToRadix to_radix_simulator = PureToRadix();
1420 EccSimulator ecc_simulator(execution_id_manager,
1421 gt,
1422 to_radix_simulator,
1423 ecc_add_event_emitter,
1424 scalar_mul_event_emitter,
1425 ecc_add_memory_event_emitter);
1426
1429 ecc_simulator.add(memory, inf, q, dst_address);
1430
1431 TestTraceContainer trace;
1432 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1433
1434 // Sanity: the honest trace satisfies the relation.
1435 check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_P_NOT_INF_CHECK);
1436
1437 // Attack: claim the infinity P is not infinity.
1438 trace.set(C::ecc_add_mem_p_is_inf, 0, 0);
1439 EXPECT_THROW_WITH_MESSAGE(check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_P_NOT_INF_CHECK),
1440 mem_aware_ecc::get_subrelation_label(mem_aware_ecc::SR_P_NOT_INF_CHECK));
1441
1442 // Even arbitrary cheater witnesses cannot rescue the attack: both diffs are zero,
1443 // so multiplying them by any inverse witness still yields zero indicators of 1.
1444 trace.set(C::ecc_add_mem_p_x_inf_diff_inv, 0, FF(123));
1445 trace.set(C::ecc_add_mem_p_y_inf_diff_inv, 0, FF(456));
1446 EXPECT_THROW_WITH_MESSAGE(check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_P_NOT_INF_CHECK),
1447 mem_aware_ecc::get_subrelation_label(mem_aware_ecc::SR_P_NOT_INF_CHECK));
1448}
1449
1450TEST(EccAddMemoryConstrainingTest, EccAddMemoryNegativeClaimInfQIsNotInf)
1451{
1452 EccTraceBuilder builder;
1453 MemoryStore memory;
1454
1455 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1456 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1457 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1458
1459 StrictMock<MockExecutionIdManager> execution_id_manager;
1460 EXPECT_CALL(execution_id_manager, get_execution_id).WillRepeatedly(Return(0));
1461 PureGreaterThan gt;
1462 PureToRadix to_radix_simulator = PureToRadix();
1463 EccSimulator ecc_simulator(execution_id_manager,
1464 gt,
1465 to_radix_simulator,
1466 ecc_add_event_emitter,
1467 scalar_mul_event_emitter,
1468 ecc_add_memory_event_emitter);
1469
1472 ecc_simulator.add(memory, p, inf, dst_address);
1473
1474 TestTraceContainer trace;
1475 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1476
1477 check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_Q_NOT_INF_CHECK);
1478
1479 trace.set(C::ecc_add_mem_q_is_inf, 0, 0);
1480 EXPECT_THROW_WITH_MESSAGE(check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_Q_NOT_INF_CHECK),
1481 mem_aware_ecc::get_subrelation_label(mem_aware_ecc::SR_Q_NOT_INF_CHECK));
1482
1483 trace.set(C::ecc_add_mem_q_x_inf_diff_inv, 0, FF(123));
1484 trace.set(C::ecc_add_mem_q_y_inf_diff_inv, 0, FF(456));
1485 EXPECT_THROW_WITH_MESSAGE(check_relation<mem_aware_ecc>(trace, mem_aware_ecc::SR_Q_NOT_INF_CHECK),
1486 mem_aware_ecc::get_subrelation_label(mem_aware_ecc::SR_Q_NOT_INF_CHECK));
1487}
1488
1489} // namespace
1490} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:224
static constexpr size_t SR_OUTPUT_X_COORD
Definition ecc.hpp:44
static std::string get_subrelation_label(size_t index)
Definition ecc.hpp:47
static std::string get_subrelation_label(size_t index)
static constexpr size_t SR_INPUT_CONSISTENCY_X
static constexpr size_t SR_TRACE_CONTINUITY
static constexpr size_t SR_INPUT_CONSISTENCY_INF
static constexpr size_t SR_INPUT_CONSISTENCY_Y
static constexpr size_t SR_SEL_ON_START_OR_END
static constexpr size_t SR_INPUT_CONSISTENCY_SCALAR
void process(const simulation::EventEmitterInterface< simulation::AluEvent >::Container &events, TraceContainer &trace)
Process the ALU events and populate the ALU relevant columns in the trace.
const FF & get(Column col, uint32_t row) const
void set(Column col, uint32_t row, const FF &value, bool use_atomic_limbs=false)
static constexpr affine_element infinity()
AluTraceBuilder builder
Definition alu.test.cpp:124
ExecutionIdManager execution_id_manager
GreaterThan gt
TestTraceContainer trace
bool expected_result
void check_interaction(tracegen::TestTraceContainer &trace)
TEST(AvmFixedVKTests, FixedVKCommitments)
Test that the fixed VK commitments agree with the ones computed from precomputed columns.
TestTraceContainer empty_trace()
Definition fixtures.cpp:156
lookup_settings< lookup_scalar_mul_double_settings_ > lookup_scalar_mul_double_settings
AvmFlavorSettings::FF FF
Definition field.hpp:10
StandardAffinePoint< AvmFlavorSettings::EmbeddedCurve::AffineElement > EmbeddedCurvePoint
Definition field.hpp:12
AvmFlavorSettings::G1::Fq Fq
Definition field.hpp:11
lookup_settings< lookup_scalar_mul_to_radix_settings_ > lookup_scalar_mul_to_radix_settings
lookup_settings< lookup_scalar_mul_add_settings_ > lookup_scalar_mul_add_settings
uint32_t MemoryAddress
simulation::PublicDataTreeReadWriteEvent event
MemoryStore memory
VectorField result