barretenberg
Loading...
Searching...
No Matches
permutation_lib.hpp
Go to the documentation of this file.
1
8#pragma once
9
10#include "barretenberg/common/ref_vector.hpp"
11#include "barretenberg/ecc/curves/bn254/fr.hpp"
13#include "barretenberg/plonk/proof_system/proving_key/proving_key.hpp"
14#include "barretenberg/polynomials/iterate_over_domain.hpp"
15#include "barretenberg/polynomials/polynomial.hpp"
16
17#include <algorithm>
18#include <cstddef>
19#include <cstdint>
20#include <initializer_list>
21#include <string>
22#include <utility>
23#include <vector>
24
25// TODO(Cody): very little code is shared; should split this up into plonk/honk files.
26
27namespace proof_system {
28
36struct cycle_node {
37 uint32_t wire_index;
38 uint32_t gate_index;
39};
40
48 uint32_t row_index = 0;
49 uint8_t column_index = 0;
50 bool is_public_input = false;
51 bool is_tag = false;
52};
53
54template <size_t NUM_WIRES> struct PermutationMapping {
55 using Mapping = std::array<std::vector<permutation_subgroup_element>, NUM_WIRES>;
56 Mapping sigmas;
57 Mapping ids;
58};
59
60using CyclicPermutation = std::vector<cycle_node>;
61
62namespace {
63
73template <typename Flavor>
74std::vector<CyclicPermutation> compute_wire_copy_cycles(const typename Flavor::CircuitBuilder& circuit_constructor)
75{
76 // Reference circuit constructor members
77 const size_t num_gates = circuit_constructor.num_gates;
78 std::span<const uint32_t> public_inputs = circuit_constructor.public_inputs;
79 const size_t num_public_inputs = public_inputs.size();
80
81 // Each variable represents one cycle
82 const size_t number_of_cycles = circuit_constructor.variables.size();
83 std::vector<CyclicPermutation> copy_cycles(number_of_cycles);
84 copy_cycles.reserve(num_gates * 3);
85
86 // Represents the index of a variable in circuit_constructor.variables
87 std::span<const uint32_t> real_variable_index = circuit_constructor.real_variable_index;
88
89 // For some flavors, we need to ensure the value in the 0th index of each wire is 0 to allow for left-shift by 1. To
90 // do this, we add the wires of the first gate in the execution trace to the "zero index" copy cycle.
91 if constexpr (Flavor::has_zero_row) {
92 for (size_t wire_idx = 0; wire_idx < Flavor::NUM_WIRES; ++wire_idx) {
93 const auto wire_index = static_cast<uint32_t>(wire_idx);
94 const uint32_t gate_index = 0; // place zeros at 0th index
95 const uint32_t zero_idx = circuit_constructor.zero_idx; // index of constant zero in variables
96 copy_cycles[zero_idx].emplace_back(cycle_node{ wire_index, gate_index });
97 }
98 }
99
100 // Define offsets for placement of public inputs and gates in execution trace
101 const size_t num_zero_rows = Flavor::has_zero_row ? 1 : 0;
102 size_t pub_inputs_offset = num_zero_rows;
103 size_t gates_offset = num_public_inputs + num_zero_rows;
104
105 // If Goblin, adjust offsets to account for ecc op gates and update copy cycles to include these gates
106 if constexpr (IsGoblinFlavor<Flavor>) {
107 // Set ecc op gate offset and update offsets for PI and conventional gates
108 const size_t op_gates_offset = num_zero_rows;
109 const size_t num_ecc_op_gates = circuit_constructor.num_ecc_op_gates;
110 pub_inputs_offset += num_ecc_op_gates;
111 gates_offset += num_ecc_op_gates;
112
113 const auto& op_wires = circuit_constructor.ecc_op_wires;
114 // Iterate over all variables of the ecc op gates, and add a corresponding node to the cycle for that variable
115 for (size_t i = 0; i < num_ecc_op_gates; ++i) {
116 for (size_t op_wire_idx = 0; op_wire_idx < Flavor::NUM_WIRES; ++op_wire_idx) {
117 const uint32_t var_index = circuit_constructor.real_variable_index[op_wires[op_wire_idx][i]];
118 const auto wire_index = static_cast<uint32_t>(op_wire_idx);
119 const auto gate_idx = static_cast<uint32_t>(i + op_gates_offset);
120 copy_cycles[var_index].emplace_back(cycle_node{ wire_index, gate_idx });
121 }
122 }
123 }
124
125 // We use the permutation argument to enforce the public input variables to be equal to values provided by the
126 // verifier. The convension we use is to place the public input values as the first rows of witness vectors.
127 // More specifically, we set the LEFT and RIGHT wires to be the public inputs and set the other elements of the row
128 // to 0. All selectors are zero at these rows, so they are fully unconstrained. The "real" gates that follow can use
129 // references to these variables.
130 //
131 // The copy cycle for the i-th public variable looks like
132 // (i) -> (n+i) -> (i') -> ... -> (i'')
133 // (Using the convention that W^L_i = W_i and W^R_i = W_{n+i}, W^O_i = W_{2n+i})
134 //
135 // This loop initializes the i-th cycle with (i) -> (n+i), meaning that we always expect W^L_i = W^R_i,
136 // for all i s.t. row i defines a public input.
137 for (size_t i = 0; i < num_public_inputs; ++i) {
138 const uint32_t public_input_index = real_variable_index[public_inputs[i]];
139 const auto gate_index = static_cast<uint32_t>(i + pub_inputs_offset);
140 // These two nodes must be in adjacent locations in the cycle for correct handling of public inputs
141 copy_cycles[public_input_index].emplace_back(cycle_node{ 0, gate_index });
142 copy_cycles[public_input_index].emplace_back(cycle_node{ 1, gate_index });
143 }
144
145 // Iterate over all variables of the "real" gates, and add a corresponding node to the cycle for that variable
146 for (size_t i = 0; i < num_gates; ++i) {
147 size_t wire_idx = 0;
148 for (auto& wire : circuit_constructor.wires) {
149 // We are looking at the j-th wire in the i-th row.
150 // The value in this position should be equal to the value of the element at index `var_index`
151 // of the `constructor.variables` vector.
152 // Therefore, we add (i,j) to the cycle at index `var_index` to indicate that w^j_i should have the values
153 // constructor.variables[var_index].
154 const uint32_t var_index = circuit_constructor.real_variable_index[wire[i]];
155 const auto wire_index = static_cast<uint32_t>(wire_idx);
156 const auto gate_idx = static_cast<uint32_t>(i + gates_offset);
157 copy_cycles[var_index].emplace_back(cycle_node{ wire_index, gate_idx });
158 ++wire_idx;
159 }
160 }
161 return copy_cycles;
162}
163
177template <typename Flavor, bool generalized>
178PermutationMapping<Flavor::NUM_WIRES> compute_permutation_mapping(
179 const typename Flavor::CircuitBuilder& circuit_constructor, typename Flavor::ProvingKey* proving_key)
180{
181 // Compute wire copy cycles (cycles of permutations)
182 auto wire_copy_cycles = compute_wire_copy_cycles<Flavor>(circuit_constructor);
183
184 PermutationMapping<Flavor::NUM_WIRES> mapping;
185
186 // Initialize the table of permutations so that every element points to itself
187 // TODO(https://github.com/AztecProtocol/barretenberg/issues/391) zip
188 for (size_t i = 0; i < Flavor::NUM_WIRES; ++i) {
189 mapping.sigmas[i].reserve(proving_key->circuit_size);
190 if constexpr (generalized) {
191 mapping.ids[i].reserve(proving_key->circuit_size);
192 }
193
194 for (size_t j = 0; j < proving_key->circuit_size; ++j) {
195 mapping.sigmas[i].emplace_back(permutation_subgroup_element{ .row_index = static_cast<uint32_t>(j),
196 .column_index = static_cast<uint8_t>(i),
197 .is_public_input = false,
198 .is_tag = false });
199 if constexpr (generalized) {
200 mapping.ids[i].emplace_back(permutation_subgroup_element{ .row_index = static_cast<uint32_t>(j),
201 .column_index = static_cast<uint8_t>(i),
202 .is_public_input = false,
203 .is_tag = false });
204 }
205 }
206 }
207
208 // Represents the index of a variable in circuit_constructor.variables (needed only for generalized)
209 std::span<const uint32_t> real_variable_tags = circuit_constructor.real_variable_tags;
210
211 // Go through each cycle
212 size_t cycle_index = 0;
213 for (auto& copy_cycle : wire_copy_cycles) {
214 for (size_t node_idx = 0; node_idx < copy_cycle.size(); ++node_idx) {
215 // Get the indices of the current node and next node in the cycle
216 cycle_node current_cycle_node = copy_cycle[node_idx];
217 // If current node is the last one in the cycle, then the next one is the first one
218 size_t next_cycle_node_index = (node_idx == copy_cycle.size() - 1 ? 0 : node_idx + 1);
219 cycle_node next_cycle_node = copy_cycle[next_cycle_node_index];
220 const auto current_row = current_cycle_node.gate_index;
221 const auto next_row = next_cycle_node.gate_index;
222
223 const auto current_column = current_cycle_node.wire_index;
224 const auto next_column = static_cast<uint8_t>(next_cycle_node.wire_index);
225 // Point current node to the next node
226 mapping.sigmas[current_column][current_row] = {
227 .row_index = next_row, .column_index = next_column, .is_public_input = false, .is_tag = false
228 };
229
230 if constexpr (generalized) {
231 bool first_node = (node_idx == 0);
232 bool last_node = (next_cycle_node_index == 0);
233
234 if (first_node) {
235 mapping.ids[current_column][current_row].is_tag = true;
236 mapping.ids[current_column][current_row].row_index = (real_variable_tags[cycle_index]);
237 }
238 if (last_node) {
239 mapping.sigmas[current_column][current_row].is_tag = true;
240
241 // TODO(Zac): yikes, std::maps (tau) are expensive. Can we find a way to get rid of this?
242 mapping.sigmas[current_column][current_row].row_index =
243 circuit_constructor.tau.at(real_variable_tags[cycle_index]);
244 }
245 }
246 }
247 cycle_index++;
248 }
249
250 // Add information about public inputs to the computation
251 const auto num_public_inputs = static_cast<uint32_t>(circuit_constructor.public_inputs.size());
252
253 // The public inputs are placed at the top of the execution trace, potentially offset by a zero row.
254 const size_t num_zero_rows = Flavor::has_zero_row ? 1 : 0;
255 size_t pub_input_offset = num_zero_rows;
256 // If Goblin, PI are further offset by number of ecc op gates
257 if constexpr (IsGoblinFlavor<Flavor>) {
258 pub_input_offset += circuit_constructor.num_ecc_op_gates;
259 }
260 for (size_t i = 0; i < num_public_inputs; ++i) {
261 size_t idx = i + pub_input_offset;
262 mapping.sigmas[0][idx].row_index = static_cast<uint32_t>(idx);
263 mapping.sigmas[0][idx].column_index = 0;
264 mapping.sigmas[0][idx].is_public_input = true;
265 if (mapping.sigmas[0][idx].is_tag) {
266 std::cerr << "MAPPING IS BOTH A TAG AND A PUBLIC INPUT" << std::endl;
267 }
268 }
269 return mapping;
270}
271
282template <typename Flavor>
283void compute_honk_style_permutation_lagrange_polynomials_from_mapping(
284 const RefVector<typename Flavor::Polynomial>& permutation_polynomials, // sigma or ID poly
285 std::array<std::vector<permutation_subgroup_element>, Flavor::NUM_WIRES>& permutation_mappings,
286 typename Flavor::ProvingKey* proving_key)
287{
288 using FF = typename Flavor::FF;
289 const size_t num_gates = proving_key->circuit_size;
290
291 size_t wire_index = 0;
292 for (auto& current_permutation_poly : permutation_polynomials) {
293 ITERATE_OVER_DOMAIN_START(proving_key->evaluation_domain);
294 const auto& current_mapping = permutation_mappings[wire_index][i];
295 if (current_mapping.is_public_input) {
296 // We intentionally want to break the cycles of the public input variables.
297 // During the witness generation, the left and right wire polynomials at index i contain the i-th public
298 // input. The CyclicPermutation created for these variables always start with (i) -> (n+i), followed by
299 // the indices of the variables in the "real" gates. We make i point to -(i+1), so that the only way of
300 // repairing the cycle is add the mapping
301 // -(i+1) -> (n+i)
302 // These indices are chosen so they can easily be computed by the verifier. They can expect the running
303 // product to be equal to the "public input delta" that is computed in <honk/utils/grand_product_delta.hpp>
304 current_permutation_poly[i] = -FF(current_mapping.row_index + 1 + num_gates * current_mapping.column_index);
305 } else if (current_mapping.is_tag) {
306 // Set evaluations to (arbitrary) values disjoint from non-tag values
307 current_permutation_poly[i] = num_gates * Flavor::NUM_WIRES + current_mapping.row_index;
308 } else {
309 // For the regular permutation we simply point to the next location by setting the evaluation to its
310 // index
311 current_permutation_poly[i] = FF(current_mapping.row_index + num_gates * current_mapping.column_index);
312 }
313 ITERATE_OVER_DOMAIN_END;
314 wire_index++;
315 }
316}
317} // namespace
318
328 const std::vector<permutation_subgroup_element>& permutation,
329 const barretenberg::evaluation_domain& small_domain)
330{
331 if (output.size() < permutation.size()) {
332 throw_or_abort("Permutation polynomial size is insufficient to store permutations.");
333 }
334 // permutation encoding:
335 // low 28 bits defines the location in witness polynomial
336 // upper 2 bits defines the witness polynomial:
337 // 0 = left
338 // 1 = right
339 // 2 = output
340 ASSERT(small_domain.log2_size > 1);
341 const barretenberg::fr* roots = small_domain.get_round_roots()[small_domain.log2_size - 2];
342 const size_t root_size = small_domain.size >> 1UL;
343 const size_t log2_root_size = static_cast<size_t>(numeric::get_msb(root_size));
344
345 ITERATE_OVER_DOMAIN_START(small_domain);
346
347 // `permutation[i]` will specify the 'index' that this wire value will map to.
348 // Here, 'index' refers to an element of our subgroup H.
349 // We can almost use `permutation[i]` to directly index our `roots` array, which contains our subgroup elements.
350 // We first have to accommodate for the fact that `roots` only contains *half* of our subgroup elements. This is
351 // because ω^{n/2} = -ω and we don't want to perform redundant work computing roots of unity.
352
353 size_t raw_idx = permutation[i].row_index;
354
355 // Step 1: is `raw_idx` >= (n / 2)? if so, we will need to index `-roots[raw_idx - subgroup_size / 2]` instead
356 // of `roots[raw_idx]`
357 const bool negative_idx = raw_idx >= root_size;
358
359 // Step 2: compute the index of the subgroup element we'll be accessing.
360 // To avoid a conditional branch, we can subtract `negative_idx << log2_root_size` from `raw_idx`.
361 // Here, `log2_root_size = numeric::get_msb(subgroup_size / 2)` (we know our subgroup size will be a power of 2,
362 // so we lose no precision here)
363 const size_t idx = raw_idx - (static_cast<size_t>(negative_idx) << log2_root_size);
364
365 // Call `conditionally_subtract_double_modulus`, using `negative_idx` as our predicate.
366 // Our roots of unity table is partially 'overloaded' - we either store the root `w`, or `modulus + w`
367 // So to ensure we correctly compute `modulus - w`, we need to compute `2 * modulus - w`
368 // The output will similarly be overloaded (containing either 2 * modulus - w, or modulus - w)
369 output[i] = roots[idx].conditionally_subtract_from_double_modulus(static_cast<uint64_t>(negative_idx));
370
371 // Finally, if our permutation maps to an index in either the right wire vector, or the output wire vector, we
372 // need to multiply our result by one of two quadratic non-residues. (This ensures that mapping into the left
373 // wires gives unique values that are not repeated in the right or output wire permutations) (ditto for right
374 // wire and output wire mappings)
375
376 if (permutation[i].is_public_input) {
377 // As per the paper which modifies plonk to include the public inputs in a permutation argument, the permutation
378 // `σ` is modified to `σ'`, where `σ'` maps all public inputs to a set of l distinct ζ elements which are
379 // disjoint from H ∪ k1·H ∪ k2·H.
380 output[i] *= barretenberg::fr::external_coset_generator();
381 } else if (permutation[i].is_tag) {
382 output[i] *= barretenberg::fr::tag_coset_generator();
383 } else {
384 {
385 const uint32_t column_index = permutation[i].column_index;
386 if (column_index > 0) {
387 output[i] *= barretenberg::fr::coset_generator(column_index - 1);
388 }
389 }
390 }
391 ITERATE_OVER_DOMAIN_END;
392}
393
402template <size_t program_width>
404 std::string label,
405 std::array<std::vector<permutation_subgroup_element>, program_width>& mappings,
407{
408 for (size_t i = 0; i < program_width; i++) {
409 std::string index = std::to_string(i + 1);
410 barretenberg::polynomial polynomial_lagrange(key->circuit_size);
411 compute_standard_plonk_lagrange_polynomial(polynomial_lagrange, mappings[i], key->small_domain);
412 key->polynomial_store.put(label + "_" + index + "_lagrange", polynomial_lagrange.share());
413 }
414}
415
425template <size_t program_width>
427{
428 for (size_t i = 0; i < program_width; ++i) {
429 std::string index = std::to_string(i + 1);
430 std::string prefix = label + "_" + index;
431
432 // Construct permutation polynomials in lagrange base
433 auto sigma_polynomial_lagrange = key->polynomial_store.get(prefix + "_lagrange");
434 // Compute permutation polynomial monomial form
435 barretenberg::polynomial sigma_polynomial(key->circuit_size);
436 barretenberg::polynomial_arithmetic::ifft(
437 (barretenberg::fr*)&sigma_polynomial_lagrange[0], &sigma_polynomial[0], key->small_domain);
438
439 // Compute permutation polynomial coset FFT form
440 barretenberg::polynomial sigma_fft(sigma_polynomial, key->large_domain.size);
441 sigma_fft.coset_fft(key->large_domain);
442
443 key->polynomial_store.put(prefix, sigma_polynomial.share());
444 key->polynomial_store.put(prefix + "_fft", sigma_fft.share());
445 }
446}
447
456template <typename Flavor>
457void compute_standard_plonk_sigma_permutations(const typename Flavor::CircuitBuilder& circuit_constructor,
458 typename Flavor::ProvingKey* key)
459{
460 // Compute the permutation table specifying which element becomes which
461 auto mapping = compute_permutation_mapping<Flavor, /*generalized=*/false>(circuit_constructor, key);
462 // Compute Plonk-style sigma polynomials from the mapping
464 // Compute their monomial and coset versions
465 compute_monomial_and_coset_fft_polynomials_from_lagrange<Flavor::NUM_WIRES>("sigma", key);
466}
467
473template <typename Flavor> inline void compute_first_and_last_lagrange_polynomials(const auto& proving_key)
474{
475 const size_t n = proving_key->circuit_size;
476 typename Flavor::Polynomial lagrange_polynomial_0(n);
477 typename Flavor::Polynomial lagrange_polynomial_n_min_1(n);
478 lagrange_polynomial_0[0] = 1;
479 proving_key->lagrange_first = lagrange_polynomial_0.share();
480
481 lagrange_polynomial_n_min_1[n - 1] = 1;
482 proving_key->lagrange_last = lagrange_polynomial_n_min_1.share();
483}
484
494template <typename Flavor>
495void compute_plonk_generalized_sigma_permutations(const typename Flavor::CircuitBuilder& circuit_constructor,
496 typename Flavor::ProvingKey* key)
497{
498 auto mapping = compute_permutation_mapping<Flavor, /*generalized=*/true>(circuit_constructor, key);
499
500 // Compute Plonk-style sigma and ID polynomials from the corresponding mappings
503 // Compute the monomial and coset-ffts for sigmas and IDs
504 compute_monomial_and_coset_fft_polynomials_from_lagrange<Flavor::NUM_WIRES>("sigma", key);
505 compute_monomial_and_coset_fft_polynomials_from_lagrange<Flavor::NUM_WIRES>("id", key);
506}
507
517template <typename Flavor>
518void compute_honk_generalized_sigma_permutations(const typename Flavor::CircuitBuilder& circuit_constructor,
519 typename Flavor::ProvingKey* proving_key)
520{
521 auto mapping = compute_permutation_mapping<Flavor, true>(circuit_constructor, proving_key);
522
523 // Compute Honk-style sigma and ID polynomials from the corresponding mappings
524 compute_honk_style_permutation_lagrange_polynomials_from_mapping<Flavor>(
525 proving_key->get_sigma_polynomials(), mapping.sigmas, proving_key);
526 compute_honk_style_permutation_lagrange_polynomials_from_mapping<Flavor>(
527 proving_key->get_id_polynomials(), mapping.ids, proving_key);
528}
529
530} // namespace proof_system
A template class for a reference vector. Behaves as if std::vector<T&> was possible.
Definition: ref_vector.hpp:20
Polynomial share() const
Definition: polynomial.cpp:141
Definition: AvmMini_flavor.hpp:21
Base class templates for structures that contain data parameterized by the fundamental polynomials of...
void compute_monomial_and_coset_fft_polynomials_from_lagrange(std::string label, plonk::proving_key *key)
Compute the monomial and coset-fft version of each lagrange polynomial of the given label.
Definition: permutation_lib.hpp:426
void compute_standard_plonk_lagrange_polynomial(barretenberg::polynomial &output, const std::vector< permutation_subgroup_element > &permutation, const barretenberg::evaluation_domain &small_domain)
Definition: permutation_lib.hpp:327
void compute_honk_generalized_sigma_permutations(const typename Flavor::CircuitBuilder &circuit_constructor, typename Flavor::ProvingKey *proving_key)
Compute generalized permutation sigmas and ids for ultra plonk.
Definition: permutation_lib.hpp:518
void compute_standard_plonk_sigma_permutations(const typename Flavor::CircuitBuilder &circuit_constructor, typename Flavor::ProvingKey *key)
Compute sigma permutation polynomials for standard plonk and put them in the polynomial cache.
Definition: permutation_lib.hpp:457
void compute_plonk_generalized_sigma_permutations(const typename Flavor::CircuitBuilder &circuit_constructor, typename Flavor::ProvingKey *key)
Compute generalized permutation sigmas and ids for ultra plonk.
Definition: permutation_lib.hpp:495
void compute_first_and_last_lagrange_polynomials(const auto &proving_key)
Compute Lagrange Polynomials L_0 and L_{n-1} and put them in the polynomial cache.
Definition: permutation_lib.hpp:473
void compute_plonk_permutation_lagrange_polynomials_from_mapping(std::string label, std::array< std::vector< permutation_subgroup_element >, program_width > &mappings, plonk::proving_key *key)
Compute lagrange polynomial from mapping (used for sigmas or ids)
Definition: permutation_lib.hpp:403
Definition: permutation_lib.hpp:54
cycle_node represents the index of a value of the circuit. It will belong to a CyclicPermutation,...
Definition: permutation_lib.hpp:36
Permutations subgroup element structure is used to hold data necessary to construct permutation polyn...
Definition: permutation_lib.hpp:47
Definition: proving_key.hpp:38