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