Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
gate_patterns.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Planned, auditors: [], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
9#include <cstddef>
10#include <cstdint>
11#include <functional>
12#include <string_view>
13#include <vector>
14
16
17using bb::GateKind;
19
20enum class Wire : uint8_t {
21 W_L,
22 W_R,
23 W_O,
24 W_4,
29};
30
38struct Selectors {
39 // The gate selector value (q_arith, q_elliptic, etc. depending on block)
40 int64_t gate_selector = 0;
41
42 // Secondary selectors (can be arbitrary field elements, so track non-zero)
43 bool q_m_nz = false;
44 bool q_1_nz = false;
45 bool q_2_nz = false;
46 bool q_3_nz = false;
47 bool q_4_nz = false;
48 bool q_5_nz = false;
49 bool q_c_nz = false;
50};
51
52using Predicate = std::function<bool(const Selectors&)>;
53
54struct WireSpec {
56 Predicate condition = [](const Selectors&) { return true; };
57};
58
66 std::string_view name;
68};
69
70// ============================================================================
71// Arithmetic Pattern (from ultra_arithmetic_relation.hpp)
72//
73// Subrelation 1:
74// q_arith * [ (-1/2) * (q_arith - 3) * (q_m * w_1 * w_2)
75// + q_1*w_1 + q_2*w_2 + q_3*w_3 + q_4*w_4 + q_c
76// + (q_arith - 1) * w_4_shift ]
77//
78// Subrelation 2 (only when q_arith == 3):
79// q_arith * (q_arith-1) * (q_arith-2) * (w_1 + w_4 - w_1_shift + q_m)
80//
81// gate_selector = q_arith
82// ============================================================================
83
84inline const GatePattern
85 ARITHMETIC = { .name = "arithmetic",
86 .wires = {
87 // w_l: linear term OR mul term (disabled when q_arith==3) OR subrel2
88 { Wire::W_L,
89 [](const Selectors& sel) {
90 return sel.q_1_nz || (sel.q_m_nz && sel.gate_selector != 3) || sel.gate_selector == 3;
91 } },
92 // w_r: linear term OR mul term (disabled when q_arith==3)
93 { Wire::W_R,
94 [](const Selectors& sel) { return sel.q_2_nz || (sel.q_m_nz && sel.gate_selector != 3); } },
95 // w_o: linear term
96 { Wire::W_O, [](const Selectors& sel) { return sel.q_3_nz; } },
97 // w_4: linear term OR subrel2 (subrel2 active when q_arith == 3)
98 { Wire::W_4, [](const Selectors& sel) { return sel.q_4_nz || sel.gate_selector == 3; } },
99 // w_4_shift: when q_arith == 2 or 3 (subrel1 has (q_arith - 1) * w_4_shift)
101 [](const Selectors& sel) { return sel.gate_selector == 2 || sel.gate_selector == 3; } },
102 // w_l_shift: subrel2 when q_arith == 3
103 { Wire::W_L_SHIFT, [](const Selectors& sel) { return sel.gate_selector == 3; } },
104 } };
105
106// ============================================================================
107// Elliptic Pattern (from elliptic_relation.hpp)
108//
109// Point addition (q_is_double == 0, i.e., q_m == 0):
110// x1 = w_r, y1 = w_o, x2 = w_l_shift, x3 = w_r_shift, y3 = w_o_shift, y2 = w_4_shift
111//
112// Point doubling (q_is_double == 1, i.e., q_m == 1):
113// x1 = w_r, y1 = w_o, x3 = w_r_shift, y3 = w_o_shift
114//
115// gate_selector = q_elliptic
116// ============================================================================
117
118inline const GatePattern ELLIPTIC = { .name = "elliptic",
119 .wires = {
120 // x1, y1: always used (both addition and doubling)
121 { Wire::W_R, [](const Selectors&) { return true; } },
122 { Wire::W_O, [](const Selectors&) { return true; } },
123 // x3, y3: always used (both addition and doubling)
124 { Wire::W_R_SHIFT, [](const Selectors&) { return true; } },
125 { Wire::W_O_SHIFT, [](const Selectors&) { return true; } },
126 // x2: only for addition (q_m == 0)
127 { Wire::W_L_SHIFT, [](const Selectors& sel) { return !sel.q_m_nz; } },
128 // y2: only for addition (q_m == 0)
129 { Wire::W_4_SHIFT, [](const Selectors& sel) { return !sel.q_m_nz; } },
130 } };
131
132// ============================================================================
133// Non-Native Field Pattern (from non_native_field_relation.hpp)
134//
135// | gate type | q_2 | q_3 | q_4 | q_m | wires constrained |
136// |---------------|-----|-----|-----|-----|------------------------------------------------|
137// | Limb Accum 1 | 0 | 1 | 1 | 0 | w_l, w_r, w_o, w_4, w_l', w_r' |
138// | Limb Accum 2 | 0 | 1 | 0 | 1 | w_o, w_4, w_l', w_r', w_o', w_4' |
139// | Product 1 | 1 | 1 | 0 | 0 | w_l, w_r, w_o, w_4, w_l', w_r' |
140// | Product 2 | 1 | 0 | 1 | 0 | all 8 wires |
141// | Product 3 | 1 | 0 | 0 | 1 | w_l, w_r, w_4, w_l', w_r', w_o', w_4' |
142//
143// gate_selector = q_nnf
144// ============================================================================
145
146namespace nnf_helpers {
147// Limb Accum 2: !q_2 && q_3 && !q_4 && q_m
148inline bool is_limb_accum_2(const Selectors& sel)
149{
150 return !sel.q_2_nz && sel.q_3_nz && !sel.q_4_nz && sel.q_m_nz;
151}
152// Product 3: q_2 && !q_3 && !q_4 && q_m
153inline bool is_product_3(const Selectors& sel)
154{
155 return sel.q_2_nz && !sel.q_3_nz && !sel.q_4_nz && sel.q_m_nz;
156}
157} // namespace nnf_helpers
158
159inline const GatePattern
160 NON_NATIVE_FIELD = { .name = "non_native_field",
161 .wires = {
162 // w_l, w_r: all gates except Limb Accum 2
163 { Wire::W_L, [](const Selectors& sel) { return !nnf_helpers::is_limb_accum_2(sel); } },
164 { Wire::W_R, [](const Selectors& sel) { return !nnf_helpers::is_limb_accum_2(sel); } },
165 // w_o: all gates except Product 3
166 { Wire::W_O, [](const Selectors& sel) { return !nnf_helpers::is_product_3(sel); } },
167 // w_4: all gates
168 { Wire::W_4, [](const Selectors&) { return true; } },
169 // w_l_shift, w_r_shift: all gates
170 { Wire::W_L_SHIFT, [](const Selectors&) { return true; } },
171 { Wire::W_R_SHIFT, [](const Selectors&) { return true; } },
172 // w_o_shift, w_4_shift: Limb Accum 2, Product 2, Product 3
173 // = q_m || (q_2 && q_4)
175 [](const Selectors& sel) { return sel.q_m_nz || (sel.q_2_nz && sel.q_4_nz); } },
177 [](const Selectors& sel) { return sel.q_m_nz || (sel.q_2_nz && sel.q_4_nz); } },
178 } };
179
180// ============================================================================
181// Memory Pattern (from memory_relation.hpp)
182//
183// | gate type | q_1 | q_2 | q_3 | q_4 | q_m | wires constrained |
184// |---------------------|-----|-----|-----|-----|-----|------------------------------|
185// | RAM/ROM access | 1 | 0 | 0 | 0 | 1 | w_l, w_r, w_o, w_4 |
186// | RAM timestamp check | 1 | 0 | 0 | 1 | 0 | w_l, w_r, w_o, w_l', w_r' |
187// | ROM consistency | 1 | 1 | 0 | 0 | 0 | w_l, w_l', w_4, w_4' |
188// | RAM consistency | 0 | 0 | 1 | 0 | 0 | all 8 wires |
189//
190// gate_selector = q_memory
191// ============================================================================
192
193namespace memory_helpers {
194// RAM timestamp check: q_1 && q_4
195inline bool is_timestamp_check(const Selectors& sel)
196{
197 return sel.q_1_nz && sel.q_4_nz;
198}
199// ROM consistency: q_1 && q_2
200inline bool is_rom_consistency(const Selectors& sel)
201{
202 return sel.q_1_nz && sel.q_2_nz;
203}
204// RAM consistency: q_3
205inline bool is_ram_consistency(const Selectors& sel)
206{
207 return sel.q_3_nz;
208}
209} // namespace memory_helpers
210
211// Note: Access gates (q_1 && q_m) are NOT handled here - they're processed separately
212// via ROM/RAM transcript methods.
213inline const GatePattern
214 MEMORY = { .name = "memory",
215 .wires = {
216 { Wire::W_L,
217 [](const Selectors& sel) {
220 } },
221 { Wire::W_R,
222 [](const Selectors& sel) {
225 } },
226 { Wire::W_O,
227 [](const Selectors& sel) {
230 } },
231 { Wire::W_4,
232 [](const Selectors& sel) {
234 } },
236 [](const Selectors& sel) {
239 } },
241 [](const Selectors& sel) {
243 } },
244 { Wire::W_O_SHIFT, [](const Selectors& sel) { return memory_helpers::is_ram_consistency(sel); } },
246 [](const Selectors& sel) {
248 } },
249 } };
250
251// ============================================================================
252// Lookup Pattern (from logderiv_lookup_relation.hpp)
253//
254// The read term uses: w_l, w_r, w_o (always)
255// Shifted wires used when step_size != 0:
256// w_l_shift if q_2 (q_r) != 0
257// w_r_shift if q_m != 0
258// w_o_shift if q_c != 0
259//
260// gate_selector = q_lookup
261// ============================================================================
262
263inline const GatePattern LOOKUP = { .name = "lookup",
264 .wires = {
265 { Wire::W_L, [](const Selectors&) { return true; } },
266 { Wire::W_R, [](const Selectors&) { return true; } },
267 { Wire::W_O, [](const Selectors&) { return true; } },
268 { Wire::W_L_SHIFT, [](const Selectors& sel) { return sel.q_2_nz; } },
269 { Wire::W_R_SHIFT, [](const Selectors& sel) { return sel.q_m_nz; } },
270 { Wire::W_O_SHIFT, [](const Selectors& sel) { return sel.q_c_nz; } },
271 } };
272
273// ============================================================================
274// Delta Range Pattern (from delta_range_constraint_relation.hpp)
275//
276// D_0 = w_2 - w_1, D_1 = w_3 - w_2, D_2 = w_4 - w_3, D_3 = w_1_shift - w_4
277//
278// gate_selector = q_delta_range
279// ============================================================================
280
281inline const GatePattern DELTA_RANGE = { .name = "delta_range",
282 .wires = {
283 { Wire::W_L, [](const Selectors&) { return true; } },
284 { Wire::W_R, [](const Selectors&) { return true; } },
285 { Wire::W_O, [](const Selectors&) { return true; } },
286 { Wire::W_4, [](const Selectors&) { return true; } },
287 { Wire::W_L_SHIFT, [](const Selectors&) { return true; } },
288 } };
289
290// ============================================================================
291// Poseidon2 Internal Pattern (from poseidon2_internal_relation.hpp)
292//
293// All 4 current wires and all 4 shifted wires are constrained
294//
295// gate_selector = q_poseidon2_internal
296// ============================================================================
297
298inline const GatePattern POSEIDON2_INTERNAL = { .name = "poseidon2_internal",
299 .wires = {
300 { Wire::W_L, [](const Selectors&) { return true; } },
301 { Wire::W_R, [](const Selectors&) { return true; } },
302 { Wire::W_O, [](const Selectors&) { return true; } },
303 { Wire::W_4, [](const Selectors&) { return true; } },
304 { Wire::W_L_SHIFT, [](const Selectors&) { return true; } },
305 { Wire::W_R_SHIFT, [](const Selectors&) { return true; } },
306 { Wire::W_O_SHIFT, [](const Selectors&) { return true; } },
307 { Wire::W_4_SHIFT, [](const Selectors&) { return true; } },
308 } };
309
310// ============================================================================
311// Poseidon2 External Pattern (from poseidon2_external_relation.hpp)
312//
313// All 4 current wires and all 4 shifted wires are constrained
314//
315// gate_selector = q_poseidon2_external
316// ============================================================================
317
318inline const GatePattern POSEIDON2_EXTERNAL = { .name = "poseidon2_external",
319 .wires = {
320 { Wire::W_L, [](const Selectors&) { return true; } },
321 { Wire::W_R, [](const Selectors&) { return true; } },
322 { Wire::W_O, [](const Selectors&) { return true; } },
323 { Wire::W_4, [](const Selectors&) { return true; } },
324 { Wire::W_L_SHIFT, [](const Selectors&) { return true; } },
325 { Wire::W_R_SHIFT, [](const Selectors&) { return true; } },
326 { Wire::W_O_SHIFT, [](const Selectors&) { return true; } },
327 { Wire::W_4_SHIFT, [](const Selectors&) { return true; } },
328 } };
329
330// ============================================================================
331// Poseidon2 Initial External Pattern (from poseidon2_initial_external_relation.hpp)
332//
333// All 4 current wires and all 4 shifted wires are constrained
334//
335// gate_selector = q_poseidon2_external_initial
336// ============================================================================
337
338inline const GatePattern POSEIDON2_INITIAL_EXTERNAL = { .name = "poseidon2_initial_external",
339 .wires = {
340 { Wire::W_L, [](const Selectors&) { return true; } },
341 { Wire::W_R, [](const Selectors&) { return true; } },
342 { Wire::W_O, [](const Selectors&) { return true; } },
343 { Wire::W_4, [](const Selectors&) { return true; } },
344 { Wire::W_L_SHIFT, [](const Selectors&) { return true; } },
345 { Wire::W_R_SHIFT, [](const Selectors&) { return true; } },
346 { Wire::W_O_SHIFT, [](const Selectors&) { return true; } },
347 { Wire::W_4_SHIFT, [](const Selectors&) { return true; } },
348 } };
349
350// ============================================================================
351// Poseidon2 Quad-Internal Pattern (from poseidon2_quad_internal_relation.hpp)
352//
353// gate_selector = q_poseidon2_quad_internal
354// ============================================================================
355
356inline const GatePattern POSEIDON2_QUAD_INTERNAL = { .name = "poseidon2_quad_internal",
357 .wires = {
358 { Wire::W_L, [](const Selectors&) { return true; } },
359 { Wire::W_R, [](const Selectors&) { return true; } },
360 { Wire::W_O, [](const Selectors&) { return true; } },
361 { Wire::W_4, [](const Selectors&) { return true; } },
362 { Wire::W_L_SHIFT, [](const Selectors&) { return true; } },
363 { Wire::W_R_SHIFT, [](const Selectors&) { return true; } },
364 { Wire::W_O_SHIFT, [](const Selectors&) { return true; } },
365 { Wire::W_4_SHIFT, [](const Selectors&) { return true; } },
366 } };
367
368// ============================================================================
369// Poseidon2 Quad-Internal Terminal Pattern
370// (from poseidon2_quad_internal_terminal_relation.hpp)
371//
372// gate_selector = q_poseidon2_quad_internal_terminal
373// ============================================================================
374
375inline const GatePattern
376 POSEIDON2_QUAD_INTERNAL_TERMINAL = { .name = "poseidon2_quad_internal_terminal",
377 .wires = {
378 { Wire::W_L, [](const Selectors&) { return true; } },
379 { Wire::W_R, [](const Selectors&) { return true; } },
380 { Wire::W_O, [](const Selectors&) { return true; } },
381 { Wire::W_4, [](const Selectors&) { return true; } },
382 { Wire::W_L_SHIFT, [](const Selectors&) { return true; } },
383 { Wire::W_R_SHIFT, [](const Selectors&) { return true; } },
384 { Wire::W_O_SHIFT, [](const Selectors&) { return true; } },
385 { Wire::W_4_SHIFT, [](const Selectors&) { return true; } },
386 } };
387
388// ============================================================================
389// Poseidon2 Transition Entry Pattern (from poseidon2_transition_entry_relation.hpp)
390//
391// gate_selector = q_poseidon2_transition_entry
392// ============================================================================
393
394inline const GatePattern POSEIDON2_TRANSITION_ENTRY = { .name = "poseidon2_transition_entry",
395 .wires = {
396 { Wire::W_L, [](const Selectors&) { return true; } },
397 { Wire::W_R, [](const Selectors&) { return true; } },
398 { Wire::W_O, [](const Selectors&) { return true; } },
399 { Wire::W_4, [](const Selectors&) { return true; } },
400 { Wire::W_R_SHIFT, [](const Selectors&) { return true; } },
401 { Wire::W_O_SHIFT, [](const Selectors&) { return true; } },
402 { Wire::W_4_SHIFT, [](const Selectors&) { return true; } },
403 } };
404
405// ============================================================================
406// Databus Pattern (from databus_lookup_relation.hpp)
407//
408// Read term uses: w_l (value), w_r (index)
409//
410// gate_selector = q_busread
411// ============================================================================
412
413inline const GatePattern DATABUS = { .name = "databus",
414 .wires = {
415 { Wire::W_L, [](const Selectors&) { return true; } },
416 { Wire::W_R, [](const Selectors&) { return true; } },
417 } };
418
419// ============================================================================
420// Bilinear / batched-eq patterns (from bilinear_or_batched_eq_check_relation.hpp; Mega-only)
421//
422// The shared arithmetic-block selector q_bilinear_batched_eq multiplexes two row-modes:
423// gate_selector == 1 (BILINEAR): a single equation over all four wires
424// q_m·w_l·w_r + q_5·w_l·w_o + q_l·w_l + q_r·w_r + q_o·w_o + q_4·w_4 + q_c = 0
425// The two products share wire w_l, so w_l is constrained by either product selector (q_m, q_5) or
426// its linear selector q_1. w_4 appears only in its linear term q_4. A wire is constrained iff any
427// of its selectors is non-zero.
428// gate_selector == 2 (BATCHED_EQ): two INDEPENDENT equalities
429// batched-eq-half-1: q_l·w_l + q_r·w_r + q_c = 0 (selectors q_1, q_2)
430// batched-eq-half-2: q_o·w_o + q_4·w_4 + q_m = 0 (selectors q_3, q_4)
431// The two halves share no witnesses by construction, so they are modelled as two patterns and
432// connected separately — connecting all four wires would hide an under-constrained witness.
433//
434// gate_selector = q_bilinear_batched_eq
435// ============================================================================
436
437inline const GatePattern
438 BILINEAR = { .name = "bilinear",
439 .wires = {
440 { Wire::W_L,
441 [](const Selectors& sel) {
442 return sel.gate_selector == 1 && (sel.q_m_nz || sel.q_5_nz || sel.q_1_nz);
443 } },
444 { Wire::W_R,
445 [](const Selectors& sel) { return sel.gate_selector == 1 && (sel.q_m_nz || sel.q_2_nz); } },
446 { Wire::W_O,
447 [](const Selectors& sel) { return sel.gate_selector == 1 && (sel.q_5_nz || sel.q_3_nz); } },
448 { Wire::W_4, [](const Selectors& sel) { return sel.gate_selector == 1 && sel.q_4_nz; } },
449 } };
450
451inline const GatePattern
452 BATCHED_EQ_HALF_1 = { .name = "batched_eq_half_1",
453 .wires = {
454 { Wire::W_L, [](const Selectors& sel) { return sel.gate_selector == 2 && sel.q_1_nz; } },
455 { Wire::W_R, [](const Selectors& sel) { return sel.gate_selector == 2 && sel.q_2_nz; } },
456 } };
457
458inline const GatePattern
459 BATCHED_EQ_HALF_2 = { .name = "batched_eq_half_2",
460 .wires = {
461 { Wire::W_O, [](const Selectors& sel) { return sel.gate_selector == 2 && sel.q_3_nz; } },
462 { Wire::W_4, [](const Selectors& sel) { return sel.gate_selector == 2 && sel.q_4_nz; } },
463 } };
464
465// ============================================================================
466// Helper functions
467// ============================================================================
468
469template <typename Block> Selectors read_selectors(Block& block, size_t gate_index, GateKind kind)
470{
471 return Selectors{
472 .gate_selector = static_cast<int64_t>(static_cast<uint64_t>(read_gate_selector(block, kind, gate_index))),
473 .q_m_nz = !block.q_m()[gate_index].is_zero(),
474 .q_1_nz = !block.q_1()[gate_index].is_zero(),
475 .q_2_nz = !block.q_2()[gate_index].is_zero(),
476 .q_3_nz = !block.q_3()[gate_index].is_zero(),
477 .q_4_nz = !block.q_4()[gate_index].is_zero(),
478 .q_5_nz = !block.q_5()[gate_index].is_zero(),
479 .q_c_nz = !block.q_c()[gate_index].is_zero(),
480 };
481}
482
483template <typename Block> uint32_t get_wire(Block& block, size_t gate_index, Wire wire)
484{
485 switch (wire) {
486 case Wire::W_L:
487 return block.w_l()[gate_index];
488 case Wire::W_R:
489 return block.w_r()[gate_index];
490 case Wire::W_O:
491 return block.w_o()[gate_index];
492 case Wire::W_4:
493 return block.w_4()[gate_index];
494 case Wire::W_L_SHIFT:
495 return block.w_l()[gate_index + 1];
496 case Wire::W_R_SHIFT:
497 return block.w_r()[gate_index + 1];
498 case Wire::W_O_SHIFT:
499 return block.w_o()[gate_index + 1];
500 case Wire::W_4_SHIFT:
501 return block.w_4()[gate_index + 1];
502 }
503 return 0;
504}
505
506inline bool is_shifted(Wire wire)
507{
508 return wire >= Wire::W_L_SHIFT;
509}
510
511template <typename Block>
512std::vector<uint32_t> extract_wires(Block& block,
513 size_t gate_index,
514 const GatePattern& pattern,
515 const Selectors& selectors)
516{
517 std::vector<uint32_t> result;
518 for (const auto& wire_spec : pattern.wires) {
519 // Bounds check for shifted wires
520 if (is_shifted(wire_spec.wire) && gate_index + 1 >= block.size()) {
521 continue;
522 }
523 if (wire_spec.condition(selectors)) {
524 result.push_back(get_wire(block, gate_index, wire_spec.wire));
525 }
526 }
527 return result;
528}
529
530} // namespace bb::gate_patterns
bool is_timestamp_check(const Selectors &sel)
bool is_rom_consistency(const Selectors &sel)
bool is_ram_consistency(const Selectors &sel)
bool is_limb_accum_2(const Selectors &sel)
bool is_product_3(const Selectors &sel)
const GatePattern POSEIDON2_TRANSITION_ENTRY
uint32_t get_wire(Block &block, size_t gate_index, Wire wire)
const GatePattern POSEIDON2_EXTERNAL
const GatePattern POSEIDON2_INTERNAL
const GatePattern LOOKUP
Selectors read_selectors(Block &block, size_t gate_index, GateKind kind)
std::function< bool(const Selectors &)> Predicate
const GatePattern POSEIDON2_INITIAL_EXTERNAL
const GatePattern DATABUS
const GatePattern BATCHED_EQ_HALF_1
const GatePattern POSEIDON2_QUAD_INTERNAL_TERMINAL
const GatePattern NON_NATIVE_FIELD
const GatePattern ELLIPTIC
const GatePattern DELTA_RANGE
const GatePattern ARITHMETIC
const GatePattern MEMORY
const GatePattern BILINEAR
std::vector< uint32_t > extract_wires(Block &block, size_t gate_index, const GatePattern &pattern, const Selectors &selectors)
const GatePattern POSEIDON2_QUAD_INTERNAL
bool is_shifted(Wire wire)
const GatePattern BATCHED_EQ_HALF_2
FF read_gate_selector(const ExecutionTraceBlock< FF, NUM_WIRES > &block, GateKind kind, size_t idx)
Gate-selector value at (block, idx) for kind, returning zero if the block does not own this kind or t...
GateKind
Tag identifying which gate selector a block owns. Used by cross-block readers to decide whether (bloc...
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Pattern defining which wires are constrained by a gate type.
std::vector< WireSpec > wires
Selector values read from a gate.
VectorField result