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