Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
bitwise.test.cpp
Go to the documentation of this file.
1#include <gmock/gmock.h>
2#include <gtest/gtest.h>
3
4#include <cstdint>
5
23
24// Imports for keccak/sha256 vulnerability exploit tests
25#include "barretenberg/aztec/aztec_constants.hpp"
41
42namespace bb::avm2::constraining {
43namespace {
44
45using ::testing::Return;
46using ::testing::StrictMock;
47
48using tracegen::BitwiseTraceBuilder;
49using tracegen::ExecutionTraceBuilder;
50using tracegen::KeccakF1600TraceBuilder;
51using tracegen::PrecomputedTraceBuilder;
52using tracegen::Sha256TraceBuilder;
53using tracegen::TestTraceContainer;
54
55using simulation::Bitwise;
56using simulation::BitwiseEvent;
57using simulation::EventEmitter;
58using simulation::FieldGreaterThan;
59using simulation::FieldGreaterThanEvent;
60using simulation::GreaterThan;
61using simulation::GreaterThanEvent;
62using simulation::MemoryStore;
63using simulation::MockExecutionIdManager;
64using simulation::RangeCheck;
65using simulation::RangeCheckEvent;
66using simulation::Sha256;
67using simulation::Sha256CompressionEvent;
68
70using C = Column;
72using keccakf1600 = bb::avm2::keccakf1600<FF>;
73using sha256_relation = bb::avm2::sha256<FF>;
74
75TEST(BitwiseConstrainingTest, EmptyRow)
76{
77 check_relation<bitwise>(testing::empty_trace());
78}
79
80// Testing a positive AND operation for each integral type (U1, U8, ... U128). Each op is one row.
81TEST(BitwiseConstrainingTest, AndWithTracegen)
82{
83 TestTraceContainer trace;
84 BitwiseTraceBuilder builder;
86 { .operation = BitwiseOperation::AND,
87 .a = MemoryValue::from(uint1_t(1)),
88 .b = MemoryValue::from(uint1_t(1)),
89 .res = 1 },
90 { .operation = BitwiseOperation::AND,
91 .a = MemoryValue::from<uint8_t>(85),
92 .b = MemoryValue::from<uint8_t>(175),
93 .res = 5 },
94 { .operation = BitwiseOperation::AND,
95 .a = MemoryValue::from<uint16_t>(5323),
96 .b = MemoryValue::from<uint16_t>(321),
97 .res = 65 },
98 { .operation = BitwiseOperation::AND,
99 .a = MemoryValue::from<uint32_t>(13793),
100 .b = MemoryValue::from<uint32_t>(10590617),
101 .res = 4481 },
102 { .operation = BitwiseOperation::AND,
103 .a = MemoryValue::from<uint64_t>(0x7bff744e3cdf79LLU),
104 .b = MemoryValue::from<uint64_t>(0x14ccccccccb6LLU),
105 .res = 0x14444c0ccc30LLU },
106 { .operation = BitwiseOperation::AND,
107 .a = MemoryValue::from<uint128_t>((uint128_t{ 0xb900000000000001 } << 64)),
108 .b = MemoryValue::from<uint128_t>((uint128_t{ 0x1006021301080000 } << 64) +
109 uint128_t{ 0x000000000000001080876844827 }),
110 .res = uint128_t{ 0x1000000000000000 } << 64 }
111 };
112
113 builder.process(events, trace);
114
115 EXPECT_EQ(trace.get_num_rows(), 6); // 6 single-row operations.
116 check_relation<bitwise>(trace);
117}
118
119// Testing a positive OR operation for each integral type (U1, U8, ... U128). Each op is one row.
120TEST(BitwiseConstrainingTest, OrWithTracegen)
121{
122 TestTraceContainer trace;
123 BitwiseTraceBuilder builder;
125 { .operation = BitwiseOperation::OR,
126 .a = MemoryValue::from(uint1_t(1)),
127 .b = MemoryValue::from(uint1_t(0)),
128 .res = 1 },
129 { .operation = BitwiseOperation::OR,
130 .a = MemoryValue::from<uint8_t>(128),
131 .b = MemoryValue::from<uint8_t>(127),
132 .res = 255 },
133 { .operation = BitwiseOperation::OR,
134 .a = MemoryValue::from<uint16_t>(5323),
135 .b = MemoryValue::from<uint16_t>(321),
136 .res = 5579 },
137 { .operation = BitwiseOperation::OR,
138 .a = MemoryValue::from<uint32_t>(13793),
139 .b = MemoryValue::from<uint32_t>(10590617),
140 .res = 10599929 },
141 { .operation = BitwiseOperation::OR,
142 .a = MemoryValue::from<uint64_t>(0x7bff744e3cdf79LLU),
143 .b = MemoryValue::from<uint64_t>(0x14ccccccccb6LLU),
144 .res = 0x7bfffccefcdfffLLU },
145 { .operation = BitwiseOperation::OR,
146 .a = MemoryValue::from<uint128_t>((uint128_t{ 0xb900000000000000 } << 64)),
147 .b = MemoryValue::from<uint128_t>((uint128_t{ 0x1006021301080000 } << 64) +
148 uint128_t{ 0x000000000000001080876844827 }),
149 .res = (uint128_t{ 0xb906021301080000 } << 64) + uint128_t{ 0x0001080876844827 } },
150 };
151
152 builder.process(events, trace);
153
154 EXPECT_EQ(trace.get_num_rows(), 6); // 6 single-row operations.
155 check_relation<bitwise>(trace);
156}
157
158// Testing a positive XOR operation for each integral type (U1, U8, ... U128). Each op is one row.
159TEST(BitwiseConstrainingTest, XorWithTracegen)
160{
161 TestTraceContainer trace;
162 BitwiseTraceBuilder builder;
163
165 { .operation = BitwiseOperation::XOR,
166 .a = MemoryValue::from(uint1_t(1)),
167 .b = MemoryValue::from(uint1_t(1)),
168 .res = 0 },
169 { .operation = BitwiseOperation::XOR,
170 .a = MemoryValue::from<uint8_t>(85),
171 .b = MemoryValue::from<uint8_t>(175),
172 .res = 250 },
173 { .operation = BitwiseOperation::XOR,
174 .a = MemoryValue::from<uint16_t>(5323),
175 .b = MemoryValue::from<uint16_t>(321),
176 .res = 5514 },
177 { .operation = BitwiseOperation::XOR,
178 .a = MemoryValue::from<uint32_t>(13793),
179 .b = MemoryValue::from<uint32_t>(10590617),
180 .res = 10595448 },
181 { .operation = BitwiseOperation::XOR,
182 .a = MemoryValue::from<uint64_t>(0x7bff744e3cdf79LLU),
183 .b = MemoryValue::from<uint64_t>(0x14ccccccccb6LLU),
184 .res = 0x7bebb882f013cfLLU },
185 { .operation = BitwiseOperation::XOR,
186 .a = MemoryValue::from<uint128_t>((uint128_t{ 0xb900000000000001 } << 64)),
187 .b = MemoryValue::from<uint128_t>((uint128_t{ 0x1006021301080000 } << 64) +
188 uint128_t{ 0x000000000000001080876844827 }),
189 .res = (uint128_t{ 0xa906021301080001 } << 64) + uint128_t{ 0x0001080876844827 } },
190 };
191
192 builder.process(events, trace);
193
194 EXPECT_EQ(trace.get_num_rows(), 6); // 6 single-row operations.
195 check_relation<bitwise>(trace);
196}
197
198TEST(BitwiseConstrainingTest, MixedOperationsWithTracegen)
199{
200 TestTraceContainer trace;
201 BitwiseTraceBuilder builder;
203 { .operation = BitwiseOperation::OR,
204 .a = MemoryValue::from(uint1_t(1)),
205 .b = MemoryValue::from(uint1_t(0)),
206 .res = 1 },
207 { .operation = BitwiseOperation::AND,
208 .a = MemoryValue::from<uint32_t>(13793),
209 .b = MemoryValue::from<uint32_t>(10590617),
210 .res = 4481 },
211 { .operation = BitwiseOperation::XOR,
212 .a = MemoryValue::from<uint16_t>(5323),
213 .b = MemoryValue::from<uint16_t>(321),
214 .res = 5514 },
215 { .operation = BitwiseOperation::XOR,
216 .a = MemoryValue::from<uint32_t>(13793),
217 .b = MemoryValue::from<uint32_t>(10590617),
218 .res = 10595448 },
219 { .operation = BitwiseOperation::AND,
220 .a = MemoryValue::from<uint8_t>(85),
221 .b = MemoryValue::from<uint8_t>(175),
222 .res = 5 },
223 { .operation = BitwiseOperation::AND,
224 .a = MemoryValue::from<uint8_t>(85),
225 .b = MemoryValue::from<uint8_t>(175),
226 .res = 5 },
227 };
228
229 builder.process(events, trace);
230
231 EXPECT_EQ(trace.get_num_rows(), 6); // 6 single-row operations.
232 check_relation<bitwise>(trace);
233}
234
235// SIMD-64: two independent U64 operations packed into one row. ia/ib/ic hold lane 0 (low 64 bits),
236// ia_simd/ib_simd/ic_simd hold lane 1 (high 64 bits); all relations must hold and the lanes must be split.
237TEST(BitwiseConstrainingTest, Simd64WithTracegen)
238{
239 TestTraceContainer trace;
240 BitwiseTraceBuilder builder;
241
242 const uint64_t a1 = 0xAAAAAAAAAAAAAAAAULL;
243 const uint64_t b1 = 0x5555555555555555ULL;
244 const uint64_t a2 = 0x123456789ABCDEF0ULL;
245 const uint64_t b2 = 0x0F0F0F0F0F0F0F0FULL;
246 const uint128_t A = (static_cast<uint128_t>(a2) << 64) | a1;
247 const uint128_t B = (static_cast<uint128_t>(b2) << 64) | b1;
248
250 { .operation = BitwiseOperation::XOR,
251 .a = MemoryValue::from<uint128_t>(A),
252 .b = MemoryValue::from<uint128_t>(B),
253 .res = A ^ B,
254 .simd_64 = true },
255 { .operation = BitwiseOperation::AND,
256 .a = MemoryValue::from<uint128_t>(A),
257 .b = MemoryValue::from<uint128_t>(B),
258 .res = A & B,
259 .simd_64 = true },
260 };
261 builder.process(events, trace);
262
263 check_relation<bitwise>(trace);
264
265 // Row 1 (the SIMD XOR): the two lanes are split across ia/ib/ic (lane 0) and ia_simd/ib_simd/ic_simd (lane 1).
266 EXPECT_EQ(trace.get(C::bitwise_sel_simd_64, 0), 1);
267 EXPECT_EQ(trace.get(C::bitwise_ia, 0), FF(a1));
268 EXPECT_EQ(trace.get(C::bitwise_ia_simd, 0), FF(a2));
269 EXPECT_EQ(trace.get(C::bitwise_ib, 0), FF(b1));
270 EXPECT_EQ(trace.get(C::bitwise_ib_simd, 0), FF(b2));
271 EXPECT_EQ(trace.get(C::bitwise_ic, 0), FF(static_cast<uint64_t>(a1 ^ b1)));
272 EXPECT_EQ(trace.get(C::bitwise_ic_simd, 0), FF(static_cast<uint64_t>(a2 ^ b2)));
273}
274
275// SIMD-64 is only valid on a U128 row: sel_simd_64 = 1 with sel_u128 = 0 must be rejected.
276TEST(BitwiseConstrainingTest, NegativeSimd64OnlyOnU128)
277{
278 TestTraceContainer trace({
279 {
280 { C::bitwise_sel, 1 },
281 { C::bitwise_sel_keccak, 1 },
282 { C::bitwise_sel_simd_64, 1 },
283 { C::bitwise_sel_u128, 1 },
284 },
285 });
286
287 check_relation<bitwise>(trace, bitwise::SR_BITW_SIMD_ONLY_ON_U128);
288
289 trace.set(C::bitwise_sel_u128, 0, 0); // SIMD on a non-U128 row
292}
293
294// Verify the byte recomposition relations catch a tampered accumulator. The recomposition gates
295// each limb by its width selector, so a U16 row (sel_u8 = sel_u16 = 1) only sums limbs 0 and 1.
296TEST(BitwiseConstrainingTest, NegativeRecomposition)
297{
298 // acc = 0x1234 decomposed into limbs 0x34 (limb 0) and 0x12 (limb 1). The output limbs are the
299 // IC_BYTE_* aliases, so we drive them via an op selector (AND) and the per-limb outputs.
300 TestTraceContainer trace({
301 {
302 { C::bitwise_sel, 1 },
303 { C::bitwise_sel_compute, 1 },
304 { C::bitwise_sel_u16, 1 },
305 { C::bitwise_sel_and, 1 },
306 { C::bitwise_ia, 0x1234 },
307 { C::bitwise_ib, 0x1234 },
308 { C::bitwise_ic, 0x1234 },
309 { C::bitwise_ia_byte_0_, 0x34 },
310 { C::bitwise_ia_byte_1_, 0x12 },
311 { C::bitwise_ib_byte_0_, 0x34 },
312 { C::bitwise_ib_byte_1_, 0x12 },
313 { C::bitwise_output_and_0_, 0x34 }, // IC_BYTE_0 = sel_and * output_and_0
314 { C::bitwise_output_and_1_, 0x12 }, // IC_BYTE_1 = sel_and * output_and_1
315 },
316 });
317
319
320 trace.set(C::bitwise_ia, 0, 0x1235); // Mutate to wrong value violating BITW_RECOMP_A
321 trace.set(C::bitwise_ib, 0, 0x1334); // Mutate to wrong value violating BITW_RECOMP_B
322 trace.set(C::bitwise_ic, 0, 0x0234); // Mutate to wrong value violating BITW_RECOMP_C
323
324 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_RECOMP_A),
326 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_RECOMP_B),
328 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_RECOMP_C),
330}
331
332// Verify that a garbage value in an inactive high-order limb cannot affect the recomposed value:
333// the limb is gated by a zero width selector and drops out of the sum entirely.
334TEST(BitwiseConstrainingTest, InactiveLimbDoesNotAffectRecomposition)
335{
336 // U8 row (sel_u8 = 1, sel_u16 = 0): only limb 0 contributes. ia = ia_byte_0 = 0x34.
337 TestTraceContainer trace({
338 {
339 { C::bitwise_sel, 1 },
340 { C::bitwise_sel_compute, 1 },
341 { C::bitwise_ia, 0x34 },
342 { C::bitwise_ia_byte_0_, 0x34 },
343 { C::bitwise_ia_byte_1_, 0xFF }, // garbage in inactive limb 1
344 },
345 });
346
347 // Passes even with a non-zero inactive limb: it is multiplied by sel_u16 = 0.
348 check_relation<bitwise>(trace, bitwise::SR_BITW_RECOMP_A);
349}
350
351// Verify the tag byte length is correctly decomposed over the width selectors.
352TEST(BitwiseConstrainingTest, NegativeTagLenDecomposition)
353{
354 // U16: sel_u16 = 1, tag_byte_len = 1 + 1 = 2.
355 TestTraceContainer trace({
356 {
357 { C::bitwise_sel, 1 },
358 { C::bitwise_sel_compute, 1 },
359 { C::bitwise_sel_u16, 1 },
360 { C::bitwise_tag_byte_len, 2 },
361 },
362 });
363
364 check_relation<bitwise>(trace, bitwise::SR_BITW_TAG_LEN_DECOMPOSITION);
365
366 trace.set(C::bitwise_tag_byte_len, 0, 3); // Inconsistent with the selectors
369}
370
371// Verify that #[INPUT_TAG_CANNOT_BE_FF] catches a prover who hides an FF tag error.
372TEST(BitwiseConstrainingTest, NegativeInputTagCannotBeFF)
373{
374 TestTraceContainer trace;
375 BitwiseTraceBuilder builder;
377 { .operation = BitwiseOperation::XOR,
380 .res = 0 },
381 };
382 builder.process(events, trace);
383
384 check_relation<bitwise>(trace, bitwise::SR_INPUT_TAG_CANNOT_BE_FF);
385
386 // Mutate: hide the FF error (row 1 is the error row; row 0 is the sentinel).
387 trace.set(C::bitwise_sel_tag_ff_err, 0, 0);
388 trace.set(C::bitwise_err, 0, 0);
389
392}
393
394// Verify that #[INPUT_TAGS_SHOULD_MATCH] catches a prover who hides a tag mismatch.
395TEST(BitwiseConstrainingTest, NegativeInputTagsShouldMatch)
396{
397 TestTraceContainer trace;
398 BitwiseTraceBuilder builder;
400 { .operation = BitwiseOperation::AND,
403 .res = 0 },
404 };
405 builder.process(events, trace);
406
407 check_relation<bitwise>(trace, bitwise::SR_INPUT_TAGS_SHOULD_MATCH);
408
409 trace.set(C::bitwise_sel_tag_mismatch_err, 0, 0);
410 trace.set(C::bitwise_err, 0, 0);
411
414}
415
416// Verify that #[RES_TAG_SHOULD_MATCH_INPUT] catches tag_c != tag_a on a non-error row.
417TEST(BitwiseConstrainingTest, NegativeResTagShouldMatchInput)
418{
419 TestTraceContainer trace;
420 BitwiseTraceBuilder builder;
422 { .operation = BitwiseOperation::AND,
423 .a = MemoryValue::from<uint8_t>(85),
424 .b = MemoryValue::from<uint8_t>(175),
425 .res = 5 },
426 };
427 builder.process(events, trace);
428
429 check_relation<bitwise>(trace, bitwise::SR_RES_TAG_SHOULD_MATCH_INPUT);
430
431 // Row 1 is the (single) operation row. Mutate tag_c to differ from tag_a.
432 trace.set(C::bitwise_tag_c, 0, static_cast<uint8_t>(MemoryTag::U32));
433
436}
437
438TEST(BitwiseConstrainingTest, MixedOperationsInteractions)
439{
440 TestTraceContainer trace;
441 BitwiseTraceBuilder builder;
442 PrecomputedTraceBuilder precomputed_builder;
444 { .operation = BitwiseOperation::OR,
445 .a = MemoryValue::from(uint1_t(1)),
446 .b = MemoryValue::from(uint1_t(0)),
447 .res = 1 },
448 { .operation = BitwiseOperation::AND,
449 .a = MemoryValue::from<uint32_t>(13793),
450 .b = MemoryValue::from<uint32_t>(10590617),
451 .res = 4481 },
452 { .operation = BitwiseOperation::XOR,
453 .a = MemoryValue::from<uint16_t>(5323),
454 .b = MemoryValue::from<uint16_t>(321),
455 .res = 5514 },
456 { .operation = BitwiseOperation::XOR,
457 .a = MemoryValue::from<uint32_t>(13793),
458 .b = MemoryValue::from<uint32_t>(10590617),
459 .res = 10595448 },
460 { .operation = BitwiseOperation::AND,
461 .a = MemoryValue::from<uint8_t>(85),
462 .b = MemoryValue::from<uint8_t>(175),
463 .res = 5 },
464 { .operation = BitwiseOperation::AND,
465 .a = MemoryValue::from<uint8_t>(85),
466 .b = MemoryValue::from<uint8_t>(175),
467 .res = 5 },
468 };
469
470 builder.process(events, trace);
471
476
477 check_all_interactions<BitwiseTraceBuilder>(trace);
478 check_relation<bitwise>(trace);
479}
480
481TEST(BitwiseConstrainingTest, BitwiseExecInteraction)
482{
483 TestTraceContainer trace({ {
484 // Bitwise Entry (error row: sel=1, err=1)
485 { C::bitwise_sel, 1 },
486 { C::bitwise_err, 1 },
487 { C::bitwise_tag_a, static_cast<uint8_t>(MemoryTag::FF) },
488 { C::bitwise_tag_b, static_cast<uint8_t>(MemoryTag::U8) },
489 { C::bitwise_ia, 0x01 },
490 { C::bitwise_tag_c, static_cast<uint8_t>(MemoryTag::U8) },
491 { C::bitwise_ib, 0x01 },
492 { C::bitwise_ic, 0x00 },
493 // Execution Entry
494 { C::execution_mem_tag_reg_0_, static_cast<uint8_t>(MemoryTag::FF) },
495 { C::execution_mem_tag_reg_1_, static_cast<uint8_t>(MemoryTag::U8) },
496 { C::bitwise_op_id, static_cast<uint8_t>(BitwiseOperation::AND) },
497 { C::execution_mem_tag_reg_2_, static_cast<uint8_t>(MemoryTag::U8) },
498 { C::execution_register_0_, 0x01 },
499 { C::execution_register_1_, 0x01 },
500 { C::execution_register_2_, 0x00 },
501 { C::execution_sel_exec_dispatch_bitwise, 1 },
502 { C::execution_sel_opcode_error, 1 },
503 { C::execution_subtrace_operation_id, static_cast<uint8_t>(BitwiseOperation::AND) },
504 } });
505
506 check_interaction<ExecutionTraceBuilder, lookup_execution_dispatch_to_bitwise_settings>(trace);
507}
508
509TEST(BitwiseConstrainingTest, InvalidBitwiseExecInteraction)
510{
511 TestTraceContainer trace({ {
512 // Bitwise Entry
513 { C::bitwise_sel, 1 },
514 { C::bitwise_ib, 0x01 },
515 { C::bitwise_ia, 0x01 },
516 { C::bitwise_tag_a, static_cast<uint8_t>(MemoryTag::U8) },
517 { C::bitwise_tag_b, static_cast<uint8_t>(MemoryTag::U8) },
518 { C::bitwise_ic, 0x00 },
519 { C::bitwise_tag_c, static_cast<uint8_t>(MemoryTag::U8) },
520 { C::bitwise_op_id, static_cast<uint8_t>(BitwiseOperation::AND) },
521
522 // Execution Entry
523 { C::execution_mem_tag_reg_0_, static_cast<uint8_t>(MemoryTag::U8) },
524 { C::execution_mem_tag_reg_1_, static_cast<uint8_t>(MemoryTag::U16) }, // Mismatch
525 { C::execution_mem_tag_reg_2_, static_cast<uint8_t>(MemoryTag::U8) },
526 { C::execution_register_0_, 0x01 },
527 { C::execution_register_1_, 0x01 },
528 { C::execution_register_2_, 0x00 },
529 { C::execution_sel_exec_dispatch_bitwise, 1 },
530 { C::execution_subtrace_operation_id, static_cast<uint8_t>(BitwiseOperation::AND) },
531 } });
532
534 (check_interaction<ExecutionTraceBuilder, lookup_execution_dispatch_to_bitwise_settings>(trace)),
535 "Failed.*EXECUTION_DISPATCH_TO_BITWISE. Could not find tuple in destination.");
536}
537
538TEST(BitwiseConstrainingTest, ErrorHandlingInputFF)
539{
540 TestTraceContainer trace;
541 BitwiseTraceBuilder builder;
542 PrecomputedTraceBuilder precomputed_builder;
543
545 { .operation = BitwiseOperation::XOR,
548 .res = 0 },
549 };
550 builder.process(events, trace);
553
554 check_relation<bitwise>(trace);
555}
556
557TEST(BitwiseConstrainingTest, ErrorHandlingInputTagMismatch)
558{
559 TestTraceContainer trace;
560 BitwiseTraceBuilder builder;
561
563 { .operation = BitwiseOperation::AND,
566 .res = 0 },
567 };
568 builder.process(events, trace);
569
570 check_relation<bitwise>(trace);
571 check_all_interactions<BitwiseTraceBuilder>(trace);
572}
573
574TEST(BitwiseConstrainingTest, ErrorHandlingMultiple)
575{
576 TestTraceContainer trace;
577 BitwiseTraceBuilder builder;
578
580 { .operation = BitwiseOperation::AND,
583 .res = 0 },
584 };
585 builder.process(events, trace);
586
587 check_relation<bitwise>(trace);
588}
589
590TEST(BitwiseConstrainingTest, ExecBitwiseDispatchOnErrorMismatch)
591{
592 // Bitwise operations on mismatch tags should error out and produce FF(0) result.
595
596 TestTraceContainer trace({ {
597 // Execution Entry
598 { C::execution_sel_exec_dispatch_bitwise, 1 },
599 { C::execution_subtrace_operation_id, static_cast<uint8_t>(BitwiseOperation::AND) },
600 { C::execution_mem_tag_reg_0_, static_cast<uint8_t>(a.get_tag()) },
601 { C::execution_mem_tag_reg_1_, static_cast<uint8_t>(b.get_tag()) },
602 { C::execution_register_0_, a.as_ff() },
603 { C::execution_register_1_, b.as_ff() },
604
605 // Output is FF(0) due to error
606 { C::execution_mem_tag_reg_2_, static_cast<uint8_t>(MemoryTag::FF) },
607 { C::execution_register_2_, 0x00 },
608 { C::execution_sel_opcode_error, 1 },
609 } });
610
611 std::vector<simulation::BitwiseEvent> event = { { .operation = BitwiseOperation::AND, .a = a, .b = b, .res = 0 } };
612
613 BitwiseTraceBuilder builder;
614 builder.process(event, trace);
615
616 check_relation<bitwise>(trace);
617 check_interaction<ExecutionTraceBuilder, lookup_execution_dispatch_to_bitwise_settings>(trace);
618}
619
620TEST(BitwiseConstrainingTest, ExecBitwiseDispatchOnErrorFF)
621{
622 // Bitwise operations on FF tags should error out and produce FF(0) result.
623 MemoryValue a =
624 MemoryValue::from_tag(MemoryTag::FF, FF("0x1b7f6afaafbe72d6c3fc1bc92828a395341af3d33f805af83f06cbf0dcaca8a9"));
625 MemoryValue b = MemoryValue::from_tag(MemoryTag::U64, 9873803468411284649ULL);
626
627 TestTraceContainer trace({ {
628 // Execution Entry
629 { C::execution_sel_exec_dispatch_bitwise, 1 },
630 { C::execution_subtrace_operation_id, static_cast<uint8_t>(BitwiseOperation::OR) },
631 { C::execution_mem_tag_reg_0_, static_cast<uint8_t>(a.get_tag()) },
632 { C::execution_mem_tag_reg_1_, static_cast<uint8_t>(b.get_tag()) },
633 { C::execution_register_0_, a.as_ff() },
634 { C::execution_register_1_, b.as_ff() },
635
636 // Output is FF(0) due to error
637 { C::execution_mem_tag_reg_2_, static_cast<uint8_t>(MemoryTag::FF) },
638 { C::execution_register_2_, 0x00 },
639 { C::execution_sel_opcode_error, 1 },
640 } });
641
642 std::vector<simulation::BitwiseEvent> event = { { .operation = BitwiseOperation::OR, .a = a, .b = b, .res = 0 } };
643
644 BitwiseTraceBuilder builder;
645 builder.process(event, trace);
646
647 check_relation<bitwise>(trace);
648 check_interaction<ExecutionTraceBuilder, lookup_execution_dispatch_to_bitwise_settings>(trace);
649}
650
652// Vulnerability Tests: keccak/sha256 must not claim bitwise error rows
654
655// A malicious prover sets sel_keccak=1 on an error row (sel=1, err=1) to forge an XOR/AND result.
656// #[BITW_NO_EXTERNAL_ON_ERROR] makes this impossible.
657TEST(BitwiseConstrainingTest, VulnerabilityKeccakOnError)
658{
659 TestTraceContainer trace({
660 {
661 { C::bitwise_sel, 1 },
662 { C::bitwise_sel_keccak, 1 },
663 { C::bitwise_err, 1 },
664 },
665 });
666
669}
670
671// Same vulnerability but for sel_sha256 (used by SHA256 compression lookups).
672TEST(BitwiseConstrainingTest, VulnerabilitySha256OnError)
673{
674 TestTraceContainer trace({
675 {
676 { C::bitwise_sel, 1 },
677 { C::bitwise_sel_sha256, 1 },
678 { C::bitwise_err, 1 },
679 },
680 });
681
684}
685
686// Full exploit attempt: forging a keccak XOR result by mutating an intermediate and adding a ghost
687// bitwise row to satisfy the lookup. Because the scalar keccak lookups invoke the bitwise lookup with
688// both input tags, a ghost error row (tag mismatch) cannot match the source tuple, so the lookup fails.
689// (The SIMD-64 keccak lookups don't pass the tag -- they can't reach error rows since sel_simd_64 is a
690// term of #[BITW_NO_EXTERNAL_ON_ERROR] -- so this exploit class is exercised on a scalar lookup.)
691TEST(BitwiseConstrainingTest, VulnerabilityFakeKeccakXorOutput)
692{
693 TestTraceContainer trace;
694 const MemoryAddress src_addr = 0;
695 const MemoryAddress dst_addr = 200;
696 testing::generate_keccak_trace(trace, { dst_addr }, { src_addr }, /*space_id=*/23);
697
698 check_relation<keccakf1600>(trace);
699 check_relation<bitwise>(trace);
700
701 uint32_t keccak_start_row = 0;
702 for (uint32_t i = 0; i < trace.get_num_rows(); i++) {
703 if (trace.get(C::keccakf1600_start, i) == FF(1)) {
704 keccak_start_row = i;
705 break;
706 }
707 }
708 ASSERT_EQ(trace.get(C::keccakf1600_start, keccak_start_row), FF(1));
709 ASSERT_EQ(trace.get(C::keccakf1600_sel_no_error, keccak_start_row), FF(1));
710
711 // theta_xor_41 is computed by the scalar (non-SIMD) lookup THETA_XOR_41, which XORs sheet 4's
712 // state_in_40 and state_in_41 into theta_xor_41 as a single U64 operation.
713 FF real_state_in_40 = trace.get(C::keccakf1600_state_in_40, keccak_start_row);
714 FF real_state_in_41 = trace.get(C::keccakf1600_state_in_41, keccak_start_row);
715 FF real_theta_xor_41 = trace.get(C::keccakf1600_theta_xor_41, keccak_start_row);
716
717 // theta_xor_41 is a committed column only constrained by the THETA_XOR_41 lookup.
718 FF fake_theta_xor_41 = FF(0xFA0E0BAD0DEADULL);
719 ASSERT_NE(fake_theta_xor_41, real_theta_xor_41);
720 trace.set(C::keccakf1600_theta_xor_41, keccak_start_row, fake_theta_xor_41);
721
722 // Forged ghost row in bitwise to try to satisfy the lookup. The source passes both tags as U64;
723 // we give the ghost row a mismatched tag_b so the tuple cannot match.
724 uint32_t forged_row = trace.get_num_rows();
725 trace.set(forged_row,
726 { {
727 { C::bitwise_sel, 1 },
728 { C::bitwise_sel_keccak, 1 }, // Destination selector for scalar keccak lookups
729 { C::bitwise_op_id, FF(AVM_BITWISE_XOR_OP_ID) },
730 { C::bitwise_ia, real_state_in_40 },
731 { C::bitwise_ib, real_state_in_41 },
732 { C::bitwise_ic, fake_theta_xor_41 }, // FAKE output!
733 { C::bitwise_tag_a, FF(MEM_TAG_U64) },
734 { C::bitwise_tag_b, FF(MEM_TAG_U32) }, // != tag_a -> mismatch
735 { C::bitwise_tag_c, 0 },
736 } });
737
738 // Keccak relations still pass (theta_xor_41 is committed, not relationally constrained).
739 check_relation<keccakf1600>(trace);
740
741 // The exploited lookup fails: keccak passes both tags (U64, U64) but the ghost row has tag_b=U32.
743 (check_interaction<KeccakF1600TraceBuilder, lookup_keccakf1600_theta_xor_41_settings>(trace)),
744 "Failed.*LOOKUP_KECCAKF1600_THETA_XOR_41. Could not find tuple in destination.");
745}
746
747// Full exploit attempt: forging a SHA256 XOR result. Same vulnerability class as the keccak test.
748TEST(BitwiseConstrainingTest, VulnerabilityFakeSha256XorOutput)
749{
750 MemoryStore mem;
751 StrictMock<MockExecutionIdManager> execution_id_manager;
752 EXPECT_CALL(execution_id_manager, get_execution_id()).WillRepeatedly(Return(1));
753
754 EventEmitter<BitwiseEvent> bitwise_event_emitter;
755 EventEmitter<GreaterThanEvent> gt_event_emitter;
756 simulation::DeduplicatingEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
757 EventEmitter<RangeCheckEvent> range_check_event_emitter;
758
760 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
761 GreaterThan gt(field_gt, range_check, gt_event_emitter);
762 Bitwise bitwise_sim(bitwise_event_emitter);
763
764 EventEmitter<Sha256CompressionEvent> sha256_event_emitter;
765 Sha256 sha256_gadget(execution_id_manager, bitwise_sim, gt, range_check, sha256_event_emitter);
766
767 std::array<uint32_t, 8> state = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
768 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
769 MemoryAddress state_addr = 0;
770 for (uint32_t i = 0; i < 8; ++i) {
771 mem.set(state_addr + i, MemoryValue::from<uint32_t>(state[i]));
772 }
773
774 std::array<uint32_t, 16> input = { 0x61626380, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x18 };
775 MemoryAddress input_addr = 8;
776 for (uint32_t i = 0; i < 16; ++i) {
777 mem.set(input_addr + i, MemoryValue::from<uint32_t>(input[i]));
778 }
779 MemoryAddress output_addr = 25;
780
781 sha256_gadget.compression(mem, state_addr, input_addr, output_addr);
782
783 TestTraceContainer trace;
784 trace.set(C::precomputed_first_row, 0, 1);
785
786 Sha256TraceBuilder sha256_builder;
787 sha256_builder.process(sha256_event_emitter.get_events(), trace);
788
789 BitwiseTraceBuilder bitwise_builder;
790 bitwise_builder.process(bitwise_event_emitter.dump_events(), trace);
791
792 check_relation<sha256_relation>(trace);
793 check_relation<bitwise>(trace);
794
795 uint32_t sha256_row = 0;
796 bool found = false;
797 for (uint32_t i = 0; i < trace.get_num_rows(); i++) {
798 if (trace.get(C::sha256_sel_compute_w, i) == FF(1)) {
799 sha256_row = i;
800 found = true;
801 break;
802 }
803 }
804 ASSERT_TRUE(found) << "Could not find sha256 row with sel_compute_w=1";
805
806 FF real_w_15_rotr_7 = trace.get(C::sha256_w_15_rotr_7, sha256_row);
807 FF real_w_15_rotr_18 = trace.get(C::sha256_w_15_rotr_18, sha256_row);
808 FF real_xor_output = trace.get(C::sha256_w_15_rotr_7_xor_w_15_rotr_18, sha256_row);
809
810 FF fake_xor_output = FF(0xDEADBEEF);
811 ASSERT_NE(fake_xor_output, real_xor_output);
812 trace.set(C::sha256_w_15_rotr_7_xor_w_15_rotr_18, sha256_row, fake_xor_output);
813
814 // Forged ghost row in bitwise (error row, tag mismatch) to try to satisfy the lookup.
815 uint32_t forged_row = trace.get_num_rows();
816 trace.set(forged_row,
817 { {
818 { C::bitwise_sel, 1 },
819 { C::bitwise_sel_sha256, 1 }, // Destination selector for sha256 lookups
820 { C::bitwise_op_id, FF(AVM_BITWISE_XOR_OP_ID) },
821 { C::bitwise_ia, real_w_15_rotr_7 }, // Real input A
822 { C::bitwise_ib, real_w_15_rotr_18 }, // Real input B
823 { C::bitwise_ic, fake_xor_output }, // FAKE output!
824 { C::bitwise_tag_a, FF(MEM_TAG_U32) }, // SHA256 uses U32
825 { C::bitwise_tag_b, FF(MEM_TAG_U8) }, // != tag_a -> mismatch
826 { C::bitwise_sel_tag_mismatch_err, 1 },
827 { C::bitwise_sel_tag_ff_err, 0 },
828 { C::bitwise_err, 1 },
829 { C::bitwise_tag_a_inv, FF(MEM_TAG_U32).invert() },
830 { C::bitwise_tag_ab_diff_inv, FF(MEM_TAG_U32 - MEM_TAG_U8).invert() },
831 { C::bitwise_tag_c, 0 },
832 } });
833
834 // SHA256 relations still pass (the XOR intermediate is committed, not relationally constrained).
835 check_relation<sha256_relation>(trace);
836
837 // The exploited lookup fails because the source passes both tags (U32, U32) but the ghost row
838 // has tag_b=U8. The sha256 tracegen registers this lookup with C::bitwise_sel as the outer
839 // destination selector.
840 {
841 tracegen::SharedIndexCache cache;
842 tracegen::LookupIntoDynamicTableGeneric<lookup_sha256_w_s_0_xor_0_settings> lookup(cache, C::bitwise_sel);
843 EXPECT_THROW_WITH_MESSAGE((lookup.process(trace)),
844 "Failed.*LOOKUP_SHA256_W_S_0_XOR_0. Could not find tuple in destination.");
845 }
846}
847
848} // namespace
849} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:224
FieldGreaterThan field_gt
RangeCheck range_check
static TaggedValue from(T value)
static TaggedValue from_tag(ValueTag tag, FF value)
static constexpr size_t SR_BITW_SIMD_ONLY_ON_U128
Definition bitwise.hpp:43
static constexpr size_t SR_BITW_TAG_LEN_DECOMPOSITION
Definition bitwise.hpp:44
static constexpr size_t SR_BITW_RECOMP_A
Definition bitwise.hpp:45
static constexpr size_t SR_INPUT_TAG_CANNOT_BE_FF
Definition bitwise.hpp:39
static constexpr size_t SR_BITW_RECOMP_B
Definition bitwise.hpp:46
static constexpr size_t SR_BITW_RECOMP_C
Definition bitwise.hpp:47
static constexpr size_t SR_RES_TAG_SHOULD_MATCH_INPUT
Definition bitwise.hpp:41
static constexpr size_t SR_BITW_NO_EXTERNAL_ON_ERROR
Definition bitwise.hpp:42
static std::string get_subrelation_label(size_t index)
Definition bitwise.hpp:52
static constexpr size_t SR_INPUT_TAGS_SHOULD_MATCH
Definition bitwise.hpp:40
void set(MemoryAddress index, MemoryValue value) override
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_misc(TraceContainer &trace, const uint32_t num_rows=PRECOMPUTED_TRACE_SIZE)
Populate miscellaneous precomputed columns: first_row selector and idx (row index).
void process_bitwise(TraceContainer &trace)
Populate the 8-bit bitwise lookup table (AND, OR, XOR).
void process_sel_range_16(TraceContainer &trace)
Generate a selector column that activates the first 2^16 (65536) rows.
void process_tag_parameters(TraceContainer &trace)
Populate the memory tag parameters table (byte length, max bits, max value per tag).
const FF & get(Column col, uint32_t row) const
void set(Column col, uint32_t row, const FF &value, bool use_atomic_limbs=false)
PrecomputedTraceBuilder precomputed_builder
Definition alu.test.cpp:120
AluTraceBuilder builder
Definition alu.test.cpp:124
EventEmitter< GreaterThanEvent > gt_event_emitter
ExecutionIdManager execution_id_manager
MemoryStore mem
uint32_t src_addr
EventEmitter< RangeCheckEvent > range_check_event_emitter
uint32_t dst_addr
GreaterThan gt
TestTraceContainer trace
FF a
FF b
TEST(AvmFixedVKTests, FixedVKCommitments)
Test that the fixed VK commitments agree with the ones computed from precomputed columns.
void generate_keccak_trace(TestTraceContainer &trace, const std::vector< MemoryAddress > &dst_addresses, const std::vector< MemoryAddress > &src_addresses, uint16_t space_id)
TestTraceContainer empty_trace()
Definition fixtures.cpp:156
TaggedValue MemoryValue
AvmFlavorSettings::FF FF
Definition field.hpp:10
uint32_t MemoryAddress
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
simulation::PublicDataTreeReadWriteEvent event
unsigned __int128 uint128_t
Definition serialize.hpp:45
Bitwise bitwise
NoopEventEmitter< FieldGreaterThanEvent > field_gt_event_emitter
NoopEventEmitter< BitwiseEvent > bitwise_event_emitter
constexpr field invert() const noexcept