Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
calldata_hashing.test.cpp
Go to the documentation of this file.
2#include <gmock/gmock.h>
3#include <gtest/gtest.h>
4
5#include <cstdint>
6#include <memory>
7#include <vector>
8
27
28namespace bb::avm2::constraining {
29namespace {
30
31using ::testing::StrictMock;
32
34
35using simulation::EventEmitter;
36using simulation::MockExecutionIdManager;
37using simulation::MockGreaterThan;
38using simulation::Poseidon2;
39using simulation::Poseidon2HashEvent;
40using simulation::Poseidon2PermutationEvent;
41using simulation::Poseidon2PermutationMemoryEvent;
42using tracegen::CalldataTraceBuilder;
43using tracegen::Poseidon2TraceBuilder;
44using tracegen::PrecomputedTraceBuilder;
45using tracegen::TestTraceContainer;
46
48using C = Column;
49using calldata_hashing = bb::avm2::calldata_hashing<FF>;
51
52class CalldataHashingConstrainingTest : public ::testing::Test {
53 public:
54 EventEmitter<Poseidon2HashEvent> hash_event_emitter;
55 EventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
56 EventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
57
58 StrictMock<MockGreaterThan> mock_gt;
59 StrictMock<MockExecutionIdManager> mock_execution_id_manager;
60
61 Poseidon2TraceBuilder poseidon2_builder;
62 PrecomputedTraceBuilder precomputed_builder;
63 CalldataTraceBuilder builder;
64};
65
66class CalldataHashingConstrainingTestTraceHelper : public CalldataHashingConstrainingTest {
67 public:
68 TestTraceContainer process_calldata_hashing_trace(std::vector<std::vector<FF>> all_calldata_fields,
69 std::vector<uint32_t> context_ids)
70 {
71 // Note: this helper expects calldata fields without the prepended separator
74 TestTraceContainer trace({
75 { { C::precomputed_first_row, 1 } },
76 });
78 uint32_t row = 1;
79 for (uint32_t j = 0; j < all_calldata_fields.size(); j++) {
80 uint32_t index = 0;
81 auto calldata_fields = all_calldata_fields[j];
82 auto context_id = context_ids[j];
83 calldata_fields.insert(calldata_fields.begin(), GENERATOR_INDEX__PUBLIC_CALLDATA);
84 auto hash = poseidon2.hash(calldata_fields);
85 auto calldata_field_at = [&calldata_fields](size_t i) -> FF {
86 return i < calldata_fields.size() ? calldata_fields[i] : 0;
87 };
88 events.push_back({
89 .context_id = context_id,
90 .calldata_size = static_cast<uint32_t>(all_calldata_fields[j].size()),
91 .calldata = all_calldata_fields[j],
92 });
93 auto padding_amount = (3 - (calldata_fields.size() % 3)) % 3;
94 auto num_rounds = (calldata_fields.size() + padding_amount) / 3;
95 for (uint32_t i = 0; i < calldata_fields.size(); i += 3) {
96 trace.set(
97 row,
98 { {
99 { C::calldata_hashing_sel, 1 },
100 { C::calldata_hashing_start, index == 0 ? 1 : 0 },
101 { C::calldata_hashing_sel_not_start, index == 0 ? 0 : 1 },
102 { C::calldata_hashing_context_id, context_id },
103 { C::calldata_hashing_calldata_size, calldata_fields.size() - 1 },
104 { C::calldata_hashing_input_len, calldata_fields.size() },
105 { C::calldata_hashing_rounds_rem, num_rounds },
106 { C::calldata_hashing_index_0_, index },
107 { C::calldata_hashing_index_1_, index + 1 },
108 { C::calldata_hashing_index_2_, index + 2 },
109 { C::calldata_hashing_input_0_, calldata_field_at(index) },
110 { C::calldata_hashing_input_1_, calldata_field_at(index + 1) },
111 { C::calldata_hashing_input_2_, calldata_field_at(index + 2) },
112 { C::calldata_hashing_output_hash, hash },
113 { C::calldata_hashing_sel_not_padding_1, (num_rounds == 1) && (padding_amount == 2) ? 0 : 1 },
114 { C::calldata_hashing_sel_not_padding_2, (num_rounds == 1) && (padding_amount > 0) ? 0 : 1 },
115 { C::calldata_hashing_latch, (num_rounds == 1) ? 1 : 0 },
116 } });
117 row++;
118 num_rounds--;
119 index += 3;
120 }
121 }
122 builder.process_retrieval(events, trace);
123 precomputed_builder.process_misc(trace, 256);
124 poseidon2_builder.process_hash(hash_event_emitter.dump_events(), trace);
125 return trace;
126 }
127};
128
129TEST_F(CalldataHashingConstrainingTest, EmptyRow)
130{
131 check_relation<calldata_hashing>(testing::empty_trace());
132}
133
134TEST_F(CalldataHashingConstrainingTest, SingleCalldataHashOneRow)
135{
138 std::vector<FF> calldata_fields = { 1, 2 };
139
141
142 auto trace = TestTraceContainer({
143 { { C::precomputed_first_row, 1 } },
144 {
145 { C::calldata_hashing_index_1_, 1 },
146 { C::calldata_hashing_index_2_, 2 },
147 { C::calldata_hashing_input_0_, GENERATOR_INDEX__PUBLIC_CALLDATA },
148 { C::calldata_hashing_input_1_, 1 },
149 { C::calldata_hashing_input_2_, 2 },
150 { C::calldata_hashing_input_len, 3 },
151 { C::calldata_hashing_latch, 1 },
152 { C::calldata_hashing_sel_not_padding_1, 1 },
153 { C::calldata_hashing_sel_not_padding_2, 1 },
154 { C::calldata_hashing_sel_not_start, 0 },
155 { C::calldata_hashing_calldata_size, 2 },
156 { C::calldata_hashing_context_id, 1 },
157 { C::calldata_hashing_index_0_, 0 },
158 { C::calldata_hashing_output_hash, hash },
159 { C::calldata_hashing_rounds_rem, 1 },
160 { C::calldata_hashing_sel, 1 },
161 { C::calldata_hashing_start, 1 },
162 },
163 });
164
165 builder.process_retrieval({ { .context_id = 1, .calldata = calldata_fields } }, trace);
167
168 check_relation<calldata_hashing>(trace);
169 check_all_interactions<CalldataTraceBuilder>(trace);
170}
171
172TEST_F(CalldataHashingConstrainingTest, SingleCalldataHashOneElt)
173{
176 std::vector<FF> calldata_fields = { 2 };
177
179
180 auto trace = TestTraceContainer({
181 { { C::precomputed_first_row, 1 } },
182 {
183 { C::calldata_hashing_index_1_, 1 },
184 { C::calldata_hashing_index_2_, 2 },
185 { C::calldata_hashing_input_0_, GENERATOR_INDEX__PUBLIC_CALLDATA },
186 { C::calldata_hashing_input_1_, 2 },
187 { C::calldata_hashing_input_2_, 0 },
188 { C::calldata_hashing_input_len, 2 },
189 { C::calldata_hashing_latch, 1 },
190 { C::calldata_hashing_sel_not_padding_1, 1 },
191 { C::calldata_hashing_sel_not_padding_2, 0 },
192 { C::calldata_hashing_sel_not_start, 0 },
193 { C::calldata_hashing_calldata_size, 1 },
194 { C::calldata_hashing_context_id, 1 },
195 { C::calldata_hashing_index_0_, 0 },
196 { C::calldata_hashing_output_hash, hash },
197 { C::calldata_hashing_rounds_rem, 1 },
198 { C::calldata_hashing_sel, 1 },
199 { C::calldata_hashing_start, 1 },
200 },
201 });
202
203 builder.process_retrieval({ { .context_id = 1, .calldata = calldata_fields } }, trace);
205
206 check_relation<calldata_hashing>(trace);
207 check_all_interactions<CalldataTraceBuilder>(trace);
208}
209
210TEST_F(CalldataHashingConstrainingTest, EmptyCalldataHash)
211{
214 std::vector<FF> calldata_fields = {};
215
217
218 auto trace = TestTraceContainer({
219 { { C::precomputed_first_row, 1 } },
220 {
221 { C::calldata_hashing_index_1_, 1 },
222 { C::calldata_hashing_index_2_, 2 },
223 { C::calldata_hashing_input_0_, GENERATOR_INDEX__PUBLIC_CALLDATA },
224 { C::calldata_hashing_input_1_, 0 },
225 { C::calldata_hashing_input_2_, 0 },
226 { C::calldata_hashing_input_len, 1 },
227 { C::calldata_hashing_latch, 1 },
228 { C::calldata_hashing_sel_not_padding_1, 0 },
229 { C::calldata_hashing_sel_not_padding_2, 0 },
230 { C::calldata_hashing_sel_not_start, 0 },
231 { C::calldata_hashing_calldata_size, 0 },
232 { C::calldata_hashing_context_id, 1 },
233 { C::calldata_hashing_index_0_, 0 },
234 { C::calldata_hashing_output_hash, hash },
235 { C::calldata_hashing_rounds_rem, 1 },
236 { C::calldata_hashing_sel, 1 },
237 { C::calldata_hashing_start, 1 },
238 },
239 });
240
241 builder.process_retrieval({ { .context_id = 1, .calldata = calldata_fields } }, trace);
243
244 check_relation<calldata_hashing>(trace);
245 check_all_interactions<CalldataTraceBuilder>(trace);
246}
247
248TEST_F(CalldataHashingConstrainingTestTraceHelper, EmptyCalldataHash)
249{
250 TestTraceContainer trace = process_calldata_hashing_trace({}, { 1 });
251
252 check_relation<calldata_hashing>(trace);
253 check_all_interactions<CalldataTraceBuilder>(trace);
254}
255
256TEST_F(CalldataHashingConstrainingTestTraceHelper, SingleCalldataHash100Fields)
257{
258 // The hardcoded value is taken from noir-projects/aztec-nr/aztec/src/hash.nr:
259 FF hash = FF("0x191383c9f8964afd3ea8879a03b7dda65d6724773966d18dcf80e452736fc1f3");
260
261 std::vector<FF> calldata_fields = {};
262 calldata_fields.reserve(100);
263 for (uint32_t i = 0; i < 100; i++) {
264 calldata_fields.push_back(FF(i));
265 }
266
267 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
268
269 check_relation<calldata_hashing>(trace);
270 check_all_interactions<CalldataTraceBuilder>(trace);
271 EXPECT_EQ(trace.get(C::calldata_hashing_output_hash, 1), hash);
272}
273
274TEST_F(CalldataHashingConstrainingTestTraceHelper, MultipleCalldataHash)
275{
276 // 50 calldata fields => hash 51 fields, no padding on 17th row
277 // 100 calldata fields => hash 101 fields, one padding field on 34th row
278 // 300 calldata fields => hash 301 fields, two padding fields on 101st row
279 std::vector<std::vector<FF>> all_calldata_fields = { random_fields(50), random_fields(100), random_fields(300) };
280
281 TestTraceContainer trace = process_calldata_hashing_trace(all_calldata_fields, { 1, 2, 3 });
282
283 check_relation<calldata_hashing>(trace);
284 check_all_interactions<CalldataTraceBuilder>(trace);
285 uint32_t latch_row = 17;
286 // First calldata:
287 EXPECT_EQ(trace.get(C::calldata_hashing_latch, latch_row), 1);
288 EXPECT_EQ(trace.get(C::calldata_hashing_sel_not_padding_2, latch_row), 1);
289 // Second calldata:
290 latch_row += 34;
291 EXPECT_EQ(trace.get(C::calldata_hashing_latch, latch_row), 1);
292 EXPECT_EQ(trace.get(C::calldata_hashing_sel_not_padding_2, latch_row), 0);
293 EXPECT_EQ(trace.get(C::calldata_hashing_sel_not_padding_1, latch_row), 1);
294 // Third calldata:
295 latch_row += 101;
296 EXPECT_EQ(trace.get(C::calldata_hashing_latch, latch_row), 1);
297 EXPECT_EQ(trace.get(C::calldata_hashing_sel_not_padding_2, latch_row), 0);
298 EXPECT_EQ(trace.get(C::calldata_hashing_sel_not_padding_1, latch_row), 0);
299}
300
301// Negative test where latch == 1 and sel == 0
302TEST_F(CalldataHashingConstrainingTest, NegativeLatchNotSel)
303{
304 TestTraceContainer trace(
305 { { { C::precomputed_first_row, 1 } }, { { C::calldata_hashing_latch, 1 }, { C::calldata_hashing_sel, 1 } } });
306
307 check_relation<calldata_hashing>(trace, calldata_hashing::SR_SEL_TOGGLED_AT_LATCH);
308 trace.set(C::calldata_hashing_sel, 1, 0); // Mutate to wrong value
310 "SEL_TOGGLED_AT_LATCH");
311 // Same idea for calldata trace:
312 trace.set(1,
313 { {
314 { C::calldata_latch, 1 },
315 { C::calldata_sel, 1 },
316 } });
317
318 check_relation<bb::avm2::calldata<FF>>(trace, bb::avm2::calldata<FF>::SR_SEL_TOGGLED_AT_LATCH);
319 trace.set(C::calldata_sel, 1, 0); // Mutate to wrong value
322 "SEL_TOGGLED_AT_LATCH");
323}
324
325TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeInvalidStartAfterLatch)
326{
327 // Process two calldata instances:
328 TestTraceContainer trace = process_calldata_hashing_trace({ random_fields(2), random_fields(3) }, { 1, 2 });
329 check_relation<calldata_hashing>(trace);
330
331 // Row = 1 is the start of the hashing for calldata with context_id = 1
332 trace.set(Column::calldata_hashing_start, 1, 0);
334 "START_AFTER_LATCH");
335 trace.set(Column::calldata_hashing_start, 1, 1);
336
337 // Row = 2 is the start of the hashing for calldata with context_id = 2
338 trace.set(Column::calldata_hashing_start, 2, 0);
340 "START_AFTER_LATCH");
341}
342
343TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeInvalidStartIndex)
344{
345 TestTraceContainer trace = process_calldata_hashing_trace({ random_fields(10) }, { 1 });
346 check_relation<calldata_hashing>(trace);
347
348 // Row = 1 is the start of the hashing for calldata with context_id = 1
349 trace.set(Column::calldata_hashing_index_0_, 1, 5);
351 "START_INDEX_IS_ZERO");
352}
353
354TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeStartIsSeparator)
355{
356 TestTraceContainer trace = process_calldata_hashing_trace({ random_fields(10) }, { 1 });
357 check_relation<calldata_hashing>(trace);
358
359 // Row = 1 is the start of the hashing for calldata with context_id = 1
360 trace.set(Column::calldata_hashing_input_0_, 1, 5);
362 "START_IS_SEPARATOR");
363}
364
365TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeInvalidIndexIncrements)
366{
367 TestTraceContainer trace = process_calldata_hashing_trace({ random_fields(10) }, { 1 });
368 check_relation<calldata_hashing>(trace);
369
370 // First row should have indices 0, 1, and 2
371 trace.set(Column::calldata_hashing_index_1_, 1, 2);
373 "INDEX_INCREMENTS_1");
374 trace.set(Column::calldata_hashing_index_1_, 1, 1);
375 trace.set(Column::calldata_hashing_index_2_, 1, 3);
377 "INDEX_INCREMENTS_2");
378 trace.set(Column::calldata_hashing_index_2_, 1, 2);
379 // Second row should have indices 3, 4, and 5
380 trace.set(Column::calldata_hashing_index_0_, 2, 2);
382 "INDEX_INCREMENTS");
383}
384
385TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeConsistency)
386{
387 std::vector<FF> calldata_fields = random_fields(10);
388 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
389 check_relation<calldata_hashing>(trace);
390
391 // Rows 1 and 2 should deal with the same calldata:
392 trace.set(Column::calldata_hashing_context_id, 2, 2);
394 "ID_CONSISTENCY");
395 trace.set(Column::calldata_hashing_context_id, 2, 1);
396
397 trace.set(Column::calldata_hashing_output_hash, 2, 2);
399 "HASH_CONSISTENCY");
400 trace.set(Column::calldata_hashing_output_hash, 2, trace.get(Column::calldata_hashing_output_hash, 1));
401
402 trace.set(Column::calldata_hashing_calldata_size, 2, 2);
404 "SIZE_CONSISTENCY");
405 trace.set(Column::calldata_hashing_calldata_size, 2, 10);
406
407 // We don't directly constrain the consistency of input_len directly, but we do constrain input_len == size + 1:
408 trace.set(Column::calldata_hashing_input_len, 1, 2);
410 check_relation<calldata_hashing>(trace, calldata_hashing::SR_CALLDATA_HASH_INPUT_LENGTH_FIELDS),
411 "CALLDATA_HASH_INPUT_LENGTH_FIELDS");
412}
413
414TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeCalldataInteraction)
415{
416 std::vector<FF> calldata_fields = random_fields(10);
417 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
418 check_all_interactions<CalldataTraceBuilder>(trace);
419
420 // Row = 2 constrains the hashing for fields at calldata.pil indices 3, 4, and 5
421 // Modify the index for the lookup of the first field of row 2 (= calldata_fields[2])
422 trace.set(Column::calldata_hashing_index_0_, 2, 0);
424 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_get_calldata_field_0_settings>(trace)),
425 "Failed.*GET_CALLDATA_FIELD_0. Could not find tuple in destination.");
426
427 // Modify the field value for the lookup of the second field of row 2 (= calldata_fields[3])
428 trace.set(Column::calldata_hashing_input_1_, 2, 0);
430 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_get_calldata_field_1_settings>(trace)),
431 "Failed.*GET_CALLDATA_FIELD_1. Could not find tuple in destination.");
432
433 // Modify the context id and attempt to lookup of the third field of row 2 (= calldata_fields[4])
434 trace.set(Column::calldata_hashing_context_id, 2, 0);
436 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_get_calldata_field_2_settings>(trace)),
437 "Failed.*GET_CALLDATA_FIELD_2. Could not find tuple in destination.");
438}
439
440TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativePaddingSelectors)
441{
442 // 9 calldata fields => hash 10 fields => two padding fields
443 std::vector<FF> calldata_fields = random_fields(9);
444 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
445 check_relation<calldata_hashing>(trace);
446 check_all_interactions<CalldataTraceBuilder>(trace);
447
448 // We cannot have padding anywhere but the last hashing row (= latch). Set padding to true on row 2:
449 trace.set(Column::calldata_hashing_sel_not_padding_2, 2, 0);
450 EXPECT_THROW_WITH_MESSAGE(check_relation<calldata_hashing>(trace, calldata_hashing::SR_PADDING_END), "PADDING_END");
451 trace.set(Column::calldata_hashing_sel_not_padding_2, 2, 1);
452
453 // We cannot have input[1] is set as padding, but input[2] is not (row 4 is the final row for this calldata hash):
454 trace.set(Column::calldata_hashing_sel_not_padding_2, 4, 1);
456 "PADDING_CONSISTENCY");
457 trace.set(Column::calldata_hashing_sel_not_padding_2, 4, 0);
458
459 // We cannot have any padding with non-zero values:
460 trace.set(Column::calldata_hashing_input_1_, 4, 1);
462 "PADDED_BY_ZERO_1");
463 trace.set(Column::calldata_hashing_input_2_, 4, 1);
465 "PADDED_BY_ZERO_2");
466}
467
468TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativePaddingUnder)
469{
470 // 9 calldata fields => hash 10 fields => two padding fields
471 // Attempt to underpad and insert an incorrect value at the end of the calldata
472 std::vector<FF> calldata_fields = random_fields(9);
473 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
474 check_relation<calldata_hashing>(trace);
475 check_all_interactions<CalldataTraceBuilder>(trace);
476
477 // Row = 4 constrains the hashing for the last field of the calldata, plus 2 padding fields
478 // We cannot claim there is only one padding field:
479 trace.set(Column::calldata_hashing_sel_not_padding_1, 4, 1);
480 // This will initially fail, because calldata_size = 9 = index[0] of row 4:
482 "CHECK_FINAL_INDEX");
483 // calldata_size is constrained to be consistent every row, and to be equal to input_len - 1:
484 for (uint32_t j = 1; j <= 4; j++) {
485 trace.set(Column::calldata_hashing_calldata_size, j, 10);
486 trace.set(Column::calldata_hashing_input_len, j, 11);
487 // poseidon's input_len is only constrained at start:
488 trace.set(Column::poseidon2_hash_input_len, j, 11);
489 }
490 // Now all relations pass...
491 check_relation<calldata_hashing>(trace);
492 // ...but the lookup to find field 1 will fail...
494 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_get_calldata_field_1_settings>(trace)),
495 "Failed.*GET_CALLDATA_FIELD_1. Could not find tuple in destination.");
496 // ...as will the lookup in the final row to check the calldata size against the index:
498 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_check_final_size_settings>(trace)),
499 "Failed.*CHECK_FINAL_SIZE. Could not find tuple in destination.");
500}
501
502TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativePaddingOver)
503{
504 // 8 calldata fields => hash 9 fields => no padding fields
505 // Attempt to overpad and omit a value at the end of the calldata
506 std::vector<FF> calldata_fields = random_fields(8);
507 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
508 check_relation<calldata_hashing>(trace);
509 check_all_interactions<CalldataTraceBuilder>(trace);
510
511 // Row = 3 constrains the hashing for the last field of the calldata
512 // We cannot claim there is any padding (to attempt to skip processing the last calldata field):
513 trace.set(Column::calldata_hashing_sel_not_padding_2, 3, 0);
514 // Since the value is non zero, and padding values must equal zero:
516 "PADDED_BY_ZERO_2");
517 // If we set the value to zero...
518 trace.set(Column::calldata_hashing_input_2_, 3, 0);
519 // ...and again fiddle with the calldata sizing:
520 for (uint32_t j = 1; j <= 3; j++) {
521 trace.set(Column::calldata_hashing_calldata_size, j, 7);
522 trace.set(Column::calldata_hashing_input_len, j, 8);
523 // poseidon's input_len is only constrained at start:
524 trace.set(Column::poseidon2_hash_input_len, j, 8);
525 }
526 // Now all relations pass...
527 check_relation<calldata_hashing>(trace);
528 // ...but the lookup in the final row to check the calldata size against the index will fail:
530 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_check_final_size_settings>(trace)),
531 "Failed.*CHECK_FINAL_SIZE. Could not find tuple in destination.");
532}
533
534TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeInputLen)
535{
536 // 8 calldata fields => hash 9 fields => no padding fields
537 // Attempt to set an incorrect input_len (and => IV value)
538 std::vector<FF> calldata_fields = random_fields(8);
539 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
540 check_relation<calldata_hashing>(trace);
541 check_all_interactions<CalldataTraceBuilder>(trace);
542
543 // Set the incorrect input_len at the first row, and the lookup into poseidon will fail:
544 trace.set(Column::calldata_hashing_input_len, 1, 0);
546 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_poseidon2_hash_settings>(trace)),
547 "Failed.*LOOKUP_CALLDATA_HASHING_POSEIDON2_HASH. Could not find tuple in destination.");
548
549 trace.set(Column::calldata_hashing_input_len, 1, 9);
550 // Set the incorrect input_len at any row, and the relation against calldata_size will fail:
551 trace.set(Column::calldata_hashing_input_len, 2, 4);
553 check_relation<calldata_hashing>(trace, calldata_hashing::SR_CALLDATA_HASH_INPUT_LENGTH_FIELDS),
554 "CALLDATA_HASH_INPUT_LENGTH_FIELDS");
555 // If we force calldata_size to be the incorrect input_len - 1, its consistency across rows will fail:
556 trace.set(Column::calldata_hashing_calldata_size, 2, 3);
558 "SIZE_CONSISTENCY");
559 // We can force all relations to pass by maintaining consistency of incorrect values:
560 for (uint32_t j = 1; j <= 3; j++) {
561 trace.set(Column::calldata_hashing_calldata_size, j, 7);
562 trace.set(Column::calldata_hashing_input_len, j, 8);
563 // poseidon's input_len is only constrained at start:
564 trace.set(Column::poseidon2_hash_input_len, j, 8);
565 }
566 // And setting the correct padding for an input_len of 8:
567 trace.set(Column::calldata_hashing_sel_not_padding_2, 3, 0);
568 trace.set(Column::calldata_hashing_input_2_, 3, 0);
569 check_relation<calldata_hashing>(trace);
570 // ...but the lookup in the final row to check the calldata size against the index will fail:
572 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_check_final_size_settings>(trace)),
573 "Failed.*CHECK_FINAL_SIZE. Could not find tuple in destination.");
574}
575
576TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeRounds)
577{
578 std::vector<FF> calldata_fields = random_fields(8);
579 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
580 check_relation<calldata_hashing>(trace);
581 check_all_interactions<CalldataTraceBuilder>(trace);
582
583 // Set the incorrect rounds_rem (should be 3 at row 1)
584 trace.set(Column::calldata_hashing_rounds_rem, 1, 1);
586 "ROUNDS_DECREMENT");
587}
588
589TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeOutputHash)
590{
591 Poseidon2 poseidon2_int =
593 std::vector<FF> calldata_fields = random_fields(5);
594
595 // Prepare a good trace for calldata hashing (minus final hash):
596 auto trace = TestTraceContainer({
597 { { C::precomputed_first_row, 1 } },
598 {
599 { C::calldata_hashing_index_1_, 1 },
600 { C::calldata_hashing_index_2_, 2 },
601 { C::calldata_hashing_input_0_, GENERATOR_INDEX__PUBLIC_CALLDATA },
602 { C::calldata_hashing_input_1_, calldata_fields[0] },
603 { C::calldata_hashing_input_2_, calldata_fields[1] },
604 { C::calldata_hashing_input_len, 6 },
605 { C::calldata_hashing_latch, 0 },
606 { C::calldata_hashing_sel_not_padding_1, 1 },
607 { C::calldata_hashing_sel_not_padding_2, 1 },
608 { C::calldata_hashing_sel_not_start, 0 },
609 { C::calldata_hashing_calldata_size, 5 },
610 { C::calldata_hashing_context_id, 1 },
611 { C::calldata_hashing_index_0_, 0 },
612 { C::calldata_hashing_rounds_rem, 2 },
613 { C::calldata_hashing_sel, 1 },
614 { C::calldata_hashing_start, 1 },
615 },
616 {
617 { C::calldata_hashing_index_1_, 4 },
618 { C::calldata_hashing_index_2_, 5 },
619 { C::calldata_hashing_input_0_, calldata_fields[2] },
620 { C::calldata_hashing_input_1_, calldata_fields[3] },
621 { C::calldata_hashing_input_2_, calldata_fields[4] },
622 { C::calldata_hashing_input_len, 6 },
623 { C::calldata_hashing_latch, 1 },
624 { C::calldata_hashing_sel_not_padding_1, 1 },
625 { C::calldata_hashing_sel_not_padding_2, 1 },
626 { C::calldata_hashing_sel_not_start, 1 },
627 { C::calldata_hashing_calldata_size, 5 },
628 { C::calldata_hashing_context_id, 1 },
629 { C::calldata_hashing_index_0_, 3 },
630 { C::calldata_hashing_rounds_rem, 1 },
631 { C::calldata_hashing_sel, 1 },
632 },
633 });
634
635 builder.process_retrieval({ { .context_id = 1, .calldata = calldata_fields } }, trace);
636 // Set the correct hash...
637 auto good_hash = poseidon2_int.hash({
639 calldata_fields[0],
640 calldata_fields[1],
641 calldata_fields[2],
642 calldata_fields[3],
643 calldata_fields[4],
644 });
645 // ...and an incorrect hash with a matching row at latch = 1:
646 auto bad_hash = poseidon2_int.hash({
647 0xa,
648 0xb,
649 0xc,
650 calldata_fields[2],
651 calldata_fields[3],
652 calldata_fields[4],
653 });
655 trace.set(Column::calldata_hashing_output_hash, 1, good_hash);
656 // Set the incorrect hash to latch:
657 trace.set(Column::calldata_hashing_output_hash, 2, bad_hash);
658 // All lookups will pass (i.e. we successfully lookup a bad row in the poseidon trace)...
659 check_all_interactions<CalldataTraceBuilder>(trace);
660 // ...but since we constrain that the hash remains consistent, the relations fail:
662 "HASH_CONSISTENCY");
663}
664
665TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativePoseidonInteraction)
666{
667 Poseidon2 poseidon2_int =
669 std::vector<FF> calldata_fields = random_fields(10);
670
671 // Prepare a good trace for calldata hashing (minus final hash):
672 auto trace = TestTraceContainer({
673 { { C::precomputed_first_row, 1 } },
674 {
675 { C::calldata_hashing_index_1_, 1 },
676 { C::calldata_hashing_index_2_, 2 },
677 { C::calldata_hashing_input_0_, GENERATOR_INDEX__PUBLIC_CALLDATA },
678 { C::calldata_hashing_input_1_, calldata_fields[0] },
679 { C::calldata_hashing_input_2_, calldata_fields[1] },
680 { C::calldata_hashing_input_len, 11 },
681 { C::calldata_hashing_latch, 0 },
682 { C::calldata_hashing_sel_not_padding_1, 1 },
683 { C::calldata_hashing_sel_not_padding_2, 1 },
684 { C::calldata_hashing_sel_not_start, 0 },
685 { C::calldata_hashing_calldata_size, 10 },
686 { C::calldata_hashing_context_id, 1 },
687 { C::calldata_hashing_index_0_, 0 },
688 { C::calldata_hashing_rounds_rem, 4 },
689 { C::calldata_hashing_sel, 1 },
690 { C::calldata_hashing_start, 1 },
691 },
692 {
693 { C::calldata_hashing_index_1_, 4 },
694 { C::calldata_hashing_index_2_, 5 },
695 { C::calldata_hashing_input_0_, calldata_fields[2] },
696 { C::calldata_hashing_input_1_, calldata_fields[3] },
697 { C::calldata_hashing_input_2_, calldata_fields[4] },
698 { C::calldata_hashing_input_len, 11 },
699 { C::calldata_hashing_latch, 0 },
700 { C::calldata_hashing_sel_not_padding_1, 1 },
701 { C::calldata_hashing_sel_not_padding_2, 1 },
702 { C::calldata_hashing_sel_not_start, 1 },
703 { C::calldata_hashing_calldata_size, 10 },
704 { C::calldata_hashing_context_id, 1 },
705 { C::calldata_hashing_index_0_, 3 },
706 { C::calldata_hashing_rounds_rem, 3 },
707 { C::calldata_hashing_sel, 1 },
708 },
709 {
710 { C::calldata_hashing_index_1_, 7 },
711 { C::calldata_hashing_index_2_, 8 },
712 { C::calldata_hashing_input_0_, calldata_fields[5] },
713 { C::calldata_hashing_input_1_, calldata_fields[6] },
714 { C::calldata_hashing_input_2_, calldata_fields[7] },
715 { C::calldata_hashing_input_len, 11 },
716 { C::calldata_hashing_latch, 0 },
717 { C::calldata_hashing_sel_not_padding_1, 1 },
718 { C::calldata_hashing_sel_not_padding_2, 1 },
719 { C::calldata_hashing_sel_not_start, 1 },
720 { C::calldata_hashing_calldata_size, 10 },
721 { C::calldata_hashing_context_id, 1 },
722 { C::calldata_hashing_index_0_, 6 },
723 { C::calldata_hashing_rounds_rem, 2 },
724 { C::calldata_hashing_sel, 1 },
725 },
726 {
727 { C::calldata_hashing_index_1_, 10 },
728 { C::calldata_hashing_index_2_, 11 },
729 { C::calldata_hashing_input_0_, calldata_fields[8] },
730 { C::calldata_hashing_input_1_, calldata_fields[9] },
731 { C::calldata_hashing_input_2_, 0 },
732 { C::calldata_hashing_input_len, 11 },
733 { C::calldata_hashing_latch, 1 },
734 { C::calldata_hashing_sel_not_padding_1, 1 },
735 { C::calldata_hashing_sel_not_padding_2, 0 },
736 { C::calldata_hashing_sel_not_start, 1 },
737 { C::calldata_hashing_calldata_size, 10 },
738 { C::calldata_hashing_context_id, 1 },
739 { C::calldata_hashing_index_0_, 9 },
740 { C::calldata_hashing_rounds_rem, 1 },
741 { C::calldata_hashing_sel, 1 },
742 },
743 });
744
745 builder.process_retrieval({ { .context_id = 1, .calldata = calldata_fields } }, trace);
746
747 auto bad_hash_prepended = poseidon2_int.hash({
748 0xa,
749 0xb,
750 0xc,
752 calldata_fields[0],
753 calldata_fields[1],
754 calldata_fields[2],
755 calldata_fields[3],
756 calldata_fields[4],
757 calldata_fields[5],
758 calldata_fields[6],
759 calldata_fields[7],
760 calldata_fields[8],
761 calldata_fields[9],
762 });
763 auto bad_hash_misordered = poseidon2_int.hash({
765 calldata_fields[0],
766 calldata_fields[1],
767 calldata_fields[5],
768 calldata_fields[6],
769 calldata_fields[7],
770 calldata_fields[2],
771 calldata_fields[3],
772 calldata_fields[4],
773 calldata_fields[8],
774 calldata_fields[9],
775 });
777 check_relation<poseidon2>(trace);
778 for (uint32_t j = 1; j <= 4; j++) {
779 trace.set(Column::calldata_hashing_output_hash, j, bad_hash_prepended);
780 }
781 // All relations will pass, and all input values exist in the poseidon trace, but since we constrain the
782 // start rows must match, the below fails at row 1:
783 check_relation<calldata_hashing>(trace);
784 EXPECT_THROW_WITH_MESSAGE((check_all_interactions<CalldataTraceBuilder>(trace)),
785 "Failed.*LOOKUP_CALLDATA_HASHING_POSEIDON2_HASH. .*row 1");
786
787 for (uint32_t j = 1; j <= 4; j++) {
788 trace.set(Column::calldata_hashing_output_hash, j, bad_hash_misordered);
789 }
790 // Again all relations will pass, but the lookup will fail at row 2 since the rounds_rem mismatch:
791 check_relation<calldata_hashing>(trace);
792 EXPECT_THROW_WITH_MESSAGE((check_all_interactions<CalldataTraceBuilder>(trace)),
793 "Failed.*LOOKUP_CALLDATA_HASHING_POSEIDON2_HASH. .*row 2");
794
795 // If we try and manipulate the input_len so rounds_rem does match...
796 trace.set(Column::calldata_hashing_rounds_rem, 2, 2);
797 trace.set(Column::calldata_hashing_calldata_size, 2, 8);
798 trace.set(Column::calldata_hashing_input_len, 2, 9);
799 // (Shift by 5 for previous hash test:)
800 trace.set(Column::poseidon2_hash_input_len, 3 + 5, 9);
801 trace.set(Column::calldata_hashing_rounds_rem, 3, 3);
802 trace.set(Column::calldata_hashing_calldata_size, 3, 12);
803 trace.set(Column::calldata_hashing_input_len, 3, 13);
804 // (Shift by 5 for previous hash test:)
805 trace.set(Column::poseidon2_hash_input_len, 2 + 5, 13);
806 // ...the poseidon trace will pass (since input_len is only constrained at start)...
807 check_relation<poseidon2>(trace);
808 // ...all lookups will pass...
809 check_all_interactions<CalldataTraceBuilder>(trace);
810 // ...but we protect against input_len manipulation with a consistency check, which would ensure incorrect values
811 // fail at latch:
813 "SIZE_CONSISTENCY");
814}
815
816} // namespace
817} // namespace bb::avm2::constraining
#define GENERATOR_INDEX__PUBLIC_CALLDATA
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_PADDING_END
static constexpr size_t SR_PADDING_CONSISTENCY
static constexpr size_t SR_START_AFTER_LATCH
static constexpr size_t SR_START_IS_SEPARATOR
static constexpr size_t SR_ID_CONSISTENCY
static constexpr size_t SR_ROUNDS_DECREMENT
static constexpr size_t SR_CHECK_FINAL_INDEX
static constexpr size_t SR_SIZE_CONSISTENCY
static constexpr size_t SR_PADDED_BY_ZERO_2
static constexpr size_t SR_INDEX_INCREMENTS_2
static constexpr size_t SR_INDEX_INCREMENTS
static constexpr size_t SR_PADDED_BY_ZERO_1
static constexpr size_t SR_SEL_TOGGLED_AT_LATCH
static constexpr size_t SR_CALLDATA_HASH_INPUT_LENGTH_FIELDS
static constexpr size_t SR_HASH_CONSISTENCY
static constexpr size_t SR_INDEX_INCREMENTS_1
static constexpr size_t SR_START_INDEX_IS_ZERO
void process_hash(const simulation::EventEmitterInterface< simulation::Poseidon2HashEvent >::Container &hash_events, TraceContainer &trace)
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_relation(const tracegen::TestTraceContainer &trace, Ts... subrelation)
TestTraceContainer empty_trace()
Definition fixtures.cpp:153
std::vector< FF > random_fields(size_t n)
Definition fixtures.cpp:23
AvmFlavorSettings::FF FF
Definition field.hpp:10
typename Flavor::FF FF
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint32_t context_id
std::vector< FF > calldata