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