barretenberg
Loading...
Searching...
No Matches
permutation_widget_impl.hpp
1#pragma once
2#include "barretenberg/common/mem.hpp"
3#include "barretenberg/common/slab_allocator.hpp"
4#include "barretenberg/plonk/proof_system/proving_key/proving_key.hpp"
5#include "barretenberg/plonk/proof_system/public_inputs/public_inputs.hpp"
6#include "barretenberg/polynomials/iterate_over_domain.hpp"
7#include "barretenberg/polynomials/polynomial.hpp"
8#include "barretenberg/polynomials/polynomial_arithmetic.hpp"
9#include "barretenberg/transcript/transcript.hpp"
10
11namespace proof_system::plonk {
12
13template <size_t program_width, bool idpolys, const size_t num_roots_cut_out_of_vanishing_polynomial>
14ProverPermutationWidget<program_width, idpolys, num_roots_cut_out_of_vanishing_polynomial>::ProverPermutationWidget(
15 proving_key* input_key)
16 : ProverRandomWidget(input_key)
17{}
18
19template <size_t program_width, bool idpolys, const size_t num_roots_cut_out_of_vanishing_polynomial>
20ProverPermutationWidget<program_width, idpolys, num_roots_cut_out_of_vanishing_polynomial>::ProverPermutationWidget(
21 const ProverPermutationWidget& other)
22 : ProverRandomWidget(other)
23{}
24
25template <size_t program_width, bool idpolys, const size_t num_roots_cut_out_of_vanishing_polynomial>
26ProverPermutationWidget<program_width, idpolys, num_roots_cut_out_of_vanishing_polynomial>::ProverPermutationWidget(
27 ProverPermutationWidget&& other)
28 : ProverRandomWidget(other)
29{}
30
31template <size_t program_width, bool idpolys, const size_t num_roots_cut_out_of_vanishing_polynomial>
32ProverPermutationWidget<program_width, idpolys, num_roots_cut_out_of_vanishing_polynomial>& ProverPermutationWidget<
33 program_width,
34 idpolys,
35 num_roots_cut_out_of_vanishing_polynomial>::operator=(const ProverPermutationWidget& other)
36{
37 ProverRandomWidget::operator=(other);
38 return *this;
39}
40
41template <size_t program_width, bool idpolys, const size_t num_roots_cut_out_of_vanishing_polynomial>
42ProverPermutationWidget<program_width, idpolys, num_roots_cut_out_of_vanishing_polynomial>& ProverPermutationWidget<
43 program_width,
44 idpolys,
45 num_roots_cut_out_of_vanishing_polynomial>::operator=(ProverPermutationWidget&& other)
46{
47 ProverRandomWidget::operator=(other);
48 return *this;
49}
50
57template <size_t program_width, bool idpolys, const size_t num_roots_cut_out_of_vanishing_polynomial>
59 compute_round_commitments(transcript::StandardTranscript& transcript, const size_t round_number, work_queue& queue)
60{
61 if (round_number != 3) {
62 return;
63 }
64
65 // Allocate scratch space in memory for computation of lagrange form of permutation polynomial
66 // 'z_perm'. Elements 2,...,n of z_perm are constructed in place in accumulators[0]. (The first
67 // element of z_perm is one, i.e. z_perm[0] == 1). The remaining accumulators are used only as scratch
68 // space.
69 size_t num_accumulators = (program_width == 1) ? 3 : program_width * 2;
70 std::shared_ptr<void> accumulators_ptrs[num_accumulators];
71 fr* accumulators[num_accumulators];
72 // Allocate the required number of length n scratch space arrays
73 for (size_t k = 0; k < num_accumulators; ++k) {
74 accumulators_ptrs[k] = get_mem_slab(key->circuit_size * sizeof(fr));
75 accumulators[k] = (fr*)accumulators_ptrs[k].get();
76 }
77
78 barretenberg::fr beta = fr::serialize_from_buffer(transcript.get_challenge("beta").begin());
79 barretenberg::fr gamma = fr::serialize_from_buffer(transcript.get_challenge("beta", 1).begin());
80
81 std::array<std::shared_ptr<fr[]>, program_width> lagrange_base_wires_ptr;
82 std::array<std::shared_ptr<fr[]>, program_width> lagrange_base_sigmas_ptr;
83 [[maybe_unused]] std::array<std::shared_ptr<fr[]>, program_width> lagrange_base_ids_ptr;
84
85 std::array<fr*, program_width> lagrange_base_wires;
86 std::array<fr*, program_width> lagrange_base_sigmas;
87 [[maybe_unused]] std::array<fr*, program_width> lagrange_base_ids;
88
89 for (size_t i = 0; i < program_width; ++i) {
90 lagrange_base_wires_ptr[i] = key->polynomial_store.get("w_" + std::to_string(i + 1) + "_lagrange").data();
91 lagrange_base_wires[i] = lagrange_base_wires_ptr[i].get();
92 lagrange_base_sigmas_ptr[i] = key->polynomial_store.get("sigma_" + std::to_string(i + 1) + "_lagrange").data();
93 lagrange_base_sigmas[i] = lagrange_base_sigmas_ptr[i].get();
94
95 // If idpolys = true, it implies that we do NOT use the identity permutation
96 // S_ID1(X) = X, S_ID2(X) = k_1X, S_ID3(X) = k_2X.
97 if constexpr (idpolys) {
98 lagrange_base_ids_ptr[i] = key->polynomial_store.get("id_" + std::to_string(i + 1) + "_lagrange").data();
99 lagrange_base_ids[i] = lagrange_base_ids_ptr[i].get();
100 }
101 }
102
103 // When we write w_i it means the evaluation of witness polynomial at i-th index.
104 // When we write w^{i} it means the generator of the subgroup to the i-th power.
105 //
106 // step 1: compute the individual terms in the permutation poylnomial.
107 //
108 // Consider the case in which we use identity permutation polynomials and let program width = 3.
109 // (extending it to the case when the permutation polynomials is not identity is trivial).
110 //
111 // coefficient of L_1: 1
112 //
113 // coefficient of L_2:
114 //
115 // coeff_of_L1 * (w_1 + γ + β.ω^{0}) . (w_{n+1} + γ + β.k_1.ω^{0}) . (w_{2n+1} + γ + β.k_2.ω^{0})
116 // ---------------------------------------------------------------------------------
117 // (w_1 + γ + β.σ(1) ) . (w_{n+1} + γ + β.σ(n+1) ) . (w_{2n+1} + γ + β.σ(2n+1) )
118 //
119 // coefficient of L_3:
120 //
121 // coeff_of_L2 * (w_2 + γ + β.ω^{1}) . (w_{n+2} + γ + β.k_1.ω^{1}) . (w_{2n+2} + γ + β.k_2.ω^{1})
122 // --------------------------------------------------------------------------------
123 // (w_2 + γ + β.σ(2) ) . (w_{n+2} + γ + β.σ(n+2) ) . (w_{2n+2} + γ + β.σ(2n+2) )
124 // and so on...
125 //
126 // accumulator data structure:
127 // numerators are stored in accumulator[0: program_width-1],
128 // denominators are stored in accumulator[program_width:]
129 //
130 // 0 1 (n-1)
131 // 0 -> (w_1 + γ + β.ω^{0} ), (w_2 + γ + β.ω^{1} ), ...., (w_n + γ + β.ω^{n-1} )
132 // 1 -> (w_{n+1} + γ + β.k_1.ω^{0}), (w_{n+1} + γ + β.k_1.ω^{2}), ...., (w_{n+1} + γ + β.k_1.ω^{n-1})
133 // 2 -> (w_{2n+1} + γ + β.k_2.ω^{0}), (w_{2n+1} + γ + β.k_2.ω^{0}), ...., (w_{2n+1} + γ + β.k_2.ω^{n-1})
134 //
135 // 3 -> (w_1 + γ + β.σ(1) ), (w_2 + γ + β.σ(2) ), ...., (w_n + γ + β.σ(n) )
136 // 4 -> (w_{n+1} + γ + β.σ(n+1) ), (w_{n+1} + γ + β.σ{n+2} ), ...., (w_{n+1} + γ + β.σ{n+n} )
137 // 5 -> (w_{2n+1} + γ + β.σ(2n+1) ), (w_{2n+1} + γ + β.σ(2n+2) ), ...., (w_{2n+1} + γ + β.σ(2n+n) )
138 //
139 // Thus, to obtain coefficient_of_L2, we need to use accumulators[:][0]:
140 // acc[0][0]*acc[1][0]*acc[2][0] / acc[program_width][0]*acc[program_width+1][0]*acc[program_width+2][0]
141 //
142 // To obtain coefficient_of_L3, we need to use accumulator[:][0] and accumulator[:][1]
143 // and so on upto coefficient_of_Ln.
144 //
145 // Recall: In a domain: num_threads * thread_size = size (= subgroup_size)
146 // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <-- n = 16
147 // j: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | num_threads = 8
148 // i: 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 thread_size = 2
149 // So i will access a different element from 0..(n-1) each time.
150 // Commented maths notation mirrors the indexing from the giant comment immediately above.
151 parallel_for(key->small_domain.num_threads, [&](size_t j) {
152 barretenberg::fr thread_root = key->small_domain.root.pow(
153 static_cast<uint64_t>(j * key->small_domain.thread_size)); // effectively ω^{i} in inner loop
154 [[maybe_unused]] barretenberg::fr cur_root_times_beta = thread_root * beta; // β.ω^{i}
155 barretenberg::fr T0;
156 barretenberg::fr wire_plus_gamma;
157 size_t start = j * key->small_domain.thread_size;
158 size_t end = (j + 1) * key->small_domain.thread_size;
159 for (size_t i = start; i < end; ++i) {
160 wire_plus_gamma = gamma + lagrange_base_wires[0][i]; // w_{i + 1} + γ
161 // i in 0..(n-1)
162 if constexpr (!idpolys) {
163 accumulators[0][i] = wire_plus_gamma + cur_root_times_beta; // w_{i + 1} + γ + β.ω^{i}
164 }
165 if constexpr (idpolys) {
166 T0 = lagrange_base_ids[0][i] * beta; // β.id(i + 1)
167 accumulators[0][i] = T0 + wire_plus_gamma; // w_{i + 1} + γ + β.id(i + 1)
168 }
169
170 T0 = lagrange_base_sigmas[0][i] * beta; // β.σ(i + 1)
171 accumulators[program_width][i] = T0 + wire_plus_gamma; // w_{i + 1} + γ + β.σ(i + 1)
172
173 for (size_t k = 1; k < program_width; ++k) {
174 wire_plus_gamma = gamma + lagrange_base_wires[k][i]; // w_{k.n + i + 1} + γ
175 // i in 0..(n-1)
176 if constexpr (idpolys) {
177 T0 = lagrange_base_ids[k][i] * beta; // β.id(k.n + i + 1)
178 } else {
179 T0 = fr::coset_generator(k - 1) * cur_root_times_beta; // β.k_{k}.ω^{i}
180 // ^coset generator k
181 }
182 accumulators[k][i] = T0 + wire_plus_gamma; // w_{k.n + i + 1} + γ + β.id(k.n + i + 1)
183
184 T0 = lagrange_base_sigmas[k][i] * beta; // β.σ(k.n + i + 1)
185 accumulators[k + program_width][i] = T0 + wire_plus_gamma; // w_{k.n + i + 1} + γ + β.σ(k.n + i + 1)
186 }
187 if constexpr (!idpolys)
188 cur_root_times_beta *= key->small_domain.root; // β.ω^{i + 1}
189 }
190 });
191
192 // Step 2: compute the constituent components of z(X). This is a small multithreading bottleneck, as we have
193 // program_width * 2 non-parallelizable processes
194 //
195 // Update the accumulator matrix a[:][:] to contain the left products like so:
196 // 0 1 2 (n-1)
197 // 0 -> (a[0][0]), (a[0][1] * a[0][0]), (a[0][2] * a[0][1]), ..., (a[0][n-1] * a[0][n-2])
198 // 1 -> (a[1][0]), (a[1][1] * a[1][0]), (a[1][2] * a[1][1]), ..., (a[1][n-1] * a[1][n-2])
199 // 2 -> (a[2][0]), (a[2][1] * a[2][0]), (a[2][2] * a[2][1]), ..., (a[2][n-1] * a[2][n-2])
200 //
201 // 3 -> (a[3][0]), (a[3][1] * a[3][0]), (a[3][2] * a[3][1]), ..., (a[3][n-1] * a[3][n-2])
202 // 4 -> (a[4][0]), (a[4][1] * a[4][0]), (a[4][2] * a[4][1]), ..., (a[4][n-1] * a[4][n-2])
203 // 5 -> (a[5][0]), (a[5][1] * a[5][0]), (a[5][2] * a[5][1]), ..., (a[5][n-1] * a[5][n-2])
204 //
205 // and so on...
206 parallel_for(program_width * 2, [&](size_t i) {
207 fr* coeffs = &accumulators[i][0]; // start from the beginning of a row
208 for (size_t j = 0; j < key->small_domain.size - 1; ++j) {
209 coeffs[j + 1] *= coeffs[j]; // iteratively update elements in subsequent columns
210 }
211 });
212
213 // step 3: concatenate together the accumulator elements into z(X)
214 //
215 // Update each element of the accumulator row a[0] to be the product of itself with the 'numerator' rows beneath
216 // it, and update each element of a[program_width] to be the product of itself with the 'denominator' rows
217 // beneath it.
218 //
219 // 0 1 (n-1)
220 // 0 -> (a[0][0] * a[1][0] * a[2][0]), (a[0][1] * a[1][1] * a[2][1]), ...., (a[0][n-1] *
221 // a[1][n-1] * a[2][n-1])
222 //
223 // pw -> (a[pw][0] * a[pw+1][0] * a[pw+2][0]), (a[pw][1] * a[pw+1][1] * a[pw+2][1]), ...., (a[pw][n-1] *
224 // a[pw+1][n-1] * a[pw+2][n-1])
225 //
226 // Note that pw = program_width
227 //
228 // Hereafter, we can compute
229 // coefficient_Lj = a[0][j]/a[pw][j]
230 //
231 // Naive way of computing these coefficients would result in n inversions, which is pretty expensive.
232 // Instead we use Montgomery's trick for batch inversion.
233 // Montgomery's trick documentation:
234 // ./src/barretenberg/ecc/curves/bn254/scalar_multiplication/scalar_multiplication.hpp/L286
235 parallel_for(key->small_domain.num_threads, [&](size_t j) {
236 const size_t start = j * key->small_domain.thread_size;
237 const size_t end =
238 ((j + 1) * key->small_domain.thread_size) - ((j == key->small_domain.num_threads - 1) ? 1 : 0);
239 barretenberg::fr inversion_accumulator = fr::one();
240 constexpr size_t inversion_index = (program_width == 1) ? 2 : program_width * 2 - 1;
241 fr* inversion_coefficients = &accumulators[inversion_index][0];
242 for (size_t i = start; i < end; ++i) {
243
244 for (size_t k = 1; k < program_width; ++k) {
245 accumulators[0][i] *= accumulators[k][i];
246 accumulators[program_width][i] *= accumulators[program_width + k][i];
247 }
248 inversion_coefficients[i] = accumulators[0][i] * inversion_accumulator;
249 inversion_accumulator *= accumulators[program_width][i];
250 }
251 inversion_accumulator = inversion_accumulator.invert();
252 for (size_t i = end - 1; i != start - 1; --i) {
253
254 // N.B. accumulators[0][i] = z_perm[i + 1]
255 // We can avoid fully reducing z_perm[i + 1] as the inverse fft will take care of that for us
256 accumulators[0][i] = inversion_accumulator * inversion_coefficients[i];
257 inversion_accumulator *= accumulators[program_width][i];
258 }
259 });
260
261 // Construct permutation polynomial 'z' in lagrange form as:
262 // z = [1 accumulators[0][0] accumulators[0][1] ... accumulators[0][n-2]]
263 polynomial z_perm(key->circuit_size);
264 z_perm[0] = fr::one();
265 barretenberg::polynomial_arithmetic::copy_polynomial(
266 accumulators[0], &z_perm[1], key->circuit_size - 1, key->circuit_size - 1);
267
268 /*
269 Adding zero knowledge to the permutation polynomial.
270 */
271 // To ensure that PLONK is honest-verifier zero-knowledge, we need to ensure that the witness polynomials
272 // and the permutation polynomial look uniformly random to an adversary. To make the witness polynomials
273 // a(X), b(X) and c(X) uniformly random, we need to add 2 random blinding factors into each of them.
274 // i.e. a'(X) = a(X) + (r_1X + r_2)
275 // where r_1 and r_2 are uniformly random scalar field elements. A natural question is:
276 // Why do we need 2 random scalars in witness polynomials? The reason is: our witness polynomials are
277 // evaluated at only 1 point (\scripted{z}), so adding a random degree-1 polynomial suffices.
278 //
279 // NOTE: In UltraPlonk, the witness polynomials are evaluated at 2 points and thus
280 // we need to add 3 random scalars in them.
281 //
282 // On the other hand, permutation polynomial z(X) is evaluated at two points, namely \scripted{z} and
283 // \scripted{z}.\omega. Hence, we need to add a random polynomial of degree 2 to ensure that the permutation
284 // polynomial looks uniformly random.
285 // z'(X) = z(X) + (r_3.X^2 + r_4.X + r_5)
286 // where r_3, r_4, r_5 are uniformly random scalar field elements.
287 //
288 // Furthermore, instead of adding random polynomials, we could directly add random scalars in the lagrange-
289 // basis forms of the witness and permutation polynomials. This is because we are using a modified vanishing
290 // polynomial of the form
291 // (X^n - 1)
292 // Z*_H(X) = ------------------------------------------
293 // (X - ω^{n-1}).(X - ω^{n-2})...(X - ω^{n-k})
294 // where ω = n-th root of unity, k = num_roots_cut_out_of_vanishing_polynomials.
295 // Thus, the last k places in the lagrange basis form of z(X) are empty. We can therefore utilise them and
296 // add random scalars as coefficients of L_{n-1}, L_{n-2},... and so on.
297 //
298 // Note: The number of coefficients in the permutation polynomial z(X) is (n - k + 1) DOCTODO: elaborate on why.
299 // (refer to Round 2 in the PLONK paper). Hence, if we cut 3 roots out of the vanishing polynomial,
300 // we are left with only 2 places (coefficients) in the z array to add randomness. To have the last 3 places
301 // available for adding random scalars, we therefore need to cut at least 4 roots out of the vanishing
302 // polynomial.
303 //
304 // Since we have valid z coefficients in positions from 0 to (n - k), we can start adding random scalars
305 // from position (n - k + 1) upto (n - k + 3).
306 //
307 // NOTE: If in future there is a need to cut off more zeros off the vanishing polynomial, this method
308 // will not change. This must be changed only if the number of evaluations of permutation polynomial
309 // changes.
310 const size_t z_randomness = 3;
311 ASSERT(z_randomness < num_roots_cut_out_of_vanishing_polynomial);
312 for (size_t k = 0; k < z_randomness; ++k) {
313 z_perm[(key->circuit_size - num_roots_cut_out_of_vanishing_polynomial) + 1 + k] = fr::random_element();
314 }
315
316 z_perm.ifft(key->small_domain);
317
318 // Commit to z:
319 queue.add_to_queue({
320 work_queue::WorkType::SCALAR_MULTIPLICATION,
321 z_perm.data(),
322 "Z_PERM",
323 key->circuit_size,
324 0,
325 });
326
327 // Compute coset-form of z:
328 queue.add_to_queue({
329 work_queue::WorkType::FFT,
330 nullptr,
331 "z_perm",
333 0,
334 });
335
336 key->polynomial_store.put("z_perm", std::move(z_perm));
337}
338
339template <size_t program_width, bool idpolys, const size_t num_roots_cut_out_of_vanishing_polynomial>
341 compute_quotient_contribution(const fr& alpha_base, const transcript::StandardTranscript& transcript)
342{
343 const polynomial& z_perm_fft = key->polynomial_store.get("z_perm_fft");
344
345 barretenberg::fr alpha_squared = alpha_base.sqr();
346 barretenberg::fr beta = fr::serialize_from_buffer(transcript.get_challenge("beta").begin());
347 barretenberg::fr gamma = fr::serialize_from_buffer(transcript.get_challenge("beta", 1).begin());
348
349 // Initialize the (n + 1)th coefficients of quotient parts so that reuse of proving
350 // keys does not use some residual data from another proof.
351 key->quotient_polynomial_parts[0][key->circuit_size] = 0;
352 key->quotient_polynomial_parts[1][key->circuit_size] = 0;
353 key->quotient_polynomial_parts[2][key->circuit_size] = 0;
354
355 // Our permutation check boils down to two 'grand product' arguments, that we represent with a single polynomial
356 // z(X). We want to test that z(X) has been constructed correctly. When evaluated at elements of ω ∈ H, the
357 // numerator of z(ω) will equal the identity permutation grand product, and the denominator will equal the copy
358 // permutation grand product.
359
360 // The identity that we need to evaluate is: z(X.ω).(permutation grand product) == z(X).(identity grand product)
361 // i.e. The next element of z is equal to the current element of z, multiplied by (identity grand product) /
362 // (permutation grand product)
363
364 // This method computes `(identity grand product).z(X).α`.
365 // The random `alpha` is there to ensure our grand product polynomial identity is linearly independent from the
366 // other polynomial identities that we are going to roll into the quotient polynomial T(X).
367
368 // Specifically, we want to compute:
369 // (w_l(X) + β.σ_1(X) + γ).(w_r(X) + β.σ_2(X) + γ).(w_o(X) + β.σ_3(X) + γ).z(X).α
370 // Once we divide by the vanishing polynomial, this will be a degree 3n polynomial. (4 * (n-1) - (n-4)).
371
372 std::array<std::shared_ptr<fr[]>, program_width> wire_ffts_ptr;
373 std::array<std::shared_ptr<fr[]>, program_width> sigma_ffts_ptr;
374 [[maybe_unused]] std::array<std::shared_ptr<fr[]>, program_width> id_ffts_ptr;
375
376 std::array<fr*, program_width> wire_ffts;
377 std::array<fr*, program_width> sigma_ffts;
378 [[maybe_unused]] std::array<fr*, program_width> id_ffts;
379
380 for (size_t i = 0; i < program_width; ++i) {
381
382 // wire_fft[0] contains the fft of the wire polynomial w_1
383 // sigma_fft[0] contains the fft of the permutation selector polynomial \sigma_1
384 wire_ffts_ptr[i] = key->polynomial_store.get("w_" + std::to_string(i + 1) + "_fft").data();
385 sigma_ffts_ptr[i] = key->polynomial_store.get("sigma_" + std::to_string(i + 1) + "_fft").data();
386 wire_ffts[i] = wire_ffts_ptr[i].get();
387 sigma_ffts[i] = sigma_ffts_ptr[i].get();
388
389 // idpolys is FALSE iff the "identity permutation" is used as a monomial
390 // as a part of the permutation polynomial
391 // <=> idpolys = FALSE
392 if constexpr (idpolys) {
393 id_ffts_ptr[i] = key->polynomial_store.get("id_" + std::to_string(i + 1) + "_fft").data();
394 id_ffts[i] = id_ffts_ptr[i].get();
395 }
396 }
397
398 // we start with lagrange polynomial L_1(X)
399 const polynomial& l_start = key->polynomial_store.get("lagrange_1_fft");
400
401 // Compute our public input component
402 std::vector<barretenberg::fr> public_inputs = many_from_buffer<fr>(transcript.get_element("public_inputs"));
403
404 barretenberg::fr public_input_delta =
405 compute_public_input_delta<fr>(public_inputs, beta, gamma, key->small_domain.root);
406
407 const size_t block_mask = key->large_domain.size - 1;
408 // Step 4: Set the quotient polynomial to be equal to
409 parallel_for(key->large_domain.num_threads, [&](size_t j) {
410 const size_t start = j * key->large_domain.thread_size;
411 const size_t end = (j + 1) * key->large_domain.thread_size;
412
413 // Leverage multi-threading by computing quotient polynomial at points
414 // (ω^{j * num_threads}, ω^{j * num_threads + 1}, ..., ω^{j * num_threads + num_threads})
415 //
416 // curr_root = ω^{j * num_threads} * g_{small} * β
417 // curr_root will be used in denominator
418 barretenberg::fr cur_root_times_beta =
419 key->large_domain.root.pow(static_cast<uint64_t>(j * key->large_domain.thread_size));
420 cur_root_times_beta *= key->small_domain.generator;
421 cur_root_times_beta *= beta;
422
423 barretenberg::fr wire_plus_gamma;
424 barretenberg::fr T0;
425 barretenberg::fr denominator;
426 barretenberg::fr numerator;
427 for (size_t i = start; i < end; ++i) {
428 wire_plus_gamma = gamma + wire_ffts[0][i];
429
430 // Numerator computation
431 if constexpr (!idpolys)
432 // identity polynomial used as a monomial: S_{id1} = x, S_{id2} = k_1.x, S_{id3} = k_2.x
433 // start with (w_l(X) + β.X + γ)
434 numerator = cur_root_times_beta + wire_plus_gamma;
435 else
436 numerator = id_ffts[0][i] * beta + wire_plus_gamma;
437
438 // Denominator computation
439 // start with (w_l(X) + β.σ_1(X) + γ)
440 denominator = sigma_ffts[0][i] * beta;
441 denominator += wire_plus_gamma;
442
443 for (size_t k = 1; k < program_width; ++k) {
444 wire_plus_gamma = gamma + wire_ffts[k][i];
445 if constexpr (!idpolys)
446 // (w_r(X) + β.(k_{k}.X) + γ)
447 T0 = fr::coset_generator(k - 1) * cur_root_times_beta;
448 if constexpr (idpolys)
449 T0 = id_ffts[k][i] * beta;
450
451 T0 += wire_plus_gamma;
452 numerator *= T0;
453
454 // (w_r(X) + β.σ_{k}(X) + γ)
455 T0 = sigma_ffts[k][i] * beta;
456 T0 += wire_plus_gamma;
457 denominator *= T0;
458 }
459
460 numerator *= z_perm_fft[i];
461 denominator *= z_perm_fft[(i + 4) & block_mask];
462
474 // The α^3 term is so that we can subsume this polynomial into the quotient polynomial,
475 // whilst ensuring the term is linearly independent form the other terms in the quotient polynomial
476
477 // We want to verify that z(X) equals `1` when evaluated at `ω_n`, the 'last' element of our
478 // multiplicative subgroup H. But PLONK's 'vanishing polynomial', Z*_H(X), isn't the true vanishing
479 // polynomial of subgroup H. We need to cut a root of unity out of Z*_H(X), specifically `ω_n`, for our
480 // grand product argument. When evaluating z(X) has been constructed correctly, we verify that
481 // z(X.ω).(identity permutation product) = z(X).(sigma permutation product), for all X \in H. But this
482 // relationship breaks down for X = ω_n, because z(X.ω) will evaluate to the *first* element of our
483 // grand product argument. The last element of z(X) has a dependency on the first element, so the first
484 // element cannot have a dependency on the last element.
485
486 // TODO: With the reduction from 2 z polynomials to a single z(X), the above no longer applies
487 // TODO: Fix this to remove the (z(X.ω) - 1).L_{n-1}(X) check
488
489 // To summarize, we can't verify claims about z(X) when evaluated at `ω_n`.
490 // But we can verify claims about z(X.ω) when evaluated at `ω_{n-1}`, which is the same thing
491
492 // To summarize the summary: If z(ω_n) = 1, then (z(X.ω) - 1).L_{n-1}(X) will be divisible by Z_H*(X)
493 // => add linearly independent term (z(X.ω) - 1).(α^3).L{n-1}(X) into the quotient polynomial to check
494 // this
495
496 // z_perm_fft already contains evaluations of Z(X).(\alpha^2)
497 // at the (4n)'th roots of unity
498 // => to get Z(X.w) instead of Z(X), index element (i+4) instead of i
499 T0 = z_perm_fft[(i + 4) & block_mask] - public_input_delta; // T0 = (Z(X.w) - (delta)).(\alpha^2)
500 T0 *= alpha_base; // T0 = (Z(X.w) - (delta)).(\alpha^3)
501
502 // T0 = (z(X.ω) - Δ).(α^3).L_{end}
503 // where L_{end} = L{n - num_roots_cut_out_of_vanishing_polynomial}.
504 //
505 // Note that L_j(X) = L_1(X . ω^{-j}) = L_1(X . ω^{n-j})
506 // => L_{end}= L_1(X . ω^{num_roots_cut_out_of_vanishing_polynomial + 1})
507 // => fetch the value at index (i + (num_roots_cut_out_of_vanishing_polynomial + 1) * 4) in l_1
508 // the factor of 4 is because l_1 is a 4n-size fft.
509 //
510 // Recall, we use l_start for l_1 for consistency in notation.
511 T0 *= l_start[(i + 4 + 4 * num_roots_cut_out_of_vanishing_polynomial) & block_mask];
512 numerator += T0;
513
514 // Step 2: Compute (z(X) - 1).(α^4).L1(X)
515 // We need to verify that z(X) equals `1` when evaluated at the first element of our subgroup H
516 // i.e. z(X) starts at 1 and ends at 1
517 // The `alpha^4` term is so that we can add this as a linearly independent term in our quotient
518 // polynomial
519 T0 = z_perm_fft[i] - fr(1); // T0 = (Z(X) - 1).(\alpha^2)
520 T0 *= alpha_squared; // T0 = (Z(X) - 1).(\alpha^4)
521 T0 *= l_start[i]; // T0 = (Z(X) - 1).(\alpha^2).L1(X)
522 numerator += T0;
523
524 // Combine into quotient polynomial
525 T0 = numerator - denominator;
526 key->quotient_polynomial_parts[i >> key->small_domain.log2_size][i & (key->circuit_size - 1)] =
527 T0 * alpha_base;
528
529 // Update our working root of unity
530 cur_root_times_beta *= key->large_domain.root;
531 }
532 });
533 return alpha_base.sqr().sqr();
534}
535
536// ###
537
538template <typename Field, typename Group, typename Transcript, const size_t num_roots_cut_out_of_vanishing_polynomial>
539VerifierPermutationWidget<Field, Group, Transcript, num_roots_cut_out_of_vanishing_polynomial>::
540 VerifierPermutationWidget()
541{}
542
561template <typename Field, typename Group, typename Transcript, const size_t num_roots_cut_out_of_vanishing_polynomial>
563 compute_quotient_evaluation_contribution(typename Transcript::Key* key,
564 const Field& alpha,
565 const Transcript& transcript,
566 Field& quotient_numerator_eval,
567 const bool idpolys)
568
569{
570 Field alpha_squared = alpha.sqr();
571 Field alpha_cubed = alpha_squared * alpha;
572 Field z = transcript.get_challenge_field_element("z"); // a.k.a. zeta or ʓ
573 Field beta = transcript.get_challenge_field_element("beta", 0);
574 Field gamma = transcript.get_challenge_field_element("beta", 1);
575 Field z_beta = z * beta;
576
577 // We need wire polynomials' and sigma polynomials' evaluations at zeta which we fetch from the transcript.
578 // Fetch a_eval, b_eval, c_eval, sigma1_eval, sigma2_eval
579 std::vector<Field> wire_evaluations;
580 std::vector<Field> sigma_evaluations;
581
582 for (size_t i = 0; i < key->program_width; ++i) {
583 std::string index = std::to_string(i + 1);
584 sigma_evaluations.emplace_back(transcript.get_field_element("sigma_" + index)); // S_σ_i(ʓ)
585 }
586
587 for (size_t i = 0; i < key->program_width; ++i) {
588 wire_evaluations.emplace_back(transcript.get_field_element(
589 "w_" + std::to_string(
590 i + 1))); // w_i(ʓ)
591 // (Note: in the Plonk paper, these polys are called a, b, c. We interchangeably call
592 // them a,b,c or w_l, w_r, w_o, or w_1, w_2, w_3,... depending on the context).
593 }
594
595 // Compute evaluations of lagrange polynomials L_1(X) and L_{n-k} at ʓ.
596 // Recall, k is the number of roots cut out of the vanishing polynomial Z_H(X), to yield Z_H*(X).
597 // Note that
598 // X^n - 1
599 // L_i(X) = L_1(X.ω^{-i + 1}) = -----------------
600 // X.ω^{-i + 1} - 1
601 //
602 Field numerator = key->z_pow_n - Field(1); // ʓ^n - 1
603
604 numerator *= key->domain.domain_inverse;
605 Field l_start = numerator / (z - Field(1)); // [ʓ^n - 1] / [n.(ʓ - 1)] =: L_1(ʓ)
606
607 // Compute ω^{num_roots_cut_out_of_vanishing_polynomial + 1}
608 Field l_end_root = (num_roots_cut_out_of_vanishing_polynomial & 1) ? key->domain.root.sqr() : key->domain.root;
609 for (size_t i = 0; i < num_roots_cut_out_of_vanishing_polynomial / 2; ++i) {
610 l_end_root *= key->domain.root.sqr();
611 }
612 Field l_end = numerator / ((z * l_end_root) - Field(1)); // [ʓ^n - 1] / [n.(ʓ.ω^{k+1} - 1)] =: L_{n-k}(ʓ)
613
614 Field z_1_shifted_eval = transcript.get_field_element("z_perm_omega");
615
616 // Recall that the full quotient numerator is the polynomial
617 // t(X) =
618 // [ a(X).b(X).qm(X) + a(X).ql(X) + b(X).qr(X) + c(X).qo(X) + qc(X) ]
619 // + α.[
620 // [ a(X) + β.X + γ)(b(X) + β.k_1.X + γ)(c(X) + β.k_2.X + γ).z(X) ]
621 // - [ a(X) + β.Sσ1(X) + γ)(b(X) + β.Sσ2(X) + γ)(c(X) + β.Sσ3(X) + γ).z(X.ω) ]
622 // ]
623 // + α^3.[ (z(X) - 1).L_1(X) ]
624 // + α^2.[ (z(X.ω) - ∆_PI).L_{n-k}(X) ]
625 //
626 // This function computes the copy constraint pair, i.e., the sum of the α^1, α^2 and α^3 terms.
627 Field T1;
628 Field T2;
629
630 // Part 1: compute the sigma contribution, i.e.
631 //
632 // sigma_contribution = (a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + γ).z(ʓ.ω).α
633 //
634 Field sigma_contribution = Field(1);
635 Field T0;
636 for (size_t i = 0; i < key->program_width - 1; ++i) {
637 T0 = sigma_evaluations[i] * beta;
638 T1 = wire_evaluations[i] + gamma;
639 T0 += T1;
640 sigma_contribution *= T0;
641 }
642
643 T0 = wire_evaluations[key->program_width - 1] + gamma;
644 sigma_contribution *= T0;
645 sigma_contribution *= z_1_shifted_eval;
646 sigma_contribution *= alpha;
647
648 // Part 2: compute the public-inputs term, i.e.
649 //
650 // (z(ʓ.ω) - ∆_{PI}).L_{n-k}(ʓ).α^2
651 //
652 // (See the separate paper which alters the 'public inputs' component of the plonk protocol)
653 std::vector<Field> public_inputs = transcript.get_field_element_vector("public_inputs");
654 Field public_input_delta = compute_public_input_delta<Field>(public_inputs, beta, gamma, key->domain.root);
655
656 T1 = z_1_shifted_eval - public_input_delta;
657 T1 *= l_end;
658 T1 *= alpha_squared;
659
660 // Part 3: compute starting lagrange polynomial term, i.e.
661 //
662 // L_1(ʓ).α^3
663 //
664 T2 = l_start * alpha_cubed;
665
666 // Combine parts 1, 2, 3.
667 // quotient_numerator_eval =
668 // α^2.(z(ʓ.ω) - ∆_{PI}).L_{n-k}(ʓ)
669 // - α^3.L_1(ʓ)
670 // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + γ).z(ʓ.ω)
671 //
672 T1 -= T2;
673 T1 -= sigma_contribution;
674 quotient_numerator_eval += T1;
675
676 // If we were using the linearization trick, we would return here. Instead we proceed to fully construct
677 // the permutation part of a purported quotient numerator value.
678
679 // Part 4: compute multiplicand of last sigma polynomial S_{sigma3}(X), i.e.
680 //
681 // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ).β.z(ʓ.ω)
682 //
683 sigma_contribution = Field(1);
684 for (size_t i = 0; i < key->program_width - 1; ++i) {
685 T0 = sigma_evaluations[i] * beta;
686 T0 += wire_evaluations[i];
687 T0 += gamma;
688 sigma_contribution *= T0;
689 }
690 sigma_contribution *= z_1_shifted_eval;
691 Field sigma_last_multiplicand = -(sigma_contribution * alpha);
692 sigma_last_multiplicand *= beta;
693
694 // Add up part 4 to the quotient_numerator_eval term
695 //
696 // At this intermediate stage, quotient_numerator_eval will be:
697 //
698 // quotient_numerator_eval = α^2.(z(ʓ.ω) - ∆_{PI}).L_{n-k}(ʓ) |
699 // - α^3.L_1(ʓ) |->
700 // quotient_numerator_eval from
701 // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + γ).z(ʓ.ω) | before
702 //
703 // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ).β.z(ʓ.ω).S_{sigma3}(ʓ)
704 // ^^^^^^^^^^^^^
705 // Evaluated at X=ʓ
706 // = α^2.(z(ʓ.ω) - ∆_{PI}).L_{n-k}(ʓ)
707 // - α^3.L_1(ʓ)
708 // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + β.S_{sigma3}(ʓ) + γ).z(ʓ.ω)
709 //
710 quotient_numerator_eval += (sigma_last_multiplicand * sigma_evaluations[key->program_width - 1]);
711
712 Field z_eval = transcript.get_field_element("z_perm");
713
714 if (idpolys) {
715
716 // Part 5.1: If idpolys = true, it indicates that we are not using the identity polynomials to
717 // represent identity permutations. In that case, we need to use the pre-defined values for
718 // representing identity permutations and then compute the coefficient of the z(X) component of r(X):
719 //
720 // [
721 // α.(a_eval + β.id_1 + γ)(b_eval + β.id_2 + γ)(c_eval + β.id_3 + γ)
722 // + α^3.L_1(ʓ)
723 // ].z(X)
724 //
725 Field id_contribution = Field(1);
726 for (size_t i = 0; i < key->program_width; ++i) {
727 Field id_evaluation = transcript.get_field_element("id_" + std::to_string(i + 1));
728 T0 = id_evaluation * beta;
729 T0 += wire_evaluations[i];
730 T0 += gamma;
731 id_contribution *= T0;
732 }
733 Field id_last_multiplicand = id_contribution * alpha;
734 T0 = l_start * alpha_cubed;
735 id_last_multiplicand += T0;
736
737 // Add up part 5.1 to the quotient_numerator_eval term, so quotient_numerator_eval will be:
738 //
739 // quotient_numerator_eval = α^2.(z(ʓ.ω) - ∆_{PI}).L_{n-k}(ʓ)
740 // - α^3.L_1(ʓ)
741 // - α.(a_eval + β.sigma1_eval + γ)(b_eval + β.sigma2_eval + γ)(c_eval + β.S_{sigma3}(ʓ) + γ).z(ʓ.ω)
742 // + [
743 // α.(a_eval + β.id_1 + γ)(b_eval + β.id_2 + γ)(c_eval + β.id_3 + γ)
744 // + α^3.L_1(ʓ)
745 // ].z(ʓ)
746 // ^^^^
747 // Evaluated at X=ʓ
748
749 quotient_numerator_eval += (id_last_multiplicand * z_eval);
750 } else {
751
752 // Part 5.2: If idpolys is false, the identity permutations are identity polynomials.
753 // So we need to compute the following term
754 //
755 // [
756 // α.(a_eval + β.ʓ + γ)(b_eval + β.k_1.ʓ + γ)(c_eval + β.k_2.ʓ + γ)
757 // + α^3.L_1(ʓ)
758 // ].z(ʓ)
759 //
760 Field z_contribution = Field(1);
761 for (size_t i = 0; i < key->program_width; ++i) {
762 Field coset_generator = (i == 0) ? Field(1) : Field::coset_generator(i - 1);
763 T0 = z_beta * coset_generator;
764 T0 += wire_evaluations[i];
765 T0 += gamma;
766 z_contribution *= T0;
767 }
768 Field z_1_multiplicand = (z_contribution * alpha);
769 T0 = l_start * alpha_cubed;
770 z_1_multiplicand += T0;
771
772 // add up part 5.2 to the quotient_numerator_eval term
773 quotient_numerator_eval += (z_1_multiplicand * z_eval);
774 }
775 return alpha_squared.sqr();
776}
777
778template <typename Field, typename Group, typename Transcript, const size_t num_roots_cut_out_of_vanishing_polynomial>
780 append_scalar_multiplication_inputs(typename Transcript::Key*,
781 const Field& alpha_base,
782 const Transcript& transcript)
783{
784 Field alpha_step = transcript.get_challenge_field_element("alpha");
785 return alpha_base * alpha_step.sqr() * alpha_step;
786}
787
788template class VerifierPermutationWidget<barretenberg::fr,
791
792} // namespace proof_system::plonk
Definition: affine_element.hpp:11
Definition: permutation_widget.hpp:29
Definition: permutation_widget.hpp:9
Definition: work_queue.hpp:11
Definition: transcript_wrappers.hpp:13
std::array< uint8_t, PRNG_OUTPUT_SIZE > get_challenge(const std::string &challenge_name, const size_t idx=0) const
Definition: transcript.cpp:308
std::vector< uint8_t > get_element(const std::string &element_name) const
Definition: transcript.cpp:392
std::shared_ptr< void > get_mem_slab(size_t size)
Definition: slab_allocator.cpp:214
Definition: widget.bench.cpp:13
BBERG_INLINE constexpr field sqr() const noexcept
Definition: field_impl.hpp:61