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