kimchi/circuits/polynomials/permutation.rs
1//! This module implements permutation constraint polynomials.
2
3//~ The permutation constraints are the following 4 constraints:
4//~
5//~ The two sides of the coin (with $\text{shift}_0 = 1$):
6//~
7//~ $$\begin{align}
8//~ & z(x) \cdot zkpm(x) \cdot \alpha^{PERM0} \cdot \\
9//~ & (w_0(x) + \beta \cdot \text{shift}_0 x + \gamma) \cdot \\
10//~ & (w_1(x) + \beta \cdot \text{shift}_1 x + \gamma) \cdot \\
11//~ & (w_2(x) + \beta \cdot \text{shift}_2 x + \gamma) \cdot \\
12//~ & (w_3(x) + \beta \cdot \text{shift}_3 x + \gamma) \cdot \\
13//~ & (w_4(x) + \beta \cdot \text{shift}_4 x + \gamma) \cdot \\
14//~ & (w_5(x) + \beta \cdot \text{shift}_5 x + \gamma) \cdot \\
15//~ & (w_6(x) + \beta \cdot \text{shift}_6 x + \gamma)
16//~ \end{align}$$
17//~
18//~ and
19//~
20//~ $$\begin{align}
21//~ & -1 \cdot z(x \omega) \cdot zkpm(x) \cdot \alpha^{PERM0} \cdot \\
22//~ & (w_0(x) + \beta \cdot \sigma_0(x) + \gamma) \cdot \\
23//~ & (w_1(x) + \beta \cdot \sigma_1(x) + \gamma) \cdot \\
24//~ & (w_2(x) + \beta \cdot \sigma_2(x) + \gamma) \cdot \\
25//~ & (w_3(x) + \beta \cdot \sigma_3(x) + \gamma) \cdot \\
26//~ & (w_4(x) + \beta \cdot \sigma_4(x) + \gamma) \cdot \\
27//~ & (w_5(x) + \beta \cdot \sigma_5(x) + \gamma) \cdot \\
28//~ & (w_6(x) + \beta \cdot \sigma_6(x) + \gamma) \cdot
29//~ \end{align}$$
30//~
31//~ the initialization of the accumulator:
32//~
33//~ $$(z(x) - 1) L_1(x) \alpha^{PERM1}$$
34//~
35//~ and the accumulator's final value:
36//~
37//~ $$(z(x) - 1) L_{n-k}(x) \alpha^{PERM2}$$
38//~
39//~ You can read more about why it looks like that in [this post](https://minaprotocol.com/blog/a-more-efficient-approach-to-zero-knowledge-for-plonk).
40//~
41use crate::{
42 circuits::{
43 constraints::ConstraintSystem,
44 polynomial::WitnessOverDomains,
45 wires::{Wire, COLUMNS, PERMUTS},
46 },
47 curve::KimchiCurve,
48 error::ProverError,
49 proof::{PointEvaluations, ProofEvaluations},
50 prover_index::ProverIndex,
51};
52use ark_ff::{FftField, PrimeField, Zero};
53use ark_poly::{
54 univariate::{DenseOrSparsePolynomial, DensePolynomial},
55 DenseUVPolynomial, EvaluationDomain, Evaluations, Polynomial, Radix2EvaluationDomain as D,
56};
57use blake2::{Blake2b512, Digest};
58use core::array;
59use o1_utils::{ExtendedDensePolynomial, ExtendedEvaluations};
60use poly_commitment::OpenProof;
61use rand::{CryptoRng, RngCore};
62use rayon::prelude::*;
63
64/// Number of constraints produced by the argument.
65pub const CONSTRAINTS: u32 = 3;
66
67/// Evaluates the polynomial
68/// (x - w^{n - i}) * (x - w^{n - i + 1}) * ... * (x - w^{n - 1})
69pub fn eval_vanishes_on_last_n_rows<F: FftField>(domain: D<F>, i: u64, x: F) -> F {
70 if i == 0 {
71 return F::one();
72 }
73 let mut term = domain.group_gen.pow([domain.size - i]);
74 let mut acc = x - term;
75 for _ in 0..i - 1 {
76 term *= domain.group_gen;
77 acc *= x - term;
78 }
79 acc
80}
81
82/// The polynomial
83/// (x - w^{n - i}) * (x - w^{n - i + 1}) * ... * (x - w^{n - 1})
84pub fn vanishes_on_last_n_rows<F: FftField>(domain: D<F>, i: u64) -> DensePolynomial<F> {
85 let constant = |a: F| DensePolynomial::from_coefficients_slice(&[a]);
86 if i == 0 {
87 return constant(F::one());
88 }
89 let x = DensePolynomial::from_coefficients_slice(&[F::zero(), F::one()]);
90 let mut term = domain.group_gen.pow([domain.size - i]);
91 let mut acc = &x - &constant(term);
92 for _ in 0..i - 1 {
93 term *= domain.group_gen;
94 acc = &acc * &(&x - &constant(term));
95 }
96 acc
97}
98
99/// Returns the end of the circuit, which is used for introducing zero-knowledge in the permutation polynomial
100pub fn zk_w<F: FftField>(domain: D<F>, zk_rows: u64) -> F {
101 domain.group_gen.pow([domain.size - zk_rows])
102}
103
104/// Evaluates the polynomial
105/// (x - w^{n - zk_rows}) * (x - w^{n - zk_rows + 1}) * (x - w^{n - 1})
106pub fn eval_permutation_vanishing_polynomial<F: FftField>(domain: D<F>, zk_rows: u64, x: F) -> F {
107 let term = domain.group_gen.pow([domain.size - zk_rows]);
108 (x - term) * (x - term * domain.group_gen) * (x - domain.group_gen.pow([domain.size - 1]))
109}
110
111/// The polynomial
112/// (x - w^{n - zk_rows}) * (x - w^{n - zk_rows + 1}) * (x - w^{n - 1})
113pub fn permutation_vanishing_polynomial<F: FftField>(
114 domain: D<F>,
115 zk_rows: u64,
116) -> DensePolynomial<F> {
117 let constant = |a: F| DensePolynomial::from_coefficients_slice(&[a]);
118 let x = DensePolynomial::from_coefficients_slice(&[F::zero(), F::one()]);
119 let term = domain.group_gen.pow([domain.size - zk_rows]);
120 &(&(&x - &constant(term)) * &(&x - &constant(term * domain.group_gen)))
121 * &(&x - &constant(domain.group_gen.pow([domain.size - 1])))
122}
123
124/// Shifts represent the shifts required in the permutation argument of PLONK.
125/// It also caches the shifted powers of omega for optimization purposes.
126pub struct Shifts<F> {
127 /// The coefficients `k` (in the Plonk paper) that create a coset when multiplied with the generator of our domain.
128 pub(crate) shifts: [F; PERMUTS],
129 /// A matrix that maps all cells coordinates `{col, row}` to their shifted field element.
130 /// For example the cell `{col:2, row:1}` will map to `omega * k2`,
131 /// which lives in `map[2][1]`
132 pub(crate) map: [Vec<F>; PERMUTS],
133}
134
135impl<F> Shifts<F>
136where
137 F: FftField,
138{
139 /// Generates the shifts for a given domain
140 pub fn new(domain: &D<F>) -> Self {
141 let mut shifts = [F::zero(); PERMUTS];
142
143 // first shift is the identity
144 shifts[0] = F::one();
145
146 // sample the other shifts
147 let mut i: u32 = 7;
148 for idx in 1..(PERMUTS) {
149 let mut shift = Self::sample(domain, &mut i);
150 // they have to be distincts
151 while shifts.contains(&shift) {
152 shift = Self::sample(domain, &mut i);
153 }
154 shifts[idx] = shift;
155 }
156
157 // create a map of cells to their shifted value
158 let map: [Vec<F>; PERMUTS] =
159 array::from_fn(|i| domain.elements().map(|elm| shifts[i] * elm).collect());
160
161 //
162 Self { shifts, map }
163 }
164
165 /// retrieve the shifts
166 pub fn shifts(&self) -> &[F; PERMUTS] {
167 &self.shifts
168 }
169
170 /// sample coordinate shifts deterministically
171 fn sample(domain: &D<F>, input: &mut u32) -> F {
172 let mut h = Blake2b512::new();
173
174 *input += 1;
175 h.update(input.to_be_bytes());
176
177 let mut shift = F::from_random_bytes(&h.finalize()[..31])
178 .expect("our field elements fit in more than 31 bytes");
179
180 while !shift.legendre().is_qnr() || domain.evaluate_vanishing_polynomial(shift).is_zero() {
181 let mut h = Blake2b512::new();
182 *input += 1;
183 h.update(input.to_be_bytes());
184 shift = F::from_random_bytes(&h.finalize()[..31])
185 .expect("our field elements fit in more than 31 bytes");
186 }
187 shift
188 }
189
190 /// Returns the field element that represents a position
191 pub(crate) fn cell_to_field(&self, &Wire { row, col }: &Wire) -> F {
192 self.map[col][row]
193 }
194}
195
196impl<F: PrimeField, G: KimchiCurve<ScalarField = F>, OpeningProof: OpenProof<G>>
197 ProverIndex<G, OpeningProof>
198{
199 /// permutation quotient poly contribution computation
200 ///
201 /// # Errors
202 ///
203 /// Will give error if `polynomial division` fails.
204 ///
205 /// # Panics
206 ///
207 /// Will panic if `power of alpha` is missing.
208 #[allow(clippy::type_complexity)]
209 pub fn perm_quot(
210 &self,
211 lagrange: &WitnessOverDomains<F>,
212 beta: F,
213 gamma: F,
214 z: &DensePolynomial<F>,
215 mut alphas: impl Iterator<Item = F>,
216 ) -> Result<(Evaluations<F, D<F>>, DensePolynomial<F>), ProverError> {
217 let alpha0 = alphas.next().expect("missing power of alpha");
218 let alpha1 = alphas.next().expect("missing power of alpha");
219 let alpha2 = alphas.next().expect("missing power of alpha");
220
221 let zk_rows = self.cs.zk_rows as usize;
222
223 // constant gamma in evaluation form (in domain d8)
224 let gamma = &self.cs.precomputations().constant_1_d8.scale(gamma);
225
226 //~ The quotient contribution of the permutation is split into two parts $perm$ and $bnd$.
227 //~ They will be used by the prover.
228 //~
229 //~ $$
230 //~ \begin{align}
231 //~ perm(x) =
232 //~ & \; a^{PERM0} \cdot zkpl(x) \cdot [ \\
233 //~ & \;\; z(x) \cdot \\
234 //~ & \;\; (w_0(x) + \gamma + x \cdot \beta \cdot \text{shift}_0) \cdot \\
235 //~ & \;\; (w_1(x) + \gamma + x \cdot \beta \cdot \text{shift}_1) \cdot \\
236 //~ & \;\; (w_2(x) + \gamma + x \cdot \beta \cdot \text{shift}_2) \cdot \\
237 //~ & \;\; (w_3(x) + \gamma + x \cdot \beta \cdot \text{shift}_3) \cdot \\
238 //~ & \;\; (w_4(x) + \gamma + x \cdot \beta \cdot \text{shift}_4) \cdot \\
239 //~ & \;\; (w_5(x) + \gamma + x \cdot \beta \cdot \text{shift}_5) \cdot \\
240 //~ & \;\; (w_6(x) + \gamma + x \cdot \beta \cdot \text{shift}_6) \cdot \\
241 //~ & \; - \\
242 //~ & \;\; z(x \cdot w) \cdot \\
243 //~ & \;\; (w_0(x) + \gamma + \sigma_0 \cdot \beta) \cdot \\
244 //~ & \;\; (w_1(x) + \gamma + \sigma_1 \cdot \beta) \cdot \\
245 //~ & \;\; (w_2(x) + \gamma + \sigma_2 \cdot \beta) \cdot \\
246 //~ & \;\; (w_3(x) + \gamma + \sigma_3 \cdot \beta) \cdot \\
247 //~ & \;\; (w_4(x) + \gamma + \sigma_4 \cdot \beta) \cdot \\
248 //~ & \;\; (w_5(x) + \gamma + \sigma_5 \cdot \beta) \cdot \\
249 //~ & \;\; (w_6(x) + \gamma + \sigma_6 \cdot \beta) \cdot \\
250 //~ &]
251 //~ \end{align}
252 //~ $$
253 //~
254 let perm = {
255 // shifts = z(x) *
256 // (w[0](x) + gamma + x * beta * shift[0]) *
257 // (w[1](x) + gamma + x * beta * shift[1]) * ...
258 // (w[6](x) + gamma + x * beta * shift[6])
259 // in evaluation form in d8
260 let shifts: Evaluations<F, D<F>> = &lagrange
261 .d8
262 .this
263 .w
264 .par_iter()
265 .zip(self.cs.shift.par_iter())
266 .map(|(witness, shift)| {
267 &(witness + gamma) + &self.cs.precomputations().poly_x_d1.scale(beta * shift)
268 })
269 .reduce_with(|mut l, r| {
270 l *= &r;
271 l
272 })
273 .unwrap()
274 * &lagrange.d8.this.z.clone();
275
276 // sigmas = z(x * w) *
277 // (w8[0] + gamma + sigma[0] * beta) *
278 // (w8[1] + gamma + sigma[1] * beta) * ...
279 // (w8[6] + gamma + sigma[6] * beta)
280 // in evaluation form in d8
281 let sigmas = &lagrange
282 .d8
283 .this
284 .w
285 .par_iter()
286 .zip(
287 self.column_evaluations
288 .get()
289 .permutation_coefficients8
290 .par_iter(),
291 )
292 .map(|(witness, sigma)| witness + &(gamma + &sigma.scale(beta)))
293 .reduce_with(|mut l, r| {
294 l *= &r;
295 l
296 })
297 .unwrap()
298 * &lagrange.d8.next.z.clone();
299
300 &(&shifts - &sigmas).scale(alpha0)
301 * &self.cs.precomputations().permutation_vanishing_polynomial_l
302 };
303
304 //~ and `bnd`:
305 //~
306 //~ $$bnd(x) =
307 //~ a^{PERM1} \cdot \frac{z(x) - 1}{x - 1}
308 //~ +
309 //~ a^{PERM2} \cdot \frac{z(x) - 1}{x - sid[n-k]}
310 //~ $$
311 let bnd = {
312 let one_poly = DensePolynomial::from_coefficients_slice(&[F::one()]);
313 let z_minus_1 = z - &one_poly;
314
315 // TODO(mimoo): use self.sid[0] instead of 1
316 // accumulator init := (z(x) - 1) / (x - 1)
317 let x_minus_1 = DensePolynomial::from_coefficients_slice(&[-F::one(), F::one()]);
318 let (bnd1, res) = DenseOrSparsePolynomial::divide_with_q_and_r(
319 &z_minus_1.clone().into(),
320 &x_minus_1.into(),
321 )
322 .ok_or(ProverError::Permutation("first division"))?;
323 if !res.is_zero() {
324 return Err(ProverError::Permutation("first division rest"));
325 }
326
327 // accumulator end := (z(x) - 1) / (x - sid[n-zk_rows])
328 let denominator = DensePolynomial::from_coefficients_slice(&[
329 -self.cs.sid[self.cs.domain.d1.size() - zk_rows],
330 F::one(),
331 ]);
332 let (bnd2, res) = DenseOrSparsePolynomial::divide_with_q_and_r(
333 &z_minus_1.into(),
334 &denominator.into(),
335 )
336 .ok_or(ProverError::Permutation("second division"))?;
337 if !res.is_zero() {
338 return Err(ProverError::Permutation("second division rest"));
339 }
340
341 &bnd1.scale(alpha1) + &bnd2.scale(alpha2)
342 };
343 Ok((perm, bnd))
344 }
345
346 /// permutation linearization poly contribution computation
347 pub fn perm_lnrz(
348 &self,
349 e: &ProofEvaluations<PointEvaluations<F>>,
350 zeta: F,
351 beta: F,
352 gamma: F,
353 alphas: impl Iterator<Item = F>,
354 ) -> Evaluations<F, D<F>> {
355 //~
356 //~ The linearization:
357 //~
358 //~ $\text{scalar} \cdot \sigma_6(x)$
359 //~
360 let zkpm_zeta = self
361 .cs
362 .precomputations()
363 .permutation_vanishing_polynomial_m
364 .evaluate(&zeta);
365 let scalar = ConstraintSystem::<F>::perm_scalars(e, beta, gamma, alphas, zkpm_zeta);
366 let evals8 = &self.column_evaluations.get().permutation_coefficients8[PERMUTS - 1].evals;
367 const STRIDE: usize = 8;
368 let n = evals8.len() / STRIDE;
369 let evals = (0..n)
370 .into_par_iter()
371 .map(|i| scalar * evals8[STRIDE * i])
372 .collect();
373 Evaluations::from_vec_and_domain(evals, D::new(n).unwrap())
374 }
375}
376
377impl<F: PrimeField> ConstraintSystem<F> {
378 pub fn perm_scalars(
379 e: &ProofEvaluations<PointEvaluations<F>>,
380 beta: F,
381 gamma: F,
382 mut alphas: impl Iterator<Item = F>,
383 zkp_zeta: F,
384 ) -> F {
385 let alpha0 = alphas
386 .next()
387 .expect("not enough powers of alpha for permutation");
388 let _alpha1 = alphas
389 .next()
390 .expect("not enough powers of alpha for permutation");
391 let _alpha2 = alphas
392 .next()
393 .expect("not enough powers of alpha for permutation");
394
395 //~ where $\text{scalar}$ is computed as:
396 //~
397 //~ $$
398 //~ \begin{align}
399 //~ z(\zeta \omega) \beta \alpha^{PERM0} zkpl(\zeta) \cdot \\
400 //~ (\gamma + \beta \sigma_0(\zeta) + w_0(\zeta)) \cdot \\
401 //~ (\gamma + \beta \sigma_1(\zeta) + w_1(\zeta)) \cdot \\
402 //~ (\gamma + \beta \sigma_2(\zeta) + w_2(\zeta)) \cdot \\
403 //~ (\gamma + \beta \sigma_3(\zeta) + w_3(\zeta)) \cdot \\
404 //~ (\gamma + \beta \sigma_4(\zeta) + w_4(\zeta)) \cdot \\
405 //~ (\gamma + \beta \sigma_5(\zeta) + w_5(\zeta)) \cdot \\
406 //~ \end{align}
407 //~$$
408 //~
409 let init = e.z.zeta_omega * beta * alpha0 * zkp_zeta;
410 let res =
411 e.w.iter()
412 .zip(e.s.iter())
413 .map(|(w, s)| gamma + (beta * s.zeta) + w.zeta)
414 .fold(init, |x, y| x * y);
415 -res
416 }
417}
418
419impl<F: PrimeField, G: KimchiCurve<ScalarField = F>, OpeningProof: OpenProof<G>>
420 ProverIndex<G, OpeningProof>
421{
422 /// permutation aggregation polynomial computation
423 ///
424 /// # Errors
425 ///
426 /// Will give error if permutation result is not correct.
427 ///
428 /// # Panics
429 ///
430 /// Will panic if `first element` is not 1.
431 pub fn perm_aggreg(
432 &self,
433 witness: &[Vec<F>; COLUMNS],
434 beta: &F,
435 gamma: &F,
436 rng: &mut (impl RngCore + CryptoRng),
437 ) -> Result<DensePolynomial<F>, ProverError> {
438 let n = self.cs.domain.d1.size();
439
440 let zk_rows = self.cs.zk_rows as usize;
441
442 // only works if first element is 1
443 assert_eq!(self.cs.domain.d1.elements().next(), Some(F::one()));
444
445 //~ To compute the permutation aggregation polynomial,
446 //~ the prover interpolates the polynomial that has the following evaluations.
447
448 //~ The first evaluation represents the initial value of the accumulator:
449 //~ $$z(g^0) = 1$$
450
451 //~ For $i = 0, \cdot, n - 4$, where $n$ is the size of the domain,
452 //~ evaluations are computed as:
453 //~
454 //~ $$z(g^{i+1}) = z_1 / z_2$$
455 //~
456 //~ with
457 //~
458 //~ $$
459 //~ \begin{align}
460 //~ z_1 = &\ (w_0(g^i + sid(g^i) \cdot beta \cdot shift_0 + \gamma) \cdot \\
461 //~ &\ (w_1(g^i) + sid(g^i) \cdot beta \cdot shift_1 + \gamma) \cdot \\
462 //~ &\ (w_2(g^i) + sid(g^i) \cdot beta \cdot shift_2 + \gamma) \cdot \\
463 //~ &\ (w_3(g^i) + sid(g^i) \cdot beta \cdot shift_3 + \gamma) \cdot \\
464 //~ &\ (w_4(g^i) + sid(g^i) \cdot beta \cdot shift_4 + \gamma) \cdot \\
465 //~ &\ (w_5(g^i) + sid(g^i) \cdot beta \cdot shift_5 + \gamma) \cdot \\
466 //~ &\ (w_6(g^i) + sid(g^i) \cdot beta \cdot shift_6 + \gamma)
467 //~ \end{align}
468 //~ $$
469 //~
470 //~ and
471 //~
472 //~ $$
473 //~ \begin{align}
474 //~ z_2 = &\ (w_0(g^i) + \sigma_0 \cdot beta + \gamma) \cdot \\
475 //~ &\ (w_1(g^i) + \sigma_1 \cdot beta + \gamma) \cdot \\
476 //~ &\ (w_2(g^i) + \sigma_2 \cdot beta + \gamma) \cdot \\
477 //~ &\ (w_3(g^i) + \sigma_3 \cdot beta + \gamma) \cdot \\
478 //~ &\ (w_4(g^i) + \sigma_4 \cdot beta + \gamma) \cdot \\
479 //~ &\ (w_5(g^i) + \sigma_5 \cdot beta + \gamma) \cdot \\
480 //~ &\ (w_6(g^i) + \sigma_6 \cdot beta + \gamma)
481 //~ \end{align}
482 //~ $$
483 //~
484
485 // We compute z such that:
486 // z[0] = 1
487 // z[j+1] = \Prod_{i=0}^{PERMUTS}(wit[i][j] + (s[i][8*j] * beta) + gamma) for j ā 0..n-1
488 //
489 // We compute every product batch separately first (one batch
490 // per iā[COLUMNS]), and then multiply all batches together.
491 //
492 // Note that we zip array of COLUMNS with array of PERMUTS;
493 // Since PERMUTS < COLUMNS, that's what's actually used.
494 let mut z: Vec<F> = witness
495 .par_iter()
496 .zip(
497 self.column_evaluations
498 .get()
499 .permutation_coefficients8
500 .par_iter(),
501 )
502 .map(|(w_i, perm_coeffs8_i)| {
503 let mut output_vec: Vec<_> = vec![F::one(); 1];
504 for (j, w_i_j) in w_i.iter().enumerate().take(n - 1) {
505 output_vec.push(*w_i_j + (perm_coeffs8_i[8 * j] * beta) + gamma);
506 }
507 output_vec
508 })
509 .reduce_with(|mut l, r| {
510 for i in 0..n {
511 l[i] *= &r[i];
512 }
513 l
514 })
515 .unwrap();
516
517 ark_ff::fields::batch_inversion::<F>(&mut z[1..n]);
518
519 let z_prefolded: Vec<F> = witness
520 .par_iter()
521 .zip(self.cs.shift.par_iter())
522 .map(|(w_i, shift_i)| {
523 let mut output_vec: Vec<_> = vec![F::one(); 1];
524 for (j, w_i_j) in w_i.iter().enumerate().take(n - 1) {
525 output_vec.push(*w_i_j + (self.cs.sid[j] * beta * shift_i) + gamma);
526 }
527 output_vec
528 })
529 .reduce_with(|mut l, r| {
530 for i in 0..n {
531 l[i] *= &r[i];
532 }
533 l
534 })
535 .unwrap();
536
537 //~ We randomize the evaluations at `n - zk_rows + 1` and `n - zk_rows + 2` in order to add
538 //~ zero-knowledge to the protocol.
539 //~
540 for j in 0..n - 1 {
541 if j != n - zk_rows && j != n - zk_rows + 1 {
542 let x = z[j];
543 z[j + 1] *= z_prefolded[j + 1] * x;
544 } else {
545 z[j + 1] = F::rand(rng);
546 }
547 }
548
549 //~ For a valid witness, we then have have $z(g^{n-zk_rows}) = 1$.
550 //~
551 if z[n - zk_rows] != F::one() {
552 return Err(ProverError::Permutation("final value"));
553 };
554
555 let res = Evaluations::<F, D<F>>::from_vec_and_domain(z, self.cs.domain.d1).interpolate();
556
557 Ok(res)
558 }
559}