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;
34
35template <typename E> E get_random_entities()
36{
37 E entities;
38 for (auto& field : entities.get_all()) {
40 }
41 return entities;
42}
43
44template <typename E> FF& get_wire(E& entities, Wire wire)
45{
46 switch (wire) {
47 case Wire::W_L:
48 return entities.w_l;
49 case Wire::W_R:
50 return entities.w_r;
51 case Wire::W_O:
52 return entities.w_o;
53 case Wire::W_4:
54 return entities.w_4;
55 case Wire::W_L_SHIFT:
56 return entities.w_l_shift;
57 case Wire::W_R_SHIFT:
58 return entities.w_r_shift;
59 case Wire::W_O_SHIFT:
60 return entities.w_o_shift;
61 case Wire::W_4_SHIFT:
62 return entities.w_4_shift;
63 }
64 __builtin_unreachable();
65}
66
67template <typename E> Selectors make_selectors(const E& entities, int64_t gate_selector_value)
68{
69 return Selectors{
70 .gate_selector = gate_selector_value,
71 .q_m_nz = !entities.q_m.is_zero(),
72 .q_1_nz = !entities.q_l.is_zero(),
73 .q_2_nz = !entities.q_r.is_zero(),
74 .q_3_nz = !entities.q_o.is_zero(),
75 .q_4_nz = !entities.q_4.is_zero(),
76 .q_c_nz = !entities.q_c.is_zero(),
77 };
78}
79
83std::set<Wire> get_pattern_wires(const GatePattern& pattern, const Selectors& selectors)
84{
85 std::set<Wire> result;
86 for (const auto& wire_spec : pattern.wires) {
87 if (wire_spec.condition(selectors)) {
88 result.insert(wire_spec.wire);
89 }
90 }
91 return result;
92}
93
99template <typename Relation, typename E>
100std::set<Wire> get_actually_constrained_wires(const E& entities, const auto& parameters)
101{
102 std::set<Wire> constrained;
103
104 // Evaluate relation at base point
106 Relation::accumulate(base_result, entities, parameters, FF(1));
107
108 // For each wire position, perturb a copy and check if output changes
109 for (Wire wire : { Wire::W_L,
110 Wire::W_R,
111 Wire::W_O,
112 Wire::W_4,
113 Wire::W_L_SHIFT,
114 Wire::W_R_SHIFT,
115 Wire::W_O_SHIFT,
116 Wire::W_4_SHIFT }) {
117 E perturbed = entities;
118 get_wire(perturbed, wire) += FF::random_element();
119
120 typename Relation::SumcheckArrayOfValuesOverSubrelations perturbed_result{};
121 Relation::accumulate(perturbed_result, perturbed, parameters, FF(1));
122
123 if (base_result != perturbed_result) {
124 constrained.insert(wire);
125 }
126 }
127
128 return constrained;
129}
130
136template <typename Relation, typename E = Entities>
137void verify_pattern(const GatePattern& pattern, auto configure_selectors)
138{
139 E entities = get_random_entities<E>();
140 FF gate_selector = configure_selectors(entities);
141 int64_t gate_selector_value = static_cast<int64_t>(uint64_t(gate_selector));
142
143 Selectors selectors = make_selectors(entities, gate_selector_value);
144 auto pattern_claims = get_pattern_wires(pattern, selectors);
145
146 auto parameters = RelationParameters<FF>::get_random();
147 auto actually_constrained = get_actually_constrained_wires<Relation, E>(entities, parameters);
148
149 EXPECT_EQ(actually_constrained, pattern_claims);
150}
151
152// =============================================================================
153// Pattern Tests
154// =============================================================================
155
156TEST(PatternTest, Arithmetic1)
157{
158 verify_pattern<ArithmeticRelation<FF>>(ARITHMETIC, [](Entities& e) { return e.q_arith = FF(1); });
159}
160
161TEST(PatternTest, Arithmetic2)
162{
163 verify_pattern<ArithmeticRelation<FF>>(ARITHMETIC, [](Entities& e) { return e.q_arith = FF(2); });
164}
165
166TEST(PatternTest, Arithmetic3)
167{
168 verify_pattern<ArithmeticRelation<FF>>(ARITHMETIC, [](Entities& e) { return e.q_arith = FF(3); });
169}
170
171TEST(PatternTest, Arithmetic3WithQmZero)
172{
173 verify_pattern<ArithmeticRelation<FF>>(ARITHMETIC, [](Entities& e) {
174 e.q_m = FF(0);
175 return e.q_arith = FF(3);
176 });
177}
178
179TEST(PatternTest, EllipticAdd)
180{
181 verify_pattern<EllipticRelation<FF>>(ELLIPTIC, [](Entities& e) {
182 e.q_m = FF(0);
183 e.q_l = FF(-1);
184 return e.q_elliptic = FF(1);
185 });
186}
187
188TEST(PatternTest, EllipticDouble)
189{
190 verify_pattern<EllipticRelation<FF>>(ELLIPTIC, [](Entities& e) {
191 e.q_m = FF(1);
192 e.q_l = FF(-1);
193 return e.q_elliptic = FF(1);
194 });
195}
196
197TEST(PatternTest, DeltaRange)
198{
199 verify_pattern<DeltaRangeConstraintRelation<FF>>(DELTA_RANGE, [](Entities& e) { return e.q_delta_range = FF(1); });
200}
201
202TEST(PatternTest, NNFLimbAccum1)
203{
204 verify_pattern<NonNativeFieldRelation<FF>>(NON_NATIVE_FIELD, [](Entities& e) {
205 e.q_r = FF(0);
206 e.q_o = FF(1);
207 e.q_4 = FF(1);
208 e.q_m = FF(0);
209 return e.q_nnf = FF(1);
210 });
211}
212
213TEST(PatternTest, NNFLimbAccum2)
214{
215 verify_pattern<NonNativeFieldRelation<FF>>(NON_NATIVE_FIELD, [](Entities& e) {
216 e.q_r = FF(0);
217 e.q_o = FF(1);
218 e.q_4 = FF(0);
219 e.q_m = FF(1);
220 return e.q_nnf = FF(1);
221 });
222}
223
224TEST(PatternTest, NNFProduct1)
225{
226 verify_pattern<NonNativeFieldRelation<FF>>(NON_NATIVE_FIELD, [](Entities& e) {
227 e.q_r = FF(1);
228 e.q_o = FF(1);
229 e.q_4 = FF(0);
230 e.q_m = FF(0);
231 return e.q_nnf = FF(1);
232 });
233}
234
235TEST(PatternTest, NNFProduct2)
236{
237 verify_pattern<NonNativeFieldRelation<FF>>(NON_NATIVE_FIELD, [](Entities& e) {
238 e.q_r = FF(1);
239 e.q_o = FF(0);
240 e.q_4 = FF(1);
241 e.q_m = FF(0);
242 return e.q_nnf = FF(1);
243 });
244}
245
246TEST(PatternTest, NNFProduct3)
247{
248 verify_pattern<NonNativeFieldRelation<FF>>(NON_NATIVE_FIELD, [](Entities& e) {
249 e.q_r = FF(1);
250 e.q_o = FF(0);
251 e.q_4 = FF(0);
252 e.q_m = FF(1);
253 return e.q_nnf = FF(1);
254 });
255}
256
257TEST(PatternTest, MemoryRamRomAccess)
258{
259 verify_pattern<MemoryRelation<FF>>(MEMORY, [](Entities& e) {
260 e.q_l = FF(1);
261 e.q_m = FF(1);
262 return e.q_memory = FF(1);
263 });
264}
265
266TEST(PatternTest, MemoryRamTimestamp)
267{
268 verify_pattern<MemoryRelation<FF>>(MEMORY, [](Entities& e) {
269 e.q_l = FF(1);
270 e.q_4 = FF(1);
271 return e.q_memory = FF(1);
272 });
273}
274
275TEST(PatternTest, MemoryRomConsistency)
276{
277 verify_pattern<MemoryRelation<FF>>(MEMORY, [](Entities& e) {
278 e.q_l = FF(1);
279 e.q_r = FF(1);
280 return e.q_memory = FF(1);
281 });
282}
283
284TEST(PatternTest, MemoryRamConsistency)
285{
286 verify_pattern<MemoryRelation<FF>>(MEMORY, [](Entities& e) {
287 e.q_o = FF(1);
288 return e.q_memory = FF(1);
289 });
290}
291
292TEST(PatternTest, Poseidon2Internal)
293{
294 // q_poseidon2_internal lives on UltraFlavor only; MegaFlavor covers all internal rounds via the
295 // compressed quad-internal block.
296 using UltraEntities = UltraFlavor::AllValues;
297 verify_pattern<Poseidon2InternalRelation<FF>, UltraEntities>(
298 POSEIDON2_INTERNAL, [](UltraEntities& e) { return e.q_poseidon2_internal = FF(1); });
299}
300
301TEST(PatternTest, Poseidon2External)
302{
303 verify_pattern<Poseidon2ExternalRelation<FF>>(POSEIDON2_EXTERNAL,
304 [](Entities& e) { return e.q_poseidon2_external = FF(1); });
305}
306
307TEST(PatternTest, Poseidon2InitialExternal)
308{
309 verify_pattern<Poseidon2InitialExternalRelation<FF>>(
310 POSEIDON2_INITIAL_EXTERNAL, [](Entities& e) { return e.q_poseidon2_external_initial = FF(1); });
311}
312
313TEST(PatternTest, LookupBasic)
314{
315 verify_pattern<LogDerivLookupRelation<FF>>(LOOKUP, [](Entities& e) {
316 // No shifted wires (step_size selectors all zero)
317 e.q_r = FF(0);
318 e.q_m = FF(0);
319 e.q_c = FF(0);
320 return e.q_lookup = FF(1);
321 });
322}
323
324TEST(PatternTest, LookupWithShiftedWires)
325{
326 verify_pattern<LogDerivLookupRelation<FF>>(LOOKUP, [](Entities& e) {
327 // Enable all shifted wires
328 e.q_r = FF(1);
329 e.q_m = FF(1);
330 e.q_c = FF(1);
331 return e.q_lookup = FF(1);
332 });
333}
334
335TEST(PatternTest, DatabusRead)
336{
337 verify_pattern<DatabusLookupRelation<FF>>(DATABUS, [](Entities& e) { return e.q_busread = FF(1); });
338}
339
340// =============================================================================
341// Failure Detection Tests
342//
343// These tests verify the perturbation testing mechanism catches pattern errors.
344// They use intentionally wrong patterns to demonstrate both over-constrained
345// and under-constrained specifications are detected.
346// =============================================================================
347
355TEST(PatternTest, DetectOverConstrained)
356{
357 // Pattern that unconditionally includes w_r when q_m != 0 (ignoring q_arith value)
358 const GatePattern OVERCONSTRAINED_PATTERN = { .name = "overconstrained",
359 .wires = {
360 { Wire::W_L,
361 [](const Selectors& sel) { return sel.q_1_nz || sel.q_m_nz; } },
362 { Wire::W_R,
363 [](const Selectors& sel) {
364 return sel.q_2_nz || sel.q_m_nz;
365 } }, // should check q_arith!=3
366 { Wire::W_O, [](const Selectors& sel) { return sel.q_3_nz; } },
367 { Wire::W_4,
368 [](const Selectors& sel) {
369 return sel.q_4_nz || sel.gate_selector >= 2;
370 } },
371 { Wire::W_4_SHIFT,
372 [](const Selectors& sel) { return sel.gate_selector >= 2; } },
373 { Wire::W_L_SHIFT,
374 [](const Selectors& sel) { return sel.gate_selector == 3; } },
375 } };
376
377 // q_arith=3 disables mul term, q_2=0 means w_r has no linear term, so w_r is unconstrained
378 Entities entities = get_random_entities<Entities>();
379 entities.q_arith = FF(3);
380 entities.q_m = FF(1);
381 entities.q_l = FF(1);
382 entities.q_r = FF(0); // q_2 = 0
383
384 Selectors selectors = make_selectors(entities, 3);
385 auto pattern_claims = get_pattern_wires(OVERCONSTRAINED_PATTERN, selectors);
386 auto correct_claims = get_pattern_wires(ARITHMETIC, selectors);
387 auto parameters = RelationParameters<FF>::get_random();
388 auto actually_constrained = get_actually_constrained_wires<ArithmeticRelation<FF>, Entities>(entities, parameters);
389
390 EXPECT_TRUE(pattern_claims.contains(Wire::W_R)) << "Over-constrained pattern claims W_R";
391 EXPECT_FALSE(actually_constrained.contains(Wire::W_R)) << "Relation does not constrain W_R in this config";
392 EXPECT_NE(pattern_claims, actually_constrained) << "Over-constrained pattern should not match relation";
393 EXPECT_EQ(correct_claims, actually_constrained) << "Correct ARITHMETIC pattern should match relation";
394}
395
402TEST(PatternTest, DetectUnderConstrained)
403{
404 // Pattern missing w_l and w_r for RAM consistency
405 const GatePattern
406 UNDERCONSTRAINED_PATTERN = { .name = "underconstrained",
407 .wires = {
408 { Wire::W_O, [](const Selectors& sel) { return sel.q_3_nz; } },
409 { Wire::W_4, [](const Selectors& sel) { return sel.q_3_nz; } },
410 { Wire::W_L_SHIFT, [](const Selectors& sel) { return sel.q_3_nz; } },
411 { Wire::W_R_SHIFT, [](const Selectors& sel) { return sel.q_3_nz; } },
412 { Wire::W_O_SHIFT, [](const Selectors& sel) { return sel.q_3_nz; } },
413 { Wire::W_4_SHIFT, [](const Selectors& sel) { return sel.q_3_nz; } },
414 } };
415
416 // RAM consistency check: q_3 != 0
417 Entities entities = get_random_entities<Entities>();
418 entities.q_memory = FF(1);
419 entities.q_o = FF(1); // q_3
420
421 Selectors selectors = make_selectors(entities, 1);
422 auto pattern_claims = get_pattern_wires(UNDERCONSTRAINED_PATTERN, selectors);
423 auto correct_claims = get_pattern_wires(MEMORY, selectors);
424 auto parameters = RelationParameters<FF>::get_random();
425 auto actually_constrained = get_actually_constrained_wires<MemoryRelation<FF>, Entities>(entities, parameters);
426
427 EXPECT_FALSE(pattern_claims.contains(Wire::W_L)) << "Under-constrained pattern missing W_L";
428 EXPECT_FALSE(pattern_claims.contains(Wire::W_R)) << "Under-constrained pattern missing W_R";
429 EXPECT_TRUE(actually_constrained.contains(Wire::W_L)) << "Relation constrains W_L";
430 EXPECT_TRUE(actually_constrained.contains(Wire::W_R)) << "Relation constrains W_R";
431 EXPECT_NE(pattern_claims, actually_constrained) << "Under-constrained pattern should not match relation";
432 EXPECT_EQ(correct_claims, actually_constrained) << "Correct MEMORY pattern should match relation";
433}
A base class labelling all entities (for instance, all of the polynomials used by the prover during s...
AllEntities_< FF > AllValues
A field element for each entity of the flavor. These entities represent the prover polynomials evalua...
ArrayOfValues< FF, RelationImpl::SUBRELATION_PARTIAL_LENGTHS > SumcheckArrayOfValuesOverSubrelations
AllValues_< HasZK > AllValues
E get_random_entities()
Selectors make_selectors(const E &entities, int64_t gate_selector_value)
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.
TEST(PatternTest, Arithmetic1)
std::set< Wire > get_actually_constrained_wires(const E &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 POSEIDON2_INITIAL_EXTERNAL
const GatePattern DATABUS
const GatePattern NON_NATIVE_FIELD
const GatePattern ELLIPTIC
const GatePattern DELTA_RANGE
const GatePattern ARITHMETIC
const GatePattern MEMORY
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.