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
22
23// Imports for keccak/sha256 vulnerability exploit tests
40
41namespace bb::avm2::constraining {
42namespace {
43
44using ::testing::Return;
45using ::testing::StrictMock;
46
47using tracegen::BitwiseTraceBuilder;
48using tracegen::ExecutionTraceBuilder;
49using tracegen::KeccakF1600TraceBuilder;
50using tracegen::PrecomputedTraceBuilder;
51using tracegen::Sha256TraceBuilder;
52using tracegen::TestTraceContainer;
53
54using simulation::Bitwise;
55using simulation::BitwiseEvent;
56using simulation::EventEmitter;
57using simulation::FieldGreaterThan;
58using simulation::FieldGreaterThanEvent;
59using simulation::GreaterThan;
60using simulation::GreaterThanEvent;
61using simulation::MemoryStore;
62using simulation::MockExecutionIdManager;
63using simulation::RangeCheck;
64using simulation::RangeCheckEvent;
65using simulation::Sha256;
66using simulation::Sha256CompressionEvent;
67
69using C = Column;
71using keccakf1600 = bb::avm2::keccakf1600<FF>;
72using sha256_relation = bb::avm2::sha256<FF>;
73
74TEST(BitwiseConstrainingTest, EmptyRow)
75{
76 check_relation<bitwise>(testing::empty_trace());
77}
78
79// Testing a positive AND operation for each integral type (U1, U8, ... U128)
80TEST(BitwiseConstrainingTest, AndWithTracegen)
81{
82 TestTraceContainer trace;
83 BitwiseTraceBuilder builder;
85 { .operation = BitwiseOperation::AND,
86 .a = MemoryValue::from(uint1_t(1)),
87 .b = MemoryValue::from(uint1_t(1)),
88 .res = 1 },
89 { .operation = BitwiseOperation::AND,
90 .a = MemoryValue::from<uint8_t>(85),
91 .b = MemoryValue::from<uint8_t>(175),
92 .res = 5 },
93 { .operation = BitwiseOperation::AND,
94 .a = MemoryValue::from<uint16_t>(5323),
95 .b = MemoryValue::from<uint16_t>(321),
96 .res = 65 },
97 { .operation = BitwiseOperation::AND,
98 .a = MemoryValue::from<uint32_t>(13793),
99 .b = MemoryValue::from<uint32_t>(10590617),
100 .res = 4481 },
101 { .operation = BitwiseOperation::AND,
102 .a = MemoryValue::from<uint64_t>(0x7bff744e3cdf79LLU),
103 .b = MemoryValue::from<uint64_t>(0x14ccccccccb6LLU),
104 .res = 0x14444c0ccc30LLU },
105 { .operation = BitwiseOperation::AND,
106 .a = MemoryValue::from<uint128_t>((uint128_t{ 0xb900000000000001 } << 64)),
107 .b = MemoryValue::from<uint128_t>((uint128_t{ 0x1006021301080000 } << 64) +
108 uint128_t{ 0x000000000000001080876844827 }),
109 .res = uint128_t{ 0x1000000000000000 } << 64 }
110 };
111
112 builder.process(events, trace);
113 trace.set(C::precomputed_first_row, 0, 1);
114
115 EXPECT_EQ(trace.get_num_rows(), 33); // 33 = 1 + 1 + 1 + 2 + 4 + 8 + 16 (extra_shift_row U1 U8 U16 U32 U64 U128)
116 check_relation<bitwise>(trace);
117}
118
119// Testing a positive OR operation for each integral type (U1, U8, ... U128)
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 trace.set(C::precomputed_first_row, 0, 1);
154
155 EXPECT_EQ(trace.get_num_rows(), 33); // 33 = 1 + 1 + 1 + 2 + 4 + 8 + 16 (extra_shift_row U1 U8 U16 U32 U64 U128)
156 check_relation<bitwise>(trace);
157}
158
159// Testing a positive XOR operation for each integral type (U1, U8, ... U128)
160TEST(BitwiseConstrainingTest, XorWithTracegen)
161{
162 TestTraceContainer trace;
163 BitwiseTraceBuilder builder;
164
166 { .operation = BitwiseOperation::XOR,
167 .a = MemoryValue::from(uint1_t(1)),
168 .b = MemoryValue::from(uint1_t(1)),
169 .res = 0 },
170 { .operation = BitwiseOperation::XOR,
171 .a = MemoryValue::from<uint8_t>(85),
172 .b = MemoryValue::from<uint8_t>(175),
173 .res = 250 },
174 { .operation = BitwiseOperation::XOR,
175 .a = MemoryValue::from<uint16_t>(5323),
176 .b = MemoryValue::from<uint16_t>(321),
177 .res = 5514 },
178 { .operation = BitwiseOperation::XOR,
179 .a = MemoryValue::from<uint32_t>(13793),
180 .b = MemoryValue::from<uint32_t>(10590617),
181 .res = 10595448 },
182 { .operation = BitwiseOperation::XOR,
183 .a = MemoryValue::from<uint64_t>(0x7bff744e3cdf79LLU),
184 .b = MemoryValue::from<uint64_t>(0x14ccccccccb6LLU),
185 .res = 0x7bebb882f013cfLLU },
186 { .operation = BitwiseOperation::XOR,
187 .a = MemoryValue::from<uint128_t>((uint128_t{ 0xb900000000000001 } << 64)),
188 .b = MemoryValue::from<uint128_t>((uint128_t{ 0x1006021301080000 } << 64) +
189 uint128_t{ 0x000000000000001080876844827 }),
190 .res = (uint128_t{ 0xa906021301080001 } << 64) + uint128_t{ 0x0001080876844827 } },
191 };
192
193 builder.process(events, trace);
194 trace.set(C::precomputed_first_row, 0, 1);
195
196 EXPECT_EQ(trace.get_num_rows(), 33); // 33 = 1 + 1 + 1 + 2 + 4 + 8 + 16 (extra_shift_row U1 U8 U16 U32 U64 U128)
197 check_relation<bitwise>(trace);
198}
199
200TEST(BitwiseConstrainingTest, MixedOperationsWithTracegen)
201{
202 TestTraceContainer trace;
203 BitwiseTraceBuilder builder;
205 { .operation = BitwiseOperation::OR,
206 .a = MemoryValue::from(uint1_t(1)),
207 .b = MemoryValue::from(uint1_t(0)),
208 .res = 1 },
209 { .operation = BitwiseOperation::AND,
210 .a = MemoryValue::from<uint32_t>(13793),
211 .b = MemoryValue::from<uint32_t>(10590617),
212 .res = 4481 },
213 { .operation = BitwiseOperation::XOR,
214 .a = MemoryValue::from<uint16_t>(5323),
215 .b = MemoryValue::from<uint16_t>(321),
216 .res = 5514 },
217 { .operation = BitwiseOperation::XOR,
218 .a = MemoryValue::from<uint32_t>(13793),
219 .b = MemoryValue::from<uint32_t>(10590617),
220 .res = 10595448 },
221 { .operation = BitwiseOperation::AND,
222 .a = MemoryValue::from<uint8_t>(85),
223 .b = MemoryValue::from<uint8_t>(175),
224 .res = 5 },
225 { .operation = BitwiseOperation::AND,
226 .a = MemoryValue::from<uint8_t>(85),
227 .b = MemoryValue::from<uint8_t>(175),
228 .res = 5 },
229 };
230
231 builder.process(events, trace);
232 trace.set(C::precomputed_first_row, 0, 1);
233
234 EXPECT_EQ(trace.get_num_rows(), 14); // 14 = 1 + 3 * 1 + 1 * 2 + 2 * 4 (extra_shift_row + 2U1 + 1U8 + 1U16 + 2U32)
235 check_relation<bitwise>(trace);
236}
237
238TEST(BitwiseConstrainingTest, NegativeWrongInit)
239{
240 TestTraceContainer trace({
241 {
242 { C::bitwise_ia_byte, 25 },
243 { C::bitwise_ib_byte, 25 },
244 { C::bitwise_ic_byte, 25 },
245 { C::bitwise_end, 1 },
246 { C::bitwise_acc_ia, 25 },
247 { C::bitwise_acc_ib, 25 },
248 { C::bitwise_acc_ic, 25 },
249 },
250 });
251
253
254 trace.set(C::bitwise_ia_byte, 0, 24); // Mutate to wrong value violating BITW_INIT_A
255 trace.set(C::bitwise_ib_byte, 0, 27); // Mutate to wrong value violating BITW_INIT_B
256 trace.set(C::bitwise_ic_byte, 0, 28); // Mutate to wrong value violating BITW_INIT_C
257
258 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_INIT_A), "BITW_INIT_A");
259 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_INIT_B), "BITW_INIT_B");
260 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_INIT_C), "BITW_INIT_C");
261}
262
263TEST(BitwiseConstrainingTest, NegativeTruncateCtr)
264{
265 TestTraceContainer trace({
266 {
267 { C::bitwise_sel, 1 },
268 { C::bitwise_ctr, 4 },
269 },
270 {
271 { C::bitwise_sel, 1 },
272 { C::bitwise_ctr, 3 },
273 },
274 {
275 { C::bitwise_sel, 1 },
276 { C::bitwise_ctr, 2 },
277 },
278 {
279 { C::bitwise_end, 1 },
280 { C::bitwise_sel, 1 },
281 { C::bitwise_ctr, 1 },
282 },
283 });
284
285 check_relation<bitwise>(trace, bitwise::SR_BITW_CTR_DECREMENT);
286
287 trace.set(C::bitwise_ctr, 3, 0);
288 trace.set(C::bitwise_end, 3, 0);
289 trace.set(C::bitwise_sel, 3, 0);
290
291 // Trace nows ends with bitwise_ctr == 2 without bitwise_end being set.
292 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_CTR_DECREMENT), "BITW_CTR_DECREMENT");
293}
294
295TEST(BitwiseConstrainingTest, NegativeGapCtr)
296{
297 TestTraceContainer trace({
298 {
299 { C::bitwise_sel, 1 },
300 { C::bitwise_ctr, 4 },
301 },
302 {
303 { C::bitwise_end, 1 },
304 { C::bitwise_sel, 1 },
305 { C::bitwise_ctr, 3 },
306 },
307 });
308
309 check_relation<bitwise>(trace, bitwise::SR_BITW_CTR_DECREMENT);
310 trace.set(C::bitwise_ctr, 1, 2); // Mutate to wrong value (ctr decreases from 4 to 2)
311 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_CTR_DECREMENT), "BITW_CTR_DECREMENT");
312}
313
314TEST(BitwiseConstrainingTest, NegativeEndSetBeforeEnd)
315{
316 TestTraceContainer trace({
317 {
318 { C::bitwise_ctr_min_one_inv, FF(7).invert() },
319 { C::bitwise_sel, 1 },
320 { C::bitwise_sel_compute, 1 },
321 { C::bitwise_ctr, 8 },
322 },
323 {
324 { C::bitwise_ctr_min_one_inv, FF(6).invert() },
325 { C::bitwise_sel, 1 },
326 { C::bitwise_sel_compute, 1 },
327 { C::bitwise_ctr, 7 },
328 },
329 {
330 { C::bitwise_ctr_min_one_inv, FF(5).invert() },
331 { C::bitwise_sel, 1 },
332 { C::bitwise_sel_compute, 1 },
333 { C::bitwise_ctr, 6 },
334 },
335 });
336
337 check_relation<bitwise>(trace, bitwise::SR_BITW_END_FOR_CTR_ONE);
338 trace.set(C::bitwise_end, 2, 1); // Mutate to wrong value (wrongly activate bitwise_end on last row)
339 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_END_FOR_CTR_ONE), "BITW_END_FOR_CTR_ONE");
340}
341
342TEST(BitwiseConstrainingTest, NegativeTraceContinuity)
343{
344 // Test that sel cannot drop from 1 to 0 mid-block (not at a latch).
345 // Use 4 rows (power of 2) to avoid padding issues with circular wrap.
346 TestTraceContainer trace({
347 {
348 // Row 0: inactive, precomputed_first_row=1 handles wrap
349 { C::precomputed_first_row, 1 },
350 },
351 {
352 { C::bitwise_sel, 1 },
353 { C::bitwise_ctr, 3 },
354 },
355 {
356 { C::bitwise_sel, 1 },
357 { C::bitwise_ctr, 2 },
358 },
359 {
360 { C::bitwise_sel, 1 },
361 { C::bitwise_end, 1 },
362 { C::bitwise_ctr, 1 },
363 },
364 });
365
366 check_relation<bitwise>(trace, bitwise::SR_TRACE_CONTINUITY);
367 trace.set(C::bitwise_sel, 2, 0); // Mutate: sel drops mid-block (not at a latch)
368 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_TRACE_CONTINUITY), "TRACE_CONTINUITY");
369}
370
371TEST(BitwiseConstrainingTest, NegativeChangeOpIDBeforeEnd)
372{
373 TestTraceContainer trace({
374 {
375 { C::bitwise_sel, 1 },
376 { C::bitwise_op_id, static_cast<uint8_t>(BitwiseOperation::XOR) },
377 },
378 {
379 { C::bitwise_sel, 1 },
380 { C::bitwise_op_id, static_cast<uint8_t>(BitwiseOperation::XOR) },
381 },
382 {
383 { C::bitwise_sel, 1 },
384 { C::bitwise_end, 1 },
385 { C::bitwise_op_id, static_cast<uint8_t>(BitwiseOperation::XOR) },
386 },
387 });
388
389 check_relation<bitwise>(trace, bitwise::SR_BITW_OP_ID_REL_CONTINUITY);
390 trace.set(C::bitwise_op_id, 1, static_cast<uint8_t>(BitwiseOperation::AND)); // Mutate to wrong value
392 "BITW_OP_ID_REL_CONTINUITY");
393}
394
395TEST(BitwiseConstrainingTest, NegativeWrongAccumulation)
396{
397 TestTraceContainer trace({
398 {
399 { C::bitwise_sel, 1 },
400 { C::bitwise_ia_byte, 0x11 },
401 { C::bitwise_ib_byte, 0x22 },
402 { C::bitwise_ic_byte, 0x33 },
403 { C::bitwise_acc_ia, 0xaa11 },
404 { C::bitwise_acc_ib, 0xbb22 },
405 { C::bitwise_acc_ic, 0xcc33 },
406 },
407 {
408 { C::bitwise_sel, 1 },
409 { C::bitwise_end, 1 },
410 { C::bitwise_acc_ia, 0xaa },
411 { C::bitwise_acc_ib, 0xbb },
412 { C::bitwise_acc_ic, 0xcc },
413 },
414 });
415
417
418 trace.set(C::bitwise_acc_ia, 0, 0xaa1f); // Mutate to wrong value violating BITW_ACC_REL_A
419 trace.set(C::bitwise_acc_ib, 0, 0xbb2f); // Mutate to wrong value violating BITW_ACC_REL_B
420 trace.set(C::bitwise_acc_ic, 0, 0xcc3f); // Mutate to wrong value violating BITW_ACC_REL_C
421
422 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_ACC_REL_A), "BITW_ACC_REL_A");
423 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_ACC_REL_B), "BITW_ACC_REL_B");
424 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_BITW_ACC_REL_C), "BITW_ACC_REL_C");
425}
426
427// Verify that #[INPUT_TAG_CANNOT_BE_FF] catches a prover who hides an FF tag error.
428// A malicious prover might set sel_tag_ff_err=0 when tag_a is actually FF (tag 0),
429// trying to bypass the error handling.
430TEST(BitwiseConstrainingTest, NegativeInputTagCannotBeFF)
431{
432 // Build a valid error trace with tag_a = FF
433 TestTraceContainer trace;
434 BitwiseTraceBuilder builder;
436 { .operation = BitwiseOperation::XOR,
439 .res = 0 },
440 };
441 builder.process(events, trace);
442
443 // Verify valid trace passes
444 check_relation<bitwise>(trace, bitwise::SR_INPUT_TAG_CANNOT_BE_FF);
445
446 // Mutate: hide the FF error by setting sel_tag_ff_err = 0
447 // Row 1 is the error row (row 0 is sentinel)
448 trace.set(C::bitwise_sel_tag_ff_err, 1, 0);
449 // Also need to fix err to be consistent (err = ff_err OR mismatch_err)
450 trace.set(C::bitwise_err, 1, 0);
451
453 "INPUT_TAG_CANNOT_BE_FF");
454}
455
456// Verify that #[INPUT_TAGS_SHOULD_MATCH] catches a prover who hides a tag mismatch.
457// A malicious prover might set sel_tag_mismatch_err=0 when tag_a != tag_b,
458// trying to proceed with the computation on mismatched tags.
459TEST(BitwiseConstrainingTest, NegativeInputTagsShouldMatch)
460{
461 // Build a valid error trace with mismatched tags
462 TestTraceContainer trace;
463 BitwiseTraceBuilder builder;
465 { .operation = BitwiseOperation::AND,
468 .res = 0 },
469 };
470 builder.process(events, trace);
471
472 // Verify valid trace passes
473 check_relation<bitwise>(trace, bitwise::SR_INPUT_TAGS_SHOULD_MATCH);
474
475 // Mutate: hide the mismatch error
476 trace.set(C::bitwise_sel_tag_mismatch_err, 1, 0);
477 trace.set(C::bitwise_err, 1, 0);
478
480 "INPUT_TAGS_SHOULD_MATCH");
481}
482
483// Verify that #[RES_TAG_SHOULD_MATCH_INPUT] catches tag_c != tag_a on a non-error start row.
484TEST(BitwiseConstrainingTest, NegativeResTagShouldMatchInput)
485{
486 // Build a valid trace with a U8 AND operation
487 TestTraceContainer trace;
488 BitwiseTraceBuilder builder;
490 { .operation = BitwiseOperation::AND,
491 .a = MemoryValue::from<uint8_t>(85),
492 .b = MemoryValue::from<uint8_t>(175),
493 .res = 5 },
494 };
495 builder.process(events, trace);
496
497 // Verify valid trace passes
498 check_relation<bitwise>(trace, bitwise::SR_RES_TAG_SHOULD_MATCH_INPUT);
499
500 // Row 1 is the start row (and also the last row for U8, since only 1 byte).
501 // Mutate: set tag_c to a different value than tag_a.
502 trace.set(C::bitwise_tag_c, 1, static_cast<uint8_t>(MemoryTag::U32));
503
505 "RES_TAG_SHOULD_MATCH_INPUT");
506}
507
508// Verify that #[END_ON_ERROR] catches end=0 when err=1.
509// A malicious prover might try to continue computation after an error.
510TEST(BitwiseConstrainingTest, NegativeEndOnError)
511{
512 // Build a valid error trace
513 TestTraceContainer trace;
514 BitwiseTraceBuilder builder;
516 { .operation = BitwiseOperation::XOR,
519 .res = 0 },
520 };
521 builder.process(events, trace);
522
523 // Verify valid trace passes
524 check_relation<bitwise>(trace, bitwise::SR_END_ON_ERROR);
525
526 // Mutate: set end=0 on the error row (row 1)
527 trace.set(C::bitwise_end, 1, 0);
528
529 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_END_ON_ERROR), "END_ON_ERROR");
530}
531
532// Verify that #[ERR_ONLY_ON_START] catches err=1 on non-start rows.
533// Without this constraint, a prover could set err=1 on the end row of a multi-row
534// block, disabling sel_compute and the byte lookup, allowing forged bitwise results.
535TEST(BitwiseConstrainingTest, NegativeErrOnlyOnStart)
536{
537 // A 2-row block: row 0 is start (sel=1, start=1), row 1 is end (sel=1, start=0).
538 TestTraceContainer trace({
539 {
540 { C::bitwise_sel, 1 },
541 { C::bitwise_start, 1 },
542 { C::bitwise_ctr, 2 },
543 },
544 {
545 { C::bitwise_sel, 1 },
546 { C::bitwise_end, 1 },
547 { C::bitwise_ctr, 1 },
548 },
549 });
550
551 check_relation<bitwise>(trace, bitwise::SR_ERR_ONLY_ON_START);
552
553 // Mutate: set err=1 on the non-start end row (row 1).
554 // This forces sel_compute=0, disabling the byte lookup and leaving ic_byte unconstrained.
555 trace.set(C::bitwise_sel_tag_mismatch_err, 1, 1);
556 trace.set(C::bitwise_err, 1, 1);
557
558 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_ERR_ONLY_ON_START), "ERR_ONLY_ON_START");
559}
560
561TEST(BitwiseConstrainingTest, MixedOperationsInteractions)
562{
563 TestTraceContainer trace;
564 BitwiseTraceBuilder builder;
565 PrecomputedTraceBuilder precomputed_builder;
567 { .operation = BitwiseOperation::OR,
568 .a = MemoryValue::from(uint1_t(1)),
569 .b = MemoryValue::from(uint1_t(0)),
570 .res = 1 },
571 { .operation = BitwiseOperation::AND,
572 .a = MemoryValue::from<uint32_t>(13793),
573 .b = MemoryValue::from<uint32_t>(10590617),
574 .res = 4481 },
575 { .operation = BitwiseOperation::XOR,
576 .a = MemoryValue::from<uint16_t>(5323),
577 .b = MemoryValue::from<uint16_t>(321),
578 .res = 5514 },
579 { .operation = BitwiseOperation::XOR,
580 .a = MemoryValue::from<uint32_t>(13793),
581 .b = MemoryValue::from<uint32_t>(10590617),
582 .res = 10595448 },
583 { .operation = BitwiseOperation::AND,
584 .a = MemoryValue::from<uint8_t>(85),
585 .b = MemoryValue::from<uint8_t>(175),
586 .res = 5 },
587 { .operation = BitwiseOperation::AND,
588 .a = MemoryValue::from<uint8_t>(85),
589 .b = MemoryValue::from<uint8_t>(175),
590 .res = 5 },
591 };
592
593 builder.process(events, trace);
594
599
600 check_all_interactions<BitwiseTraceBuilder>(trace);
601 check_relation<bitwise>(trace);
602}
603
604TEST(BitwiseConstrainingTest, BitwiseExecInteraction)
605{
606 TestTraceContainer trace({ {
607 // Bitwise Entry (error row: sel=1, err=1, start=1, end=1)
608 { C::bitwise_sel, 1 },
609 { C::bitwise_err, 1 },
610 { C::bitwise_start, 1 },
611 { C::bitwise_end, 1 },
612 { C::bitwise_tag_a, static_cast<uint8_t>(MemoryTag::FF) },
613 { C::bitwise_tag_b, static_cast<uint8_t>(MemoryTag::U8) },
614 { C::bitwise_acc_ia, 0x01 },
615 { C::bitwise_tag_c, static_cast<uint8_t>(MemoryTag::U8) },
616 { C::bitwise_acc_ib, 0x01 },
617 { C::bitwise_acc_ic, 0x00 },
618 // Execution Entry
619 { C::execution_mem_tag_reg_0_, static_cast<uint8_t>(MemoryTag::FF) },
620 { C::execution_mem_tag_reg_1_, static_cast<uint8_t>(MemoryTag::U8) },
621 { C::bitwise_op_id, static_cast<uint8_t>(BitwiseOperation::AND) },
622 { C::execution_mem_tag_reg_2_, static_cast<uint8_t>(MemoryTag::U8) },
623 { C::execution_register_0_, 0x01 },
624 { C::execution_register_1_, 0x01 },
625 { C::execution_register_2_, 0x00 },
626 { C::execution_sel_exec_dispatch_bitwise, 1 },
627 { C::execution_sel_opcode_error, 1 },
628 { C::execution_subtrace_operation_id, static_cast<uint8_t>(BitwiseOperation::AND) },
629 } });
630
631 check_interaction<ExecutionTraceBuilder, lookup_execution_dispatch_to_bitwise_settings>(trace);
632}
633
634TEST(BitwiseConstrainingTest, InvalidBitwiseExecInteraction)
635{
636 TestTraceContainer trace({ {
637 // Bitwise Entry
638 { C::bitwise_sel, 1 },
639 { C::bitwise_acc_ib, 0x01 },
640 { C::bitwise_acc_ia, 0x01 },
641 { C::bitwise_tag_a, static_cast<uint8_t>(MemoryTag::U8) },
642 { C::bitwise_tag_b, static_cast<uint8_t>(MemoryTag::U8) },
643 { C::bitwise_acc_ic, 0x00 },
644 { C::bitwise_tag_c, static_cast<uint8_t>(MemoryTag::U8) },
645 { C::bitwise_op_id, static_cast<uint8_t>(BitwiseOperation::AND) },
646
647 // Execution Entry
648 { C::execution_mem_tag_reg_0_, static_cast<uint8_t>(MemoryTag::U8) },
649 { C::execution_mem_tag_reg_1_, static_cast<uint8_t>(MemoryTag::U16) }, // Mismatch
650 { C::execution_mem_tag_reg_2_, static_cast<uint8_t>(MemoryTag::U8) },
651 { C::execution_register_0_, 0x01 },
652 { C::execution_register_1_, 0x01 },
653 { C::execution_register_2_, 0x00 },
654 { C::execution_sel_exec_dispatch_bitwise, 1 },
655 { C::execution_subtrace_operation_id, static_cast<uint8_t>(BitwiseOperation::AND) },
656 } });
657
659 (check_interaction<ExecutionTraceBuilder, lookup_execution_dispatch_to_bitwise_settings>(trace)),
660 "Failed.*EXECUTION_DISPATCH_TO_BITWISE. Could not find tuple in destination.");
661}
662
663TEST(BitwiseConstrainingTest, ErrorHandlingInputFF)
664{
665 TestTraceContainer trace;
666 BitwiseTraceBuilder builder;
667 PrecomputedTraceBuilder precomputed_builder;
668
670 { .operation = BitwiseOperation::XOR,
673 .res = 0 },
674 };
675 builder.process(events, trace);
676 trace.set(C::precomputed_first_row, 0, 1);
679
680 check_relation<bitwise>(trace);
681}
682
683TEST(BitwiseConstrainingTest, ErrorHandlingInputTagMismatch)
684{
685 TestTraceContainer trace;
686 BitwiseTraceBuilder builder;
687
689 { .operation = BitwiseOperation::AND,
692 .res = 0 },
693 };
694 builder.process(events, trace);
695 trace.set(C::precomputed_first_row, 0, 1);
696
697 check_relation<bitwise>(trace);
698 check_all_interactions<BitwiseTraceBuilder>(trace);
699}
700
701TEST(BitwiseConstrainingTest, ErrorHandlingMultiple)
702{
703 TestTraceContainer trace;
704 BitwiseTraceBuilder builder;
705
707 { .operation = BitwiseOperation::AND,
710 .res = 0 },
711 };
712 builder.process(events, trace);
713 trace.set(C::precomputed_first_row, 0, 1);
714
715 check_relation<bitwise>(trace);
716}
717
718TEST(BitwiseConstrainingTest, ExecBitwiseDispatchOnErrorMismatch)
719{
720 // Bitwise operations on mismatch tags should error out and produce FF(0) result.
723
724 TestTraceContainer trace({ {
725 // Execution Entry
726 { C::execution_sel_exec_dispatch_bitwise, 1 },
727 { C::execution_subtrace_operation_id, static_cast<uint8_t>(BitwiseOperation::AND) },
728 { C::execution_mem_tag_reg_0_, static_cast<uint8_t>(a.get_tag()) },
729 { C::execution_mem_tag_reg_1_, static_cast<uint8_t>(b.get_tag()) },
730 { C::execution_register_0_, a.as_ff() },
731 { C::execution_register_1_, b.as_ff() },
732
733 // Output is FF(0) due to error
734 { C::execution_mem_tag_reg_2_, static_cast<uint8_t>(MemoryTag::FF) },
735 { C::execution_register_2_, 0x00 },
736 { C::execution_sel_opcode_error, 1 },
737 } });
738
739 std::vector<simulation::BitwiseEvent> event = { { .operation = BitwiseOperation::AND, .a = a, .b = b, .res = 0 } };
740
741 BitwiseTraceBuilder builder;
742 builder.process(event, trace);
743 trace.set(C::precomputed_first_row, 0, 1);
744
745 check_relation<bitwise>(trace);
746 check_interaction<ExecutionTraceBuilder, lookup_execution_dispatch_to_bitwise_settings>(trace);
747}
748
749TEST(BitwiseConstrainingTest, ExecBitwiseDispatchOnErrorFF)
750{
751 // Bitwise operations on FF tags should error out and produce FF(0) result.
752 MemoryValue a =
753 MemoryValue::from_tag(MemoryTag::FF, FF("0x1b7f6afaafbe72d6c3fc1bc92828a395341af3d33f805af83f06cbf0dcaca8a9"));
754 MemoryValue b = MemoryValue::from_tag(MemoryTag::U64, 9873803468411284649ULL);
755
756 TestTraceContainer trace({ {
757 // Execution Entry
758 { C::execution_sel_exec_dispatch_bitwise, 1 },
759 { C::execution_subtrace_operation_id, static_cast<uint8_t>(BitwiseOperation::OR) },
760 { C::execution_mem_tag_reg_0_, static_cast<uint8_t>(a.get_tag()) },
761 { C::execution_mem_tag_reg_1_, static_cast<uint8_t>(b.get_tag()) },
762 { C::execution_register_0_, a.as_ff() },
763 { C::execution_register_1_, b.as_ff() },
764
765 // Output is FF(0) due to error
766 { C::execution_mem_tag_reg_2_, static_cast<uint8_t>(MemoryTag::FF) },
767 { C::execution_register_2_, 0x00 },
768 { C::execution_sel_opcode_error, 1 },
769 } });
770
771 std::vector<simulation::BitwiseEvent> event = { { .operation = BitwiseOperation::OR, .a = a, .b = b, .res = 0 } };
772
773 BitwiseTraceBuilder builder;
774 builder.process(event, trace);
775 trace.set(C::precomputed_first_row, 0, 1);
776
777 check_relation<bitwise>(trace);
778 check_interaction<ExecutionTraceBuilder, lookup_execution_dispatch_to_bitwise_settings>(trace);
779}
780
782// Vulnerability Tests: Missing start * (1 - sel) = 0 constraint
784
785// This test demonstrates a SECURITY VULNERABILITY in bitwise.pil:
786// The `start_keccak` selector is not protected to only be active when `sel == 1`.
787// A malicious prover can set start_keccak=1 on inactive rows (sel=0) and claim
788// arbitrary XOR/AND results, bypassing all bitwise constraints.
789//
790// The fix is to add:
791// #[BITW_NO_EXTERNAL_START_ON_ERROR]
792// (start_keccak + start_sha256) * (1 - sel) = 0;
793//
794// This is the same vulnerability class as poseidon2_hash.pil (fixed in that file).
795TEST(BitwiseConstrainingTest, VulnerabilityStartKeccakWithoutSel)
796{
797 // Create a ghost row where start_keccak=1 but sel=0.
798 // This represents a forged row that a malicious prover could create to
799 // claim: state_in_00 XOR state_in_01 = FAKE_OUTPUT
800 // without actually computing the XOR!
801 FF fake_input_a = FF(0xAAAABBBBCCCCDDDDULL);
802 FF fake_input_b = FF(0x1111222233334444ULL);
803 FF fake_output = FF(0x999999999999ULL); // NOT the real XOR!
804
805 TestTraceContainer trace({
806 {
807 // Ghost row: sel=1 (forced by SEL_ON_START_OR_END), start_keccak=1, err=1
808 // Error rows have sel_compute=0, so BYTE_OPERATIONS lookup is disabled,
809 // allowing arbitrary acc_ic values.
810 { C::bitwise_sel, 1 },
811 { C::bitwise_start, 1 },
812 { C::bitwise_start_keccak, 1 },
813 // Error handling: trigger tag mismatch to get err=1, end=1
814 // This avoids the #[INTEGRAL_TAG_LENGTH] lookup (sel_get_ctr=start*(1-err)=0)
815 { C::bitwise_tag_a, FF(MEM_TAG_U64) }, // U64 = 5
816 { C::bitwise_tag_b, FF(MEM_TAG_U32) }, // != tag_a, triggers mismatch
817 { C::bitwise_sel_tag_mismatch_err, 1 },
818 { C::bitwise_sel_tag_ff_err, 0 },
819 { C::bitwise_err, 1 },
820 { C::bitwise_end, 1 },
821 { C::bitwise_sel_get_ctr, 0 }, // start*(1-err) = 1*0 = 0
822 { C::bitwise_ctr, 0 },
823 // Inverses for tag check constraints
824 { C::bitwise_tag_a_inv, FF(MEM_TAG_U64).invert() },
825 { C::bitwise_tag_ab_diff_inv, FF(MEM_TAG_U64 - MEM_TAG_U32).invert() },
826 // FAKE XOR computation - not constrained when sel_compute=0!
827 { C::bitwise_op_id, FF(AVM_BITWISE_XOR_OP_ID) },
828 { C::bitwise_acc_ia, fake_input_a },
829 { C::bitwise_acc_ib, fake_input_b },
830 { C::bitwise_acc_ic, fake_output }, // FAKE output!
831 // INIT constraints require acc_* = *_byte when end=1
832 { C::bitwise_ia_byte, fake_input_a },
833 { C::bitwise_ib_byte, fake_input_b },
834 { C::bitwise_ic_byte, fake_output },
835 // tag_c unconstrained when err=1 (RES_TAG_SHOULD_MATCH_INPUT gated by (1-err))
836 { C::bitwise_tag_c, 0 },
837 },
838 });
839
840 // Now that the vulnerability is fixed, we expect an error:
841 EXPECT_THROW_WITH_MESSAGE((check_relation<bitwise>(trace)), "BITW_NO_EXTERNAL_START_ON_ERROR");
842}
843
844// Same vulnerability but for start_sha256 (used by SHA256 compression lookups).
845TEST(BitwiseConstrainingTest, VulnerabilityStartSha256WithoutSel)
846{
847 FF fake_input_a = FF(0xAABBCCDD);
848 FF fake_input_b = FF(0x11223344);
849 FF fake_output = FF(0x99999999); // NOT the real XOR!
850
851 TestTraceContainer trace({
852 {
853 { C::bitwise_sel, 1 },
854 { C::bitwise_start, 1 },
855 { C::bitwise_start_sha256, 1 },
856 { C::bitwise_tag_a, FF(MEM_TAG_U32) }, // SHA256 uses U32
857 { C::bitwise_tag_b, FF(MEM_TAG_U8) }, // != tag_a, triggers mismatch
858 { C::bitwise_sel_tag_mismatch_err, 1 },
859 { C::bitwise_sel_tag_ff_err, 0 },
860 { C::bitwise_err, 1 },
861 { C::bitwise_end, 1 },
862 { C::bitwise_sel_get_ctr, 0 },
863 { C::bitwise_ctr, 0 },
864 { C::bitwise_tag_a_inv, FF(MEM_TAG_U32).invert() },
865 { C::bitwise_tag_ab_diff_inv, FF(MEM_TAG_U32 - MEM_TAG_U8).invert() },
866 { C::bitwise_op_id, FF(AVM_BITWISE_XOR_OP_ID) },
867 { C::bitwise_acc_ia, fake_input_a },
868 { C::bitwise_acc_ib, fake_input_b },
869 { C::bitwise_acc_ic, fake_output },
870 { C::bitwise_ia_byte, fake_input_a },
871 { C::bitwise_ib_byte, fake_input_b },
872 { C::bitwise_ic_byte, fake_output },
873 { C::bitwise_tag_c, 0 },
874 },
875 });
876
877 // Now that the vulnerability is fixed, we expect an error:
878 EXPECT_THROW_WITH_MESSAGE((check_relation<bitwise>(trace)), "BITW_NO_EXTERNAL_START_ON_ERROR");
879}
880
881// This test demonstrates a full exploit: forging a keccak XOR result.
882// It generates a valid keccak trace, mutates an intermediate XOR output to a fake value,
883// and adds a ghost row in bitwise to satisfy the lookup.
884// Both source (keccak) and destination (bitwise) pass check_relation, and the exploited
885// lookup passes check_interaction.
886TEST(BitwiseConstrainingTest, VulnerabilityFakeKeccakXorOutput)
887{
888 // =========================================================================
889 // STEP 1: Generate a valid keccak + bitwise trace
890 // =========================================================================
891 TestTraceContainer trace;
892 const MemoryAddress src_addr = 0;
893 const MemoryAddress dst_addr = 200;
894 testing::generate_keccak_trace(trace, { dst_addr }, { src_addr }, /*space_id=*/23);
895
896 // =========================================================================
897 // STEP 2: Verify the trace is valid before attack
898 // =========================================================================
899 check_relation<keccakf1600>(trace);
900 check_relation<bitwise>(trace);
901
902 // =========================================================================
903 // STEP 3: Find the keccak start row and read the real intermediate values
904 // =========================================================================
905 uint32_t keccak_start_row = 0;
906 for (uint32_t i = 0; i < trace.get_num_rows(); i++) {
907 if (trace.get(C::keccakf1600_start, i) == FF(1)) {
908 keccak_start_row = i;
909 break;
910 }
911 }
912 ASSERT_EQ(trace.get(C::keccakf1600_start, keccak_start_row), FF(1));
913 ASSERT_EQ(trace.get(C::keccakf1600_sel_no_error, keccak_start_row), FF(1));
914
915 FF real_state_in_00 = trace.get(C::keccakf1600_state_in_00, keccak_start_row);
916 FF real_state_in_01 = trace.get(C::keccakf1600_state_in_01, keccak_start_row);
917 FF real_theta_xor_01 = trace.get(C::keccakf1600_theta_xor_01, keccak_start_row);
918
919 // =========================================================================
920 // STEP 4: ATTACK - Mutate theta_xor_01 to a FAKE value
921 // =========================================================================
922 // theta_xor_01 is a committed column only constrained by the THETA_XOR_01 lookup.
923 // No PIL relation directly enforces theta_xor_01 = state_in_00 XOR state_in_01.
924 FF fake_theta_xor_01 = FF(0xFA0E0BAD0DEADULL);
925 ASSERT_NE(fake_theta_xor_01, real_theta_xor_01);
926 trace.set(C::keccakf1600_theta_xor_01, keccak_start_row, fake_theta_xor_01);
927
928 // =========================================================================
929 // STEP 5: Add FORGED ghost row in bitwise to satisfy the lookup
930 // =========================================================================
931 uint32_t forged_row = trace.get_num_rows();
932 trace.set(forged_row,
933 { {
934 { C::bitwise_sel, 1 }, // sel=1 (required by SEL_ON_START_OR_END)
935 { C::bitwise_start, 1 }, // Can be matched by keccak lookup
936 { C::bitwise_start_keccak, 1 }, // Destination selector for keccak XOR lookups
937 { C::bitwise_op_id, FF(AVM_BITWISE_XOR_OP_ID) },
938 { C::bitwise_acc_ia, real_state_in_00 }, // Real input A
939 { C::bitwise_acc_ib, real_state_in_01 }, // Real input B
940 { C::bitwise_acc_ic, fake_theta_xor_01 }, // FAKE output!
941 { C::bitwise_ia_byte, real_state_in_00 },
942 { C::bitwise_ib_byte, real_state_in_01 },
943 { C::bitwise_ic_byte, fake_theta_xor_01 },
944 { C::bitwise_tag_a, FF(MEM_TAG_U64) },
945 { C::bitwise_tag_b, FF(MEM_TAG_U32) }, // != tag_a → mismatch
946 { C::bitwise_sel_tag_mismatch_err, 1 },
947 { C::bitwise_sel_tag_ff_err, 0 },
948 { C::bitwise_err, 1 },
949 { C::bitwise_end, 1 },
950 { C::bitwise_sel_get_ctr, 0 },
951 { C::bitwise_ctr, 0 },
952 { C::bitwise_tag_a_inv, FF(MEM_TAG_U64).invert() },
953 { C::bitwise_tag_ab_diff_inv, FF(MEM_TAG_U64 - MEM_TAG_U32).invert() },
954 { C::bitwise_tag_c, 0 },
955 } });
956
957 // =========================================================================
958 // STEP 6: Verify ALL relations pass
959 // =========================================================================
960 // Keccak relations pass because theta_xor_01 is committed, not relationally constrained
961 check_relation<keccakf1600>(trace);
962 // Bitwise relations pass because ghost row is an error row (sel=1, err=1, sel_compute=0)
963 // Commented out as fixed: check_relation<bitwise>(trace);
964
965 // =========================================================================
966 // STEP 7: Verify the exploited lookup passes
967 // =========================================================================
968 // The THETA_XOR_01 lookup:
969 // Source: sel_no_error { xor_op_id, state_in_00, state_in_01, theta_xor_01, tag_u64 }
970 // Dest: bitwise.start_keccak { op_id, acc_ia, acc_ib, acc_ic, tag_a }
971 //
972 // Source tuple: (XOR_OP, real_state_in_00, real_state_in_01, FAKE, U64)
973 // Dest tuple: (XOR_OP, real_state_in_00, real_state_in_01, FAKE, U64) ← ghost row matches!
974 check_interaction<KeccakF1600TraceBuilder, lookup_keccakf1600_theta_xor_01_settings>(trace);
975
976 // =========================================================================
977 // VULNERABILITY DEMONSTRATED: KECCAK XOR FORGERY
978 // =========================================================================
979 // The attacker has successfully proven:
980 // state_in_00 XOR state_in_01 = fake_theta_xor_01
981 //
982 // When the TRUE relationship is:
983 // state_in_00 XOR state_in_01 = real_theta_xor_01
984 //
985 // IMPACT: The keccak permutation is COMPLETELY BROKEN. By forging intermediate
986 // XOR results, an attacker can produce arbitrary keccak hash outputs, breaking
987 // all security guarantees of the hash function.
988
989 // Now that the vulnerability is fixed, we expect an error:
990 EXPECT_THROW_WITH_MESSAGE((check_relation<bitwise>(trace)), "BITW_NO_EXTERNAL_START_ON_ERROR");
991}
992
993// This test demonstrates a full exploit: forging a SHA256 XOR result.
994// Same vulnerability class as the keccak test but exploiting start_sha256.
995TEST(BitwiseConstrainingTest, VulnerabilityFakeSha256XorOutput)
996{
997 // =========================================================================
998 // STEP 1: Generate a valid SHA256 + bitwise trace
999 // =========================================================================
1000 MemoryStore mem;
1001 StrictMock<MockExecutionIdManager> execution_id_manager;
1002 EXPECT_CALL(execution_id_manager, get_execution_id()).WillRepeatedly(Return(1));
1003
1004 EventEmitter<BitwiseEvent> bitwise_event_emitter;
1005 EventEmitter<GreaterThanEvent> gt_event_emitter;
1006 simulation::DeduplicatingEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
1007 EventEmitter<RangeCheckEvent> range_check_event_emitter;
1008
1010 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
1011 GreaterThan gt(field_gt, range_check, gt_event_emitter);
1012 Bitwise bitwise_sim(bitwise_event_emitter);
1013
1014 EventEmitter<Sha256CompressionEvent> sha256_event_emitter;
1015 Sha256 sha256_gadget(execution_id_manager, bitwise_sim, gt, sha256_event_emitter);
1016
1017 std::array<uint32_t, 8> state = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
1018 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
1019 MemoryAddress state_addr = 0;
1020 for (uint32_t i = 0; i < 8; ++i) {
1021 mem.set(state_addr + i, MemoryValue::from<uint32_t>(state[i]));
1022 }
1023
1024 std::array<uint32_t, 16> input = { 0x61626380, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x18 };
1025 MemoryAddress input_addr = 8;
1026 for (uint32_t i = 0; i < 16; ++i) {
1027 mem.set(input_addr + i, MemoryValue::from<uint32_t>(input[i]));
1028 }
1029 MemoryAddress output_addr = 25;
1030
1031 sha256_gadget.compression(mem, state_addr, input_addr, output_addr);
1032
1033 TestTraceContainer trace;
1034 trace.set(C::precomputed_first_row, 0, 1);
1035
1036 Sha256TraceBuilder sha256_builder;
1037 sha256_builder.process(sha256_event_emitter.get_events(), trace);
1038
1039 BitwiseTraceBuilder bitwise_builder;
1040 bitwise_builder.process(bitwise_event_emitter.dump_events(), trace);
1041
1042 // =========================================================================
1043 // STEP 2: Verify the trace is valid before attack
1044 // =========================================================================
1045 check_relation<sha256_relation>(trace);
1046 check_relation<bitwise>(trace);
1047
1048 // =========================================================================
1049 // STEP 3: Find a sha256 row with sel_compute_w=1 and read intermediates
1050 // =========================================================================
1051 uint32_t sha256_row = 0;
1052 bool found = false;
1053 for (uint32_t i = 0; i < trace.get_num_rows(); i++) {
1054 if (trace.get(C::sha256_sel_compute_w, i) == FF(1)) {
1055 sha256_row = i;
1056 found = true;
1057 break;
1058 }
1059 }
1060 ASSERT_TRUE(found) << "Could not find sha256 row with sel_compute_w=1";
1061
1062 FF real_w_15_rotr_7 = trace.get(C::sha256_w_15_rotr_7, sha256_row);
1063 FF real_w_15_rotr_18 = trace.get(C::sha256_w_15_rotr_18, sha256_row);
1064 FF real_xor_output = trace.get(C::sha256_w_15_rotr_7_xor_w_15_rotr_18, sha256_row);
1065
1066 // =========================================================================
1067 // STEP 4: ATTACK - Mutate the XOR intermediate to a FAKE value
1068 // =========================================================================
1069 FF fake_xor_output = FF(0xDEADBEEF);
1070 ASSERT_NE(fake_xor_output, real_xor_output);
1071 trace.set(C::sha256_w_15_rotr_7_xor_w_15_rotr_18, sha256_row, fake_xor_output);
1072
1073 // =========================================================================
1074 // STEP 5: Add FORGED ghost row in bitwise to satisfy the lookup
1075 // =========================================================================
1076 uint32_t forged_row = trace.get_num_rows();
1077 trace.set(forged_row,
1078 { {
1079 { C::bitwise_sel, 1 }, // sel=1 (required by SEL_ON_START_OR_END)
1080 { C::bitwise_start, 1 },
1081 { C::bitwise_start_sha256, 1 }, // Destination selector for sha256 lookups
1082 { C::bitwise_op_id, FF(AVM_BITWISE_XOR_OP_ID) },
1083 { C::bitwise_acc_ia, real_w_15_rotr_7 }, // Real input A
1084 { C::bitwise_acc_ib, real_w_15_rotr_18 }, // Real input B
1085 { C::bitwise_acc_ic, fake_xor_output }, // FAKE output!
1086 { C::bitwise_ia_byte, real_w_15_rotr_7 },
1087 { C::bitwise_ib_byte, real_w_15_rotr_18 },
1088 { C::bitwise_ic_byte, fake_xor_output },
1089 { C::bitwise_tag_a, FF(MEM_TAG_U32) }, // SHA256 uses U32
1090 { C::bitwise_tag_b, FF(MEM_TAG_U8) }, // != tag_a → mismatch
1091 { C::bitwise_sel_tag_mismatch_err, 1 },
1092 { C::bitwise_sel_tag_ff_err, 0 },
1093 { C::bitwise_err, 1 },
1094 { C::bitwise_end, 1 },
1095 { C::bitwise_sel_get_ctr, 0 },
1096 { C::bitwise_ctr, 0 },
1097 { C::bitwise_tag_a_inv, FF(MEM_TAG_U32).invert() },
1098 { C::bitwise_tag_ab_diff_inv, FF(MEM_TAG_U32 - MEM_TAG_U8).invert() },
1099 { C::bitwise_tag_c, 0 },
1100 } });
1101
1102 // =========================================================================
1103 // STEP 6: Verify ALL relations pass
1104 // =========================================================================
1105 // SHA256 relations pass because w_15_rotr_7_xor_w_15_rotr_18 is committed,
1106 // not relationally constrained
1107 check_relation<sha256_relation>(trace);
1108 // Bitwise relations pass because ghost row is an error row (sel=1, err=1, sel_compute=0)
1109 // Commented out as fixed: check_relation<bitwise>(trace);
1110
1111 // =========================================================================
1112 // STEP 7: Verify the exploited lookup passes
1113 // =========================================================================
1114 // The W_S_0_XOR_0 lookup:
1115 // Source: sel_compute_w { w_15_rotr_7, w_15_rotr_18, w_15_rotr_7_xor_w_15_rotr_18, xor_sel, u32_tag }
1116 // Dest: bitwise.start_sha256 { acc_ia, acc_ib, acc_ic, op_id, tag_a }
1117 //
1118 // Source tuple: (real_rotr_7, real_rotr_18, FAKE, XOR_OP, U32)
1119 // Dest tuple: (real_rotr_7, real_rotr_18, FAKE, XOR_OP, U32) ← ghost row matches!
1120 //
1121 // NOTE: We can't use check_interaction<Sha256TraceBuilder, ...> because Sha256TraceBuilder
1122 // registers this lookup with Column::bitwise_sel as the outer destination selector (production
1123 // optimization). KeccakF1600TraceBuilder uses Column::bitwise_start instead, which is why
1124 // the keccak test can use check_interaction directly. Here we use bitwise_start to match.
1125 {
1126 tracegen::SharedIndexCache cache;
1127 tracegen::LookupIntoDynamicTableGeneric<lookup_sha256_w_s_0_xor_0_settings> lookup(cache, C::bitwise_start);
1128 lookup.process(trace);
1129 }
1130
1131 // =========================================================================
1132 // VULNERABILITY DEMONSTRATED: SHA256 XOR FORGERY
1133 // =========================================================================
1134 // The attacker has successfully proven a fake XOR result in the SHA256
1135 // message schedule computation. By forging intermediate XOR results,
1136 // an attacker can produce arbitrary SHA256 compression outputs.
1137
1138 // Now that the vulnerability is fixed, we expect an error:
1139 EXPECT_THROW_WITH_MESSAGE((check_relation<bitwise>(trace)), "BITW_NO_EXTERNAL_START_ON_ERROR");
1140}
1141
1142// Verify that start=1 or end=1 forces sel=1.
1143TEST(BitwiseConstrainingTest, NegativeSelOnStartOrEnd)
1144{
1145 TestTraceContainer trace({
1146 {
1147 { C::bitwise_sel, 1 },
1148 { C::bitwise_start, 1 },
1149 { C::bitwise_ctr, 2 },
1150 },
1151 {
1152 { C::bitwise_sel, 1 },
1153 { C::bitwise_end, 1 },
1154 { C::bitwise_ctr, 1 },
1155 },
1156 });
1157
1158 check_relation<bitwise>(trace, bitwise::SR_SEL_ON_START_OR_END);
1159
1160 // Mutate: sel=0 on start row
1161 trace.set(C::bitwise_sel, 0, 0);
1162 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_SEL_ON_START_OR_END), "SEL_ON_START_OR_END");
1163
1164 // Restore start row, mutate: sel=0 on end row
1165 trace.set(C::bitwise_sel, 0, 1);
1166 trace.set(C::bitwise_sel, 1, 0);
1167 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_SEL_ON_START_OR_END), "SEL_ON_START_OR_END");
1168}
1169
1170// Verify that after a latch (end=1), the next active row must have start=1.
1171TEST(BitwiseConstrainingTest, NegativeStartAfterLatch)
1172{
1173 // Two consecutive single-row blocks.
1174 TestTraceContainer trace({
1175 {
1176 { C::bitwise_sel, 1 },
1177 { C::bitwise_start, 1 },
1178 { C::bitwise_end, 1 },
1179 { C::bitwise_ctr, 1 },
1180 },
1181 {
1182 { C::bitwise_sel, 1 },
1183 { C::bitwise_start, 1 },
1184 { C::bitwise_end, 1 },
1185 { C::bitwise_ctr, 1 },
1186 },
1187 });
1188
1189 check_relation<bitwise>(trace, bitwise::SR_START_AFTER_LATCH);
1190
1191 // Mutate: remove start from second block
1192 trace.set(C::bitwise_start, 1, 0);
1193 EXPECT_THROW_WITH_MESSAGE(check_relation<bitwise>(trace, bitwise::SR_START_AFTER_LATCH), "START_AFTER_LATCH");
1194}
1195
1196} // namespace
1197} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
#define MEM_TAG_U32
#define MEM_TAG_U8
#define AVM_BITWISE_XOR_OP_ID
#define MEM_TAG_U64
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_CTR_DECREMENT
Definition bitwise.hpp:50
static constexpr size_t SR_BITW_ACC_REL_C
Definition bitwise.hpp:57
static constexpr size_t SR_BITW_ACC_REL_B
Definition bitwise.hpp:56
static constexpr size_t SR_BITW_ACC_REL_A
Definition bitwise.hpp:55
static constexpr size_t SR_TRACE_CONTINUITY
Definition bitwise.hpp:41
static constexpr size_t SR_INPUT_TAG_CANNOT_BE_FF
Definition bitwise.hpp:47
static constexpr size_t SR_BITW_INIT_C
Definition bitwise.hpp:54
static constexpr size_t SR_BITW_OP_ID_REL_CONTINUITY
Definition bitwise.hpp:49
static constexpr size_t SR_BITW_END_FOR_CTR_ONE
Definition bitwise.hpp:51
static constexpr size_t SR_BITW_INIT_B
Definition bitwise.hpp:53
static constexpr size_t SR_END_ON_ERROR
Definition bitwise.hpp:44
static constexpr size_t SR_SEL_ON_START_OR_END
Definition bitwise.hpp:40
static constexpr size_t SR_START_AFTER_LATCH
Definition bitwise.hpp:42
static constexpr size_t SR_RES_TAG_SHOULD_MATCH_INPUT
Definition bitwise.hpp:46
static constexpr size_t SR_BITW_INIT_A
Definition bitwise.hpp:52
static constexpr size_t SR_ERR_ONLY_ON_START
Definition bitwise.hpp:45
static constexpr size_t SR_INPUT_TAGS_SHOULD_MATCH
Definition bitwise.hpp:48
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)
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:153
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