Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
databus_lookup_relation_consistency.test.cpp
Go to the documentation of this file.
1
15#include <gtest/gtest.h>
16
17using namespace bb;
18
19using FF = fr;
20
26 // Wires used in read gates
27 FF w_l; // value being read
28 FF w_r; // index into bus column
29 FF databus_id; // id/index in the bus (for write term)
30
31 // Read gate selector
33
34 // Column selectors (determine which bus column is being read)
35 FF q_l; // kernel calldata selector
36 FF q_r; // first app calldata selector
37 FF q_o; // second app calldata selector
38 FF q_4; // third app calldata selector
39 FF q_m; // return data selector
40
41 // Kernel calldata (bus_idx = 0)
45
46 // First app calldata (bus_idx = 1)
50
51 // Second app calldata (bus_idx = 2)
55
56 // Third app calldata (bus_idx = 3)
60
61 // Return data (bus_idx = 4)
65
95
96 // Create inputs representing a valid read gate for kernel_calldata
98 {
99 DatabusInputElements result{};
100
101 // Set up a read from kernel_calldata at index 5, value 42
102 result.w_l = FF(42); // value being read
103 result.w_r = FF(5); // index
104 result.databus_id = FF(5); // same index in the bus
105 result.kernel_calldata = FF(42); // value in bus matches
106
107 // Enable read gate for kernel_calldata
108 result.q_busread = FF(1);
109 result.q_l = FF(1); // kernel_calldata selector
110 result.q_r = FF(0);
111 result.q_o = FF(0);
112 result.q_4 = FF(0);
113 result.q_m = FF(0);
114
115 // Read counts
116 result.kernel_calldata_read_counts = FF(1);
117
118 // Other columns inactive
119 result.first_app_calldata_read_counts = FF(0);
120 result.second_app_calldata_read_counts = FF(0);
121 result.third_app_calldata_read_counts = FF(0);
122 result.return_data_read_counts = FF(0);
123
124 return result;
125 }
126};
127
128class DatabusLookupRelationConsistency : public testing::Test {
129 public:
132
137 const DatabusInputElements& input_elements,
138 const RelationParameters<FF>& parameters)
139 {
141 Relation::accumulate(accumulator, input_elements, parameters, FF(1));
142 EXPECT_EQ(accumulator, expected_values);
143 }
144};
145
150 const DatabusInputElements& in, const RelationParameters<FF>& params)
151{
152 const auto& beta = params.beta;
153 const auto& gamma = params.gamma;
154
156 std::fill(expected_values.begin(), expected_values.end(), FF(0));
157
158 // Read term (same for all columns): value + index * beta + gamma
159 auto lookup_term = in.w_l + in.w_r * beta + gamma;
160
161 // Lambda to compute subrelations for a given bus column
162 auto compute_column_subrelations =
163 [&](size_t bus_idx, FF column_selector, FF bus_value, FF read_counts, FF inverses) {
164 auto is_read = in.q_busread * column_selector;
165 auto table_term = bus_value + in.databus_id * beta + gamma;
166
167 // Common: I * L * T - 1
168 auto common = lookup_term * table_term * inverses - FF(1);
169
170 // Subrelation 1a: Inverse correctness on read rows: (I*L*T - 1) * is_read
171 expected_values[bus_idx * 3] = common * is_read;
172
173 // Subrelation 1b: Inverse correctness on write rows: (I*L*T - 1) * count
174 expected_values[bus_idx * 3 + 1] = common * read_counts;
175
176 // Subrelation 2: Log-derivative lookup (no scaling factor since linearly dependent)
177 expected_values[bus_idx * 3 + 2] = (is_read * table_term - read_counts * lookup_term) * inverses;
178 };
179
180 // Bus column 0 (kernel_calldata)
181 compute_column_subrelations(
183
184 // Bus column 1 (first_app_calldata)
185 compute_column_subrelations(
187
188 // Bus column 2 (second_app_calldata)
189 compute_column_subrelations(
191
192 // Bus column 3 (third_app_calldata)
193 compute_column_subrelations(
195
196 // Bus column 4 (return_data)
197 compute_column_subrelations(4, in.q_m, in.return_data, in.return_data_read_counts, in.return_data_inverses);
198
199 return expected_values;
200}
201
207{
208 const auto run_test = [](bool random_inputs) {
211
212 const auto parameters = RelationParameters<FF>::get_random();
213 auto expected_values = compute_expected_values(in, parameters);
214
215 validate_relation_execution(expected_values, in, parameters);
216 };
217
218 run_test(/*random_inputs=*/false);
219 run_test(/*random_inputs=*/true);
220}
221
226{
227 const auto parameters = RelationParameters<FF>::get_random();
228
230 in.q_busread = FF(0);
231 in.q_l = FF(0);
232 in.q_r = FF(0);
233 in.q_o = FF(0);
234 in.q_4 = FF(0);
235 in.q_m = FF(0);
241
242 // Set other values non-zero to ensure they don't affect inactive gates
243 in.w_l = FF(42);
244 in.w_r = FF(5);
245 in.databus_id = FF(5);
246 in.kernel_calldata = FF(42);
247 in.kernel_calldata_inverses = FF(0); // inverse should be 0 when inactive
248 in.first_app_calldata = FF(100);
250 in.second_app_calldata = FF(200);
252 in.third_app_calldata = FF(300);
254 in.return_data = FF(400);
255 in.return_data_inverses = FF(0);
256
258 Relation::accumulate(accumulator, in, parameters, FF(1));
259
260 // All subrelations should be 0 when inactive
261 for (size_t i = 0; i < NUM_SUBRELATIONS; i++) {
262 EXPECT_EQ(accumulator[i], FF(0)) << "Subrelation " << i << " should be zero for inactive gates";
263 }
264}
265
271{
272 const auto parameters = RelationParameters<FF>::get_random();
273 const auto& beta = parameters.beta;
274 const auto& gamma = parameters.gamma;
275
277
278 // Set up a read gate for kernel_calldata
279 in.q_busread = FF(1);
280 in.q_l = FF(1); // kernel_calldata selector
281 in.q_r = FF(0);
282 in.q_o = FF(0);
283
284 // Value and index
285 FF value = FF(42);
286 FF index = FF(5);
287 in.w_l = value; // value being read
288 in.w_r = index; // index
289 in.databus_id = index; // same index in the bus
290 in.kernel_calldata = value; // value in bus matches
291
292 // Compute the correct inverse
293 auto lookup_term = value + index * beta + gamma;
294 auto table_term = value + index * beta + gamma; // same since value and index match
295 auto inverse = (lookup_term * table_term).invert();
296 in.kernel_calldata_inverses = inverse;
297
299
300 // Other columns inactive
308 in.return_data_inverses = FF(0);
309
311 Relation::accumulate(accumulator, in, parameters, FF(1));
312
313 // (1a) Inverse correctness on read: (I*L*T - 1) * is_read = (1 - 1) * 1 = 0
314 EXPECT_EQ(accumulator[0], FF(0));
315
316 // (1b) Inverse correctness on write: (I*L*T - 1) * count = (1 - 1) * 1 = 0
317 EXPECT_EQ(accumulator[1], FF(0));
318
319 // (2) Lookup: (is_read*T - count*L) * I = (T - L) * I = 0 (since L == T here)
320 EXPECT_EQ(accumulator[2], FF(0));
321
322 // Other columns should have all-zero subrelations (inactive)
323 for (size_t i = 3; i < NUM_SUBRELATIONS; i++) {
324 EXPECT_EQ(accumulator[i], FF(0)) << "Inactive column subrelation " << i << " should be zero";
325 }
326}
327
331TEST_F(DatabusLookupRelationConsistency, MismatchedReadWriteTerms)
332{
333 const auto parameters = RelationParameters<FF>::get_random();
334 const auto& beta = parameters.beta;
335 const auto& gamma = parameters.gamma;
336
338
339 // Set up a read gate for kernel_calldata
340 in.q_busread = FF(1);
341 in.q_l = FF(1);
342 in.q_r = FF(0);
343 in.q_o = FF(0);
344
345 // Value being read differs from value in bus!
346 FF read_value = FF(42);
347 FF bus_value = FF(100); // Different!
348 FF index = FF(5);
349
350 in.w_l = read_value;
351 in.w_r = index;
352 in.databus_id = index;
353 in.kernel_calldata = bus_value;
354
355 auto lookup_term = read_value + index * beta + gamma;
356 auto table_term = bus_value + index * beta + gamma;
357 auto inverse = (lookup_term * table_term).invert();
358 in.kernel_calldata_inverses = inverse;
359
368 in.return_data_inverses = FF(0);
369
371 Relation::accumulate(accumulator, in, parameters, FF(1));
372
373 // (1a) Inverse correctness still satisfied (I is correct for these terms)
374 EXPECT_EQ(accumulator[0], FF(0));
375
376 // (1b) Inverse correctness on write: (I*L*T - 1) * count = 0 * 1 = 0 (I*L*T = 1)
377 EXPECT_EQ(accumulator[1], FF(0));
378
379 // (2) Lookup subrelation is non-zero because lookup_term != table_term
380 // (is_read * table_term - count * lookup_term) * I = (table_term - lookup_term) * I
381 FF expected_lookup = (table_term - lookup_term) * inverse;
382 EXPECT_EQ(accumulator[2], expected_lookup);
383 EXPECT_NE(accumulator[2], FF(0));
384}
385
391TEST_F(DatabusLookupRelationConsistency, InverseUnconstrainedAtInactiveRows)
392{
393 const auto parameters = RelationParameters<FF>::get_random();
394
396 in.q_busread = FF(0);
397 in.q_l = FF(0);
398 in.q_r = FF(0);
399 in.q_o = FF(0);
405
406 // Set inverses to arbitrary nonzero values — should not matter
411 in.return_data_inverses = FF(111);
412
413 in.w_l = FF(42);
414 in.w_r = FF(5);
415 in.databus_id = FF(5);
416 in.kernel_calldata = FF(42);
417
419 Relation::accumulate(accumulator, in, parameters, FF(1));
420
421 // (1a) gated by is_read = 0: always zero regardless of I
422 EXPECT_EQ(accumulator[0], FF(0));
423 EXPECT_EQ(accumulator[3], FF(0));
424 EXPECT_EQ(accumulator[6], FF(0));
425
426 // (1b) gated by count = 0: always zero regardless of I
427 EXPECT_EQ(accumulator[1], FF(0));
428 EXPECT_EQ(accumulator[4], FF(0));
429 EXPECT_EQ(accumulator[7], FF(0));
430
431 // (2) lookup: (0 * T - 0 * L) * I = 0 regardless of I
432 EXPECT_EQ(accumulator[2], FF(0));
433 EXPECT_EQ(accumulator[5], FF(0));
434 EXPECT_EQ(accumulator[8], FF(0));
435}
436
441TEST_F(DatabusLookupRelationConsistency, WrongInverseOnReadRowFails)
442{
443 const auto parameters = RelationParameters<FF>::get_random();
444 const auto& beta = parameters.beta;
445 const auto& gamma = parameters.gamma;
446
448 in.q_busread = FF(1);
449 in.q_l = FF(1);
450 in.q_r = FF(0);
451 in.q_o = FF(0);
452
453 FF value = FF(42);
454 FF index = FF(5);
455 in.w_l = value;
456 in.w_r = index;
457 in.databus_id = index;
459
460 // Set a WRONG inverse (just some arbitrary value, not 1/(L*T))
462 in.kernel_calldata_read_counts = FF(0); // pure read row, no write
470 in.return_data_inverses = FF(0);
471
472 auto lookup_term = value + index * beta + gamma;
473 auto table_term = value + index * beta + gamma;
474
476 Relation::accumulate(accumulator, in, parameters, FF(1));
477
478 // (1a) should be nonzero: (I*L*T - 1) * is_read = (777*L*T - 1) * 1 != 0
479 FF expected_1a = (FF(777) * lookup_term * table_term - FF(1)) * FF(1); // is_read = q_busread * q_l = 1
480 EXPECT_EQ(accumulator[0], expected_1a);
481 EXPECT_NE(accumulator[0], FF(0));
482
483 // (1b) should be zero: gated by count = 0
484 EXPECT_EQ(accumulator[1], FF(0));
485}
486
491TEST_F(DatabusLookupRelationConsistency, WrongInverseOnWriteRowFails)
492{
493 const auto parameters = RelationParameters<FF>::get_random();
494 const auto& beta = parameters.beta;
495 const auto& gamma = parameters.gamma;
496
498 // No read gate active
499 in.q_busread = FF(0);
500 in.q_l = FF(0);
501 in.q_r = FF(0);
502 in.q_o = FF(0);
503
504 FF value = FF(42);
505 FF index = FF(5);
506 in.databus_id = index;
508 in.w_l = FF(0); // irrelevant (no read gate)
509 in.w_r = FF(0);
510
511 // Row has nonzero read_count (it's been read from elsewhere) but wrong inverse
513 in.kernel_calldata_inverses = FF(999); // WRONG
514
522 in.return_data_inverses = FF(0);
523
524 auto lookup_term = in.w_l + in.w_r * beta + gamma; // = gamma (wires are 0)
525 auto table_term = value + index * beta + gamma;
526
528 Relation::accumulate(accumulator, in, parameters, FF(1));
529
530 // (1a) should be zero: gated by is_read = q_busread * q_l = 0
531 EXPECT_EQ(accumulator[0], FF(0));
532
533 // (1b) should be nonzero: (I*L*T - 1) * count = (999*L*T - 1) * 3 != 0
534 FF expected_1b = (FF(999) * lookup_term * table_term - FF(1)) * FF(3);
535 EXPECT_EQ(accumulator[1], expected_1b);
536 EXPECT_NE(accumulator[1], FF(0));
537}
538
544TEST_F(DatabusLookupRelationConsistency, CorrectInverseOnWriteRow)
545{
546 const auto parameters = RelationParameters<FF>::get_random();
547 const auto& beta = parameters.beta;
548 const auto& gamma = parameters.gamma;
549
551 in.q_busread = FF(0);
552 in.q_l = FF(0);
553 in.q_r = FF(0);
554 in.q_o = FF(0);
555
556 FF value = FF(42);
557 FF index = FF(5);
558 in.databus_id = index;
560 in.w_l = FF(0);
561 in.w_r = FF(0);
562
563 auto lookup_term = in.w_l + in.w_r * beta + gamma;
564 auto table_term = value + index * beta + gamma;
565
566 // Correct inverse
567 in.kernel_calldata_inverses = (lookup_term * table_term).invert();
569
577 in.return_data_inverses = FF(0);
578
580 Relation::accumulate(accumulator, in, parameters, FF(1));
581
582 // (1a) zero: is_read = 0
583 EXPECT_EQ(accumulator[0], FF(0));
584
585 // (1b) zero: (1 - 1) * 3 = 0
586 EXPECT_EQ(accumulator[1], FF(0));
587
588 // (2) lookup: (0*T - 3*L) * I = -3*L/(L*T) = -3/T
589 FF expected_lookup = (FF(0) * table_term - FF(3) * lookup_term) * (lookup_term * table_term).invert();
590 EXPECT_EQ(accumulator[2], expected_lookup);
591 EXPECT_NE(accumulator[2], FF(0)); // nonzero contribution (balances across the full trace sum)
592}
static void validate_relation_execution(const std::array< FF, NUM_SUBRELATIONS > &expected_values, const DatabusInputElements &input_elements, const RelationParameters< FF > &parameters)
Validate that the relation's accumulate function produces expected values.
Log-derivative lookup argument relation for establishing DataBus reads.
static constexpr std::array< size_t, NUM_SUB_RELATION_PER_IDX *NUM_BUS_COLUMNS > SUBRELATION_PARTIAL_LENGTHS
static void accumulate(ContainerOverSubrelations &accumulator, const AllEntities &in, const Parameters &params, const FF &scaling_factor)
Accumulate the log derivative databus lookup argument subrelation contributions for each databus colu...
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:155
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Input elements for DatabusLookupRelation testing.
static DatabusInputElements get_valid_calldata_read()
Container for parameters used by the grand product (permutation, lookup) Honk relations.
static RelationParameters get_random()
constexpr field invert() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept