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; // calldata selector
36 FF q_r; // secondary_calldata selector
37 FF q_o; // return_data selector
38
39 // Calldata (bus_idx = 0)
43
44 // Secondary calldata (bus_idx = 1)
48
49 // Return data (bus_idx = 2)
53
75
76 // Create inputs representing a valid read gate for calldata
78 {
79 DatabusInputElements result{};
80
81 // Set up a read from calldata at index 5, value 42
82 result.w_l = FF(42); // value being read
83 result.w_r = FF(5); // index
84 result.databus_id = FF(5); // same index in the bus
85 result.calldata = FF(42); // value in bus matches
86
87 // Enable read gate for calldata
88 result.q_busread = FF(1);
89 result.q_l = FF(1); // calldata selector
90 result.q_r = FF(0);
91 result.q_o = FF(0);
92
93 // Read counts
94 result.calldata_read_counts = FF(1);
95
96 // Other columns inactive
97 result.secondary_calldata_read_counts = FF(0);
98 result.return_data_read_counts = FF(0);
99
100 return result;
101 }
102};
103
104class DatabusLookupRelationConsistency : public testing::Test {
105 public:
107 static constexpr size_t NUM_SUBRELATIONS = 9; // 3 subrelations per bus column, 3 columns
108
113 const DatabusInputElements& input_elements,
114 const RelationParameters<FF>& parameters)
115 {
117 Relation::accumulate(accumulator, input_elements, parameters, FF(1));
118 EXPECT_EQ(accumulator, expected_values);
119 }
120};
121
125static std::array<FF, 9> compute_expected_values(const DatabusInputElements& in, const RelationParameters<FF>& params)
126{
127 const auto& beta = params.beta;
128 const auto& gamma = params.gamma;
129
130 std::array<FF, 9> expected_values;
131 std::fill(expected_values.begin(), expected_values.end(), FF(0));
132
133 // Read term (same for all columns): value + index * beta + gamma
134 auto lookup_term = in.w_l + in.w_r * beta + gamma;
135
136 // Lambda to compute subrelations for a given bus column
137 auto compute_column_subrelations =
138 [&](size_t bus_idx, FF column_selector, FF bus_value, FF read_counts, FF inverses) {
139 auto is_read = in.q_busread * column_selector;
140 auto table_term = bus_value + in.databus_id * beta + gamma;
141
142 // Common: I * L * T - 1
143 auto common = lookup_term * table_term * inverses - FF(1);
144
145 // Subrelation 1a: Inverse correctness on read rows: (I*L*T - 1) * is_read
146 expected_values[bus_idx * 3] = common * is_read;
147
148 // Subrelation 1b: Inverse correctness on write rows: (I*L*T - 1) * count
149 expected_values[bus_idx * 3 + 1] = common * read_counts;
150
151 // Subrelation 2: Log-derivative lookup (no scaling factor since linearly dependent)
152 expected_values[bus_idx * 3 + 2] = (is_read * table_term - read_counts * lookup_term) * inverses;
153 };
154
155 // Bus column 0 (calldata)
156 compute_column_subrelations(0, in.q_l, in.calldata, in.calldata_read_counts, in.calldata_inverses);
157
158 // Bus column 1 (secondary_calldata)
159 compute_column_subrelations(
161
162 // Bus column 2 (return_data)
163 compute_column_subrelations(2, in.q_o, in.return_data, in.return_data_read_counts, in.return_data_inverses);
164
165 return expected_values;
166}
167
173{
174 const auto run_test = [](bool random_inputs) {
177
178 const auto parameters = RelationParameters<FF>::get_random();
179 auto expected_values = compute_expected_values(in, parameters);
180
181 validate_relation_execution(expected_values, in, parameters);
182 };
183
184 run_test(/*random_inputs=*/false);
185 run_test(/*random_inputs=*/true);
186}
187
192{
193 const auto parameters = RelationParameters<FF>::get_random();
194
196 in.q_busread = FF(0);
197 in.q_l = FF(0);
198 in.q_r = FF(0);
199 in.q_o = FF(0);
200 in.calldata_read_counts = FF(0);
203
204 // Set other values non-zero to ensure they don't affect inactive gates
205 in.w_l = FF(42);
206 in.w_r = FF(5);
207 in.databus_id = FF(5);
208 in.calldata = FF(42);
209 in.calldata_inverses = FF(0); // inverse should be 0 when inactive
210
212 Relation::accumulate(accumulator, in, parameters, FF(1));
213
214 // All subrelations should be 0 when inactive
215 for (size_t i = 0; i < NUM_SUBRELATIONS; i++) {
216 EXPECT_EQ(accumulator[i], FF(0)) << "Subrelation " << i << " should be zero for inactive gates";
217 }
218}
219
225{
226 const auto parameters = RelationParameters<FF>::get_random();
227 const auto& beta = parameters.beta;
228 const auto& gamma = parameters.gamma;
229
231
232 // Set up a read gate for calldata
233 in.q_busread = FF(1);
234 in.q_l = FF(1); // calldata selector
235 in.q_r = FF(0);
236 in.q_o = FF(0);
237
238 // Value and index
239 FF value = FF(42);
240 FF index = FF(5);
241 in.w_l = value; // value being read
242 in.w_r = index; // index
243 in.databus_id = index; // same index in the bus
244 in.calldata = value; // value in bus matches
245
246 // Compute the correct inverse
247 auto lookup_term = value + index * beta + gamma;
248 auto table_term = value + index * beta + gamma; // same since value and index match
249 auto inverse = (lookup_term * table_term).invert();
250 in.calldata_inverses = inverse;
251
252 in.calldata_read_counts = FF(1);
253
254 // Other columns inactive
258 in.return_data_inverses = FF(0);
259
261 Relation::accumulate(accumulator, in, parameters, FF(1));
262
263 // (1a) Inverse correctness on read: (I*L*T - 1) * is_read = (1 - 1) * 1 = 0
264 EXPECT_EQ(accumulator[0], FF(0));
265
266 // (1b) Inverse correctness on write: (I*L*T - 1) * count = (1 - 1) * 1 = 0
267 EXPECT_EQ(accumulator[1], FF(0));
268
269 // (2) Lookup: (is_read*T - count*L) * I = (T - L) * I = 0 (since L == T here)
270 EXPECT_EQ(accumulator[2], FF(0));
271
272 // Other columns should have all-zero subrelations (inactive)
273 for (size_t i = 3; i < NUM_SUBRELATIONS; i++) {
274 EXPECT_EQ(accumulator[i], FF(0)) << "Inactive column subrelation " << i << " should be zero";
275 }
276}
277
281TEST_F(DatabusLookupRelationConsistency, MismatchedReadWriteTerms)
282{
283 const auto parameters = RelationParameters<FF>::get_random();
284 const auto& beta = parameters.beta;
285 const auto& gamma = parameters.gamma;
286
288
289 // Set up a read gate for calldata
290 in.q_busread = FF(1);
291 in.q_l = FF(1);
292 in.q_r = FF(0);
293 in.q_o = FF(0);
294
295 // Value being read differs from value in bus!
296 FF read_value = FF(42);
297 FF bus_value = FF(100); // Different!
298 FF index = FF(5);
299
300 in.w_l = read_value;
301 in.w_r = index;
302 in.databus_id = index;
303 in.calldata = bus_value;
304
305 auto lookup_term = read_value + index * beta + gamma;
306 auto table_term = bus_value + index * beta + gamma;
307 auto inverse = (lookup_term * table_term).invert();
308 in.calldata_inverses = inverse;
309
310 in.calldata_read_counts = FF(1);
314 in.return_data_inverses = FF(0);
315
317 Relation::accumulate(accumulator, in, parameters, FF(1));
318
319 // (1a) Inverse correctness still satisfied (I is correct for these terms)
320 EXPECT_EQ(accumulator[0], FF(0));
321
322 // (1b) Inverse correctness on write: (I*L*T - 1) * count = 0 * 1 = 0 (I*L*T = 1)
323 EXPECT_EQ(accumulator[1], FF(0));
324
325 // (2) Lookup subrelation is non-zero because lookup_term != table_term
326 // (is_read * table_term - count * lookup_term) * I = (table_term - lookup_term) * I
327 FF expected_lookup = (table_term - lookup_term) * inverse;
328 EXPECT_EQ(accumulator[2], expected_lookup);
329 EXPECT_NE(accumulator[2], FF(0));
330}
331
337TEST_F(DatabusLookupRelationConsistency, InverseUnconstrainedAtInactiveRows)
338{
339 const auto parameters = RelationParameters<FF>::get_random();
340
342 in.q_busread = FF(0);
343 in.q_l = FF(0);
344 in.q_r = FF(0);
345 in.q_o = FF(0);
346 in.calldata_read_counts = FF(0);
349
350 // Set inverses to arbitrary nonzero values — should not matter
351 in.calldata_inverses = FF(999);
353 in.return_data_inverses = FF(555);
354
355 in.w_l = FF(42);
356 in.w_r = FF(5);
357 in.databus_id = FF(5);
358 in.calldata = FF(42);
359
361 Relation::accumulate(accumulator, in, parameters, FF(1));
362
363 // (1a) gated by is_read = 0: always zero regardless of I
364 EXPECT_EQ(accumulator[0], FF(0));
365 EXPECT_EQ(accumulator[3], FF(0));
366 EXPECT_EQ(accumulator[6], FF(0));
367
368 // (1b) gated by count = 0: always zero regardless of I
369 EXPECT_EQ(accumulator[1], FF(0));
370 EXPECT_EQ(accumulator[4], FF(0));
371 EXPECT_EQ(accumulator[7], FF(0));
372
373 // (2) lookup: (0 * T - 0 * L) * I = 0 regardless of I
374 EXPECT_EQ(accumulator[2], FF(0));
375 EXPECT_EQ(accumulator[5], FF(0));
376 EXPECT_EQ(accumulator[8], FF(0));
377}
378
383TEST_F(DatabusLookupRelationConsistency, WrongInverseOnReadRowFails)
384{
385 const auto parameters = RelationParameters<FF>::get_random();
386 const auto& beta = parameters.beta;
387 const auto& gamma = parameters.gamma;
388
390 in.q_busread = FF(1);
391 in.q_l = FF(1);
392 in.q_r = FF(0);
393 in.q_o = FF(0);
394
395 FF value = FF(42);
396 FF index = FF(5);
397 in.w_l = value;
398 in.w_r = index;
399 in.databus_id = index;
400 in.calldata = value;
401
402 // Set a WRONG inverse (just some arbitrary value, not 1/(L*T))
403 in.calldata_inverses = FF(777);
404 in.calldata_read_counts = FF(0); // pure read row, no write
408 in.return_data_inverses = FF(0);
409
410 auto lookup_term = value + index * beta + gamma;
411 auto table_term = value + index * beta + gamma;
412
414 Relation::accumulate(accumulator, in, parameters, FF(1));
415
416 // (1a) should be nonzero: (I*L*T - 1) * is_read = (777*L*T - 1) * 1 != 0
417 FF expected_1a = (FF(777) * lookup_term * table_term - FF(1)) * FF(1); // is_read = q_busread * q_l = 1
418 EXPECT_EQ(accumulator[0], expected_1a);
419 EXPECT_NE(accumulator[0], FF(0));
420
421 // (1b) should be zero: gated by count = 0
422 EXPECT_EQ(accumulator[1], FF(0));
423}
424
429TEST_F(DatabusLookupRelationConsistency, WrongInverseOnWriteRowFails)
430{
431 const auto parameters = RelationParameters<FF>::get_random();
432 const auto& beta = parameters.beta;
433 const auto& gamma = parameters.gamma;
434
436 // No read gate active
437 in.q_busread = FF(0);
438 in.q_l = FF(0);
439 in.q_r = FF(0);
440 in.q_o = FF(0);
441
442 FF value = FF(42);
443 FF index = FF(5);
444 in.databus_id = index;
445 in.calldata = value;
446 in.w_l = FF(0); // irrelevant (no read gate)
447 in.w_r = FF(0);
448
449 // Row has nonzero read_count (it's been read from elsewhere) but wrong inverse
450 in.calldata_read_counts = FF(3);
451 in.calldata_inverses = FF(999); // WRONG
452
456 in.return_data_inverses = FF(0);
457
458 auto lookup_term = in.w_l + in.w_r * beta + gamma; // = gamma (wires are 0)
459 auto table_term = value + index * beta + gamma;
460
462 Relation::accumulate(accumulator, in, parameters, FF(1));
463
464 // (1a) should be zero: gated by is_read = q_busread * q_l = 0
465 EXPECT_EQ(accumulator[0], FF(0));
466
467 // (1b) should be nonzero: (I*L*T - 1) * count = (999*L*T - 1) * 3 != 0
468 FF expected_1b = (FF(999) * lookup_term * table_term - FF(1)) * FF(3);
469 EXPECT_EQ(accumulator[1], expected_1b);
470 EXPECT_NE(accumulator[1], FF(0));
471}
472
478TEST_F(DatabusLookupRelationConsistency, CorrectInverseOnWriteRow)
479{
480 const auto parameters = RelationParameters<FF>::get_random();
481 const auto& beta = parameters.beta;
482 const auto& gamma = parameters.gamma;
483
485 in.q_busread = FF(0);
486 in.q_l = FF(0);
487 in.q_r = FF(0);
488 in.q_o = FF(0);
489
490 FF value = FF(42);
491 FF index = FF(5);
492 in.databus_id = index;
493 in.calldata = value;
494 in.w_l = FF(0);
495 in.w_r = FF(0);
496
497 auto lookup_term = in.w_l + in.w_r * beta + gamma;
498 auto table_term = value + index * beta + gamma;
499
500 // Correct inverse
501 in.calldata_inverses = (lookup_term * table_term).invert();
502 in.calldata_read_counts = FF(3);
503
507 in.return_data_inverses = FF(0);
508
510 Relation::accumulate(accumulator, in, parameters, FF(1));
511
512 // (1a) zero: is_read = 0
513 EXPECT_EQ(accumulator[0], FF(0));
514
515 // (1b) zero: (1 - 1) * 3 = 0
516 EXPECT_EQ(accumulator[1], FF(0));
517
518 // (2) lookup: (0*T - 3*L) * I = -3*L/(L*T) = -3/T
519 FF expected_lookup = (FF(0) * table_term - FF(3) * lookup_term) * (lookup_term * table_term).invert();
520 EXPECT_EQ(accumulator[2], expected_lookup);
521 EXPECT_NE(accumulator[2], FF(0)); // nonzero contribution (balances across the full trace sum)
522}
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 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