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);
975 "SELECTOR_ON_START_OR_END");
976}
977
978TEST(ScalarMulConstrainingTest, NegativeMutateScalarOnEnd)
979{
980 EccTraceBuilder builder;
981
982 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
983 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
984 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
985
986 StrictMock<MockExecutionIdManager> execution_id_manager;
987 StrictMock<MockGreaterThan> gt;
988 PureToRadix to_radix_simulator = PureToRadix();
989 EccSimulator ecc_simulator(execution_id_manager,
990 gt,
991 to_radix_simulator,
992 ecc_add_event_emitter,
993 scalar_mul_event_emitter,
994 ecc_add_memory_event_emitter);
995
996 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
997 ecc_simulator.scalar_mul(p, scalar);
998
999 TestTraceContainer trace({
1000 { { C::precomputed_first_row, 1 } },
1001 });
1002
1003 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
1004 // Mutate the scalar on the end row
1005 trace.set(Column::scalar_mul_scalar, 254, 27);
1006 EXPECT_THROW_WITH_MESSAGE(check_relation<scalar_mul>(trace, scalar_mul::SR_INPUT_CONSISTENCY_SCALAR),
1007 "INPUT_CONSISTENCY_SCALAR");
1008}
1009
1010TEST(ScalarMulConstrainingTest, NegativeMutatePointXOnEnd)
1011{
1012 EccTraceBuilder builder;
1013
1014 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
1015 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1016 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1017
1018 StrictMock<MockExecutionIdManager> execution_id_manager;
1019 StrictMock<MockGreaterThan> gt;
1020 PureToRadix to_radix_simulator = PureToRadix();
1021 EccSimulator ecc_simulator(execution_id_manager,
1022 gt,
1023 to_radix_simulator,
1024 ecc_add_event_emitter,
1025 scalar_mul_event_emitter,
1026 ecc_add_memory_event_emitter);
1027
1028 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
1029 ecc_simulator.scalar_mul(p, scalar);
1030
1031 TestTraceContainer trace({
1032 { { C::precomputed_first_row, 1 } },
1033 });
1034
1035 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
1036 // Mutate the point on the end row
1037 trace.set(Column::scalar_mul_point_x, 254, q.x());
1038
1039 EXPECT_THROW_WITH_MESSAGE(check_relation<scalar_mul>(trace, scalar_mul::SR_INPUT_CONSISTENCY_X),
1040 "INPUT_CONSISTENCY_X");
1041}
1042
1043TEST(ScalarMulConstrainingTest, NegativeMutatePointYOnEnd)
1044{
1045 EccTraceBuilder builder;
1046
1047 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
1048 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1049 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1050
1051 StrictMock<MockExecutionIdManager> execution_id_manager;
1052 StrictMock<MockGreaterThan> gt;
1053 PureToRadix to_radix_simulator = PureToRadix();
1054 EccSimulator ecc_simulator(execution_id_manager,
1055 gt,
1056 to_radix_simulator,
1057 ecc_add_event_emitter,
1058 scalar_mul_event_emitter,
1059 ecc_add_memory_event_emitter);
1060
1061 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
1062 ecc_simulator.scalar_mul(p, scalar);
1063
1064 TestTraceContainer trace({
1065 { { C::precomputed_first_row, 1 } },
1066 });
1067
1068 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
1069 // Mutate the point on the end row
1070 trace.set(Column::scalar_mul_point_y, 254, q.y());
1071
1072 EXPECT_THROW_WITH_MESSAGE(check_relation<scalar_mul>(trace, scalar_mul::SR_INPUT_CONSISTENCY_Y),
1073 "INPUT_CONSISTENCY_Y");
1074}
1075
1076TEST(ScalarMulConstrainingTest, NegativeMutatePointInfOnEnd)
1077{
1078 EccTraceBuilder builder;
1079
1080 NoopEventEmitter<EccAddEvent> ecc_add_event_emitter;
1081 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1082 NoopEventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1083
1084 StrictMock<MockExecutionIdManager> execution_id_manager;
1085 StrictMock<MockGreaterThan> gt;
1086 PureToRadix to_radix_simulator = PureToRadix();
1087 EccSimulator ecc_simulator(execution_id_manager,
1088 gt,
1089 to_radix_simulator,
1090 ecc_add_event_emitter,
1091 scalar_mul_event_emitter,
1092 ecc_add_memory_event_emitter);
1093
1094 FF scalar = FF("0x0cc4c71e882bc62b7b3d1964a8540cb5211339dfcddd2e095fd444bf1aed4f09");
1095 ecc_simulator.scalar_mul(p, scalar);
1096
1097 TestTraceContainer trace({
1098 { { C::precomputed_first_row, 1 } },
1099 });
1100
1101 builder.process_scalar_mul(scalar_mul_event_emitter.dump_events(), trace);
1102 // Mutate the point on the end row
1103 trace.set(Column::scalar_mul_point_inf, 254, 1);
1104
1105 EXPECT_THROW_WITH_MESSAGE(check_relation<scalar_mul>(trace, scalar_mul::SR_INPUT_CONSISTENCY_INF),
1106 "INPUT_CONSISTENCY_INF");
1107}
1108
1110// Memory Aware Ecc Add
1112
1113TEST(EccAddMemoryConstrainingTest, EccAddMemoryEmptyRow)
1114{
1115 check_relation<mem_aware_ecc>(testing::empty_trace());
1116}
1117
1118TEST(EccAddMemoryConstrainingTest, EccAddMemory)
1119{
1120 TestTraceContainer trace;
1121 EccTraceBuilder builder;
1122 MemoryStore memory;
1123
1124 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1125 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1126 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1127 NoopEventEmitter<ToRadixEvent> to_radix_event_emitter;
1128
1129 StrictMock<MockExecutionIdManager> execution_id_manager;
1130 EXPECT_CALL(execution_id_manager, get_execution_id)
1131 .WillRepeatedly(Return(0)); // Use a fixed execution IDfor the test
1132 PureGreaterThan gt;
1133 PureToRadix to_radix_simulator = PureToRadix();
1134 EccSimulator ecc_simulator(execution_id_manager,
1135 gt,
1136 to_radix_simulator,
1137 ecc_add_event_emitter,
1138 scalar_mul_event_emitter,
1139 ecc_add_memory_event_emitter);
1140
1142 ecc_simulator.add(memory, p, q, dst_address);
1143 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1144 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
1145
1146 check_relation<mem_aware_ecc>(trace);
1147}
1148
1149TEST(EccAddMemoryConstrainingTest, EccAddMemoryInteractions)
1150{
1151
1152 EccTraceBuilder builder;
1153 MemoryStore memory;
1154
1155 StrictMock<MockExecutionIdManager> execution_id_manager;
1156 EXPECT_CALL(execution_id_manager, get_execution_id)
1157 .WillRepeatedly(Return(0)); // Use a fixed execution IDfor the test
1158 PureGreaterThan gt;
1159 PureToRadix to_radix_simulator = PureToRadix();
1160
1161 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1162 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1163 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1164 NoopEventEmitter<ToRadixEvent> to_radix_event_emitter;
1165 EccSimulator ecc_simulator(execution_id_manager,
1166 gt,
1167 to_radix_simulator,
1168 ecc_add_event_emitter,
1169 scalar_mul_event_emitter,
1170 ecc_add_memory_event_emitter);
1171
1172 EmbeddedCurvePoint result = p + q;
1173
1174 uint32_t dst_address = 0x1000;
1175 // Set the execution and gt traces
1176 TestTraceContainer trace = TestTraceContainer({
1177 // Row 0
1178 {
1179 // Execution
1180 { C::execution_sel, 1 },
1181 { C::execution_sel_exec_dispatch_ecc_add, 1 },
1182 { C::execution_rop_6_, dst_address },
1183 { C::execution_register_0_, p.x() },
1184 { C::execution_register_1_, p.y() },
1185 { C::execution_register_2_, p.is_infinity() ? 1 : 0 },
1186 { C::execution_register_3_, q.x() },
1187 { C::execution_register_4_, q.y() },
1188 { C::execution_register_5_, q.is_infinity() ? 1 : 0 },
1189 // GT - dst out of range check
1190 { C::gt_sel, 1 },
1191 { C::gt_input_a, dst_address + 2 }, // highest write address is dst_address + 2
1192 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
1193 { C::gt_res, 0 },
1194 // Memory Writes
1195 { C::memory_address, dst_address },
1196 { C::memory_value, result.x() },
1197 { C::memory_sel, 1 },
1198 { C::memory_rw, 1 }, // write
1199 { C::memory_tag, static_cast<uint8_t>(MemoryTag::FF) },
1200 },
1201 {
1202 // Memory Writes
1203 { C::memory_address, dst_address + 1 },
1204 { C::memory_value, result.y() },
1205 { C::memory_sel, 1 },
1206 { C::memory_rw, 1 }, // write
1207 { C::memory_tag, static_cast<uint8_t>(MemoryTag::FF) },
1208 },
1209 {
1210 // Memory Writes
1211 { C::memory_address, dst_address + 2 },
1212 { C::memory_value, result.is_infinity() },
1213 { C::memory_sel, 1 },
1214 { C::memory_rw, 1 }, // write
1215 { C::memory_tag, static_cast<uint8_t>(MemoryTag::U1) },
1216 },
1217 });
1218
1219 ecc_simulator.add(memory, p, q, dst_address);
1220
1221 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1222 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
1223
1224 check_all_interactions<EccTraceBuilder>(trace);
1225 check_relation<mem_aware_ecc>(trace);
1226}
1227
1228TEST(EccAddMemoryConstrainingTest, EccAddMemoryInvalidDstRange)
1229{
1230
1231 EccTraceBuilder builder;
1232 MemoryStore memory;
1233
1234 NoopEventEmitter<ToRadixEvent> to_radix_event_emitter;
1235 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1236 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1237 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1238
1239 StrictMock<MockExecutionIdManager> execution_id_manager;
1240 EXPECT_CALL(execution_id_manager, get_execution_id)
1241 .WillRepeatedly(Return(0)); // Use a fixed execution IDfor the test
1242 PureGreaterThan gt;
1243 PureToRadix to_radix_simulator = PureToRadix();
1244
1245 EccSimulator ecc_simulator(execution_id_manager,
1246 gt,
1247 to_radix_simulator,
1248 ecc_add_event_emitter,
1249 scalar_mul_event_emitter,
1250 ecc_add_memory_event_emitter);
1251
1252 uint32_t dst_address = AVM_HIGHEST_MEM_ADDRESS - 1; // Invalid address, will result in out of range error
1253 // Set the execution and gt traces
1254 TestTraceContainer trace = TestTraceContainer({
1255 // Row 0
1256 {
1257 // Execution
1258 { C::execution_sel, 1 },
1259 { C::execution_sel_exec_dispatch_ecc_add, 1 },
1260 { C::execution_rop_6_, dst_address },
1261 { C::execution_register_0_, p.x() },
1262 { C::execution_register_1_, p.y() },
1263 { C::execution_register_2_, p.is_infinity() ? 1 : 0 },
1264 { C::execution_register_3_, q.x() },
1265 { C::execution_register_4_, q.y() },
1266 { C::execution_register_5_, q.is_infinity() ? 1 : 0 },
1267 { C::execution_sel_opcode_error, 1 },
1268 // GT - dst out of range check
1269 { C::gt_sel, 1 },
1270 { C::gt_input_a, static_cast<uint64_t>(dst_address) + 2 },
1271 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
1272 { C::gt_res, 1 },
1273 },
1274 });
1275
1276 EXPECT_THROW_WITH_MESSAGE(ecc_simulator.add(memory, p, q, dst_address), "EccException.* dst address out of range");
1277
1278 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1279 EXPECT_EQ(ecc_add_event_emitter.get_events().size(), 0); // Expect 0 add events since error in ecc_mem
1280
1281 check_all_interactions<EccTraceBuilder>(trace);
1282 check_relation<mem_aware_ecc>(trace);
1283}
1284
1285TEST(EccAddMemoryConstrainingTest, EccAddMemoryPointError)
1286{
1287
1288 EccTraceBuilder builder;
1289 MemoryStore memory;
1290 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1291 NoopEventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1292 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1293
1294 StrictMock<MockExecutionIdManager> execution_id_manager;
1295 EXPECT_CALL(execution_id_manager, get_execution_id)
1296 .WillRepeatedly(Return(0)); // Use a fixed execution IDfor the test
1297 PureGreaterThan gt;
1298 PureToRadix to_radix_simulator = PureToRadix();
1299
1300 EccSimulator ecc_simulator(execution_id_manager,
1301 gt,
1302 to_radix_simulator,
1303 ecc_add_event_emitter,
1304 scalar_mul_event_emitter,
1305 ecc_add_memory_event_emitter);
1306
1307 // Point P is not on the curve
1308 FF p_x("0x0000000000063d46918a156cae92db1bcbc4072a27ec81dc82ea959abdbcf16a");
1309 FF p_y("0x00000000000c1370462c74775765d07fc21fd1093cc988149d3aa763bb3dbb60");
1310 EmbeddedCurvePoint p(p_x, p_y, false);
1311
1312 uint32_t dst_address = 0x1000;
1313
1314 EXPECT_CALL(execution_id_manager, get_execution_id()).WillOnce(::testing::Return(0));
1315 // Set the execution and gt traces
1316 TestTraceContainer trace = TestTraceContainer({
1317 // Row 0
1318 {
1319 // Execution
1320 { C::execution_sel, 1 },
1321 { C::execution_sel_exec_dispatch_ecc_add, 1 },
1322 { C::execution_rop_6_, dst_address },
1323 { C::execution_register_0_, p.x() },
1324 { C::execution_register_1_, p.y() },
1325 { C::execution_register_2_, p.is_infinity() ? 1 : 0 },
1326 { C::execution_register_3_, q.x() },
1327 { C::execution_register_4_, q.y() },
1328 { C::execution_register_5_, q.is_infinity() ? 1 : 0 },
1329 { C::execution_sel_opcode_error, 1 }, // Indicate an error in the operation
1330 // GT - dst out of range check
1331 { C::gt_sel, 1 },
1332 { C::gt_input_a, dst_address + 2 }, // highest write address is dst_address + 2
1333 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
1334 { C::gt_res, 0 },
1335 },
1336 });
1337
1338 EXPECT_THROW(ecc_simulator.add(memory, p, q, dst_address), simulation::EccException);
1339
1340 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1341 // Expect no events to be emitted since the operation failed
1342 EXPECT_EQ(ecc_add_event_emitter.get_events().size(), 0);
1343
1344 check_all_interactions<EccTraceBuilder>(trace);
1345 check_relation<mem_aware_ecc>(trace);
1346}
1347
1348TEST(EccAddMemoryConstrainingTest, InfinityRepresentations)
1349{
1350 EccTraceBuilder builder;
1351 MemoryStore memory;
1352
1353 EventEmitter<EccAddEvent> ecc_add_event_emitter;
1354 EventEmitter<ScalarMulEvent> scalar_mul_event_emitter;
1355 EventEmitter<EccAddMemoryEvent> ecc_add_memory_event_emitter;
1356
1357 StrictMock<MockExecutionIdManager> execution_id_manager;
1358 EXPECT_CALL(execution_id_manager, get_execution_id)
1359 .WillRepeatedly(Return(0)); // Use a fixed execution ID for the test
1360 PureGreaterThan gt;
1361 PureToRadix to_radix_simulator = PureToRadix();
1362 EccSimulator ecc_simulator(execution_id_manager,
1363 gt,
1364 to_radix_simulator,
1365 ecc_add_event_emitter,
1366 scalar_mul_event_emitter,
1367 ecc_add_memory_event_emitter);
1369
1370 // Point P is infinity
1372 // EmbeddedCurvePoint preserves raw coordinates (see StandardAffinePointTest)
1374 EmbeddedCurvePoint inf_alt = EmbeddedCurvePoint(1, 2, true);
1375 TestTraceContainer trace;
1376
1377 // Internal add() expects normalized points:
1378 EXPECT_THROW_WITH_MESSAGE(ecc_simulator.add(inf, inf_alt), "normalized");
1379
1380 // Coordinates are normalized in tracegen, so even though inf_bb and inf_alt have different coordinates, the circuit
1381 // correctly assigns double_op = true when doubling inf:
1382 ecc_simulator.add(memory, inf, inf_alt, dst_address);
1383 // As above for the noir (0, 0) and bb (x, 0) inf reps:
1384 ecc_simulator.add(memory, inf, inf_bb, dst_address + 3);
1385
1386 builder.process_add(ecc_add_event_emitter.dump_events(), trace);
1387 check_relation<ecc>(trace);
1388 EXPECT_EQ(trace.get(C::ecc_double_op, 0), 1);
1389
1390 // Set memory reads:
1391 trace.set(0,
1392 { { // Execution
1393 { C::execution_sel, 1 },
1394 { C::execution_sel_exec_dispatch_ecc_add, 1 },
1395 { C::execution_rop_6_, dst_address },
1396 { C::execution_register_0_, inf.x() },
1397 { C::execution_register_1_, inf.y() },
1398 { C::execution_register_2_, inf.is_infinity() ? 1 : 0 },
1399 { C::execution_register_3_, inf_alt.x() },
1400 { C::execution_register_4_, inf_alt.y() },
1401 { C::execution_register_5_, inf_alt.is_infinity() ? 1 : 0 },
1402 // GT - dst out of range check
1403 { C::gt_sel, 1 },
1404 { C::gt_input_a, dst_address + 2 }, // highest write address is dst_address + 2
1405 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
1406 { C::gt_res, 0 } } });
1407 trace.set(1,
1408 { { // Execution
1409 { C::execution_sel, 1 },
1410 { C::execution_sel_exec_dispatch_ecc_add, 1 },
1411 { C::execution_rop_6_, dst_address + 3 },
1412 { C::execution_register_0_, inf.x() },
1413 { C::execution_register_1_, inf.y() },
1414 { C::execution_register_2_, inf.is_infinity() ? 1 : 0 },
1415 { C::execution_register_3_, inf_bb.x() },
1416 { C::execution_register_4_, inf_bb.y() },
1417 { C::execution_register_5_, inf_bb.is_infinity() ? 1 : 0 },
1418 // GT - dst out of range check
1419 { C::gt_sel, 1 },
1420 { C::gt_input_a, dst_address + 5 },
1421 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
1422 { C::gt_res, 0 } } });
1423
1424 builder.process_add_with_memory(ecc_add_memory_event_emitter.dump_events(), trace);
1425
1426 // The original coordinates are stored in memory for the read...
1427 EXPECT_EQ(trace.get(C::ecc_add_mem_q_x, 1), inf_bb.x());
1428 EXPECT_EQ(trace.get(C::ecc_add_mem_q_y, 1), inf_bb.y());
1429 // ...but normalised coordinates are sent to the ecc subtrace:
1430 EXPECT_EQ(trace.get(C::ecc_add_mem_q_x_n, 1), 0);
1431 EXPECT_EQ(trace.get(C::ecc_add_mem_q_y_n, 1), 0);
1432 check_relation<mem_aware_ecc>(trace);
1433 check_relation<ecc>(trace);
1434 check_all_interactions<EccTraceBuilder>(trace);
1435 check_interaction<tracegen::ExecutionTraceBuilder, bb::avm2::perm_execution_dispatch_to_ecc_add_settings>(trace);
1436}
1437
1438} // namespace
1439} // 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:45
static constexpr size_t SR_INPUT_CONSISTENCY_X
static constexpr size_t SR_SELECTOR_ON_START_OR_END
static constexpr size_t SR_INPUT_CONSISTENCY_INF
static constexpr size_t SR_SELECTOR_CONSISTENCY
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
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