Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
permutation_lib.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Raju], commit: 21a7e3670e6 }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
14#pragma once
15
21
22#include <cstddef>
23#include <cstdint>
24#include <vector>
25
26namespace bb {
27
34struct cycle_node {
35 uint32_t wire_idx;
36 uint32_t gate_idx;
37};
38
43struct Mapping {
44 std::shared_ptr<uint32_t[]> row_idx; // row idx of next entry in copy cycle
45 std::shared_ptr<uint8_t[]> col_idx; // column idx of next entry in copy cycle
46 std::shared_ptr<bool[]> is_public_input; // if we are a sigma polynomial, is the current row a public input row?
47 // (always false for id polynomials.)
48 std::shared_ptr<bool[]>
49 is_tag; // is this element a tag, (N.B. For each permutation polynomial (i.e., id_i or
50 // sigma_j), only one element per cycle is a tag. This follows the generalized permutation argument.)
51 size_t _size = 0;
52
53 Mapping() = default;
54
55 size_t size() const { return _size; }
56
57 Mapping(size_t n)
58 : row_idx(_allocate_aligned_memory<uint32_t>(n))
62 , _size(n)
63 {}
64};
65
66template <size_t NUM_WIRES> struct PermutationMapping {
69
74 PermutationMapping(size_t circuit_size)
75 {
76 BB_BENCH_NAME("PermutationMapping constructor");
77
78 for (size_t wire_idx = 0; wire_idx < NUM_WIRES; ++wire_idx) {
79 sigmas[wire_idx] = Mapping(circuit_size);
80 ids[wire_idx] = Mapping(circuit_size);
81 }
82
83 parallel_for([&](const ThreadChunk& chunk) {
84 BB_BENCH_TRACY_NAME("Permutation::init_mappings");
85 // Initialize every element to point to itself
86 for (uint8_t col_idx = 0; col_idx < NUM_WIRES; ++col_idx) {
87 for (size_t i : chunk.range(circuit_size)) {
88 auto row_idx = static_cast<uint32_t>(i);
89 auto idx = static_cast<ptrdiff_t>(row_idx);
90 // sigma polynomials
91 sigmas[col_idx].row_idx[idx] = row_idx;
92 sigmas[col_idx].col_idx[idx] = col_idx;
93 sigmas[col_idx].is_public_input[idx] = false;
94 sigmas[col_idx].is_tag[idx] = false;
95 // id polynomials
96 ids[col_idx].row_idx[idx] = row_idx;
97 ids[col_idx].col_idx[idx] = col_idx;
98 ids[col_idx].is_public_input[idx] = false; // always false.
99 ids[col_idx].is_tag[idx] = false;
100 }
101 }
102 });
103 }
104};
105
107
108namespace {
109
123template <typename Flavor>
124PermutationMapping<Flavor::NUM_WIRES> compute_permutation_mapping(
125 const typename Flavor::CircuitBuilder& circuit_constructor,
126 const size_t dyadic_size,
127 const std::vector<CyclicPermutation>& wire_copy_cycles)
128{
129 BB_BENCH_NAME("compute_permutation_mapping");
130
131 // Initialize the table of permutations so that every element points to itself
132 PermutationMapping<Flavor::NUM_WIRES> mapping(dyadic_size);
133
134 // Represents the idx of a variable in circuit_constructor.variables
135 std::span<const uint32_t> real_variable_tags = circuit_constructor.real_variable_tags;
136
137 // Cycles are disjoint by construction of the generalized permutation argument: every (gate_idx, wire_idx) position
138 // belongs to exactly one variable, hence to exactly one cycle. Per-(col, row) writes from different cycles never
139 // alias, so parallelising across cycle_idx is safe without per-thread staging or merge.
141 wire_copy_cycles.size(),
142 [&](size_t cycle_idx) {
143 const CyclicPermutation& cycle = wire_copy_cycles[cycle_idx];
144 const auto cycle_size = cycle.size();
145 if (cycle_size == 0) {
146 return;
147 }
148
149 const cycle_node& first_node = cycle[0];
150 const cycle_node& last_node = cycle[cycle_size - 1];
151
152 const auto first_row = static_cast<ptrdiff_t>(first_node.gate_idx);
153 const auto first_col = first_node.wire_idx;
154 const auto last_row = static_cast<ptrdiff_t>(last_node.gate_idx);
155 const auto last_col = last_node.wire_idx;
156
157 // First node: id gets tagged with the cycle's variable tag
158 mapping.ids[first_col].is_tag[first_row] = true;
159 mapping.ids[first_col].row_idx[first_row] = real_variable_tags[cycle_idx];
160
161 // Last node: sigma gets tagged and points to tau(tag) instead of wrapping to first node
162 mapping.sigmas[last_col].is_tag[last_row] = true;
163 mapping.sigmas[last_col].row_idx[last_row] = circuit_constructor.tau().at(real_variable_tags[cycle_idx]);
164
165 // All nodes except the last: sigma points to the next node in the cycle
166 for (size_t node_idx = 0; node_idx + 1 < cycle_size; ++node_idx) {
167 const cycle_node& current_node = cycle[node_idx];
168 const cycle_node& next_node = cycle[node_idx + 1];
169
170 const auto current_row = static_cast<ptrdiff_t>(current_node.gate_idx);
171 const auto current_col = current_node.wire_idx;
172 // Point current node to next node.
173 mapping.sigmas[current_col].row_idx[current_row] = next_node.gate_idx;
174 mapping.sigmas[current_col].col_idx[current_row] = static_cast<uint8_t>(next_node.wire_idx);
175 }
176 },
177 /*heuristic_cost=*/thread_heuristics::FF_COPY_COST * 8);
178
179 // Add information about public inputs so that the cycles can be altered later; See the construction of the
180 // permutation polynomials for details. This _only_ effects sigma_0, the 0th sigma polynomial, as the structure of
181 // the algorithm only requires modifying sigma_0(i) where i is a public input row. (Note that at such a row, the
182 // non-zero wire values are in w_l and w_r, and both of them contain the public input.)
183 const auto num_public_inputs = static_cast<uint32_t>(circuit_constructor.num_public_inputs());
184
185 auto pub_inputs_offset = circuit_constructor.blocks.pub_inputs.trace_offset();
186 for (size_t i = 0; i < num_public_inputs; ++i) {
187 uint32_t idx = static_cast<uint32_t>(i + pub_inputs_offset);
188 mapping.sigmas[0].row_idx[static_cast<ptrdiff_t>(idx)] = idx;
189 mapping.sigmas[0].col_idx[static_cast<ptrdiff_t>(idx)] = 0;
190 mapping.sigmas[0].is_public_input[static_cast<ptrdiff_t>(idx)] = true;
191 BB_ASSERT(!mapping.sigmas[0].is_tag[static_cast<ptrdiff_t>(idx)], "MAPPING IS BOTH A TAG AND A PUBLIC INPUT");
192 }
193 return mapping;
194}
195
205template <typename Flavor>
206void compute_honk_style_permutation_lagrange_polynomials_from_mapping(
207 const RefSpan<typename Flavor::Polynomial>& permutation_polynomials,
208 const std::array<Mapping, Flavor::NUM_WIRES>& permutation_mappings)
209{
210 using FF = typename Flavor::FF;
211
212 size_t domain_size = permutation_polynomials[0].size();
213
214 // SEPARATOR ensures that the evaluations of `id_i` (`sigma_i`) and `id_j`(`sigma_j`) polynomials on the boolean
215 // hypercube do not intersect for i != j.
216 const size_t SEPARATOR = PERMUTATION_ARGUMENT_VALUE_SEPARATOR;
217 BB_ASSERT_LT(permutation_polynomials[0].size(), SEPARATOR);
218
219 const MultithreadData thread_data = calculate_thread_data(domain_size);
220
221 size_t wire_idx = 0;
222 for (auto& current_permutation_poly : permutation_polynomials) {
223 parallel_for(thread_data.num_threads, [&](size_t j) {
224 BB_BENCH_TRACY_NAME("Permutation::compute_polys");
225 const size_t start = thread_data.start[j];
226 const size_t end = thread_data.end[j];
227 for (size_t i = start; i < end; ++i) {
228 const size_t poly_idx = i + current_permutation_poly.start_index();
229 const auto idx = static_cast<ptrdiff_t>(poly_idx);
230 const auto& current_row_idx = permutation_mappings[wire_idx].row_idx[idx];
231 const auto& current_col_idx = permutation_mappings[wire_idx].col_idx[idx];
232 const auto& current_is_tag = permutation_mappings[wire_idx].is_tag[idx];
233 const auto& current_is_public_input =
234 permutation_mappings[wire_idx].is_public_input[idx]; // this is only `true` for sigma polynomials,
235 // it is always false for the ID polynomials.
236 if (current_is_public_input) {
237 // We intentionally want to break the cycles of the public input variables as an optimization.
238 // During the witness generation, both the left and right wire polynomials (w_l and w_r
239 // respectively) at row idx i contain the i-th public input. Let n = SEPARATOR. The initial
240 // CyclicPermutation created for these variables copy-constrained to the ith public input
241 // therefore always starts with (i) -> (n+i), followed by the indices of the variables in the
242 // "real" gates (i.e., the gates not merely present to set-up inputs).
243 //
244 // We change this and make i point to -(i+1). This choice "unbalances" the grand product
245 // argument, so that the final result of the grand product is _not_ 1. These indices are chosen
246 // so they can easily be computed by the verifier (just knowing the public inputs), and this
247 // algorithm constitutes a specification of the "permutation argument with public inputs"
248 // optimization due to Gabizon and Williamson. The verifier can expect the final product to be
249 // equal to the "public input delta" that is computed in <honk/library/grand_product_delta.hpp>.
250 current_permutation_poly.at(poly_idx) = -FF(current_row_idx + 1 + SEPARATOR * current_col_idx);
251 } else if (current_is_tag) {
252 // Set evaluations to (arbitrary) values disjoint from non-tag values. This is for the
253 // multiset-equality part of the generalized permutation argument, which requires auxiliary
254 // values which have not been used as indices. In particular, these are the actual tags assigned
255 // to the cycle.
256 current_permutation_poly.at(poly_idx) = SEPARATOR * Flavor::NUM_WIRES + current_row_idx;
257 } else {
258 // For the regular permutation we simply point to the next location by setting the
259 // evaluation to its idx
260 current_permutation_poly.at(poly_idx) = FF(current_row_idx + SEPARATOR * current_col_idx);
261 }
262 }
263 });
264 wire_idx++;
265 }
266}
267} // namespace
268
273template <typename Flavor>
275 typename Flavor::ProverPolynomials& polynomials,
276 const std::vector<CyclicPermutation>& copy_cycles)
277{
278 const size_t polynomial_size = polynomials.get_polynomial_size();
279 auto mapping = compute_permutation_mapping<Flavor>(circuit, polynomial_size, copy_cycles);
280
281 // Compute Honk-style sigma and ID polynomials from the corresponding mappings
282 {
283 BB_BENCH_NAME("compute_honk_style_permutation_lagrange_polynomials_from_mapping");
284 compute_honk_style_permutation_lagrange_polynomials_from_mapping<Flavor>(polynomials.get_sigmas(),
285 mapping.sigmas);
286 }
287 {
288 BB_BENCH_NAME("compute_honk_style_permutation_lagrange_polynomials_from_mapping");
289 compute_honk_style_permutation_lagrange_polynomials_from_mapping<Flavor>(polynomials.get_ids(), mapping.ids);
290 }
291}
292
293} // namespace bb
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
#define BB_ASSERT_LT(left, right,...)
Definition assert.hpp:143
#define BB_BENCH_NAME(name)
Definition bb_bench.hpp:264
#define BB_BENCH_TRACY_NAME(name)
Definition bb_bench.hpp:256
A container for the prover polynomials.
typename Curve::ScalarField FF
Base class templates shared across Honk flavors.
constexpr size_t FF_COPY_COST
Definition thread.hpp:144
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
MultithreadData calculate_thread_data(size_t num_iterations, size_t min_iterations_per_thread)
Calculates number of threads and index bounds for each thread.
Definition thread.cpp:207
std::shared_ptr< Fr[]> _allocate_aligned_memory(size_t n_elements)
void parallel_for_heuristic(size_t num_points, const std::function< void(size_t, size_t, size_t)> &func, size_t heuristic_cost)
Split a loop into several loops running in parallel based on operations in 1 iteration.
Definition thread.cpp:171
void compute_permutation_argument_polynomials(const typename Flavor::CircuitBuilder &circuit, typename Flavor::ProverPolynomials &polynomials, const std::vector< CyclicPermutation > &copy_cycles)
Compute Honk-style permutation sigma/id polynomials and add to prover_instance, where the copy_cycles...
constexpr uint32_t PERMUTATION_ARGUMENT_VALUE_SEPARATOR
Definition constants.hpp:9
std::vector< cycle_node > CyclicPermutation
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:111
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Stores permutation mapping data for a single wire column.
std::shared_ptr< bool[]> is_public_input
Mapping(size_t n)
std::shared_ptr< uint32_t[]> row_idx
std::shared_ptr< bool[]> is_tag
size_t size() const
Mapping()=default
std::shared_ptr< uint8_t[]> col_idx
PermutationMapping(size_t circuit_size)
Construct a permutation mapping default initialized so every element is in a cycle by itself.
std::array< Mapping, NUM_WIRES > ids
std::array< Mapping, NUM_WIRES > sigmas
auto range(size_t size, size_t offset=0) const
Definition thread.hpp:152
cycle_node represents the idx of a value of the circuit. It will belong to a CyclicPermutation,...