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