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