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
9#include "barretenberg/aztec/aztec_constants.hpp"
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/labs/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),
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
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),
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),
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),
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),
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),
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),
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),
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),
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),
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),
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),
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),
456 trace.set(Column::calldata_hashing_sel_not_padding_2, 2, 1);
457
458 // We cannot have input[1] is set as padding, but input[2] is not (row 4 is the final row for this calldata hash):
459 trace.set(Column::calldata_hashing_sel_not_padding_2, 4, 1);
460 EXPECT_THROW_WITH_MESSAGE(check_relation<calldata_hashing>(trace, calldata_hashing::SR_PADDING_CONSISTENCY),
462 trace.set(Column::calldata_hashing_sel_not_padding_2, 4, 0);
463
464 // We cannot have any padding with non-zero values:
465 trace.set(Column::calldata_hashing_input_1_, 4, 1);
466 EXPECT_THROW_WITH_MESSAGE(check_relation<calldata_hashing>(trace, calldata_hashing::SR_PADDED_BY_ZERO_1),
468 trace.set(Column::calldata_hashing_input_2_, 4, 1);
469 EXPECT_THROW_WITH_MESSAGE(check_relation<calldata_hashing>(trace, calldata_hashing::SR_PADDED_BY_ZERO_2),
471}
472
473TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativePaddingUnder)
474{
475 // 9 calldata fields => hash 10 fields => two padding fields
476 // Attempt to underpad and insert an incorrect value at the end of the calldata
477 std::vector<FF> calldata_fields = random_fields(9);
478 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
479 check_relation<calldata_hashing>(trace);
480 check_all_interactions<CalldataTraceBuilder>(trace);
481
482 // Row = 4 constrains the hashing for the last field of the calldata, plus 2 padding fields
483 // We cannot claim there is only one padding field:
484 trace.set(Column::calldata_hashing_sel_not_padding_1, 4, 1);
485 // This will initially fail, because calldata_size = 9 = index[0] of row 4:
486 EXPECT_THROW_WITH_MESSAGE(check_relation<calldata_hashing>(trace, calldata_hashing::SR_CHECK_FINAL_INDEX),
488 // calldata_size is constrained to be consistent every row, and to be equal to input_len - 1:
489 for (uint32_t j = 1; j <= 4; j++) {
490 trace.set(Column::calldata_hashing_calldata_size, j, 10);
491 trace.set(Column::calldata_hashing_input_len, j, 11);
492 // poseidon's input_len is only constrained at start:
493 trace.set(Column::poseidon2_hash_input_len, j, 11);
494 }
495 // Now all relations pass...
496 check_relation<calldata_hashing>(trace);
497 // ...but the lookup to find field 1 will fail...
499 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_get_calldata_field_1_settings>(trace)),
500 "Failed.*GET_CALLDATA_FIELD_1. Could not find tuple in destination.");
501 // ...as will the permutation in the final row to check the calldata size against the index:
503 (check_interaction<CalldataTraceBuilder, perm_calldata_hashing_check_final_size_settings>(trace)),
504 "Failure to build permutation.*CHECK_FINAL_SIZE");
505}
506
507TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativePaddingOver)
508{
509 // 8 calldata fields => hash 9 fields => no padding fields
510 // Attempt to overpad and omit a value at the end of the calldata
511 std::vector<FF> calldata_fields = random_fields(8);
512 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
513 check_relation<calldata_hashing>(trace);
514 check_all_interactions<CalldataTraceBuilder>(trace);
515
516 // Row = 3 constrains the hashing for the last field of the calldata
517 // We cannot claim there is any padding (to attempt to skip processing the last calldata field):
518 trace.set(Column::calldata_hashing_sel_not_padding_2, 3, 0);
519 // Since the value is non zero, and padding values must equal zero:
520 EXPECT_THROW_WITH_MESSAGE(check_relation<calldata_hashing>(trace, calldata_hashing::SR_PADDED_BY_ZERO_2),
522 // If we set the value to zero...
523 trace.set(Column::calldata_hashing_input_2_, 3, 0);
524 // ...and again fiddle with the calldata sizing:
525 for (uint32_t j = 1; j <= 3; j++) {
526 trace.set(Column::calldata_hashing_calldata_size, j, 7);
527 trace.set(Column::calldata_hashing_input_len, j, 8);
528 // poseidon's input_len is only constrained at start:
529 trace.set(Column::poseidon2_hash_input_len, j, 8);
530 }
531 // Now all relations pass...
532 check_relation<calldata_hashing>(trace);
533 // ...but the permutation in the final row to check the calldata size against the index will fail:
535 (check_interaction<CalldataTraceBuilder, perm_calldata_hashing_check_final_size_settings>(trace)),
536 "Failure to build permutation.*CHECK_FINAL_SIZE");
537}
538
539TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeInputLen)
540{
541 // 8 calldata fields => hash 9 fields => no padding fields
542 // Attempt to set an incorrect input_len (and => IV value)
543 std::vector<FF> calldata_fields = random_fields(8);
544 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
545 check_relation<calldata_hashing>(trace);
546 check_all_interactions<CalldataTraceBuilder>(trace);
547
548 // Set the incorrect input_len at the first row, and the lookup into poseidon will fail:
549 trace.set(Column::calldata_hashing_input_len, 1, 0);
551 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_poseidon2_hash_settings>(trace)),
552 "Failed.*LOOKUP_CALLDATA_HASHING_POSEIDON2_HASH. Could not find tuple in destination.");
553
554 trace.set(Column::calldata_hashing_input_len, 1, 9);
555 // Set the incorrect input_len at any row, and the relation against calldata_size will fail:
556 trace.set(Column::calldata_hashing_input_len, 2, 4);
558 check_relation<calldata_hashing>(trace, calldata_hashing::SR_CALLDATA_HASH_INPUT_LENGTH_FIELDS),
560 // If we force calldata_size to be the incorrect input_len - 1, its consistency across rows will fail:
561 trace.set(Column::calldata_hashing_calldata_size, 2, 3);
562 EXPECT_THROW_WITH_MESSAGE(check_relation<calldata_hashing>(trace, calldata_hashing::SR_SIZE_CONSISTENCY),
564 // We can force all relations to pass by maintaining consistency of incorrect values:
565 for (uint32_t j = 1; j <= 3; j++) {
566 trace.set(Column::calldata_hashing_calldata_size, j, 7);
567 trace.set(Column::calldata_hashing_input_len, j, 8);
568 // poseidon's input_len is only constrained at start:
569 trace.set(Column::poseidon2_hash_input_len, j, 8);
570 }
571 // And setting the correct padding for an input_len of 8:
572 trace.set(Column::calldata_hashing_sel_not_padding_2, 3, 0);
573 trace.set(Column::calldata_hashing_input_2_, 3, 0);
574 check_relation<calldata_hashing>(trace);
575 // ...but the permutation in the final row to check the calldata size against the index will fail:
577 (check_interaction<CalldataTraceBuilder, perm_calldata_hashing_check_final_size_settings>(trace)),
578 "Failure to build permutation.*CHECK_FINAL_SIZE");
579}
580
581TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeRounds)
582{
583 std::vector<FF> calldata_fields = random_fields(8);
584 TestTraceContainer trace = process_calldata_hashing_trace({ calldata_fields }, { 1 });
585 check_relation<calldata_hashing>(trace);
586 check_all_interactions<CalldataTraceBuilder>(trace);
587
588 // Set the incorrect rounds_rem (should be 3 at row 1)
589 trace.set(Column::calldata_hashing_rounds_rem, 1, 1);
590 EXPECT_THROW_WITH_MESSAGE(check_relation<calldata_hashing>(trace, calldata_hashing::SR_ROUNDS_DECREMENT),
592}
593
594TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativeOutputHash)
595{
596 Poseidon2 poseidon2_int =
598 std::vector<FF> calldata_fields = random_fields(5);
599
600 // Prepare a good trace for calldata hashing (minus final hash):
601 auto trace = TestTraceContainer({
602 { { C::precomputed_first_row, 1 } },
603 {
604 { C::calldata_hashing_index_1_, 1 },
605 { C::calldata_hashing_index_2_, 2 },
606 { C::calldata_hashing_input_0_, DOM_SEP__PUBLIC_CALLDATA },
607 { C::calldata_hashing_input_1_, calldata_fields[0] },
608 { C::calldata_hashing_input_2_, calldata_fields[1] },
609 { C::calldata_hashing_input_len, 6 },
610 { C::calldata_hashing_end, 0 },
611 { C::calldata_hashing_sel_not_padding_1, 1 },
612 { C::calldata_hashing_sel_not_padding_2, 1 },
613 { C::calldata_hashing_sel_not_start, 0 },
614 { C::calldata_hashing_calldata_size, 5 },
615 { C::calldata_hashing_context_id, 1 },
616 { C::calldata_hashing_index_0_, 0 },
617 { C::calldata_hashing_rounds_rem, 2 },
618 { C::calldata_hashing_sel, 1 },
619 { C::calldata_hashing_start, 1 },
620 },
621 {
622 { C::calldata_hashing_index_1_, 4 },
623 { C::calldata_hashing_index_2_, 5 },
624 { C::calldata_hashing_input_0_, calldata_fields[2] },
625 { C::calldata_hashing_input_1_, calldata_fields[3] },
626 { C::calldata_hashing_input_2_, calldata_fields[4] },
627 { C::calldata_hashing_input_len, 6 },
628 { C::calldata_hashing_end, 1 },
629 { C::calldata_hashing_sel_not_padding_1, 1 },
630 { C::calldata_hashing_sel_not_padding_2, 1 },
631 { C::calldata_hashing_sel_not_start, 1 },
632 { C::calldata_hashing_calldata_size, 5 },
633 { C::calldata_hashing_context_id, 1 },
634 { C::calldata_hashing_index_0_, 3 },
635 { C::calldata_hashing_rounds_rem, 1 },
636 { C::calldata_hashing_sel, 1 },
637 { C::calldata_hashing_sel_end_not_empty, 1 },
638 },
639 });
640
641 builder.process_retrieval({ { .context_id = 1, .calldata = calldata_fields } }, trace);
642 // Set the correct hash...
643 auto good_hash = poseidon2_int.hash({
644 DOM_SEP__PUBLIC_CALLDATA,
645 calldata_fields[0],
646 calldata_fields[1],
647 calldata_fields[2],
648 calldata_fields[3],
649 calldata_fields[4],
650 });
651 // ...and an incorrect hash with a matching row at end = 1:
652 auto bad_hash = poseidon2_int.hash({
653 0xa,
654 0xb,
655 0xc,
656 calldata_fields[2],
657 calldata_fields[3],
658 calldata_fields[4],
659 });
661 trace.set(Column::calldata_hashing_output_hash, 1, good_hash);
662 // Set the incorrect hash to end:
663 trace.set(Column::calldata_hashing_output_hash, 2, bad_hash);
664 // All lookups will pass (i.e. we successfully lookup a bad row in the poseidon trace)...
665 check_all_interactions<CalldataTraceBuilder>(trace);
666 // ...but since we constrain that the hash remains consistent, the relations fail:
667 EXPECT_THROW_WITH_MESSAGE(check_relation<calldata_hashing>(trace, calldata_hashing::SR_HASH_CONSISTENCY),
669}
670
671TEST_F(CalldataHashingConstrainingTestTraceHelper, NegativePoseidonInteraction)
672{
673 Poseidon2 poseidon2_int =
675 std::vector<FF> calldata_fields = random_fields(10);
676
677 // Prepare a good trace for calldata hashing (minus final hash):
678 auto trace = TestTraceContainer({
679 { { C::precomputed_first_row, 1 } },
680 {
681 { C::calldata_hashing_index_0_, 0 },
682 { C::calldata_hashing_index_1_, 1 },
683 { C::calldata_hashing_index_2_, 2 },
684 { C::calldata_hashing_input_0_, DOM_SEP__PUBLIC_CALLDATA },
685 { C::calldata_hashing_input_1_, calldata_fields[0] },
686 { C::calldata_hashing_input_2_, calldata_fields[1] },
687 { C::calldata_hashing_input_len, 11 },
688 { C::calldata_hashing_end, 0 },
689 { C::calldata_hashing_sel_not_padding_1, 1 },
690 { C::calldata_hashing_sel_not_padding_2, 1 },
691 { C::calldata_hashing_sel_not_start, 0 },
692 { C::calldata_hashing_calldata_size, 10 },
693 { C::calldata_hashing_context_id, 1 },
694 { C::calldata_hashing_rounds_rem, 4 },
695 { C::calldata_hashing_sel, 1 },
696 { C::calldata_hashing_start, 1 },
697 },
698 {
699 { C::calldata_hashing_index_0_, 3 },
700 { C::calldata_hashing_index_1_, 4 },
701 { C::calldata_hashing_index_2_, 5 },
702 { C::calldata_hashing_input_0_, calldata_fields[2] },
703 { C::calldata_hashing_input_1_, calldata_fields[3] },
704 { C::calldata_hashing_input_2_, calldata_fields[4] },
705 { C::calldata_hashing_input_len, 11 },
706 { C::calldata_hashing_end, 0 },
707 { C::calldata_hashing_sel_not_padding_1, 1 },
708 { C::calldata_hashing_sel_not_padding_2, 1 },
709 { C::calldata_hashing_sel_not_start, 1 },
710 { C::calldata_hashing_calldata_size, 10 },
711 { C::calldata_hashing_context_id, 1 },
712 { C::calldata_hashing_rounds_rem, 3 },
713 { C::calldata_hashing_sel, 1 },
714 },
715 {
716 { C::calldata_hashing_index_0_, 6 },
717 { C::calldata_hashing_index_1_, 7 },
718 { C::calldata_hashing_index_2_, 8 },
719 { C::calldata_hashing_input_0_, calldata_fields[5] },
720 { C::calldata_hashing_input_1_, calldata_fields[6] },
721 { C::calldata_hashing_input_2_, calldata_fields[7] },
722 { C::calldata_hashing_input_len, 11 },
723 { C::calldata_hashing_end, 0 },
724 { C::calldata_hashing_sel_not_padding_1, 1 },
725 { C::calldata_hashing_sel_not_padding_2, 1 },
726 { C::calldata_hashing_sel_not_start, 1 },
727 { C::calldata_hashing_calldata_size, 10 },
728 { C::calldata_hashing_context_id, 1 },
729 { C::calldata_hashing_rounds_rem, 2 },
730 { C::calldata_hashing_sel, 1 },
731 },
732 {
733 { C::calldata_hashing_index_0_, 9 },
734 { C::calldata_hashing_index_1_, 10 },
735 { C::calldata_hashing_index_2_, 11 },
736 { C::calldata_hashing_input_0_, calldata_fields[8] },
737 { C::calldata_hashing_input_1_, calldata_fields[9] },
738 { C::calldata_hashing_input_2_, 0 },
739 { C::calldata_hashing_input_len, 11 },
740 { C::calldata_hashing_end, 1 },
741 { C::calldata_hashing_sel_not_padding_1, 1 },
742 { C::calldata_hashing_sel_not_padding_2, 0 },
743 { C::calldata_hashing_sel_not_start, 1 },
744 { C::calldata_hashing_calldata_size, 10 },
745 { C::calldata_hashing_context_id, 1 },
746 { C::calldata_hashing_rounds_rem, 1 },
747 { C::calldata_hashing_sel, 1 },
748 { C::calldata_hashing_sel_end_not_empty, 1 },
749 },
750 });
751
752 builder.process_retrieval({ { .context_id = 1, .calldata = calldata_fields } }, trace);
753
754 auto bad_hash_prepended = poseidon2_int.hash({
755 0xa,
756 0xb,
757 0xc,
758 DOM_SEP__PUBLIC_CALLDATA,
759 calldata_fields[0],
760 calldata_fields[1],
761 calldata_fields[2],
762 calldata_fields[3],
763 calldata_fields[4],
764 calldata_fields[5],
765 calldata_fields[6],
766 calldata_fields[7],
767 calldata_fields[8],
768 calldata_fields[9],
769 });
770
771 auto bad_hash_misordered = poseidon2_int.hash({
772 DOM_SEP__PUBLIC_CALLDATA,
773 calldata_fields[0],
774 calldata_fields[1],
775 calldata_fields[5],
776 calldata_fields[6],
777 calldata_fields[7],
778 calldata_fields[2],
779 calldata_fields[3],
780 calldata_fields[4],
781 calldata_fields[8],
782 calldata_fields[9],
783 });
784
785 auto bad_hash_padded_zero = poseidon2_int.hash({
786 DOM_SEP__PUBLIC_CALLDATA,
787 calldata_fields[0],
788 calldata_fields[1],
789 calldata_fields[2],
790 calldata_fields[3],
791 calldata_fields[4],
792 calldata_fields[5],
793 calldata_fields[6],
794 calldata_fields[7],
795 calldata_fields[8],
796 calldata_fields[9],
797 0,
798 });
799
801 check_relation<poseidon2>(trace);
802 for (uint32_t j = 1; j <= 4; j++) {
803 trace.set(Column::calldata_hashing_output_hash, j, bad_hash_prepended);
804 }
805 // All relations will pass, and all input values exist in the poseidon trace, but since we constrain the
806 // start rows must match, the below fails at row 1:
807 check_relation<calldata_hashing>(trace);
809 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_poseidon2_hash_settings>(trace)),
810 "Failed.*LOOKUP_CALLDATA_HASHING_POSEIDON2_HASH. Could not find tuple in destination.");
811
812 for (uint32_t j = 1; j <= 4; j++) {
813 trace.set(Column::calldata_hashing_output_hash, j, bad_hash_misordered);
814 }
815 // Again all relations will pass, but the lookup will fail at row 2 since the rounds_rem mismatch:
816 check_relation<calldata_hashing>(trace);
818 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_poseidon2_hash_settings>(trace)),
819 "Failed.*LOOKUP_CALLDATA_HASHING_POSEIDON2_HASH. Could not find tuple in destination.");
820
821 for (uint32_t j = 1; j <= 4; j++) {
822 trace.set(Column::calldata_hashing_output_hash, j, bad_hash_padded_zero);
823 }
824 // Again all relations will pass, but the lookup will fail at row 3 since the rounds_rem mismatch:
825 check_relation<calldata_hashing>(trace);
827 (check_interaction<CalldataTraceBuilder, lookup_calldata_hashing_poseidon2_hash_settings>(trace)),
828 "Failed.*LOOKUP_CALLDATA_HASHING_POSEIDON2_HASH. Could not find tuple in destination.");
829
830 // If we adjust input_len ...
831 for (uint32_t j = 1; j <= 4; j++) {
832 trace.set(Column::calldata_hashing_input_len, j, 12);
833 }
834
835 // ...all interactions will pass...
836 check_all_interactions<CalldataTraceBuilder>(trace);
837
838 // ... but input_len is not consistent with calldata_size + 1:
840 check_relation<calldata_hashing>(trace, calldata_hashing::SR_CALLDATA_HASH_INPUT_LENGTH_FIELDS),
842
843 // If we adjust calldata_size to match input_len...
844 for (uint32_t j = 1; j <= 4; j++) {
845 trace.set(Column::calldata_hashing_calldata_size, j, 11);
846 }
847
848 // ... this will pass...
849 check_relation<calldata_hashing>(trace, calldata_hashing::SR_CALLDATA_HASH_INPUT_LENGTH_FIELDS);
850
851 // ... but the sub-relation with padding consistency fails:
852 EXPECT_THROW_WITH_MESSAGE(check_relation<calldata_hashing>(trace, calldata_hashing::SR_CHECK_FINAL_INDEX),
854}
855
856} // namespace
857} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:224
StrictMock< MockGreaterThan > mock_gt
EventEmitter< Poseidon2PermutationMemoryEvent > perm_mem_event_emitter
EventEmitter< Poseidon2PermutationEvent > perm_event_emitter
EventEmitter< Poseidon2HashEvent > hash_event_emitter
Poseidon2TraceBuilder poseidon2_builder
StrictMock< MockExecutionIdManager > mock_execution_id_manager
static 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 std::string get_subrelation_label(size_t index)
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, bool use_atomic_limbs=false)
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, TwoLayerAvmRecursion)
A test of the Two Layer AVM recursive verifier.
void check_relation(const tracegen::TestTraceContainer &trace, Ts... subrelation)
TestTraceContainer empty_trace()
Definition fixtures.cpp:156
std::vector< FF > random_fields(size_t n)
Definition fixtures.cpp:26
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint32_t context_id