Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
bc_hashing.test.cpp
Go to the documentation of this file.
1#include <gmock/gmock.h>
2#include <gtest/gtest.h>
3
4#include <cstdint>
5#include <memory>
6#include <vector>
7
25
26namespace bb::avm2::constraining {
27namespace {
28
29using ::testing::StrictMock;
30
33
35using simulation::EventEmitter;
36using simulation::MockExecutionIdManager;
37using simulation::MockGreaterThan;
38using simulation::Poseidon2;
39using simulation::Poseidon2HashEvent;
40using simulation::Poseidon2PermutationEvent;
41using simulation::Poseidon2PermutationMemoryEvent;
42using tracegen::BytecodeTraceBuilder;
43using tracegen::Poseidon2TraceBuilder;
44using tracegen::PrecomputedTraceBuilder;
45using tracegen::TestTraceContainer;
46
48using C = Column;
49using bc_hashing = bb::avm2::bc_hashing<FF>;
50using bc_decomposition = bb::avm2::bc_decomposition<FF>;
52
53class BytecodeHashingConstrainingTest : public ::testing::Test {
54 public:
55 EventEmitter<Poseidon2HashEvent> hash_event_emitter;
56 EventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
57 EventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
58
59 StrictMock<MockGreaterThan> mock_gt;
60 StrictMock<MockExecutionIdManager> mock_execution_id_manager;
61
62 Poseidon2TraceBuilder poseidon2_builder;
63 PrecomputedTraceBuilder precomputed_builder;
64 BytecodeTraceBuilder builder;
65};
66
67class BytecodeHashingConstrainingTestTraceHelper : public BytecodeHashingConstrainingTest {
68 public:
69 TestTraceContainer process_bc_hashing_trace(std::vector<std::vector<FF>> all_bytecode_fields,
70 std::vector<FF> bytecode_ids,
71 std::vector<size_t> bytecode_size_in_bytes)
72 {
73 // Note: this helper expects bytecode fields without the prepended separator and does not complete decomposition
76 TestTraceContainer trace({
77 { { C::precomputed_first_row, 1 } },
78 });
79 uint32_t row = 1;
80 for (uint32_t j = 0; j < all_bytecode_fields.size(); j++) {
81 uint32_t pc_index = 0;
82 auto bytecode_fields = all_bytecode_fields[j];
83 auto bytecode_id = bytecode_ids[j];
84 bytecode_fields.insert(bytecode_fields.begin(),
85 compute_public_bytecode_first_field(bytecode_size_in_bytes[j]));
86 poseidon2.hash(bytecode_fields); // Required to populate Poseidon2 hash events
87 auto bytecode_field_at = [&bytecode_fields](size_t i) -> FF {
88 return i < bytecode_fields.size() ? bytecode_fields[i] : 0;
89 };
90 auto padding_amount = (3 - (bytecode_fields.size() % 3)) % 3;
91 auto num_rounds = (bytecode_fields.size() + padding_amount) / 3;
92 for (uint32_t i = 0; i < bytecode_fields.size(); i += 3) {
93 bool start = i == 0;
94 bool end = i + 3 >= bytecode_fields.size();
95 auto pc_index_1 = start ? 0 : pc_index + 31;
96 trace.set(row,
97 { {
98 { C::bc_hashing_bytecode_id, bytecode_id },
99 { C::bc_hashing_end, end },
100 { C::bc_hashing_size_in_bytes, bytecode_size_in_bytes[j] },
101 { C::bc_hashing_input_len, bytecode_fields.size() },
102 { C::bc_hashing_rounds_rem, num_rounds },
103 { C::bc_hashing_packed_fields_0, bytecode_field_at(i) },
104 { C::bc_hashing_packed_fields_1, bytecode_field_at(i + 1) },
105 { C::bc_hashing_packed_fields_2, bytecode_field_at(i + 2) },
106 { C::bc_hashing_pc_index, pc_index },
107 { C::bc_hashing_pc_index_1, pc_index_1 },
108 { C::bc_hashing_pc_index_2, pc_index_1 + 31 },
109 { C::bc_hashing_sel, 1 },
110 { C::bc_hashing_sel_not_padding_1, end && padding_amount == 2 ? 0 : 1 },
111 { C::bc_hashing_sel_not_padding_2, end && padding_amount > 0 ? 0 : 1 },
112 { C::bc_hashing_padding, padding_amount },
113 { C::bc_hashing_sel_not_start, !start },
114 { C::bc_hashing_start, start },
115 } });
116 row++;
117 num_rounds--;
118 pc_index = pc_index_1 + 62;
119 }
120 }
121 precomputed_builder.process_misc(trace, 256);
122 poseidon2_builder.process_hash(hash_event_emitter.dump_events(), trace);
123 return trace;
124 }
125};
126
127TEST_F(BytecodeHashingConstrainingTest, EmptyRow)
128{
129 check_relation<bc_hashing>(testing::empty_trace());
130}
131
132TEST_F(BytecodeHashingConstrainingTest, SingleBytecodeHashOneRow)
133{
136 std::vector<FF> bytecode_fields = { 1, 2 };
137 std::vector<uint8_t> bytecode = {};
138
139 for (auto bytecode_field : bytecode_fields) {
140 auto bytes = to_buffer(bytecode_field);
141 // Each field elt of encoded bytecode represents 31 bytes, hence start at +1:
142 bytecode.insert(bytecode.end(), bytes.begin() + 1, bytes.end());
143 }
145 auto hash = poseidon2.hash({ sep, 1, 2 });
146
147 auto trace = TestTraceContainer({
148 { { C::precomputed_first_row, 1 } },
149 {
150 { C::bc_hashing_size_in_bytes, bytecode.size() },
151 { C::bc_hashing_input_len, 3 },
152 { C::bc_hashing_end, 1 },
153 { C::bc_hashing_packed_fields_0, sep },
154 { C::bc_hashing_packed_fields_1, 1 },
155 { C::bc_hashing_packed_fields_2, 2 },
156 { C::bc_hashing_pc_index_1, 0 },
157 { C::bc_hashing_pc_index_2, 31 },
158 { C::bc_hashing_sel_not_padding_1, 1 },
159 { C::bc_hashing_sel_not_padding_2, 1 },
160 { C::bc_hashing_bytecode_id, hash },
161 { C::bc_hashing_pc_index, 0 },
162 { C::bc_hashing_rounds_rem, 1 },
163 { C::bc_hashing_sel, 1 },
164 { C::bc_hashing_start, 1 },
165 },
166 });
167
169 builder.process_decomposition(
170 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
172
173 check_relation<bc_hashing>(trace);
174 check_all_interactions<BytecodeTraceBuilder>(trace);
175}
176
177TEST_F(BytecodeHashingConstrainingTestTraceHelper, SingleBytecodeHash100Fields)
178{
179 // The hardcoded value is taken from noir-projects/labs/aztec-nr/aztec/src/hash.nr:
180 FF hash = FF("0x09348974e76c3602893d7a4b4bb52c2ec746f1ade5004ac471d0fbb4587a81a6");
181
182 std::vector<FF> bytecode_fields = {};
183 for (uint32_t i = 1; i < 100; i++) {
184 bytecode_fields.push_back(FF(i));
185 }
186 std::vector<uint8_t> bytecode = {};
187 for (auto bytecode_field : bytecode_fields) {
188 auto bytes = to_buffer(bytecode_field);
189 // Each field elt of encoded bytecode represents 31 bytes, but to_buffer returns 32, hence start at +1:
190 bytecode.insert(bytecode.end(), bytes.begin() + 1, bytes.end());
191 }
192 TestTraceContainer trace = process_bc_hashing_trace({ bytecode_fields }, { hash }, { bytecode.size() });
193
194 builder.process_decomposition(
195 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
196
197 check_relation<bc_hashing>(trace);
198 check_all_interactions<BytecodeTraceBuilder>(trace);
199}
200
201TEST_F(BytecodeHashingConstrainingTestTraceHelper, SingleBytecodeHashMax)
202{
203 std::vector<uint8_t> bytecode = random_bytes(static_cast<size_t>(31 * MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS));
204 std::vector<FF> bytecode_fields = simulation::encode_bytecode(bytecode);
205 std::vector<FF> prepended_fields = { compute_public_bytecode_first_field(bytecode.size()) };
206 prepended_fields.insert(prepended_fields.end(), bytecode_fields.begin(), bytecode_fields.end());
207 FF hash = RawPoseidon2::hash(prepended_fields);
208
209 TestTraceContainer trace = process_bc_hashing_trace({ bytecode_fields }, { hash }, { bytecode.size() });
210 builder.process_decomposition(
211 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
212
213 check_relation<bc_hashing>(trace);
214 check_all_interactions<BytecodeTraceBuilder>(trace);
215}
216
217TEST_F(BytecodeHashingConstrainingTestTraceHelper, MultipleBytecodeHash)
218{
219 // 40 bytes => hash 3 fields, no padding
220 // 20 bytes => hash 2 fields, one padding field
221 // 80 bytes => hash 4 fields, two padding fields
223 std::vector<std::vector<FF>> all_bytecode_fields;
224 std::vector<FF> hashes;
225 std::vector<size_t> byte_sizes;
226 for (uint32_t i = 0; i < all_bytecode.size(); i++) {
227 all_bytecode_fields.push_back(simulation::encode_bytecode(all_bytecode[i]));
228 std::vector<FF> prepended_fields = { compute_public_bytecode_first_field(all_bytecode[i].size()) };
229 prepended_fields.insert(prepended_fields.end(), all_bytecode_fields[i].begin(), all_bytecode_fields[i].end());
230 hashes.push_back(RawPoseidon2::hash(prepended_fields));
231 byte_sizes.push_back(all_bytecode[i].size());
232 }
233
234 TestTraceContainer trace = process_bc_hashing_trace(all_bytecode_fields, hashes, byte_sizes);
236
237 for (uint32_t j = 0; j < all_bytecode.size(); j++) {
238 const auto& bytecode = all_bytecode[j];
239 decomp_events.push_back(simulation::BytecodeDecompositionEvent{
240 .bytecode_id = hashes[j], .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) });
241 }
242 builder.process_decomposition({ decomp_events }, trace);
243
244 check_relation<bc_hashing>(trace);
245 check_all_interactions<BytecodeTraceBuilder>(trace);
246}
247
248TEST_F(BytecodeHashingConstrainingTest, BytecodeInteractions)
249{
250 TestTraceContainer trace({
251 { { C::precomputed_first_row, 1 } },
252 });
253
254 std::vector<uint8_t> bytecode = random_bytes(123);
255 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
256 std::vector<FF> prepended_fields = { compute_public_bytecode_first_field(bytecode.size()) };
257 prepended_fields.insert(prepended_fields.end(), fields.begin(), fields.end());
258 FF hash = RawPoseidon2::hash(prepended_fields);
259
260 builder.process_hashing({ { .bytecode_id = hash,
261 .bytecode_length_in_bytes = static_cast<uint32_t>(bytecode.size()),
262 .bytecode_fields = fields } },
263 trace);
264 builder.process_decomposition(
265 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
266
267 tracegen::MultiPermutationBuilder<perm_bc_hashing_get_packed_field_0_settings,
270 perm_builder(C::bc_decomposition_sel_packed);
271 perm_builder.process(trace);
272
273 check_multipermutation_interaction<BytecodeTraceBuilder,
277 check_relation<bc_hashing>(trace);
278 check_relation<bb::avm2::bc_decomposition<FF>>(trace);
279}
280
281// Negative test where latch == 1 and sel == 0
282TEST_F(BytecodeHashingConstrainingTest, NegativeLatchNotSel)
283{
284 TestTraceContainer trace;
285 trace.set(0,
286 { {
287 { C::bc_hashing_end, 1 },
288 { C::bc_hashing_sel, 1 },
289 } });
290
291 check_relation<bc_hashing>(trace, bc_hashing::SR_SEL_ON_START_OR_END);
292 trace.set(C::bc_hashing_sel, 0, 0); // Mutate to wrong value
293 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_SEL_ON_START_OR_END),
295}
296
297TEST_F(BytecodeHashingConstrainingTest, NegativeInvalidStartAfterLatch)
298{
299 TestTraceContainer trace({
300 { { C::precomputed_first_row, 1 } },
301 });
302 builder.process_hashing(
303 { { .bytecode_id = 1, .bytecode_length_in_bytes = 62, .bytecode_fields = random_fields(2) },
304 { .bytecode_id = 2, .bytecode_length_in_bytes = 93, .bytecode_fields = random_fields(3) } },
305 trace);
306 check_relation<bc_hashing>(trace, bc_hashing::SR_START_AFTER_LATCH);
307
308 // Row = 2 is the start of the hashing for bytecode id = 2
309 trace.set(Column::bc_hashing_start, 2, 0);
310 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_START_AFTER_LATCH),
312}
313
314TEST_F(BytecodeHashingConstrainingTest, NegativeInvalidPCIncrement)
315{
316 TestTraceContainer trace({
317 { { C::precomputed_first_row, 1 } },
318 });
319 builder.process_hashing(
320 {
321 { .bytecode_id = 1, .bytecode_length_in_bytes = 124, .bytecode_fields = random_fields(4) },
322 },
323 trace);
324 check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS);
325 check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS_1);
326 check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS_2);
327
328 // This is the last row of the bytecode hashing, pc_index should be 62
329 trace.set(Column::bc_hashing_pc_index, 2, 10);
330 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS),
332 trace.set(Column::bc_hashing_pc_index, 2, 62);
333 trace.set(Column::bc_hashing_pc_index_1, 2, 97);
334 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS_1),
336 trace.set(Column::bc_hashing_pc_index_1, 2, 93);
337 // The next pc_index should be 124 = pc_index_1 + 31
338 check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS_2);
339 trace.set(Column::bc_hashing_pc_index_2, 2, 10);
340 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS_2),
342}
343
344TEST_F(BytecodeHashingConstrainingTest, NegativeStartIsSeparator)
345{
346 TestTraceContainer trace({
347 { { C::precomputed_first_row, 1 } },
348 });
349 builder.process_hashing({ { .bytecode_id = 1, .bytecode_length_in_bytes = 62, .bytecode_fields = { 1, 2 } } },
350 trace);
351 check_relation<bc_hashing>(trace, bc_hashing::SR_START_IS_FIRST_FIELD);
352
353 // Row = 1 is the start of the hashing for bytecode id = 1
354 trace.set(Column::bc_hashing_packed_fields_0, 1, 1);
355 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_START_IS_FIRST_FIELD),
357}
358
359TEST_F(BytecodeHashingConstrainingTest, NegativeBytecodeInteraction)
360{
361 TestTraceContainer trace({
362 { { C::precomputed_first_row, 1 } },
363 });
364
365 std::vector<uint8_t> bytecode = random_bytes(150);
366 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
367 std::vector<FF> prepended_fields = { compute_public_bytecode_first_field(bytecode.size()) };
368 prepended_fields.insert(prepended_fields.end(), fields.begin(), fields.end());
369 FF hash = RawPoseidon2::hash(prepended_fields);
370
371 builder.process_hashing({ { .bytecode_id = hash, .bytecode_length_in_bytes = 150, .bytecode_fields = fields } },
372 trace);
373 builder.process_decomposition(
374 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
375 tracegen::MultiPermutationBuilder<perm_bc_hashing_get_packed_field_0_settings,
378 perm_builder(C::bc_decomposition_sel_packed);
379 perm_builder.process(trace);
380
381 // Row = 2 constrains the hashing for the last 3 fields of the bytecode (no padding)
382 // Modify the pc index for the permutation of the first packed field of row 2 (= prepended_fields[3])
383 trace.set(Column::bc_hashing_pc_index, 2, 0);
388 "Failed.*GET_PACKED_FIELD_0. Could not find tuple in destination.");
389 trace.set(Column::bc_hashing_pc_index, 2, 62);
390 // Modify the field value for the permutation of the second packed field of row 2 (= prepended_fields[4])
391 trace.set(Column::bc_hashing_packed_fields_1, 2, 0);
396 "Failed.*GET_PACKED_FIELD_1. Could not find tuple in destination.");
397 trace.set(Column::bc_hashing_packed_fields_1, 2, prepended_fields[4]);
398
399 // Modify the pc index for the permutation of the third packed field of row 2 (= fields[5])
400 trace.set(Column::bc_hashing_pc_index_2, 2, 0);
405 "Failed.*GET_PACKED_FIELD_2. Could not find tuple in destination.");
406
407 // Reset for next test:
408 trace.set(Column::bc_hashing_pc_index_2, 2, 124);
409 check_multipermutation_interaction<BytecodeTraceBuilder,
413
414 // Modify the bytecode id for the permutation:
415 trace.set(Column::bc_hashing_bytecode_id, 2, 0);
420 "Failed.*GET_PACKED_FIELD_.*. Could not find tuple in destination.");
421}
422
423TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativePaddingSelectors)
424{
425 // 80 bytes => hash 4 fields, two padding fields
426 std::vector<uint8_t> bytecode = random_bytes(80);
427 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
428
429 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { 1 }, { bytecode.size() });
430 builder.process_decomposition(
431 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
432
433 // Row = 2 constrains the hashing for the last field of the bytecode, plus 2 padding fields
434 // We cannot have padding anywhere but the last hashing row (= latch):
435 trace.set(Column::bc_hashing_end, 2, 0);
436 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDING_END),
438 trace.set(Column::bc_hashing_end, 2, 1);
439
440 // We cannot have packed_fields_1 is padding, but packed_fields_2 is not:
441 trace.set(Column::bc_hashing_sel_not_padding_2, 2, 1);
442 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDING_CONSISTENCY),
444 trace.set(Column::bc_hashing_sel_not_padding_2, 2, 0);
445
446 // We cannot have any padding with non-zero values:
447 trace.set(Column::bc_hashing_packed_fields_1, 2, 1);
448 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDED_BY_ZERO_1),
450 trace.set(Column::bc_hashing_packed_fields_2, 2, 1);
451 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDED_BY_ZERO_2),
453}
454
455TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativePaddingUnder)
456{
457 // 80 bytes => hash 4 fields, two padding fields
458 std::vector<uint8_t> bytecode = random_bytes(80);
459 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
460 std::vector<FF> prepended_fields = { compute_public_bytecode_first_field(bytecode.size()) };
461 prepended_fields.insert(prepended_fields.end(), fields.begin(), fields.end());
462 FF hash = RawPoseidon2::hash(prepended_fields);
463
464 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { hash }, { bytecode.size() });
465 builder.process_decomposition(
466 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
467
468 // Row = 2 constrains the hashing for the last field of the bytecode, plus 2 padding fields
469 // We cannot claim there is only one padding field:
470 trace.set(Column::bc_hashing_sel_not_padding_1, 2, 1);
471 // This will initially fail, because pc_at_final_field does not correspond to the pc at field 1...
472 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDING_COMPUTATION),
474 // ...setting it to that padding = 1 and input_len = 5 will make all relations to pass...
475 trace.set(Column::bc_hashing_padding, 2, 1);
476 trace.set(Column::bc_hashing_padding, 1, 1);
477 trace.set(Column::bc_hashing_input_len, 1, 5);
478 check_relation<bc_hashing>(trace);
479 // ...but the lookup to find field 1 will fail...
484 "Failed.*GET_PACKED_FIELD_1. Could not find tuple in destination.");
485}
486
487TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativePaddingOver)
488{
489 // 100 bytes => hash 5 fields, one padding field
490 std::vector<uint8_t> bytecode = random_bytes(100);
491 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
492 std::vector<FF> prepended_fields = { compute_public_bytecode_first_field(bytecode.size()) };
493 prepended_fields.insert(prepended_fields.end(), fields.begin(), fields.end());
494 FF hash = RawPoseidon2::hash(prepended_fields);
495
496 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { hash }, { bytecode.size() });
497 builder.process_decomposition(
498 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
499
500 // Row = 2 constrains the hashing for the last fields of the bytecode, plus 1 padding field
501 // We cannot claim there are two padding fields (to attempt to skip processing the last bytecode field):
502 trace.set(Column::bc_hashing_sel_not_padding_1, 2, 0);
503 // Padding computation will fail:
504 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDING_COMPUTATION),
506 // If we set padding = 2, fields_1 = 0, and input_len = 4, all relations will pass.
507 trace.set(Column::bc_hashing_padding, 2, 2);
508 trace.set(Column::bc_hashing_padding, 1, 2);
509 trace.set(Column::bc_hashing_packed_fields_1, 2, 0);
510 trace.set(Column::bc_hashing_input_len, 1, 4);
511 check_relation<bc_hashing>(trace);
512
513 // The multipermutation interaction will pass but a multipermutation selector `sel_packed_read[1]` will be
514 // untoggled, which will fail the #[PACKED_ROW_NEEDS_PERM_SELECTOR] (in bc_decomposition.pil) constraint.
515 check_multipermutation_interaction<BytecodeTraceBuilder,
519
521 check_relation<bc_decomposition>(trace, bc_decomposition::SR_PACKED_ROW_NEEDS_PERM_SELECTOR),
523}
524
525TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativeInputLen)
526{
527 // 80 bytes => hash 4 fields, two padding fields
528 std::vector<uint8_t> bytecode = random_bytes(80);
529 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
530
531 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { 1 }, { bytecode.size() });
532 builder.process_decomposition(
533 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
534
535 // Set the incorrect input_len at the first row, and the lookup into (an honest) poseidon will fail:
536 trace.set(Column::bc_hashing_input_len, 1, 0);
538 (check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_poseidon2_hash_settings>(trace)),
539 "LOOKUP_BC_HASHING_POSEIDON2_HASH");
540
541 trace.set(Column::bc_hashing_input_len, 1, 4);
542
543 // Set the incorrect input_len at the start row, and the constraining length check will fail:
544 trace.set(Column::bc_hashing_input_len, 1, 0);
545 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_BYTECODE_LENGTH_FIELDS),
547}
548
549TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativeRounds)
550{
551 // 80 bytes => hash 4 fields, two padding fields
552 std::vector<uint8_t> bytecode = random_bytes(80);
553 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
554
555 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { 1 }, { bytecode.size() });
556 builder.process_decomposition(
557 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
558
559 // Setting the incorrect number of rounds remaining will fail relative to the next row...
560 trace.set(Column::bc_hashing_rounds_rem, 1, 3);
561 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_ROUNDS_DECREMENT),
563
564 // ...and even if decremented correctly, will fail at latch if rounds_rem != 1:
565 trace.set(Column::bc_hashing_rounds_rem, 2, 2);
566 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_ROUNDS_DECREMENT),
568}
569
570TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativeOutputHash)
571{
572 std::vector<FF> bytecode_fields = random_fields(10);
573 std::vector<FF> prepended_fields = { compute_public_bytecode_first_field(bytecode_fields.size() * 31) };
574 prepended_fields.insert(prepended_fields.end(), bytecode_fields.begin(), bytecode_fields.end());
575 FF hash = RawPoseidon2::hash(prepended_fields);
576 TestTraceContainer trace = process_bc_hashing_trace({ bytecode_fields }, { hash }, { bytecode_fields.size() * 31 });
577
578 check_relation<bc_hashing>(trace);
579 check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_poseidon2_hash_settings>(trace);
580
581 // Change any of the output_hash values
582 trace.set(Column::bc_hashing_bytecode_id, 2, 123);
584 (check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_poseidon2_hash_settings>(trace)),
585 "LOOKUP_BC_HASHING_POSEIDON2_HASH");
586}
587
588TEST_F(BytecodeHashingConstrainingTest, NegativeSingleBytecodeHashIncrements)
589{
592 // Attempt to skip some init fields:
593 // decomp: 3 fields 1, 2, 3 => real hash [ sep, 1, 2, 3 ] => try and claim hash [ sep, 2, 3 ] => start = 1,
594 // pc_index = 31. Note that this is protected by the addition of precomputed.first_row in #[PC_INCREMENTS]
595 std::vector<uint8_t> bytecode = random_bytes(static_cast<size_t>(31 * 3));
596 std::vector<FF> bytecode_fields = simulation::encode_bytecode(bytecode);
597
599
600 auto bad_hash = poseidon2.hash({ sep, bytecode_fields[1], bytecode_fields[2] });
601
602 auto trace = TestTraceContainer({
603 { { C::precomputed_first_row, 1 } },
604 {
605 { C::bc_hashing_end, 1 },
606 { C::bc_hashing_packed_fields_0, sep },
607 { C::bc_hashing_packed_fields_1, bytecode_fields[1] },
608 { C::bc_hashing_packed_fields_2, bytecode_fields[2] },
609 { C::bc_hashing_pc_index_1, 31 },
610 { C::bc_hashing_pc_index_2, 62 },
611 { C::bc_hashing_sel_not_padding_1, 1 },
612 { C::bc_hashing_sel_not_padding_2, 1 },
613 { C::bc_hashing_bytecode_id, bad_hash },
614 { C::bc_hashing_pc_index, 31 },
615 { C::bc_hashing_sel, 1 },
616 { C::bc_hashing_sel_not_start, 0 },
617 { C::bc_hashing_start, 1 },
618 },
619 });
620
623 builder.process_decomposition(
624 { { .bytecode_id = bad_hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
625
626 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS),
628}
629
630TEST_F(BytecodeHashingConstrainingTest, NegativeSingleBytecodeHashLength)
631{
634 // Attempt to prepend fields to the hash
635 // decomp: 3 fields 1, 2, 3 => real hash [ sep, 1, 2, 3 ] => try and claim hash [ a, b, c, sep, 1, 2, 3 ]
636 std::vector<uint8_t> bytecode = random_bytes(static_cast<size_t>(31 * 3));
637 std::vector<FF> bytecode_fields = simulation::encode_bytecode(bytecode);
638
640
641 auto bad_hash = poseidon2.hash({ 0xa, 0xb, 0xc, sep, bytecode_fields[0], bytecode_fields[1], bytecode_fields[2] });
642
643 auto trace = TestTraceContainer({
644 { { C::precomputed_first_row, 1 } },
645 {
646 { C::bc_hashing_input_len, 7 },
647 { C::bc_hashing_packed_fields_0, sep },
648 { C::bc_hashing_packed_fields_1, bytecode_fields[0] },
649 { C::bc_hashing_packed_fields_2, bytecode_fields[1] },
650 { C::bc_hashing_pc_index_1, 0 },
651 { C::bc_hashing_pc_index_2, 31 },
652 { C::bc_hashing_sel_not_padding_1, 1 },
653 { C::bc_hashing_sel_not_padding_2, 1 },
654 { C::bc_hashing_bytecode_id, bad_hash },
655 { C::bc_hashing_pc_index, 0 },
656 { C::bc_hashing_rounds_rem, 2 },
657 { C::bc_hashing_sel, 1 },
658 { C::bc_hashing_start, 1 },
659 },
660 {
661 { C::bc_hashing_input_len, 7 },
662 { C::bc_hashing_end, 1 },
663 { C::bc_hashing_packed_fields_0, bytecode_fields[2] },
664 { C::bc_hashing_packed_fields_1, 0 },
665 { C::bc_hashing_packed_fields_2, 0 },
666 { C::bc_hashing_pc_index_1, 93 },
667 { C::bc_hashing_pc_index_2, 124 },
668 { C::bc_hashing_bytecode_id, bad_hash },
669 { C::bc_hashing_pc_index, 62 },
670 { C::bc_hashing_rounds_rem, 1 },
671 { C::bc_hashing_sel, 1 },
672 { C::bc_hashing_sel_not_start, 1 },
673 },
674 });
675
678 builder.process_decomposition(
679 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
680
681 // The correct rows (for input chunks [sep, 1, 2] and [3, 0, 0]) will exist in the poseidon trace, but the start
682 // rows do not line up:
684 (check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_poseidon2_hash_settings>(trace)),
685 "LOOKUP_BC_HASHING_POSEIDON2_HASH");
686 // At the final row, the length check will fail:
687 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_BYTECODE_LENGTH_FIELDS),
689}
690
691TEST_F(BytecodeHashingConstrainingTest, NegativeSingleBytecodeHashLengthBytes)
692{
695 // Attempt to extend the bytecode by zero value bytes (without requiring a new field)
696 // bc: 0xa...f of size 90 => real hash [ (90|sep), 0xa.., .., 0xf000000 ] => try and claim bc = 0xa...f0000 of
697 // size 92 with hash [ (92|sep), 0xa.., .., 0xf000000 ]
698 std::vector<uint8_t> bytecode = random_bytes(static_cast<size_t>(90));
699 std::vector<FF> bytecode_fields = simulation::encode_bytecode(bytecode);
700
702
703 auto bad_hash = poseidon2.hash({ sep, bytecode_fields[0], bytecode_fields[1], bytecode_fields[2] });
704
705 auto trace = TestTraceContainer({
706 { { C::precomputed_first_row, 1 } },
707 {
708 { C::bc_hashing_size_in_bytes, 92 },
709 { C::bc_hashing_input_len, 4 },
710 { C::bc_hashing_packed_fields_0, sep },
711 { C::bc_hashing_packed_fields_1, bytecode_fields[0] },
712 { C::bc_hashing_packed_fields_2, bytecode_fields[1] },
713 { C::bc_hashing_pc_index_1, 0 },
714 { C::bc_hashing_pc_index_2, 31 },
715 { C::bc_hashing_sel_not_padding_1, 1 },
716 { C::bc_hashing_sel_not_padding_2, 1 },
717 { C::bc_hashing_bytecode_id, bad_hash },
718 { C::bc_hashing_pc_index, 0 },
719 { C::bc_hashing_rounds_rem, 2 },
720 { C::bc_hashing_padding, 2 },
721 { C::bc_hashing_sel, 1 },
722 { C::bc_hashing_start, 1 },
723 },
724 {
725 { C::bc_hashing_input_len, 4 },
726 { C::bc_hashing_end, 1 },
727 { C::bc_hashing_packed_fields_0, bytecode_fields[2] },
728 { C::bc_hashing_packed_fields_1, 0 },
729 { C::bc_hashing_packed_fields_2, 0 },
730 { C::bc_hashing_pc_index_1, 93 },
731 { C::bc_hashing_pc_index_2, 124 },
732 { C::bc_hashing_bytecode_id, bad_hash },
733 { C::bc_hashing_pc_index, 62 },
734 { C::bc_hashing_rounds_rem, 1 },
735 { C::bc_hashing_sel, 1 },
736 { C::bc_hashing_sel_not_start, 1 },
737 },
738 });
739
742 builder.process_decomposition(
743 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
744
745 // The field length is correct (90 and 92 bytes both require 3 fields, total 4 for sep):
746 check_relation<bc_hashing>(trace, bc_hashing::SR_BYTECODE_LENGTH_FIELDS);
747
748 // The claimed byte length is incorrect:
750 (check_interaction<BytecodeTraceBuilder, perm_bc_hashing_bytecode_length_bytes_settings>(trace)),
751 "PERM_BC_HASHING_BYTECODE_LENGTH_BYTES");
752}
753
754// =====================================================================
755// Ghost Row Injection Vulnerability Tests
756// =====================================================================
757// These tests verify that ghost rows (sel=0) cannot fire permutations.
758// The fix: sel_not_padding_1 * (1 - sel) = 0 and sel_not_padding_2 * (1 - sel) = 0
759// ensure these selectors are forced to 0 when sel=0.
760
761TEST_F(BytecodeHashingConstrainingTest, NegativeGhostRowInjectionBlocked)
762{
763 // Try to create a ghost row (sel=0) with sel_not_padding_1=1 or sel_not_padding_2=1
764 // which would fire the #[GET_PACKED_FIELD_1] or #[GET_PACKED_FIELD_2] permutations
765 TestTraceContainer trace({
766 { { C::precomputed_first_row, 1 } },
767 {
768 { C::bc_hashing_sel, 0 }, // Ghost row: gadget not active
769 { C::bc_hashing_sel_not_padding_1, 1 }, // Try to fire permutation anyway
770 { C::bc_hashing_sel_not_padding_2, 0 },
771 },
772 });
773
774 // The fix: sel_not_padding_1 * (1 - sel) = 0
775 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace),
777
778 // Reset and try with sel_not_padding_2
779 trace.set(C::bc_hashing_sel_not_padding_1, 1, 0);
780 trace.set(C::bc_hashing_sel_not_padding_2, 1, 1);
781
782 // The fix: sel_not_padding_2 * (1 - sel) = 0
783 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace),
785}
786
787TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativePaddingPropagationMultiRow)
788{
789 // 248 bytes => 8 fields + 1 sep = 9 fields => padding = 0, 3 rounds
790 // Verify PADDING_PROPAGATION across a longer computation block.
791 std::vector<uint8_t> bytecode = random_bytes(248);
792 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
793
794 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { 1 }, { bytecode.size() });
795 builder.process_decomposition(
796 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
797
798 check_relation<bc_hashing>(trace, bc_hashing::SR_PADDING_PROPAGATION);
799
800 // Rows 1, 2, 3 are the three rounds. Changing padding on the middle row should break propagation.
801 trace.set(C::bc_hashing_padding, 2, 1);
802 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDING_PROPAGATION),
804}
805
806TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativeBytecodeFieldLengthViaPadding)
807{
808 // Verify that #[BYTECODE_LENGTH_FIELDS] catches a mismatch between padding and input_len at start.
809 // Constraint: start * (3 * rounds_rem - padding - input_len) = 0
810 // 93 bytes => 3 fields + 1 sep = 4 fields => padding = 2, rounds_rem = 2, input_len = 4
811 // Check: 3 * 2 - 2 - 4 = 0 ✓
812 std::vector<uint8_t> bytecode = random_bytes(93);
813 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
814
815 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { 1 }, { bytecode.size() });
816 builder.process_decomposition(
817 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
818
819 check_relation<bc_hashing>(trace, bc_hashing::SR_BYTECODE_LENGTH_FIELDS);
820
821 // Corrupt padding at the start row to break the link between rounds_rem, padding, and input_len.
822 // Set padding to 0 at start (should be 2). Now 3 * 2 - 0 - 4 = 2 ≠ 0
823 trace.set(C::bc_hashing_padding, 1, 0);
824 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_BYTECODE_LENGTH_FIELDS),
826}
827
828} // namespace
829} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:224
StrictMock< MockGreaterThan > mock_gt
EventEmitter< Poseidon2PermutationMemoryEvent > perm_mem_event_emitter
EventEmitter< Poseidon2PermutationEvent > perm_event_emitter
EventEmitter< Poseidon2HashEvent > hash_event_emitter
Poseidon2TraceBuilder poseidon2_builder
StrictMock< MockExecutionIdManager > mock_execution_id_manager
static std::string get_subrelation_label(size_t index)
static constexpr size_t SR_PACKED_ROW_NEEDS_PERM_SELECTOR
static constexpr size_t SR_PADDING_CONSISTENCY
static constexpr size_t SR_SEL_NOT_PADDING_REQUIRES_SEL
static constexpr size_t SR_PADDING_PROPAGATION
static constexpr size_t SR_ROUNDS_DECREMENT
static constexpr size_t SR_SEL_ON_START_OR_END
static constexpr size_t SR_PADDING_END
static constexpr size_t SR_PC_INCREMENTS_2
static constexpr size_t SR_START_IS_FIRST_FIELD
static constexpr size_t SR_START_AFTER_LATCH
static constexpr size_t SR_PADDED_BY_ZERO_2
static constexpr size_t SR_PADDING_COMPUTATION
static constexpr size_t SR_PADDED_BY_ZERO_1
static std::string get_subrelation_label(size_t index)
static constexpr size_t SR_PC_INCREMENTS
static constexpr size_t SR_BYTECODE_LENGTH_FIELDS
static constexpr size_t SR_PC_INCREMENTS_1
void process_hash(const simulation::EventEmitterInterface< simulation::Poseidon2HashEvent >::Container &hash_events, TraceContainer &trace)
Processes the hash events for the Poseidon2 hash function. It populates the columns for the poseidon2...
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 set(Column col, uint32_t row, const FF &value, bool use_atomic_limbs=false)
static FF hash(const std::vector< FF > &input)
Hashes a vector of field elements.
PrecomputedTraceBuilder precomputed_builder
Definition alu.test.cpp:120
AluTraceBuilder builder
Definition alu.test.cpp:124
TestTraceContainer trace
std::vector< uint8_t > bytecode
TEST_F(AvmRecursiveTests, TwoLayerAvmRecursion)
A test of the Two Layer AVM recursive verifier.
void check_multipermutation_interaction(tracegen::TestTraceContainer &trace)
std::vector< FF > encode_bytecode(std::span< const uint8_t > bytecode)
Encodes the bytecode into a vector of field elements. Each field element represents 31 bytes of the b...
FF compute_public_bytecode_first_field(size_t bytecode_size)
std::vector< uint8_t > random_bytes(size_t n)
Definition fixtures.cpp:36
TestTraceContainer empty_trace()
Definition fixtures.cpp:156
std::vector< FF > random_fields(size_t n)
Definition fixtures.cpp:26
AvmFlavorSettings::FF FF
Definition field.hpp:10
permutation_settings< perm_bc_hashing_get_packed_field_2_settings_ > perm_bc_hashing_get_packed_field_2_settings
permutation_settings< perm_bc_hashing_get_packed_field_1_settings_ > perm_bc_hashing_get_packed_field_1_settings
permutation_settings< perm_bc_hashing_get_packed_field_0_settings_ > perm_bc_hashing_get_packed_field_0_settings
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::vector< uint8_t > to_buffer(T const &value)