Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
gate_patterns.test.cpp
Go to the documentation of this file.
1
12#include "gate_patterns.hpp"
26#include <gtest/gtest.h>
27#include <set>
28
29using namespace bb;
30using namespace bb::gate_patterns;
31
32using FF = fr;
35
36template <typename EntitiesT = Entities> EntitiesT get_random_entities()
37{
38 EntitiesT entities;
39 for (auto& field : entities.get_all()) {
41 }
42 return entities;
43}
44
45template <typename EntitiesT> FF& get_wire(EntitiesT& entities, Wire wire)
46{
47 switch (wire) {
48 case Wire::W_L:
49 return entities.w_l();
50 case Wire::W_R:
51 return entities.w_r();
52 case Wire::W_O:
53 return entities.w_o();
54 case Wire::W_4:
55 return entities.w_4();
56 case Wire::W_L_SHIFT:
57 return entities.w_l_shift();
58 case Wire::W_R_SHIFT:
59 return entities.w_r_shift();
60 case Wire::W_O_SHIFT:
61 return entities.w_o_shift();
62 case Wire::W_4_SHIFT:
63 return entities.w_4_shift();
64 }
65 __builtin_unreachable();
66}
67
68template <typename EntitiesT> Selectors make_selectors(const EntitiesT& entities, int64_t gate_selector_value)
69{
70 Selectors selectors{
71 .gate_selector = gate_selector_value,
72 .q_m_nz = !entities.q_m().is_zero(),
73 .q_1_nz = !entities.q_l().is_zero(),
74 .q_2_nz = !entities.q_r().is_zero(),
75 .q_3_nz = !entities.q_o().is_zero(),
76 .q_4_nz = !entities.q_4().is_zero(),
77 .q_c_nz = !entities.q_c().is_zero(),
78 };
79 // q_5 is committed only in the Mega flavors (used by the bilinear gate's second product).
80 if constexpr (requires { entities.q_5(); }) {
81 selectors.q_5_nz = !entities.q_5().is_zero();
82 }
83 return selectors;
84}
85
89std::set<Wire> get_pattern_wires(const GatePattern& pattern, const Selectors& selectors)
90{
92 for (const auto& wire_spec : pattern.wires) {
93 if (wire_spec.condition(selectors)) {
94 result.insert(wire_spec.wire);
95 }
96 }
97 return result;
98}
99
105template <typename Relation, typename EntitiesT>
106std::set<Wire> get_actually_constrained_wires(const EntitiesT& entities, const auto& parameters)
107{
108 std::set<Wire> constrained;
109
110 // Evaluate relation at base point
112 Relation::accumulate(base_result, entities, parameters, FF(1));
113
114 // For each wire position, perturb a copy and check if output changes
115 for (Wire wire : { Wire::W_L,
116 Wire::W_R,
117 Wire::W_O,
118 Wire::W_4,
119 Wire::W_L_SHIFT,
120 Wire::W_R_SHIFT,
121 Wire::W_O_SHIFT,
122 Wire::W_4_SHIFT }) {
123 EntitiesT perturbed = entities;
124 get_wire(perturbed, wire) += FF::random_element();
125
126 typename Relation::SumcheckArrayOfValuesOverSubrelations perturbed_result{};
127 Relation::accumulate(perturbed_result, perturbed, parameters, FF(1));
128
129 if (base_result != perturbed_result) {
130 constrained.insert(wire);
131 }
132 }
133
134 return constrained;
135}
136
142template <typename Relation, typename EntitiesT = Entities>
143void verify_pattern(const GatePattern& pattern, auto configure_selectors)
144{
145 EntitiesT entities = get_random_entities<EntitiesT>();
146 FF gate_selector = configure_selectors(entities);
147 int64_t gate_selector_value = static_cast<int64_t>(uint64_t(gate_selector));
148
149 Selectors selectors = make_selectors(entities, gate_selector_value);
150 auto pattern_claims = get_pattern_wires(pattern, selectors);
151
152 auto parameters = RelationParameters<FF>::get_random();
153 auto actually_constrained = get_actually_constrained_wires<Relation>(entities, parameters);
154
155 EXPECT_EQ(actually_constrained, pattern_claims);
156}
157
158// =============================================================================
159// Pattern Tests
160// =============================================================================
161
162TEST(PatternTest, Arithmetic1)
163{
164 verify_pattern<ArithmeticRelation<FF>>(ARITHMETIC, [](Entities& e) { return e.q_arith() = FF(1); });
165}
166
167TEST(PatternTest, Arithmetic2)
168{
169 verify_pattern<ArithmeticRelation<FF>>(ARITHMETIC, [](Entities& e) { return e.q_arith() = FF(2); });
170}
171
172TEST(PatternTest, Arithmetic3)
173{
174 verify_pattern<ArithmeticRelation<FF>>(ARITHMETIC, [](Entities& e) { return e.q_arith() = FF(3); });
175}
176
177TEST(PatternTest, Arithmetic3WithQmZero)
178{
179 verify_pattern<ArithmeticRelation<FF>>(ARITHMETIC, [](Entities& e) {
180 e.q_m() = FF(0);
181 return e.q_arith() = FF(3);
182 });
183}
184
185TEST(PatternTest, EllipticAdd)
186{
187 verify_pattern<EllipticRelation<FF>>(ELLIPTIC, [](Entities& e) {
188 e.q_m() = FF(0);
189 e.q_l() = FF(-1);
190 return e.q_elliptic() = FF(1);
191 });
192}
193
194TEST(PatternTest, EllipticDouble)
195{
196 verify_pattern<EllipticRelation<FF>>(ELLIPTIC, [](Entities& e) {
197 e.q_m() = FF(1);
198 e.q_l() = FF(-1);
199 return e.q_elliptic() = FF(1);
200 });
201}
202
203TEST(PatternTest, DeltaRange)
204{
205 verify_pattern<DeltaRangeConstraintRelation<FF>>(DELTA_RANGE,
206 [](Entities& e) { return e.q_delta_range() = FF(1); });
207}
208
209TEST(PatternTest, NNFLimbAccum1)
210{
211 verify_pattern<NonNativeFieldRelation<FF>>(NON_NATIVE_FIELD, [](Entities& e) {
212 e.q_r() = FF(0);
213 e.q_o() = FF(1);
214 e.q_4() = FF(1);
215 e.q_m() = FF(0);
216 return e.q_nnf() = FF(1);
217 });
218}
219
220TEST(PatternTest, NNFLimbAccum2)
221{
222 verify_pattern<NonNativeFieldRelation<FF>>(NON_NATIVE_FIELD, [](Entities& e) {
223 e.q_r() = FF(0);
224 e.q_o() = FF(1);
225 e.q_4() = FF(0);
226 e.q_m() = FF(1);
227 return e.q_nnf() = FF(1);
228 });
229}
230
231TEST(PatternTest, NNFProduct1)
232{
233 verify_pattern<NonNativeFieldRelation<FF>>(NON_NATIVE_FIELD, [](Entities& e) {
234 e.q_r() = FF(1);
235 e.q_o() = FF(1);
236 e.q_4() = FF(0);
237 e.q_m() = FF(0);
238 return e.q_nnf() = FF(1);
239 });
240}
241
242TEST(PatternTest, NNFProduct2)
243{
244 verify_pattern<NonNativeFieldRelation<FF>>(NON_NATIVE_FIELD, [](Entities& e) {
245 e.q_r() = FF(1);
246 e.q_o() = FF(0);
247 e.q_4() = FF(1);
248 e.q_m() = FF(0);
249 return e.q_nnf() = FF(1);
250 });
251}
252
253TEST(PatternTest, NNFProduct3)
254{
255 verify_pattern<NonNativeFieldRelation<FF>>(NON_NATIVE_FIELD, [](Entities& e) {
256 e.q_r() = FF(1);
257 e.q_o() = FF(0);
258 e.q_4() = FF(0);
259 e.q_m() = FF(1);
260 return e.q_nnf() = FF(1);
261 });
262}
263
264TEST(PatternTest, MemoryRamRomAccess)
265{
266 verify_pattern<MemoryRelation<FF>>(MEMORY, [](Entities& e) {
267 e.q_l() = FF(1);
268 e.q_m() = FF(1);
269 return e.q_memory() = FF(1);
270 });
271}
272
273TEST(PatternTest, MemoryRamTimestamp)
274{
275 verify_pattern<MemoryRelation<FF>>(MEMORY, [](Entities& e) {
276 e.q_l() = FF(1);
277 e.q_4() = FF(1);
278 return e.q_memory() = FF(1);
279 });
280}
281
282TEST(PatternTest, MemoryRomConsistency)
283{
284 verify_pattern<MemoryRelation<FF>>(MEMORY, [](Entities& e) {
285 e.q_l() = FF(1);
286 e.q_r() = FF(1);
287 return e.q_memory() = FF(1);
288 });
289}
290
291TEST(PatternTest, MemoryRamConsistency)
292{
293 verify_pattern<MemoryRelation<FF>>(MEMORY, [](Entities& e) {
294 e.q_o() = FF(1);
295 return e.q_memory() = FF(1);
296 });
297}
298
299TEST(PatternTest, Poseidon2Internal)
300{
301 verify_pattern<Poseidon2InternalRelation<FF>, UltraEntities>(
302 POSEIDON2_INTERNAL, [](UltraEntities& e) { return e.q_poseidon2_internal() = FF(1); });
303}
304
305TEST(PatternTest, Poseidon2External)
306{
307 verify_pattern<Poseidon2ExternalRelation<FF>>(POSEIDON2_EXTERNAL,
308 [](Entities& e) { return e.q_poseidon2_external() = FF(1); });
309}
310
311TEST(PatternTest, LookupBasic)
312{
313 verify_pattern<LogDerivLookupRelation<FF>>(LOOKUP, [](Entities& e) {
314 // No shifted wires (step_size selectors all zero)
315 e.q_r() = FF(0);
316 e.q_m() = FF(0);
317 e.q_c() = FF(0);
318 return e.q_lookup() = FF(1);
319 });
320}
321
322TEST(PatternTest, LookupWithShiftedWires)
323{
324 verify_pattern<LogDerivLookupRelation<FF>>(LOOKUP, [](Entities& e) {
325 // Enable all shifted wires
326 e.q_r() = FF(1);
327 e.q_m() = FF(1);
328 e.q_c() = FF(1);
329 return e.q_lookup() = FF(1);
330 });
331}
332
333TEST(PatternTest, DatabusRead)
334{
335 // The kernel_calldata bus relation is shape-equivalent to every other Mega bus relation;
336 // verifying it covers the pattern check for q_busread reads.
337 using KernelCalldataLookup = bb::SingleBusLookupRelation<FF,
338 MegaFlavor::EntityId::kernel_calldata,
339 MegaFlavor::EntityId::kernel_calldata_read_counts,
340 MegaFlavor::EntityId::kernel_calldata_inverses,
341 MegaFlavor::EntityId::kernel_calldata_indicator,
342 MegaFlavor::EntityId::q_l>;
343 verify_pattern<KernelCalldataLookup>(DATABUS, [](Entities& e) { return e.q_busread() = FF(1); });
344}
345
346TEST(PatternTest, BilinearFull)
347{
348 // BILINEAR mode (q_bilinear_batched_eq = 1): all selectors non-zero, so both products and all linear
349 // terms are present and the four wires are constrained.
350 verify_pattern<BilinearOrBatchedEqCheckRelation<FF>>(BILINEAR,
351 [](Entities& e) { return e.q_bilinear_batched_eq() = FF(1); });
352}
353
354TEST(PatternTest, BilinearSecondProductAndItsLinearsAbsent)
355{
356 // BILINEAR mode with the second product (q_5) and the w_o/w_4 linear terms (q_o/q_4) zeroed: only
357 // w_l and w_r remain constrained.
358 verify_pattern<BilinearOrBatchedEqCheckRelation<FF>>(BILINEAR, [](Entities& e) {
359 e.q_5() = FF(0);
360 e.q_o() = FF(0);
361 e.q_4() = FF(0);
362 return e.q_bilinear_batched_eq() = FF(1);
363 });
364}
365
366TEST(PatternTest, BatchedEq)
367{
368 // BATCHED_EQ mode (q_bilinear_batched_eq = 2): two independent equalities. Each half-pattern claims its own
369 // pair of wires; their union must equal the wires the relation actually constrains.
370 Entities entities = get_random_entities();
371 entities.q_bilinear_batched_eq() = FF(2);
372
373 Selectors selectors = make_selectors(entities, 2);
374 auto half_1 = get_pattern_wires(BATCHED_EQ_HALF_1, selectors);
375 auto half_2 = get_pattern_wires(BATCHED_EQ_HALF_2, selectors);
376
377 EXPECT_EQ(half_1, (std::set<Wire>{ Wire::W_L, Wire::W_R }));
378 EXPECT_EQ(half_2, (std::set<Wire>{ Wire::W_O, Wire::W_4 }));
379
380 std::set<Wire> combined = half_1;
381 combined.insert(half_2.begin(), half_2.end());
382
383 auto parameters = RelationParameters<FF>::get_random();
384 auto actually_constrained =
385 get_actually_constrained_wires<BilinearOrBatchedEqCheckRelation<FF>>(entities, parameters);
386 EXPECT_EQ(actually_constrained, combined);
387}
388
389TEST(PatternTest, BatchedEqSingleHalf)
390{
391 // A single-half BATCHED_EQ row leaves batched-eq-half-2 zero (its q_o, q_4 selectors and the q_m constant), so
392 // only w_l and w_r are constrained and BATCHED_EQ_HALF_2 claims nothing.
393 Entities entities = get_random_entities();
394 entities.q_o() = FF(0);
395 entities.q_4() = FF(0);
396 entities.q_m() = FF(0);
397 entities.q_bilinear_batched_eq() = FF(2);
398
399 Selectors selectors = make_selectors(entities, 2);
400 auto half_1 = get_pattern_wires(BATCHED_EQ_HALF_1, selectors);
401 auto half_2 = get_pattern_wires(BATCHED_EQ_HALF_2, selectors);
402
403 EXPECT_EQ(half_1, (std::set<Wire>{ Wire::W_L, Wire::W_R }));
404 EXPECT_TRUE(half_2.empty());
405
406 std::set<Wire> combined = half_1;
407 combined.insert(half_2.begin(), half_2.end());
408
409 auto parameters = RelationParameters<FF>::get_random();
410 auto actually_constrained =
411 get_actually_constrained_wires<BilinearOrBatchedEqCheckRelation<FF>>(entities, parameters);
412 EXPECT_EQ(actually_constrained, combined);
413}
414
415// =============================================================================
416// Failure Detection Tests
417//
418// These tests verify the perturbation testing mechanism catches pattern errors.
419// They use intentionally wrong patterns to demonstrate both over-constrained
420// and under-constrained specifications are detected.
421// =============================================================================
422
430TEST(PatternTest, DetectOverConstrained)
431{
432 // Pattern that unconditionally includes w_r when q_m != 0 (ignoring q_arith value)
433 const GatePattern OVERCONSTRAINED_PATTERN = { .name = "overconstrained",
434 .wires = {
435 { Wire::W_L,
436 [](const Selectors& sel) { return sel.q_1_nz || sel.q_m_nz; } },
437 { Wire::W_R,
438 [](const Selectors& sel) {
439 return sel.q_2_nz || sel.q_m_nz;
440 } }, // should check q_arith!=3
441 { Wire::W_O, [](const Selectors& sel) { return sel.q_3_nz; } },
442 { Wire::W_4,
443 [](const Selectors& sel) {
444 return sel.q_4_nz || sel.gate_selector >= 2;
445 } },
446 { Wire::W_4_SHIFT,
447 [](const Selectors& sel) { return sel.gate_selector >= 2; } },
448 { Wire::W_L_SHIFT,
449 [](const Selectors& sel) { return sel.gate_selector == 3; } },
450 } };
451
452 // q_arith=3 disables mul term, q_2=0 means w_r has no linear term, so w_r is unconstrained
453 Entities entities = get_random_entities();
454 entities.q_arith() = FF(3);
455 entities.q_m() = FF(1);
456 entities.q_l() = FF(1);
457 entities.q_r() = FF(0); // q_2 = 0
458
459 Selectors selectors = make_selectors(entities, 3);
460 auto pattern_claims = get_pattern_wires(OVERCONSTRAINED_PATTERN, selectors);
461 auto correct_claims = get_pattern_wires(ARITHMETIC, selectors);
462 auto parameters = RelationParameters<FF>::get_random();
463 auto actually_constrained = get_actually_constrained_wires<ArithmeticRelation<FF>>(entities, parameters);
464
465 EXPECT_TRUE(pattern_claims.contains(Wire::W_R)) << "Over-constrained pattern claims W_R";
466 EXPECT_FALSE(actually_constrained.contains(Wire::W_R)) << "Relation does not constrain W_R in this config";
467 EXPECT_NE(pattern_claims, actually_constrained) << "Over-constrained pattern should not match relation";
468 EXPECT_EQ(correct_claims, actually_constrained) << "Correct ARITHMETIC pattern should match relation";
469}
470
477TEST(PatternTest, DetectUnderConstrained)
478{
479 // Pattern missing w_l and w_r for RAM consistency
480 const GatePattern
481 UNDERCONSTRAINED_PATTERN = { .name = "underconstrained",
482 .wires = {
483 { Wire::W_O, [](const Selectors& sel) { return sel.q_3_nz; } },
484 { Wire::W_4, [](const Selectors& sel) { return sel.q_3_nz; } },
485 { Wire::W_L_SHIFT, [](const Selectors& sel) { return sel.q_3_nz; } },
486 { Wire::W_R_SHIFT, [](const Selectors& sel) { return sel.q_3_nz; } },
487 { Wire::W_O_SHIFT, [](const Selectors& sel) { return sel.q_3_nz; } },
488 { Wire::W_4_SHIFT, [](const Selectors& sel) { return sel.q_3_nz; } },
489 } };
490
491 // RAM consistency check: q_3 != 0
492 Entities entities = get_random_entities();
493 entities.q_memory() = FF(1);
494 entities.q_o() = FF(1); // q_3
495
496 Selectors selectors = make_selectors(entities, 1);
497 auto pattern_claims = get_pattern_wires(UNDERCONSTRAINED_PATTERN, selectors);
498 auto correct_claims = get_pattern_wires(MEMORY, selectors);
499 auto parameters = RelationParameters<FF>::get_random();
500 auto actually_constrained = get_actually_constrained_wires<MemoryRelation<FF>>(entities, parameters);
501
502 EXPECT_FALSE(pattern_claims.contains(Wire::W_L)) << "Under-constrained pattern missing W_L";
503 EXPECT_FALSE(pattern_claims.contains(Wire::W_R)) << "Under-constrained pattern missing W_R";
504 EXPECT_TRUE(actually_constrained.contains(Wire::W_L)) << "Relation constrains W_L";
505 EXPECT_TRUE(actually_constrained.contains(Wire::W_R)) << "Relation constrains W_R";
506 EXPECT_NE(pattern_claims, actually_constrained) << "Under-constrained pattern should not match relation";
507 EXPECT_EQ(correct_claims, actually_constrained) << "Correct MEMORY pattern should match relation";
508}
AllEntities< FF > AllValues
A field element for each entity of the flavor. These entities represent the prover polynomials evalua...
A wrapper for Relations to expose methods used by the Sumcheck prover or verifier to add the contribu...
ArrayOfValues< FF, RelationImpl::SUBRELATION_PARTIAL_LENGTHS > SumcheckArrayOfValuesOverSubrelations
A field element for each entity of the flavor. These entities represent the prover polynomials evalua...
EntitiesT get_random_entities()
UltraFlavor::AllValues UltraEntities
std::set< Wire > get_pattern_wires(const GatePattern &pattern, const Selectors &selectors)
Get the set of wires that a pattern claims are constrained.
void verify_pattern(const GatePattern &pattern, auto configure_selectors)
Generic test: verify a pattern matches what the relation actually constrains.
Selectors make_selectors(const EntitiesT &entities, int64_t gate_selector_value)
TEST(PatternTest, Arithmetic1)
std::set< Wire > get_actually_constrained_wires(const EntitiesT &entities, const auto &parameters)
Get the set of wires that actually affect a relation's output.
uint32_t get_wire(Block &block, size_t gate_index, Wire wire)
const GatePattern POSEIDON2_EXTERNAL
const GatePattern POSEIDON2_INTERNAL
const GatePattern LOOKUP
const GatePattern DATABUS
const GatePattern BATCHED_EQ_HALF_1
const GatePattern NON_NATIVE_FIELD
const GatePattern ELLIPTIC
const GatePattern DELTA_RANGE
const GatePattern ARITHMETIC
const GatePattern MEMORY
const GatePattern BILINEAR
const GatePattern BATCHED_EQ_HALF_2
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static RelationParameters get_random()
static field random_element(numeric::RNG *engine=nullptr) noexcept
Pattern defining which wires are constrained by a gate type.
std::vector< WireSpec > wires
Selector values read from a gate.
VectorField result