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
34using simulation::EventEmitter;
35using simulation::MockExecutionIdManager;
36using simulation::MockGreaterThan;
37using simulation::Poseidon2;
38using simulation::Poseidon2HashEvent;
39using simulation::Poseidon2PermutationEvent;
40using simulation::Poseidon2PermutationMemoryEvent;
41using tracegen::BytecodeTraceBuilder;
42using tracegen::Poseidon2TraceBuilder;
43using tracegen::PrecomputedTraceBuilder;
44using tracegen::TestTraceContainer;
45
47using C = Column;
48using bc_hashing = bb::avm2::bc_hashing<FF>;
50
51class BytecodeHashingConstrainingTest : public ::testing::Test {
52 public:
53 EventEmitter<Poseidon2HashEvent> hash_event_emitter;
54 EventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
55 EventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
56
57 StrictMock<MockGreaterThan> mock_gt;
58 StrictMock<MockExecutionIdManager> mock_execution_id_manager;
59
60 Poseidon2TraceBuilder poseidon2_builder;
61 PrecomputedTraceBuilder precomputed_builder;
62 BytecodeTraceBuilder builder;
63};
64
65class BytecodeHashingConstrainingTestTraceHelper : public BytecodeHashingConstrainingTest {
66 public:
67 TestTraceContainer process_bc_hashing_trace(std::vector<std::vector<FF>> all_bytecode_fields,
68 std::vector<FF> bytecode_ids)
69 {
70 // Note: this helper expects bytecode fields without the prepended separator and does not complete decomposition
73 TestTraceContainer trace({
74 { { C::precomputed_first_row, 1 } },
75 });
76 uint32_t row = 1;
77 for (uint32_t j = 0; j < all_bytecode_fields.size(); j++) {
78 uint32_t pc_index = 0;
79 auto bytecode_fields = all_bytecode_fields[j];
80 auto bytecode_id = bytecode_ids[j];
81 bytecode_fields.insert(bytecode_fields.begin(), GENERATOR_INDEX__PUBLIC_BYTECODE);
82 auto hash = poseidon2.hash(bytecode_fields);
83 auto bytecode_field_at = [&bytecode_fields](size_t i) -> FF {
84 return i < bytecode_fields.size() ? bytecode_fields[i] : 0;
85 };
86 auto padding_amount = (3 - (bytecode_fields.size() % 3)) % 3;
87 auto num_rounds = (bytecode_fields.size() + padding_amount) / 3;
88 for (uint32_t i = 0; i < bytecode_fields.size(); i += 3) {
89 bool start = i == 0;
90 bool end = i + 3 >= bytecode_fields.size();
91 auto pc_index_1 = start ? 0 : pc_index + 31;
92 trace.set(row,
93 { {
94 { C::bc_hashing_bytecode_id, bytecode_id },
95 { C::bc_hashing_latch, end },
96 { C::bc_hashing_output_hash, hash },
97 { C::bc_hashing_input_len, bytecode_fields.size() },
98 { C::bc_hashing_rounds_rem, num_rounds },
99 { C::bc_hashing_packed_fields_0, bytecode_field_at(i) },
100 { C::bc_hashing_packed_fields_1, bytecode_field_at(i + 1) },
101 { C::bc_hashing_packed_fields_2, bytecode_field_at(i + 2) },
102 { C::bc_hashing_pc_index, pc_index },
103 { C::bc_hashing_pc_index_1, pc_index_1 },
104 { C::bc_hashing_pc_index_2, pc_index_1 + 31 },
105 { C::bc_hashing_sel, 1 },
106 { C::bc_hashing_sel_not_padding_1, end && padding_amount == 2 ? 0 : 1 },
107 { C::bc_hashing_sel_not_padding_2, end && padding_amount > 0 ? 0 : 1 },
108 { C::bc_hashing_sel_not_start, !start },
109 { C::bc_hashing_start, start },
110 } });
111 if (end) {
112 // TODO(MW): Cleanup: below sets the pc at which the final field starts.
113 // It can't just be pc_index + 31 * padding_amount because we 'skip' 31 bytes at start == 1 to force
114 // the first field to be the separator.
115 trace.set(row,
116 { {
117 { C::bc_hashing_pc_at_final_field,
118 padding_amount == 2 ? pc_index : pc_index_1 + (31 * (1 - padding_amount)) },
119 } });
120 }
121 row++;
122 num_rounds--;
123 pc_index = pc_index_1 + 62;
124 }
125 }
126 precomputed_builder.process_misc(trace, 256);
127 poseidon2_builder.process_hash(hash_event_emitter.dump_events(), trace);
128 return trace;
129 }
130};
131
132TEST_F(BytecodeHashingConstrainingTest, EmptyRow)
133{
134 check_relation<bc_hashing>(testing::empty_trace());
135}
136
137TEST_F(BytecodeHashingConstrainingTest, SingleBytecodeHashOneRow)
138{
141 std::vector<FF> bytecode_fields = { 1, 2 };
142 std::vector<uint8_t> bytecode = {};
143
144 for (auto bytecode_field : bytecode_fields) {
145 auto bytes = to_buffer(bytecode_field);
146 // Each field elt of encoded bytecode represents 31 bytes, hence start at +1:
147 bytecode.insert(bytecode.end(), bytes.begin() + 1, bytes.end());
148 }
149
151
152 auto trace = TestTraceContainer({
153 { { C::precomputed_first_row, 1 } },
154 {
155 { C::bc_hashing_input_len, 3 },
156 { C::bc_hashing_latch, 1 },
157 { C::bc_hashing_packed_fields_0, GENERATOR_INDEX__PUBLIC_BYTECODE },
158 { C::bc_hashing_packed_fields_1, 1 },
159 { C::bc_hashing_packed_fields_2, 2 },
160 { C::bc_hashing_pc_at_final_field, 31 },
161 { C::bc_hashing_pc_index_1, 0 },
162 { C::bc_hashing_pc_index_2, 31 },
163 { C::bc_hashing_sel_not_padding_1, 1 },
164 { C::bc_hashing_sel_not_padding_2, 1 },
165 { C::bc_hashing_bytecode_id, hash },
166 { C::bc_hashing_output_hash, hash },
167 { C::bc_hashing_pc_index, 0 },
168 { C::bc_hashing_rounds_rem, 1 },
169 { C::bc_hashing_sel, 1 },
170 { C::bc_hashing_start, 1 },
171 },
172 });
173
175 builder.process_decomposition(
176 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
178
179 check_relation<bc_hashing>(trace);
180 check_all_interactions<BytecodeTraceBuilder>(trace);
181}
182
183TEST_F(BytecodeHashingConstrainingTestTraceHelper, SingleBytecodeHash100Fields)
184{
185 // The hardcoded value is taken from noir-projects/aztec-nr/aztec/src/hash.nr:
186 FF hash = FF("0x16d621c3387156ef53754679e7b2c9be8f0bceeb44aa59a74991df3b0b42a0bf");
187
188 std::vector<FF> bytecode_fields = {};
189 for (uint32_t i = 1; i < 100; i++) {
190 bytecode_fields.push_back(FF(i));
191 }
192
193 TestTraceContainer trace = process_bc_hashing_trace({ bytecode_fields }, { hash });
194
195 std::vector<uint8_t> bytecode = {};
196 for (auto bytecode_field : bytecode_fields) {
197 auto bytes = to_buffer(bytecode_field);
198 // Each field elt of encoded bytecode represents 31 bytes, but to_buffer returns 32, hence start at +1:
199 bytecode.insert(bytecode.end(), bytes.begin() + 1, bytes.end());
200 }
201
202 builder.process_decomposition(
203 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
204
205 check_relation<bc_hashing>(trace);
206 check_all_interactions<BytecodeTraceBuilder>(trace);
207 EXPECT_EQ(trace.get(C::bc_hashing_output_hash, 1), hash);
208}
209
210TEST_F(BytecodeHashingConstrainingTestTraceHelper, SingleBytecodeHashMax)
211{
212 std::vector<uint8_t> bytecode = random_bytes(static_cast<size_t>(31 * MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS));
213 std::vector<FF> bytecode_fields = simulation::encode_bytecode(bytecode);
214 std::vector<FF> prepended_fields = { GENERATOR_INDEX__PUBLIC_BYTECODE };
215 prepended_fields.insert(prepended_fields.end(), bytecode_fields.begin(), bytecode_fields.end());
216 FF hash = RawPoseidon2::hash(prepended_fields);
217
218 TestTraceContainer trace = process_bc_hashing_trace({ bytecode_fields }, { hash });
219 builder.process_decomposition(
220 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
221
222 check_relation<bc_hashing>(trace);
223 check_all_interactions<BytecodeTraceBuilder>(trace);
224}
225
226TEST_F(BytecodeHashingConstrainingTestTraceHelper, MultipleBytecodeHash)
227{
228 // 40 bytes => hash 3 fields, no padding
229 // 20 bytes => hash 2 fields, one padding field
230 // 80 bytes => hash 4 fields, two padding fields
232 std::vector<std::vector<FF>> all_bytecode_fields;
233 std::vector<FF> hashes;
234 for (uint32_t i = 0; i < all_bytecode.size(); i++) {
235 all_bytecode_fields.push_back(simulation::encode_bytecode(all_bytecode[i]));
236 std::vector<FF> prepended_fields = { GENERATOR_INDEX__PUBLIC_BYTECODE };
237 prepended_fields.insert(prepended_fields.end(), all_bytecode_fields[i].begin(), all_bytecode_fields[i].end());
238 hashes.push_back(RawPoseidon2::hash(prepended_fields));
239 }
240
241 TestTraceContainer trace = process_bc_hashing_trace(all_bytecode_fields, hashes);
243
244 for (uint32_t j = 0; j < all_bytecode.size(); j++) {
245 const auto& bytecode = all_bytecode[j];
246 decomp_events.push_back(simulation::BytecodeDecompositionEvent{
247 .bytecode_id = hashes[j], .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) });
248 }
249 builder.process_decomposition({ decomp_events }, trace);
250
251 check_relation<bc_hashing>(trace);
252 check_all_interactions<BytecodeTraceBuilder>(trace);
253}
254
255TEST_F(BytecodeHashingConstrainingTest, BytecodeInteractions)
256{
257 TestTraceContainer trace({
258 { { C::precomputed_first_row, 1 } },
259 });
260
261 std::vector<uint8_t> bytecode = random_bytes(123);
262 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
263 std::vector<FF> prepended_fields = { GENERATOR_INDEX__PUBLIC_BYTECODE };
264 prepended_fields.insert(prepended_fields.end(), fields.begin(), fields.end());
265 FF hash = RawPoseidon2::hash(prepended_fields);
266
267 builder.process_hashing({ { .bytecode_id = hash, .bytecode_length = 40, .bytecode_fields = fields } }, trace);
268 builder.process_decomposition(
269 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
270
271 tracegen::MultiPermutationBuilder<perm_bc_hashing_get_packed_field_0_settings,
274 perm_builder(C::bc_decomposition_sel_packed);
275 perm_builder.process(trace);
276
277 check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_check_final_bytes_remaining_settings>(trace);
278 check_multipermutation_interaction<BytecodeTraceBuilder,
282 check_relation<bc_hashing>(trace);
283 check_relation<bb::avm2::bc_decomposition<FF>>(trace);
284}
285
286// Negative test where latch == 1 and sel == 0
287TEST_F(BytecodeHashingConstrainingTest, NegativeLatchNotSel)
288{
289 TestTraceContainer trace;
290 trace.set(0,
291 { {
292 { C::bc_hashing_latch, 1 },
293 { C::bc_hashing_sel, 1 },
294 } });
295
296 check_relation<bc_hashing>(trace, bc_hashing::SR_SEL_TOGGLED_AT_LATCH);
297 trace.set(C::bc_hashing_sel, 0, 0); // Mutate to wrong value
299 "SEL_TOGGLED_AT_LATCH");
300}
301
302TEST_F(BytecodeHashingConstrainingTest, NegativeInvalidStartAfterLatch)
303{
304 TestTraceContainer trace({
305 { { C::precomputed_first_row, 1 } },
306 });
307 builder.process_hashing({ { .bytecode_id = 1, .bytecode_length = 62, .bytecode_fields = random_fields(2) },
308 { .bytecode_id = 2, .bytecode_length = 93, .bytecode_fields = random_fields(3) } },
309 trace);
310 check_relation<bc_hashing>(trace, bc_hashing::SR_START_AFTER_LATCH);
311
312 // Row = 2 is the start of the hashing for bytecode id = 2
313 trace.set(Column::bc_hashing_start, 2, 0);
314 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_START_AFTER_LATCH), "START_AFTER_LATCH");
315}
316
317TEST_F(BytecodeHashingConstrainingTest, NegativeInvalidPCIncrement)
318{
319 TestTraceContainer trace({
320 { { C::precomputed_first_row, 1 } },
321 });
322 builder.process_hashing(
323 {
324 { .bytecode_id = 1, .bytecode_length = 124, .bytecode_fields = random_fields(4) },
325 },
326 trace);
327 check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS);
328
329 // This is the last row of the bytecode hashing, pc_index should be 62
330 trace.set(Column::bc_hashing_pc_index, 2, 10);
331 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS), "PC_INCREMENTS");
332 // The next pc_index should be 93 = pc_index + 31
333 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS_1), "PC_INCREMENTS_1");
334 // The next pc_index should be 124 = pc_index_1 + 31
335 check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS_2);
336 trace.set(Column::bc_hashing_pc_index_2, 2, 10);
337 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS_2), "PC_INCREMENTS_2");
338}
339
340TEST_F(BytecodeHashingConstrainingTest, NegativeStartIsSeparator)
341{
342 TestTraceContainer trace({
343 { { C::precomputed_first_row, 1 } },
344 });
345 builder.process_hashing({ { .bytecode_id = 1, .bytecode_length = 62, .bytecode_fields = { 1, 2 } } }, trace);
346 check_relation<bc_hashing>(trace, bc_hashing::SR_START_IS_SEPARATOR);
347
348 // Row = 1 is the start of the hashing for bytecode id = 1
349 trace.set(Column::bc_hashing_packed_fields_0, 1, 1);
351 "START_IS_SEPARATOR");
352}
353
354TEST_F(BytecodeHashingConstrainingTest, NegativeBytecodeInteraction)
355{
356 TestTraceContainer trace({
357 { { C::precomputed_first_row, 1 } },
358 });
359
360 std::vector<uint8_t> bytecode = random_bytes(150);
361 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
362 std::vector<FF> prepended_fields = { GENERATOR_INDEX__PUBLIC_BYTECODE };
363 prepended_fields.insert(prepended_fields.end(), fields.begin(), fields.end());
364 FF hash = RawPoseidon2::hash(prepended_fields);
365
366 builder.process_hashing({ { .bytecode_id = hash, .bytecode_length = 150, .bytecode_fields = fields } }, trace);
367 builder.process_decomposition(
368 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
369 tracegen::MultiPermutationBuilder<perm_bc_hashing_get_packed_field_0_settings,
372 perm_builder(C::bc_decomposition_sel_packed);
373 perm_builder.process(trace);
374
375 // Row = 2 constrains the hashing for the last 3 fields of the bytecode (no padding)
376 // Modify the pc index for the permutation of the first packed field of row 2 (= prepended_fields[3])
377 trace.set(Column::bc_hashing_pc_index, 2, 0);
382 "Failed.*GET_PACKED_FIELD_0. Could not find tuple in destination.");
383 trace.set(Column::bc_hashing_pc_index, 2, 62);
384 // Modify the field value for the permutation of the second packed field of row 2 (= prepended_fields[4])
385 trace.set(Column::bc_hashing_packed_fields_1, 2, 0);
390 "Failed.*GET_PACKED_FIELD_1. Could not find tuple in destination.");
391 trace.set(Column::bc_hashing_packed_fields_1, 2, prepended_fields[4]);
392
393 // Modify the pc index for the permutation of the third packed field of row 2 (= fields[5])
394 trace.set(Column::bc_hashing_pc_index_2, 2, 0);
399 "Failed.*GET_PACKED_FIELD_2. Could not find tuple in destination.");
400
401 // Reset for next test:
402 trace.set(Column::bc_hashing_pc_index_2, 2, 124);
403 check_multipermutation_interaction<BytecodeTraceBuilder,
407
408 // Modify the bytecode id for the permutation:
409 trace.set(Column::bc_hashing_bytecode_id, 2, 0);
414 "Failed.*GET_PACKED_FIELD_.*. Could not find tuple in destination.");
415}
416
417TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativePaddingSelectors)
418{
419 // 80 bytes => hash 4 fields, two padding fields
420 std::vector<uint8_t> bytecode = random_bytes(80);
421 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
422
423 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { 1 });
424 builder.process_decomposition(
425 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
426
427 // Row = 2 constrains the hashing for the last field of the bytecode, plus 2 padding fields
428 // We cannot have padding anywhere but the last hashing row (= latch):
429 trace.set(Column::bc_hashing_latch, 2, 0);
430 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDING_END), "PADDING_END");
431 trace.set(Column::bc_hashing_latch, 2, 1);
432
433 // We cannot have packed_fields_1 is padding, but packed_fields_2 is not:
434 trace.set(Column::bc_hashing_sel_not_padding_2, 2, 1);
436 "PADDING_CONSISTENCY");
437 trace.set(Column::bc_hashing_sel_not_padding_2, 2, 0);
438
439 // We cannot have any padding with non-zero values:
440 trace.set(Column::bc_hashing_packed_fields_1, 2, 1);
441 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDED_BY_ZERO_1), "PADDED_BY_ZERO_1");
442 trace.set(Column::bc_hashing_packed_fields_2, 2, 1);
443 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDED_BY_ZERO_2), "PADDED_BY_ZERO_2");
444}
445
446TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativePaddingUnder)
447{
448 // 80 bytes => hash 4 fields, two padding fields
449 std::vector<uint8_t> bytecode = random_bytes(80);
450 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
451 std::vector<FF> prepended_fields = { GENERATOR_INDEX__PUBLIC_BYTECODE };
452 prepended_fields.insert(prepended_fields.end(), fields.begin(), fields.end());
453 FF hash = RawPoseidon2::hash(prepended_fields);
454
455 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { hash });
456 builder.process_decomposition(
457 { { .bytecode_id = hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
458
459 // Row = 2 constrains the hashing for the last field of the bytecode, plus 2 padding fields
460 // We cannot claim there is only one padding field:
461 trace.set(Column::bc_hashing_sel_not_padding_1, 2, 1);
462 // This will initially fail, because pc_at_final_field does not correspond to the pc at field 1...
464 "PADDING_CORRECTNESS");
465 // ...setting it to that of field 2 will force the relation to pass...
466 trace.set(Column::bc_hashing_pc_at_final_field, 2, 93);
467 check_relation<bc_hashing>(trace, bc_hashing::SR_PADDING_CORRECTNESS);
468 // ...but the lookup to find field 1 will fail...
473 "Failed.*GET_PACKED_FIELD_1. Could not find tuple in destination.");
474 // ...and the lookup to check the final field against bytes remaining will fail:
476 (check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_check_final_bytes_remaining_settings>(trace)),
477 "Failed.*CHECK_FINAL_BYTES_REMAINING. Could not find tuple in destination.");
478}
479
480TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativePaddingOver)
481{
482 // 100 bytes => hash 5 fields, one padding field
483 std::vector<uint8_t> bytecode = random_bytes(100);
484 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
485
486 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { 1 });
487 builder.process_decomposition(
488 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
489
490 // Row = 2 constrains the hashing for the last fields of the bytecode, plus 1 padding field
491 // We cannot claim there are two padding fields (to attempt to skip processing the last bytecode field):
492 trace.set(Column::bc_hashing_sel_not_padding_1, 2, 0);
493 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PADDED_BY_ZERO_1), "PADDED_BY_ZERO_1");
494 // If we incorrectly set packed_fields_1 to 0 and pc_at_final_field to pc_index_1...
495 trace.set(Column::bc_hashing_packed_fields_1, 2, 0);
496 trace.set(Column::bc_hashing_pc_at_final_field, 2, 62);
497 // ...then the lookup into decomp will fail (bytes_remaining > 31):
499 (check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_check_final_bytes_remaining_settings>(trace)),
500 "Failed.*CHECK_FINAL_BYTES_REMAINING. Could not find tuple in destination.");
501}
502
503TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativeInputLen)
504{
505 // 80 bytes => hash 4 fields, two padding fields
506 std::vector<uint8_t> bytecode = random_bytes(80);
507 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
508
509 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { 1 });
510 builder.process_decomposition(
511 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
512
513 // Set the incorrect input_len at the first row, and the lookup into (an honest) poseidon will fail:
514 trace.set(Column::bc_hashing_input_len, 1, 0);
516 (check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_poseidon2_hash_settings>(trace)),
517 "LOOKUP_BC_HASHING_POSEIDON2_HASH");
518
519 trace.set(Column::bc_hashing_input_len, 1, 4);
520
521 // Set the incorrect input_len at the final row, and the constraining length check will fail:
522 trace.set(Column::bc_hashing_input_len, 2, 0);
524 "BYTECODE_LENGTH_FIELDS");
525}
526
527TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativeRounds)
528{
529 // 80 bytes => hash 4 fields, two padding fields
530 std::vector<uint8_t> bytecode = random_bytes(80);
531 std::vector<FF> fields = simulation::encode_bytecode(bytecode);
532
533 TestTraceContainer trace = process_bc_hashing_trace({ fields }, { 1 });
534 builder.process_decomposition(
535 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
536
537 // Setting the incorrect number of rounds remaining will fail relative to the next row...
538 trace.set(Column::bc_hashing_rounds_rem, 1, 3);
539 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_ROUNDS_DECREMENT), "ROUNDS_DECREMENT");
540
541 // ...and even if decremented correctly, will fail at latch if rounds_rem != 1:
542 trace.set(Column::bc_hashing_rounds_rem, 2, 2);
543 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_ROUNDS_DECREMENT), "ROUNDS_DECREMENT");
544}
545
546TEST_F(BytecodeHashingConstrainingTestTraceHelper, NegativeOutputHash)
547{
548 std::vector<FF> bytecode_fields = random_fields(10);
549 std::vector<FF> prepended_fields = { GENERATOR_INDEX__PUBLIC_BYTECODE };
550 prepended_fields.insert(prepended_fields.end(), bytecode_fields.begin(), bytecode_fields.end());
551 FF hash = RawPoseidon2::hash(prepended_fields);
552 TestTraceContainer trace = process_bc_hashing_trace({ bytecode_fields }, { hash });
553
554 check_relation<bc_hashing>(trace);
555 check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_poseidon2_hash_settings>(trace);
556
557 // Change any of the output_hash values
558 trace.set(Column::bc_hashing_output_hash, 2, 123);
560 (check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_poseidon2_hash_settings>(trace)),
561 "LOOKUP_BC_HASHING_POSEIDON2_HASH");
562}
563
564TEST_F(BytecodeHashingConstrainingTest, NegativeSingleBytecodeHashIncrements)
565{
568 // Attempt to skip some init fields:
569 // decomp: 3 fields 1, 2, 3 => real hash [ sep, 1, 2, 3 ] => try and claim hash [ sep, 2, 3 ] => start = 1, pc_index
570 // = 31. Note that this is protected by the addition of precomputed.first_row in #[PC_INCREMENTS]
571 std::vector<uint8_t> bytecode = random_bytes(static_cast<size_t>(31 * 3));
572 std::vector<FF> bytecode_fields = simulation::encode_bytecode(bytecode);
573
574 auto bad_hash = poseidon2.hash({ GENERATOR_INDEX__PUBLIC_BYTECODE, bytecode_fields[1], bytecode_fields[2] });
575
576 auto trace = TestTraceContainer({
577 { { C::precomputed_first_row, 1 } },
578 {
579 { C::bc_hashing_latch, 1 },
580 { C::bc_hashing_packed_fields_0, GENERATOR_INDEX__PUBLIC_BYTECODE },
581 { C::bc_hashing_packed_fields_1, bytecode_fields[1] },
582 { C::bc_hashing_packed_fields_2, bytecode_fields[2] },
583 { C::bc_hashing_pc_at_final_field, 62 },
584 { C::bc_hashing_pc_index_1, 31 },
585 { C::bc_hashing_pc_index_2, 62 },
586 { C::bc_hashing_sel_not_padding_1, 1 },
587 { C::bc_hashing_sel_not_padding_2, 1 },
588 { C::bc_hashing_bytecode_id, 1 },
589 { C::bc_hashing_output_hash, bad_hash },
590 { C::bc_hashing_pc_index, 31 },
591 { C::bc_hashing_sel, 1 },
592 { C::bc_hashing_sel_not_start, 0 },
593 { C::bc_hashing_start, 1 },
594 },
595 });
596
599 builder.process_decomposition(
600 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
601
602 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_PC_INCREMENTS), "PC_INCREMENTS");
603}
604
605TEST_F(BytecodeHashingConstrainingTest, NegativeSingleBytecodeHashLength)
606{
609 // Attempt to prepend fields to the hash
610 // decomp: 3 fields 1, 2, 3 => real hash [ sep, 1, 2, 3 ] => try and claim hash [ a, b, c, sep, 1, 2, 3 ]
611 std::vector<uint8_t> bytecode = random_bytes(static_cast<size_t>(31 * 3));
612 std::vector<FF> bytecode_fields = simulation::encode_bytecode(bytecode);
613
614 auto bad_hash = poseidon2.hash({ 0xa,
615 0xb,
616 0xc,
618 bytecode_fields[0],
619 bytecode_fields[1],
620 bytecode_fields[2] });
621
622 auto trace = TestTraceContainer({
623 { { C::precomputed_first_row, 1 } },
624 {
625 { C::bc_hashing_input_len, 7 },
626 { C::bc_hashing_packed_fields_0, GENERATOR_INDEX__PUBLIC_BYTECODE },
627 { C::bc_hashing_packed_fields_1, bytecode_fields[0] },
628 { C::bc_hashing_packed_fields_2, bytecode_fields[1] },
629 { C::bc_hashing_pc_index_1, 0 },
630 { C::bc_hashing_pc_index_2, 31 },
631 { C::bc_hashing_sel_not_padding_1, 1 },
632 { C::bc_hashing_sel_not_padding_2, 1 },
633 { C::bc_hashing_bytecode_id, 1 },
634 { C::bc_hashing_output_hash, bad_hash },
635 { C::bc_hashing_pc_index, 0 },
636 { C::bc_hashing_rounds_rem, 2 },
637 { C::bc_hashing_sel, 1 },
638 { C::bc_hashing_start, 1 },
639 },
640 {
641 { C::bc_hashing_input_len, 7 },
642 { C::bc_hashing_latch, 1 },
643 { C::bc_hashing_packed_fields_0, bytecode_fields[2] },
644 { C::bc_hashing_packed_fields_1, 0 },
645 { C::bc_hashing_packed_fields_2, 0 },
646 { C::bc_hashing_pc_at_final_field, 62 },
647 { C::bc_hashing_pc_index_1, 93 },
648 { C::bc_hashing_pc_index_2, 124 },
649 { C::bc_hashing_bytecode_id, 1 },
650 { C::bc_hashing_output_hash, bad_hash },
651 { C::bc_hashing_pc_index, 62 },
652 { C::bc_hashing_rounds_rem, 1 },
653 { C::bc_hashing_sel, 1 },
654 { C::bc_hashing_sel_not_start, 1 },
655 },
656 });
657
660 builder.process_decomposition(
661 { { .bytecode_id = 1, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
662
663 // The correct rows (for input chunks [sep, 1, 2] and [3, 0, 0]) will exist in the poseidon trace, but the start
664 // rows do not line up:
666 (check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_poseidon2_hash_settings>(trace)),
667 "LOOKUP_BC_HASHING_POSEIDON2_HASH");
668 // At the final row, the length check will fail:
670 "BYTECODE_LENGTH_FIELDS");
671}
672
673TEST_F(BytecodeHashingConstrainingTest, NegativeSingleBytecodeHashOutputConsistency)
674{
677 // Attempt to prepend fields to the hash
678 // decomp: 5 fields 1, 2, 3, 4, 5 => real hash [ sep, 1, 2, 3, 4, 5 ] => try and claim hash [a, b, c, 3, 4, 5]
679 std::vector<uint8_t> bytecode = random_bytes(static_cast<size_t>(31 * 5));
680 std::vector<FF> bytecode_fields = simulation::encode_bytecode(bytecode);
681
683 bytecode_fields[0],
684 bytecode_fields[1],
685 bytecode_fields[2],
686 bytecode_fields[3],
687 bytecode_fields[4] });
688 auto bad_hash = poseidon2.hash({ 0xa, 0xb, 0xc, bytecode_fields[2], bytecode_fields[3], bytecode_fields[4] });
689
690 auto trace = TestTraceContainer({
691 { { C::precomputed_first_row, 1 } },
692 {
693 { C::bc_hashing_input_len, 6 },
694 { C::bc_hashing_packed_fields_0, GENERATOR_INDEX__PUBLIC_BYTECODE },
695 { C::bc_hashing_packed_fields_1, bytecode_fields[0] },
696 { C::bc_hashing_packed_fields_2, bytecode_fields[1] },
697 { C::bc_hashing_pc_index_1, 0 },
698 { C::bc_hashing_pc_index_2, 31 },
699 { C::bc_hashing_sel_not_padding_1, 1 },
700 { C::bc_hashing_sel_not_padding_2, 1 },
701 { C::bc_hashing_bytecode_id, good_hash },
702 { C::bc_hashing_output_hash, good_hash },
703 { C::bc_hashing_pc_index, 0 },
704 { C::bc_hashing_rounds_rem, 2 },
705 { C::bc_hashing_sel, 1 },
706 { C::bc_hashing_start, 1 },
707 },
708 {
709 { C::bc_hashing_input_len, 6 },
710 { C::bc_hashing_latch, 1 },
711 { C::bc_hashing_packed_fields_0, bytecode_fields[2] },
712 { C::bc_hashing_packed_fields_1, bytecode_fields[3] },
713 { C::bc_hashing_packed_fields_2, bytecode_fields[4] },
714 { C::bc_hashing_pc_at_final_field, 124 },
715 { C::bc_hashing_pc_index_1, 93 },
716 { C::bc_hashing_pc_index_2, 124 },
717 { C::bc_hashing_sel_not_padding_1, 1 },
718 { C::bc_hashing_sel_not_padding_2, 1 },
719 { C::bc_hashing_bytecode_id, good_hash },
720 { C::bc_hashing_output_hash, bad_hash },
721 { C::bc_hashing_pc_index, 62 },
722 { C::bc_hashing_rounds_rem, 1 },
723 { C::bc_hashing_sel, 1 },
724 { C::bc_hashing_sel_not_start, 1 },
725 },
726 });
729 builder.process_decomposition(
730 { { .bytecode_id = good_hash, .bytecode = std::make_shared<std::vector<uint8_t>>(bytecode) } }, trace);
731
732 // The 'correct' rows (for input chunks [sep, 1, 2] and [3, 4, 5]) will exist in the poseidon trace, so the lookups
733 // will pass...
734 check_all_interactions<BytecodeTraceBuilder>(trace);
735 // ...but the hash check will fail:
736 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_HASH_IS_ID), "HASH_IS_ID");
737 // Changing the id to match will fail the lookup into decomposition, and the propagation check:
738 trace.set(Column::bc_hashing_bytecode_id, 2, bad_hash);
739 check_relation<bc_hashing>(trace, bc_hashing::SR_HASH_IS_ID);
741 (check_interaction<BytecodeTraceBuilder, lookup_bc_hashing_check_final_bytes_remaining_settings>(trace)),
742 "Failed.*CHECK_FINAL_BYTES_REMAINING. Could not find tuple in destination.");
743 EXPECT_THROW_WITH_MESSAGE(check_relation<bc_hashing>(trace, bc_hashing::SR_ID_PROPAGATION), "ID_PROPAGATION");
744}
745} // namespace
746} // namespace bb::avm2::constraining
#define GENERATOR_INDEX__PUBLIC_BYTECODE
#define MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS
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 constexpr size_t SR_SEL_TOGGLED_AT_LATCH
static constexpr size_t SR_PADDING_CONSISTENCY
static constexpr size_t SR_ROUNDS_DECREMENT
static constexpr size_t SR_PADDING_END
static constexpr size_t SR_PC_INCREMENTS_2
static constexpr size_t SR_START_AFTER_LATCH
static constexpr size_t SR_PADDED_BY_ZERO_2
static constexpr size_t SR_PADDING_CORRECTNESS
static constexpr size_t SR_HASH_IS_ID
static constexpr size_t SR_START_IS_SEPARATOR
static constexpr size_t SR_ID_PROPAGATION
static constexpr size_t SR_PADDED_BY_ZERO_1
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)
void process_misc(TraceContainer &trace, const uint32_t num_rows=MAX_AVM_TRACE_SIZE)
const FF & get(Column col, uint32_t row) const
void set(Column col, uint32_t row, const FF &value)
static FF hash(const std::vector< FF > &input)
Hashes a vector of field elements.
Implements a parallelized batch insertion indexed tree Accepts template argument of the type of store...
PrecomputedTraceBuilder precomputed_builder
Definition alu.test.cpp:119
AluTraceBuilder builder
Definition alu.test.cpp:123
TestTraceContainer trace
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessage)
Definition macros.hpp:7
void hash(State &state) noexcept
TEST_F(AvmRecursiveTests, GoblinRecursion)
A test of the Goblinized AVM recursive verifier.
void check_multipermutation_interaction(tracegen::TestTraceContainer &trace)
std::vector< FF > encode_bytecode(std::span< const uint8_t > bytecode)
std::vector< uint8_t > random_bytes(size_t n)
Definition fixtures.cpp:33
TestTraceContainer empty_trace()
Definition fixtures.cpp:153
std::vector< FF > random_fields(size_t n)
Definition fixtures.cpp:23
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
AvmFlavorSettings::FF FF
Definition field.hpp:10
typename Flavor::FF FF
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::vector< uint8_t > to_buffer(T const &value)