Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
to_radix.test.cpp
Go to the documentation of this file.
1#include <gmock/gmock.h>
2#include <gtest/gtest.h>
3
25
26namespace bb::avm2::constraining {
27namespace {
28
29using ::testing::Return;
30using ::testing::StrictMock;
31
32using tracegen::ExecutionTraceBuilder;
33using tracegen::GreaterThanTraceBuilder;
34using tracegen::PrecomputedTraceBuilder;
35using tracegen::TestTraceContainer;
36using tracegen::ToRadixTraceBuilder;
37
39using C = Column;
40using to_radix = bb::avm2::to_radix<FF>;
41using to_radix_mem = bb::avm2::to_radix_mem<FF>;
42using ToRadixSimulator = simulation::ToRadix;
43
44using simulation::EventEmitter;
45using simulation::GreaterThan;
46using simulation::GreaterThanEvent;
47using simulation::MockExecutionIdManager;
48using simulation::MockFieldGreaterThan;
49using simulation::NoopEventEmitter;
50using simulation::PureGreaterThan;
51using simulation::RangeCheck;
52using simulation::RangeCheckEvent;
53using simulation::ToRadixEvent;
54using simulation::ToRadixMemoryEvent;
55
56TEST(ToRadixConstrainingTest, EmptyRow)
57{
58 check_relation<to_radix>(testing::empty_trace());
59}
60
61TEST(ToRadixConstrainingTest, ToLeBitsBasicTest)
62{
63 EventEmitter<ToRadixEvent> to_radix_event_emitter;
64 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
65
66 PureGreaterThan gt;
67 StrictMock<MockExecutionIdManager> execution_id_manager;
68 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
69
70 auto [bits, truncated] = to_radix_simulator.to_le_bits(FF::one(), 254);
71
72 EXPECT_EQ(bits.size(), 254);
73 EXPECT_FALSE(truncated);
74
75 TestTraceContainer trace({
76 { { C::precomputed_first_row, 1 } },
77 });
78
79 ToRadixTraceBuilder builder;
80 builder.process(to_radix_event_emitter.dump_events(), trace);
81 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 254);
82 check_relation<to_radix>(trace);
83}
84
85TEST(ToRadixConstrainingTest, ToLeBitsPMinusOne)
86{
87 EventEmitter<ToRadixEvent> to_radix_event_emitter;
88 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
89
90 PureGreaterThan gt;
91 StrictMock<MockExecutionIdManager> execution_id_manager;
92 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
93
94 auto [bits, truncated] = to_radix_simulator.to_le_bits(FF::neg_one(), 254);
95
96 EXPECT_EQ(bits.size(), 254);
97 EXPECT_FALSE(truncated);
98
99 TestTraceContainer trace({
100 { { C::precomputed_first_row, 1 } },
101 });
102
103 ToRadixTraceBuilder builder;
104 builder.process(to_radix_event_emitter.dump_events(), trace);
105 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 254);
106 check_relation<to_radix>(trace);
107}
108
109TEST(ToRadixConstrainingTest, ToLeBitsShortest)
110{
111 EventEmitter<ToRadixEvent> to_radix_event_emitter;
112 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
113
114 PureGreaterThan gt;
115 StrictMock<MockExecutionIdManager> execution_id_manager;
116 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
117
118 auto [bits, truncated] = to_radix_simulator.to_le_bits(FF::one(), 1);
119
120 EXPECT_EQ(bits.size(), 1);
121 EXPECT_FALSE(truncated);
122
123 TestTraceContainer trace({
124 { { C::precomputed_first_row, 1 } },
125 });
126
127 ToRadixTraceBuilder builder;
128 builder.process(to_radix_event_emitter.dump_events(), trace);
129 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 1);
130 check_relation<to_radix>(trace);
131}
132
133TEST(ToRadixConstrainingTest, ToLeBitsPadded)
134{
135 EventEmitter<ToRadixEvent> to_radix_event_emitter;
136 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
137
138 PureGreaterThan gt;
139 StrictMock<MockExecutionIdManager> execution_id_manager;
140 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
141
142 auto [bits, truncated] = to_radix_simulator.to_le_bits(FF::one(), 500);
143
144 EXPECT_EQ(bits.size(), 500);
145 EXPECT_FALSE(truncated);
146
147 TestTraceContainer trace({
148 { { C::precomputed_first_row, 1 } },
149 });
150
151 ToRadixTraceBuilder builder;
152 builder.process(to_radix_event_emitter.dump_events(), trace);
153 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 500);
154 check_relation<to_radix>(trace);
155}
156
157TEST(ToRadixConstrainingTest, ToLeRadixBasic)
158{
159 EventEmitter<ToRadixEvent> to_radix_event_emitter;
160 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
161
162 PureGreaterThan gt;
163 StrictMock<MockExecutionIdManager> execution_id_manager;
164 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
165
166 FF value = FF::one();
167 auto [bytes, truncated] = to_radix_simulator.to_le_radix(value, 32, 256);
168
169 auto expected_bytes = value.to_buffer();
170 // to_buffer is BE
171 std::reverse(expected_bytes.begin(), expected_bytes.end());
172 EXPECT_EQ(bytes, expected_bytes);
173 EXPECT_FALSE(truncated);
174
175 TestTraceContainer trace({
176 { { C::precomputed_first_row, 1 } },
177 });
178
179 ToRadixTraceBuilder builder;
180 builder.process(to_radix_event_emitter.dump_events(), trace);
181 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 32);
182 check_relation<to_radix>(trace);
183}
184
185TEST(ToRadixConstrainingTest, ToLeRadixPMinusOne)
186{
187 EventEmitter<ToRadixEvent> to_radix_event_emitter;
188 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
189
190 PureGreaterThan gt;
191 StrictMock<MockExecutionIdManager> execution_id_manager;
192 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
193
194 FF value = FF::neg_one();
195 auto [bytes, truncated] = to_radix_simulator.to_le_radix(value, 32, 256);
196
197 auto expected_bytes = value.to_buffer();
198 // to_buffer is BE
199 std::reverse(expected_bytes.begin(), expected_bytes.end());
200 EXPECT_EQ(bytes, expected_bytes);
201 EXPECT_FALSE(truncated);
202
203 TestTraceContainer trace({
204 { { C::precomputed_first_row, 1 } },
205 });
206
207 ToRadixTraceBuilder builder;
208 builder.process(to_radix_event_emitter.dump_events(), trace);
209 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 32);
210 check_relation<to_radix>(trace);
211}
212
213TEST(ToRadixConstrainingTest, ToLeRadixOneByte)
214{
215 EventEmitter<ToRadixEvent> to_radix_event_emitter;
216 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
217
218 PureGreaterThan gt;
219 StrictMock<MockExecutionIdManager> execution_id_manager;
220 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
221
222 auto [bytes, truncated] = to_radix_simulator.to_le_radix(FF::one(), 1, 256);
223
224 std::vector<uint8_t> expected_bytes = { 1 };
225 EXPECT_EQ(bytes, expected_bytes);
226 EXPECT_FALSE(truncated);
227
228 TestTraceContainer trace({
229 { { C::precomputed_first_row, 1 } },
230 });
231
232 ToRadixTraceBuilder builder;
233 builder.process(to_radix_event_emitter.dump_events(), trace);
234 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 1);
235 check_relation<to_radix>(trace);
236}
237
238TEST(ToRadixConstrainingTest, ToLeRadixPadded)
239{
240 EventEmitter<ToRadixEvent> to_radix_event_emitter;
241 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
242
243 PureGreaterThan gt;
244 StrictMock<MockExecutionIdManager> execution_id_manager;
245 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
246
247 FF value = FF::neg_one();
248 auto [bytes, truncated] = to_radix_simulator.to_le_radix(value, 64, 256);
249
250 auto expected_bytes = value.to_buffer();
251 // to_buffer is BE
252 std::reverse(expected_bytes.begin(), expected_bytes.end());
253 expected_bytes.resize(64);
254 EXPECT_EQ(bytes, expected_bytes);
255 EXPECT_FALSE(truncated);
256
257 TestTraceContainer trace({
258 { { C::precomputed_first_row, 1 } },
259 });
260
261 ToRadixTraceBuilder builder;
262 builder.process(to_radix_event_emitter.dump_events(), trace);
263 EXPECT_EQ(trace.get_num_rows(), /*start_row=*/1 + 64);
264 check_relation<to_radix>(trace);
265}
266
267TEST(ToRadixConstrainingTest, ToLeBitsInteractions)
268{
269 EventEmitter<ToRadixEvent> to_radix_event_emitter;
270 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
271
272 PureGreaterThan gt;
273 StrictMock<MockExecutionIdManager> execution_id_manager;
274 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
275
276 to_radix_simulator.to_le_bits(FF::neg_one(), 254);
277
278 TestTraceContainer trace({
279 { { C::precomputed_first_row, 1 } },
280 });
281
282 ToRadixTraceBuilder to_radix_builder;
283 to_radix_builder.process(to_radix_event_emitter.dump_events(), trace);
284 tracegen::PrecomputedTraceBuilder precomputed_builder;
289
290 check_interaction<ToRadixTraceBuilder,
296
297 check_relation<to_radix>(trace);
298}
299
300TEST(ToRadixConstrainingTest, ToLeRadixInteractions)
301{
302 EventEmitter<ToRadixEvent> to_radix_event_emitter;
303 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
304
305 PureGreaterThan gt;
306 StrictMock<MockExecutionIdManager> execution_id_manager;
307 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
308
309 to_radix_simulator.to_le_radix(FF::neg_one(), 32, 256);
310
311 TestTraceContainer trace({
312 { { C::precomputed_first_row, 1 } },
313 });
314
315 ToRadixTraceBuilder to_radix_builder;
316 to_radix_builder.process(to_radix_event_emitter.dump_events(), trace);
317 tracegen::PrecomputedTraceBuilder precomputed_builder;
318
323
324 check_interaction<ToRadixTraceBuilder,
330
331 check_relation<to_radix>(trace);
332}
333
334TEST(ToRadixConstrainingTest, NegativeOverflowCheck)
335{
336 TestTraceContainer trace({
337 { { C::precomputed_first_row, 1 } },
338 });
339
340 std::vector<uint8_t> modulus_le_bits(256, 0);
341 for (size_t i = 0; i < 256; i++) {
342 modulus_le_bits[i] = static_cast<uint8_t>(FF::modulus.get_bit(i));
343 }
344
345 ToRadixEvent event = { .value = FF::zero(), .radix = 2, .limbs = modulus_le_bits };
346 std::vector<ToRadixEvent> events = { event };
347
348 ToRadixTraceBuilder builder;
349 builder.process(events, trace);
350
351 EXPECT_THROW_WITH_MESSAGE(check_relation<to_radix>(trace, to_radix::SR_OVERFLOW_CHECK), "OVERFLOW_CHECK");
352}
353
354TEST(ToRadixConstrainingTest, NegativeConsistency)
355{
356 EventEmitter<ToRadixEvent> to_radix_event_emitter;
357 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
358
359 PureGreaterThan gt;
360 StrictMock<MockExecutionIdManager> execution_id_manager;
361 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
362
363 to_radix_simulator.to_le_radix(FF(256), 32, 256);
364
365 TestTraceContainer trace({
366 { { C::precomputed_first_row, 1 } },
367 });
368
369 ToRadixTraceBuilder builder;
370 builder.process(to_radix_event_emitter.dump_events(), trace);
371
372 // Disable the selector in the middle
373 trace.set(Column::to_radix_sel, 6, 0);
374
376 "SELECTOR_CONSISTENCY");
377
378 // Mutate the radix
379 trace.set(Column::to_radix_radix, 5, 200);
380
382 "CONSTANT_CONSISTENCY_RADIX");
383
384 // Mutate the value
385 trace.set(Column::to_radix_value, 4, 27);
386
388 "CONSTANT_CONSISTENCY_VALUE");
389
390 // Mutate the safe_limbs
391 trace.set(Column::to_radix_safe_limbs, 3, 200);
392
394 "CONSTANT_CONSISTENCY_SAFE_LIMBS");
395}
396
398// ToRadix Memory Tests
400
401TEST(ToRadixMemoryConstrainingTest, EmptyRow)
402{
403 check_relation<to_radix_mem>(testing::empty_trace());
404}
405
406TEST(ToRadixMemoryConstrainingTest, BasicTest)
407{
408 // Values
409 FF value = FF(1337);
410 uint32_t radix = 10;
411 uint32_t num_limbs = 4;
412 uint32_t dst_addr = 10;
413
414 TestTraceContainer trace = TestTraceContainer({
415 // Row 0
416 {
417 { C::precomputed_first_row, 1 },
418 // GT check - Dst > AVM_HIGHEST_MEM_ADDRESS = false
419 { C::gt_sel, 1 },
420 { C::gt_input_a, dst_addr + num_limbs - 1 },
421 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
422 { C::gt_res, 0 }, // GT should return true
423 // Execution Trace (No gas)
424 { C::execution_sel, 1 },
425 { C::execution_sel_exec_dispatch_to_radix, 1 },
426 { C::execution_register_0_, value },
427 { C::execution_register_1_, radix },
428 { C::execution_register_2_, num_limbs },
429 { C::execution_register_3_, 0 }, // is_output_bits
430 { C::execution_rop_4_, dst_addr },
431
432 },
433 // Row 1
434 {
435 // To Radix Mem
436 { C::to_radix_mem_sel, 1 },
437 { C::to_radix_mem_max_mem_addr, AVM_HIGHEST_MEM_ADDRESS },
438 { C::to_radix_mem_two, 2 },
439 { C::to_radix_mem_two_five_six, 256 },
440 // Memory Inputs
441 { C::to_radix_mem_execution_clk, 0 },
442 { C::to_radix_mem_space_id, 0 },
443 { C::to_radix_mem_dst_addr, dst_addr },
444 { C::to_radix_mem_max_write_addr, dst_addr + num_limbs - 1 },
445 // To Radix Inputs
446 { C::to_radix_mem_value_to_decompose, value },
447 { C::to_radix_mem_radix, radix },
448 { C::to_radix_mem_num_limbs, num_limbs },
449 { C::to_radix_mem_is_output_bits, 0 },
450 // Control Flow
451 { C::to_radix_mem_start, 1 },
452 { C::to_radix_mem_num_limbs_minus_one_inv, num_limbs - 1 == 0 ? 0 : FF(num_limbs - 1).invert() },
453 // Helpers
454 { C::to_radix_mem_sel_num_limbs_is_zero, 0 },
455 { C::to_radix_mem_num_limbs_inv, FF(num_limbs).invert() },
456 { C::to_radix_mem_sel_value_is_zero, 0 },
457 { C::to_radix_mem_value_inv, value.invert() },
458 // Output
459 { C::to_radix_mem_limb_value, 1 },
460 { C::to_radix_mem_sel_should_decompose, 1 },
461 { C::to_radix_mem_sel_should_write_mem, 1 },
462 { C::to_radix_mem_limb_index_to_lookup, num_limbs - 1 },
463 { C::to_radix_mem_value_found, 1 },
464 { C::to_radix_mem_output_tag, static_cast<uint8_t>(MemoryTag::U8) },
465
466 // GT check - 2 > radix = false
467 { C::gt_sel, 1 },
468 { C::gt_input_a, 2 },
469 { C::gt_input_b, radix },
470 { C::gt_res, 0 }, // GT should return false
471 },
472 // Row 2
473 {
474 { C::to_radix_mem_sel, 1 },
475 // Memory Inputs
476 { C::to_radix_mem_execution_clk, 0 },
477 { C::to_radix_mem_space_id, 0 },
478 { C::to_radix_mem_dst_addr, dst_addr + 1 },
479 // To Radix Inputs
480 { C::to_radix_mem_value_to_decompose, value },
481 { C::to_radix_mem_radix, radix },
482 { C::to_radix_mem_num_limbs, num_limbs - 1 },
483 { C::to_radix_mem_is_output_bits, 0 },
484 // Control Flow
485 // num_limbs_minus_one = (num_limbs - 1) - 1)
486 { C::to_radix_mem_num_limbs_minus_one_inv, FF(num_limbs - 2).invert() },
487 // Output
488 { C::to_radix_mem_limb_value, 3 },
489 { C::to_radix_mem_sel_should_decompose, 1 },
490 { C::to_radix_mem_sel_should_write_mem, 1 },
491 { C::to_radix_mem_limb_index_to_lookup, num_limbs - 2 },
492 { C::to_radix_mem_output_tag, static_cast<uint8_t>(MemoryTag::U8) },
493 // GT check - Radix > 256 = false
494 { C::gt_sel, 1 },
495 { C::gt_input_a, radix },
496 { C::gt_input_b, 256 },
497 { C::gt_res, 0 }, // GT should return false
498 },
499 // Row 3
500 {
501 { C::to_radix_mem_sel, 1 },
502 // Memory Inputs
503 { C::to_radix_mem_execution_clk, 0 },
504 { C::to_radix_mem_space_id, 0 },
505 { C::to_radix_mem_dst_addr, dst_addr + 2 },
506 // To Radix Inputs
507 { C::to_radix_mem_value_to_decompose, value },
508 { C::to_radix_mem_radix, radix },
509 { C::to_radix_mem_num_limbs, num_limbs - 2 },
510 { C::to_radix_mem_is_output_bits, 0 },
511 // Control Flow
512 // num_limbs_minus_one = (num_limbs - 2) - 1)
513 { C::to_radix_mem_num_limbs_minus_one_inv, FF(num_limbs - 3).invert() },
514 // Output
515 { C::to_radix_mem_limb_value, 3 },
516 { C::to_radix_mem_sel_should_decompose, 1 },
517 { C::to_radix_mem_sel_should_write_mem, 1 },
518 { C::to_radix_mem_limb_index_to_lookup, num_limbs - 3 },
519 { C::to_radix_mem_output_tag, static_cast<uint8_t>(MemoryTag::U8) },
520 },
521 // Row 4
522 {
523 { C::to_radix_mem_sel, 1 },
524 // Memory Inputs
525 { C::to_radix_mem_execution_clk, 0 },
526 { C::to_radix_mem_space_id, 0 },
527 { C::to_radix_mem_dst_addr, 13 },
528 // To Radix Inputs
529 { C::to_radix_mem_value_to_decompose, value },
530 { C::to_radix_mem_radix, radix },
531 { C::to_radix_mem_num_limbs, num_limbs - 3 },
532 { C::to_radix_mem_is_output_bits, 0 },
533 // Control Flow
534 { C::to_radix_mem_last, 1 },
535 // Output
536 { C::to_radix_mem_limb_value, 7 },
537 { C::to_radix_mem_sel_should_decompose, 1 },
538 { C::to_radix_mem_sel_should_write_mem, 1 },
539 { C::to_radix_mem_limb_index_to_lookup, num_limbs - 4 },
540 { C::to_radix_mem_output_tag, static_cast<uint8_t>(MemoryTag::U8) },
541 },
542 });
543
544 // Set the memory values and addresses
545 MemoryAddress value_addr = 0xdeadbeef;
546 MemoryAddress radix_addr = 0x12345678;
547 MemoryAddress num_limbs_addr = 0xc0ffee;
548 MemoryAddress is_output_bits_addr = 0xfeedface;
549
551 { value_addr, MemoryValue::from<FF>(value) },
552 { radix_addr, MemoryValue::from<uint32_t>(radix) },
553 { num_limbs_addr, MemoryValue::from<uint32_t>(num_limbs) },
554 { is_output_bits_addr, MemoryValue::from<uint1_t>(false) },
555 { dst_addr, MemoryValue::from<uint8_t>(1) },
556 { dst_addr + 1, MemoryValue::from<uint8_t>(3) },
557 { dst_addr + 2, MemoryValue::from<uint8_t>(3) },
558 { dst_addr + 3, MemoryValue::from<uint8_t>(7) },
559 };
560
561 for (uint32_t i = 0; i < memory_values.size(); ++i) {
562 const auto& [addr, value] = memory_values[i];
563 trace.set(i,
564 {
565 { { C::memory_sel, 1 },
566 { C::memory_space_id, 0 },
567 { C::memory_address, addr },
568 { C::memory_value, value.as_ff() },
569 { C::memory_tag, static_cast<uint8_t>(value.get_tag()) },
570 { C::memory_rw, i > 3 ? 1 : 0 } },
571 });
572 }
573
574 EventEmitter<ToRadixEvent> to_radix_event_emitter;
575 NoopEventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
576
577 PureGreaterThan gt;
578 StrictMock<MockExecutionIdManager> execution_id_manager;
579 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
580
581 // Generate the events for the to_radix subtrace
582 to_radix_simulator.to_le_radix(value, num_limbs, radix);
583
584 ToRadixTraceBuilder builder;
585 auto events = to_radix_event_emitter.get_events();
586 builder.process(to_radix_event_emitter.dump_events(), trace);
587
588 PrecomputedTraceBuilder precomputed_builder;
591 precomputed_builder.process_misc(trace, 257); // Needed for precomputed safe limbs table
592
593 check_relation<to_radix_mem>(trace);
594 check_all_interactions<ToRadixTraceBuilder>(trace);
595
596 // Negative test: disable memory write after the start row:
597 trace.set(Column::to_radix_mem_sel_should_write_mem, 2, 0);
599 "SEL_SHOULD_WRITE_MEM_CONTINUITY");
600
601 // Negative test: disable decomposition after the start row:
602 trace.set(Column::to_radix_mem_sel_should_decompose, 2, 0);
604 "SEL_SHOULD_DECOMPOSE_CONTINUITY");
605}
606
607TEST(ToRadixMemoryConstrainingTest, DstOutOfRange)
608{
609 // Values
610 FF value = FF(1337);
611 uint32_t radix = 10;
612 uint32_t num_limbs = 2;
613 uint32_t dst_addr = static_cast<uint32_t>(AVM_HIGHEST_MEM_ADDRESS - 1); // This will cause an out-of-bounds error
614
615 TestTraceContainer trace = TestTraceContainer({
616 // Row 0
617 {
618 { C::precomputed_first_row, 1 },
619 // GT check
620 { C::gt_sel, 1 },
621 { C::gt_input_a, dst_addr + num_limbs - 1 },
622 { C::gt_input_b, AVM_HIGHEST_MEM_ADDRESS },
623 { C::gt_res, 1 }, // GT should return true
624 },
625 // Row 1
626 {
627 // Execution Trace (No gas)
628 { C::execution_sel, 1 },
629 { C::execution_sel_exec_dispatch_to_radix, 1 },
630 { C::execution_register_0_, value },
631 { C::execution_register_1_, radix },
632 { C::execution_register_2_, num_limbs },
633 { C::execution_register_3_, 0 }, // is_output_bits
634 { C::execution_rop_4_, dst_addr },
635 { C::execution_sel_opcode_error, 1 },
636
637 // To Radix Mem
638 { C::to_radix_mem_sel, 1 },
639 { C::to_radix_mem_max_mem_addr, AVM_HIGHEST_MEM_ADDRESS },
640 { C::to_radix_mem_two, 2 },
641 { C::to_radix_mem_two_five_six, 256 },
642 // Memory Inputs
643 { C::to_radix_mem_execution_clk, 0 },
644 { C::to_radix_mem_space_id, 0 },
645 { C::to_radix_mem_dst_addr, dst_addr },
646 { C::to_radix_mem_max_write_addr, dst_addr + num_limbs - 1 },
647 // To Radix Inputs
648 { C::to_radix_mem_value_to_decompose, value },
649 { C::to_radix_mem_radix, radix },
650 { C::to_radix_mem_num_limbs, num_limbs },
651 { C::to_radix_mem_is_output_bits, 0 },
652 // Errors
653 { C::to_radix_mem_sel_dst_out_of_range_err, 1 },
654 { C::to_radix_mem_input_validation_error, 1 },
655 { C::to_radix_mem_err, 1 },
656 // Control Flow
657 { C::to_radix_mem_start, 1 },
658 { C::to_radix_mem_last, 1 },
659 { C::to_radix_mem_num_limbs_minus_one_inv, num_limbs - 1 == 0 ? 0 : FF(num_limbs - 1).invert() },
660 // Helpers
661 { C::to_radix_mem_sel_num_limbs_is_zero, 0 },
662 { C::to_radix_mem_num_limbs_inv, FF(num_limbs).invert() },
663 { C::to_radix_mem_sel_value_is_zero, 0 },
664 { C::to_radix_mem_value_inv, value.invert() },
665 },
666 });
667
668 check_relation<to_radix_mem>(trace);
669 check_interaction<ToRadixTraceBuilder, lookup_to_radix_mem_check_dst_addr_in_range_settings>(trace);
670 check_interaction<ExecutionTraceBuilder, perm_execution_dispatch_to_to_radix_settings>(trace);
671}
672
673TEST(ToRadixMemoryConstrainingTest, InvalidRadix)
674{
675 // Values
676 FF value = FF(1337);
677 uint32_t radix = 0; // Invalid radix
678 uint32_t num_limbs = 2;
679 uint32_t dst_addr = 10;
680
681 TestTraceContainer trace = TestTraceContainer({
682 // Row 0
683 {
684 { C::precomputed_first_row, 1 },
685 // GT check
686 { C::gt_sel, 1 },
687 { C::gt_input_a, 2 },
688 { C::gt_input_b, radix },
689 { C::gt_res, 1 }, // GT should return true
690 },
691 // Row 1
692 {
693 { C::to_radix_mem_sel, 1 },
694 { C::to_radix_mem_max_mem_addr, AVM_HIGHEST_MEM_ADDRESS },
695 { C::to_radix_mem_two, 2 },
696 { C::to_radix_mem_two_five_six, 256 },
697 // Memory Inputs
698 { C::to_radix_mem_execution_clk, 0 },
699 { C::to_radix_mem_space_id, 0 },
700 { C::to_radix_mem_dst_addr, dst_addr },
701 { C::to_radix_mem_max_write_addr, dst_addr + num_limbs - 1 },
702 // To Radix Inputs
703 { C::to_radix_mem_value_to_decompose, value },
704 { C::to_radix_mem_radix, radix },
705 { C::to_radix_mem_num_limbs, num_limbs },
706 { C::to_radix_mem_is_output_bits, 0 },
707 // Errors
708 { C::to_radix_mem_sel_radix_lt_2_err, 1 },
709 { C::to_radix_mem_input_validation_error, 1 },
710 { C::to_radix_mem_err, 1 },
711 // Control Flow
712 { C::to_radix_mem_start, 1 },
713 { C::to_radix_mem_last, 1 },
714 { C::to_radix_mem_num_limbs_minus_one_inv, num_limbs - 1 == 0 ? 0 : FF(num_limbs - 1).invert() },
715 // Helpers
716 { C::to_radix_mem_sel_num_limbs_is_zero, 0 },
717 { C::to_radix_mem_num_limbs_inv, FF(num_limbs).invert() },
718 { C::to_radix_mem_sel_value_is_zero, 0 },
719 { C::to_radix_mem_value_inv, value.invert() },
720 },
721 });
722 check_relation<to_radix_mem>(trace);
723 check_interaction<ToRadixTraceBuilder, lookup_to_radix_mem_check_radix_lt_2_settings>(trace);
724}
725
726TEST(ToRadixMemoryConstrainingTest, InvalidBitwiseRadix)
727{
728 // Values
729 FF value = FF(1337);
730 uint32_t radix = 3; // Invalid radix since is_output_bits is true
731 uint32_t num_limbs = 2;
732 uint32_t dst_addr = 10;
733 bool is_output_bits = true;
734
735 TestTraceContainer trace = TestTraceContainer({
736 // Row 0
737 {
738 { C::precomputed_first_row, 1 },
739 // GT check
740 { C::gt_sel, 1 },
741 { C::gt_input_a, 2 },
742 { C::gt_input_b, radix },
743 { C::gt_res, 0 }, // GT should return false
744 },
745 // Row 1
746 {
747 { C::to_radix_mem_sel, 1 },
748 { C::to_radix_mem_max_mem_addr, AVM_HIGHEST_MEM_ADDRESS },
749 { C::to_radix_mem_two, 2 },
750 { C::to_radix_mem_two_five_six, 256 },
751 // Memory Inputs
752 { C::to_radix_mem_execution_clk, 0 },
753 { C::to_radix_mem_space_id, 0 },
754 { C::to_radix_mem_dst_addr, dst_addr },
755 { C::to_radix_mem_max_write_addr, dst_addr + num_limbs - 1 },
756 // To Radix Inputs
757 { C::to_radix_mem_value_to_decompose, value },
758 { C::to_radix_mem_radix, radix },
759 { C::to_radix_mem_num_limbs, num_limbs },
760 { C::to_radix_mem_is_output_bits, is_output_bits ? 1 : 0 },
761 // Errors
762 { C::to_radix_mem_sel_invalid_bitwise_radix, 1 }, // Invalid bitwise radix
763 { C::to_radix_mem_input_validation_error, 1 },
764 { C::to_radix_mem_err, 1 },
765 // Control Flow
766 { C::to_radix_mem_start, 1 },
767 { C::to_radix_mem_last, 1 },
768 { C::to_radix_mem_num_limbs_minus_one_inv, num_limbs - 1 == 0 ? 0 : FF(num_limbs - 1).invert() },
769 // Helpers
770 { C::to_radix_mem_sel_num_limbs_is_zero, 0 },
771 { C::to_radix_mem_num_limbs_inv, FF(num_limbs).invert() },
772 { C::to_radix_mem_sel_value_is_zero, 0 },
773 { C::to_radix_mem_value_inv, value.invert() },
774 },
775 });
776 check_relation<to_radix_mem>(trace);
777 check_interaction<ToRadixTraceBuilder, lookup_to_radix_mem_check_radix_lt_2_settings>(trace);
778}
779
780TEST(ToRadixMemoryConstrainingTest, InvalidNumLimbsForValue)
781{
782 // Values
783 FF value = FF(1337);
784 uint32_t radix = 3;
785 uint32_t num_limbs = 0; // num limbs should not be 0 if value != 0
786 uint32_t dst_addr = 10;
787 bool is_output_bits = false;
788
789 TestTraceContainer trace = TestTraceContainer({
790 // Row 0
791 {
792 { C::precomputed_first_row, 1 },
793 // GT check
794 { C::gt_sel, 1 },
795 { C::gt_input_a, 2 },
796 { C::gt_input_b, radix },
797 { C::gt_res, 0 }, // GT should return false
798 },
799 // Row 1
800 {
801 { C::to_radix_mem_sel, 1 },
802 { C::to_radix_mem_max_mem_addr, AVM_HIGHEST_MEM_ADDRESS },
803 { C::to_radix_mem_two, 2 },
804 { C::to_radix_mem_two_five_six, 256 },
805 // Memory Inputs
806 { C::to_radix_mem_execution_clk, 0 },
807 { C::to_radix_mem_space_id, 0 },
808 { C::to_radix_mem_dst_addr, dst_addr },
809 { C::to_radix_mem_max_write_addr, dst_addr + num_limbs - 1 },
810 // To Radix Inputs
811 { C::to_radix_mem_value_to_decompose, value },
812 { C::to_radix_mem_radix, radix },
813 { C::to_radix_mem_num_limbs, num_limbs },
814 { C::to_radix_mem_is_output_bits, is_output_bits ? 1 : 0 },
815 // Errors
816 { C::to_radix_mem_sel_invalid_num_limbs_err, 1 }, // num_limbs should not be 0 if value != 0
817 { C::to_radix_mem_input_validation_error, 1 },
818 { C::to_radix_mem_err, 1 },
819 // Control Flow
820 { C::to_radix_mem_start, 1 },
821 { C::to_radix_mem_last, 1 },
822 { C::to_radix_mem_num_limbs_minus_one_inv, num_limbs - 1 == 0 ? 0 : FF(num_limbs - 1).invert() },
823 // Helpers
824 { C::to_radix_mem_sel_num_limbs_is_zero, 1 }, // num limbs is zero
825 { C::to_radix_mem_num_limbs_inv, 0 },
826 { C::to_radix_mem_sel_value_is_zero, 0 },
827 { C::to_radix_mem_value_inv, value.invert() },
828 },
829 });
830 check_relation<to_radix_mem>(trace);
831 check_interaction<ToRadixTraceBuilder, lookup_to_radix_mem_check_radix_lt_2_settings>(trace);
832}
833
834TEST(ToRadixMemoryConstrainingTest, TruncationError)
835{
836 // Values
837 FF value = FF(1337);
838 uint32_t radix = 10;
839 uint32_t num_limbs = 3;
840 uint32_t dst_addr = 10;
841 bool is_output_bits = false;
842
843 TestTraceContainer trace = TestTraceContainer({
844 // Row 0
845 {
846 { C::precomputed_first_row, 1 },
847 // GT check
848 { C::gt_sel, 1 },
849 { C::gt_input_a, 2 },
850 { C::gt_input_b, radix },
851 { C::gt_res, 0 }, // GT should return false
852 },
853 // Row 1
854 {
855 { C::to_radix_mem_sel, 1 },
856 { C::to_radix_mem_max_mem_addr, AVM_HIGHEST_MEM_ADDRESS },
857 { C::to_radix_mem_two, 2 },
858 { C::to_radix_mem_two_five_six, 256 },
859 // Memory Inputs
860 { C::to_radix_mem_execution_clk, 0 },
861 { C::to_radix_mem_space_id, 0 },
862 { C::to_radix_mem_dst_addr, dst_addr },
863 { C::to_radix_mem_max_write_addr, dst_addr + num_limbs - 1 },
864 // To Radix Inputs
865 { C::to_radix_mem_value_to_decompose, value },
866 { C::to_radix_mem_radix, radix },
867 { C::to_radix_mem_num_limbs, num_limbs },
868 { C::to_radix_mem_is_output_bits, is_output_bits ? 1 : 0 },
869 // Errors
870 { C::to_radix_mem_sel_truncation_error, 1 }, // found = false on the last le limb
871 { C::to_radix_mem_err, 1 },
872 // Control Flow
873 { C::to_radix_mem_start, 1 },
874 { C::to_radix_mem_last, 1 },
875 { C::to_radix_mem_num_limbs_minus_one_inv, num_limbs - 1 == 0 ? 0 : FF(num_limbs - 1).invert() },
876 // Decomposition
877 { C::to_radix_mem_sel_should_decompose, 1 },
878 { C::to_radix_mem_limb_index_to_lookup, num_limbs - 1 },
879 { C::to_radix_mem_limb_value, 3 },
880 { C::to_radix_mem_value_found, 0 },
881 // Helpers
882 { C::to_radix_mem_num_limbs_inv, FF(num_limbs).invert() },
883 { C::to_radix_mem_sel_value_is_zero, 0 },
884 { C::to_radix_mem_value_inv, value.invert() },
885 },
886 });
887 check_relation<to_radix_mem>(trace);
888 check_interaction<ToRadixTraceBuilder, lookup_to_radix_mem_check_radix_lt_2_settings>(trace);
889
890 // Negative test: truncation error should be on if found = false on the start row
891 trace.set(C::to_radix_mem_sel_truncation_error, 1, 0);
893 "TRUNCATION_ERROR");
894 trace.set(C::to_radix_mem_sel_truncation_error, 1, 1);
895
896 // Negative test: truncation error can't be on if found = true on the start row
897 trace.set(C::to_radix_mem_value_found, 1, 1);
899 "TRUNCATION_ERROR");
900}
901
902TEST(ToRadixMemoryConstrainingTest, ZeroNumLimbsAndZeroValueIsNoop)
903{
904 // Values
905 FF value = FF(0);
906 uint32_t radix = 3;
907 uint32_t num_limbs = 0; // num limbs can be zero since value is zero
908 uint32_t dst_addr = 10;
909 bool is_output_bits = false;
910
911 TestTraceContainer trace = TestTraceContainer({
912 // Row 0
913 {
914 { C::precomputed_first_row, 1 },
915 // GT check
916 { C::gt_sel, 1 },
917 { C::gt_input_a, 2 },
918 { C::gt_input_b, radix },
919 { C::gt_res, 0 }, // GT should return false
920 },
921 // Row 1
922 {
923 { C::to_radix_mem_sel, 1 },
924 { C::to_radix_mem_max_mem_addr, AVM_HIGHEST_MEM_ADDRESS },
925 { C::to_radix_mem_two, 2 },
926 { C::to_radix_mem_two_five_six, 256 },
927 // Memory Inputs
928 { C::to_radix_mem_execution_clk, 0 },
929 { C::to_radix_mem_space_id, 0 },
930 { C::to_radix_mem_dst_addr, dst_addr },
931 { C::to_radix_mem_max_write_addr, dst_addr + num_limbs - 1 },
932 // To Radix Inputs
933 { C::to_radix_mem_value_to_decompose, value },
934 { C::to_radix_mem_radix, radix },
935 { C::to_radix_mem_num_limbs, num_limbs },
936 { C::to_radix_mem_is_output_bits, is_output_bits ? 1 : 0 },
937 // Control Flow
938 { C::to_radix_mem_start, 1 },
939 { C::to_radix_mem_last, 1 },
940 { C::to_radix_mem_num_limbs_minus_one_inv, num_limbs - 1 == 0 ? 0 : FF(num_limbs - 1).invert() },
941 // Helpers
942 { C::to_radix_mem_sel_num_limbs_is_zero, 1 }, // num limbs is zero
943 { C::to_radix_mem_num_limbs_inv, 0 },
944 { C::to_radix_mem_sel_value_is_zero, 1 },
945 { C::to_radix_mem_value_inv, 0 },
946 },
947 });
948 check_relation<to_radix_mem>(trace);
949 check_interaction<ToRadixTraceBuilder, lookup_to_radix_mem_check_radix_lt_2_settings>(trace);
950}
951
952TEST(ToRadixMemoryConstrainingTest, ComplexTest)
953{
954 EventEmitter<ToRadixEvent> to_radix_event_emitter;
955 EventEmitter<ToRadixMemoryEvent> to_radix_mem_event_emitter;
956 EventEmitter<RangeCheckEvent> range_check_emitter;
957 EventEmitter<GreaterThanEvent> gt_emitter;
958
959 simulation::MemoryStore memory;
960 StrictMock<MockExecutionIdManager> execution_id_manager;
961 StrictMock<MockFieldGreaterThan> field_gt;
962 RangeCheck range_check(range_check_emitter);
963 GreaterThan gt(field_gt, range_check, gt_emitter);
964 EXPECT_CALL(execution_id_manager, get_execution_id()).WillOnce(Return(0)).WillOnce(Return(1));
965 ToRadixSimulator to_radix_simulator(execution_id_manager, gt, to_radix_event_emitter, to_radix_mem_event_emitter);
966
967 FF value = FF::neg_one();
968 uint32_t radix = 2;
969 uint32_t num_limbs = 256;
971 bool is_output_bits = true;
972 // Two calls to test transitions between contiguous chunks of computation
973 to_radix_simulator.to_be_radix(memory, value, radix, num_limbs, is_output_bits, dst_addr);
974 to_radix_simulator.to_be_radix(
975 memory, /*value=*/FF(1337), /*radix=*/10, /*num_limbs=*/6, /*is_output_bits=*/false, /*dst_addr=*/0xdeadbeef);
976
977 TestTraceContainer trace;
978 ToRadixTraceBuilder builder;
979 builder.process(to_radix_event_emitter.dump_events(), trace);
980 builder.process_with_memory(to_radix_mem_event_emitter.dump_events(), trace);
981
982 GreaterThanTraceBuilder gt_builder;
983 gt_builder.process(gt_emitter.dump_events(), trace);
984
985 PrecomputedTraceBuilder precomputed_builder;
988 precomputed_builder.process_misc(trace, 257); // Needed for precomputed safe limbs table
989
990 check_relation<to_radix>(trace);
991 check_relation<to_radix_mem>(trace);
992 // Skip the memory writes
993 check_interaction<ToRadixTraceBuilder,
1004}
1005
1006} // namespace
1007
1008} // namespace bb::avm2::constraining
#define AVM_HIGHEST_MEM_ADDRESS
static constexpr size_t SR_SEL_SHOULD_WRITE_MEM_CONTINUITY
static constexpr size_t SR_SEL_SHOULD_DECOMPOSE_CONTINUITY
static constexpr size_t SR_TRUNCATION_ERROR
static constexpr size_t SR_SELECTOR_CONSISTENCY
Definition to_radix.hpp:42
static constexpr size_t SR_OVERFLOW_CHECK
Definition to_radix.hpp:43
static constexpr size_t SR_CONSTANT_CONSISTENCY_SAFE_LIMBS
Definition to_radix.hpp:46
static constexpr size_t SR_CONSTANT_CONSISTENCY_RADIX
Definition to_radix.hpp:44
static constexpr size_t SR_CONSTANT_CONSISTENCY_VALUE
Definition to_radix.hpp:45
void process(const simulation::EventEmitterInterface< simulation::AluEvent >::Container &events, TraceContainer &trace)
void process(const simulation::EventEmitterInterface< simulation::GreaterThanEvent >::Container &events, TraceContainer &trace)
Definition gt_trace.cpp:11
void process_to_radix_p_decompositions(TraceContainer &trace)
void process_to_radix_safe_limbs(TraceContainer &trace)
void process_misc(TraceContainer &trace, const uint32_t num_rows=MAX_AVM_TRACE_SIZE)
void set(Column col, uint32_t row, const FF &value)
PrecomputedTraceBuilder precomputed_builder
Definition alu.test.cpp:119
AluTraceBuilder builder
Definition alu.test.cpp:123
GreaterThanTraceBuilder gt_builder
Definition alu.test.cpp:122
ExecutionIdManager execution_id_manager
uint32_t dst_addr
RangeCheck range_check
GreaterThan gt
TestTraceContainer trace
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessage)
Definition macros.hpp:7
void check_interaction(tracegen::TestTraceContainer &trace)
TEST(TxExecutionConstrainingTest, WriteTreeValue)
Definition tx.test.cpp:402
TestTraceContainer empty_trace()
Definition fixtures.cpp:153
lookup_settings< lookup_to_radix_limb_less_than_radix_range_settings_ > lookup_to_radix_limb_less_than_radix_range_settings
lookup_settings< lookup_to_radix_limb_p_diff_range_settings_ > lookup_to_radix_limb_p_diff_range_settings
lookup_settings< lookup_to_radix_mem_check_dst_addr_in_range_settings_ > lookup_to_radix_mem_check_dst_addr_in_range_settings
lookup_settings< lookup_to_radix_mem_check_radix_lt_2_settings_ > lookup_to_radix_mem_check_radix_lt_2_settings
lookup_settings< lookup_to_radix_limb_range_settings_ > lookup_to_radix_limb_range_settings
lookup_settings< lookup_to_radix_mem_check_radix_gt_256_settings_ > lookup_to_radix_mem_check_radix_gt_256_settings
lookup_settings< lookup_to_radix_fetch_safe_limbs_settings_ > lookup_to_radix_fetch_safe_limbs_settings
lookup_settings< lookup_to_radix_mem_input_output_to_radix_settings_ > lookup_to_radix_mem_input_output_to_radix_settings
uint32_t MemoryAddress
lookup_settings< lookup_to_radix_fetch_p_limb_settings_ > lookup_to_radix_fetch_p_limb_settings
AvmFlavorSettings::FF FF
Definition field.hpp:10
typename Flavor::FF FF
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
FieldGreaterThan field_gt
MemoryStore memory