Skip to main content

w3f_plonk_common/
domain.rs

1use crate::FieldColumn;
2use ark_ff::{batch_inversion, FftField, Field, Zero};
3use ark_poly::univariate::DensePolynomial;
4use ark_poly::{
5    DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial,
6};
7use ark_std::{vec, vec::Vec};
8use getrandom_or_panic::getrandom_or_panic;
9
10// Domains for performing calculations with constraint polynomials of degree up to 4.
11#[derive(Clone)]
12pub struct Domains<F: FftField> {
13    pub x1: GeneralEvaluationDomain<F>,
14    pub x4: GeneralEvaluationDomain<F>,
15}
16
17impl<F: FftField> Domains<F> {
18    fn new(n: usize) -> Self {
19        let x1 = GeneralEvaluationDomain::<F>::new(n)
20            .unwrap_or_else(|| panic!("No domain of size {}", n));
21        let x4 = GeneralEvaluationDomain::<F>::new(4 * n)
22            .unwrap_or_else(|| panic!("No domain of size {}", 4 * n));
23        Self { x1, x4 }
24    }
25
26    fn column_from_evals(&self, padded_evals: Vec<F>, payload_len: usize) -> FieldColumn<F> {
27        debug_assert_eq!(padded_evals.len(), self.x1.size());
28        let evals = Evaluations::from_vec_and_domain(padded_evals, self.x1);
29        let poly = evals.interpolate_by_ref();
30        let evals_4x = poly.evaluate_over_domain_by_ref(self.x4);
31        FieldColumn {
32            poly,
33            evals,
34            evals_4x,
35            payload_len,
36        }
37    }
38
39    fn column_from_poly(&self, poly: DensePolynomial<F>) -> FieldColumn<F> {
40        debug_assert!(poly.degree() + 1 <= self.x1.size());
41        let evals_4x = self.amplify(&poly);
42        let evals = evals_4x.evals.iter().step_by(4).cloned().collect();
43        let evals = Evaluations::from_vec_and_domain(evals, self.x1);
44        FieldColumn {
45            poly,
46            evals,
47            evals_4x,
48            payload_len: self.x1.size(),
49        }
50    }
51
52    // Amplifies the number of the evaluations of the polynomial so it can be multiplied in linear time.
53    fn amplify(&self, poly: &DensePolynomial<F>) -> Evaluations<F> {
54        poly.evaluate_over_domain_by_ref(self.x4)
55    }
56}
57
58#[derive(Clone)]
59pub struct Domain<F: FftField> {
60    pub domains: Domains<F>,
61    pub zk_rows: usize,
62    pub capacity: usize,
63    pub not_last_row: FieldColumn<F>,
64    pub l_first: FieldColumn<F>,
65    pub l_last: FieldColumn<F>,
66    zk_rows_prod: DensePolynomial<F>,
67    blinding: bool,
68}
69
70impl<F: FftField> Domain<F> {
71    /// Returns a "non-blinding" domain of full `capacity = N`.
72    pub fn no_zk(n: usize) -> Self {
73        Self::with_zk_rows(n, 0)
74    }
75
76    /// Returns a domain that blinds (column) polynomials.
77    /// The highest `zk_rows` evaluations (aka Lagrange coefficients) are set random.
78    /// After interpolation that is equivallent to adding `r(X).Z'(X)`, for a random `deg(r) = zk_rows - 1`.
79    pub fn with_zk_rows(n: usize, zk_rows: usize) -> Self {
80        let domains = Domains::new(n);
81        let domain_size = domains.x1.size();
82        let capacity = domain_size - zk_rows;
83        let last_row_index = capacity - 1;
84
85        let l_first = l_i(0, domain_size);
86        let l_first = domains.column_from_evals(l_first, 0);
87        let l_last = l_i(last_row_index, domain_size);
88        let l_last = domains.column_from_evals(l_last, 0);
89
90        let (zk_rows_prod, last_row) = compute_row_polys(domains.x1, zk_rows);
91        let not_last_row = domains.column_from_poly(last_row);
92
93        Self {
94            domains,
95            zk_rows,
96            capacity,
97            not_last_row,
98            l_first,
99            l_last,
100            zk_rows_prod,
101            blinding: zk_rows != 0,
102        }
103    }
104
105    /// Disables column blinding, preserving the domain layout (`zk_rows`, `capacity`):
106    /// the zk rows are padded with zeros instead of random values.
107    /// Proofs generated over such a domain are deterministic, thus NOT zero-knowledge,
108    /// but remain valid for verifiers configured with the blinding-enabled domain.
109    /// Intended for reproducible test vectors generation.
110    pub fn without_blinding(mut self) -> Self {
111        self.blinding = false;
112        self
113    }
114
115    #[cfg(test)]
116    pub const ZK_ROWS_TEST: usize = 3;
117
118    #[cfg(test)]
119    pub fn test_domain(n: usize, hiding: bool) -> Self {
120        if hiding {
121            Self::with_zk_rows(n, Self::ZK_ROWS_TEST)
122        } else {
123            Self::with_zk_rows(n, 0)
124        }
125    }
126
127    pub fn is_hiding(&self) -> bool {
128        self.zk_rows != 0
129    }
130
131    pub fn compute_quotient(&self, poly: &DensePolynomial<F>) -> Option<DensePolynomial<F>> {
132        let (q, r) = self.div_by_z_with_remainder(poly);
133        r.is_zero().then_some(q)
134    }
135
136    fn div_by_z_with_remainder(
137        &self,
138        p: &DensePolynomial<F>,
139    ) -> (DensePolynomial<F>, DensePolynomial<F>) {
140        let dividend = if self.is_hiding() {
141            &(p * &self.zk_rows_prod)
142        } else {
143            p
144        };
145        dividend.divide_by_vanishing_poly(self.domains.x1)
146    }
147
148    fn _column(&self, mut values: Vec<F>, public: bool) -> FieldColumn<F> {
149        let payload_len = values.len();
150        assert!(payload_len <= self.capacity);
151        if self.blinding && !public {
152            values.resize(self.capacity, F::zero());
153            let rng = &mut getrandom_or_panic();
154            values.resize_with(self.domain_size(), || F::rand(rng));
155        } else {
156            values.resize(self.domain_size(), F::zero());
157        }
158        self.domains.column_from_evals(values, payload_len)
159    }
160
161    pub fn column(&self, values: Vec<F>) -> FieldColumn<F> {
162        self._column(values, false)
163    }
164
165    pub fn public_column(&self, values: Vec<F>) -> FieldColumn<F> {
166        self._column(values, true)
167    }
168
169    pub fn domain(&self) -> GeneralEvaluationDomain<F> {
170        self.domains.x1
171    }
172
173    pub fn domain_size(&self) -> usize {
174        self.domain().size()
175    }
176
177    pub fn omega(&self) -> F {
178        self.domain().group_gen()
179    }
180
181    pub fn evaluate(&self, zeta: F) -> EvaluatedDomain<F> {
182        EvaluatedDomain::new(self.domain(), zeta, self.zk_rows)
183    }
184}
185
186fn l_i<F: FftField>(i: usize, n: usize) -> Vec<F> {
187    let mut l_i = vec![F::zero(); n];
188    l_i[i] = F::one();
189    l_i
190}
191
192/// For the generator `w = domain.group_gen()` of a domain of size `N`, returns `w^{N-1}, w^{N-2}, ..., w^0 = 1`.
193fn elements_rev<F: FftField, D: EvaluationDomain<F>>(domain: D) -> impl Iterator<Item = F> {
194    let w_inv = domain.group_gen_inv();
195    debug_assert_eq!(w_inv * domain.group_gen(), F::one()); // w^{n-1} = w^{-1}
196    ark_std::iter::successors(Some(w_inv), move |wi| (!wi.is_one()).then(|| w_inv * wi))
197}
198
199/// `Z(c) = X - c`
200fn z_poly<F: Field>(c: F) -> DensePolynomial<F> {
201    DensePolynomial::from_coefficients_vec(vec![-c, F::one()])
202}
203
204fn one<F: Field>() -> DensePolynomial<F> {
205    DensePolynomial::from_coefficients_vec(vec![F::one()])
206}
207
208/// For a domain of size `N` returns the vanishing polynomial `Z` of its suffix, `deg(Z) = zk_rows`,
209/// `Z(X) = (X - w^{N-1}) * (X - w^{N-2}) * ... * (X - w^{N-zk_rows})`,
210/// and the following not included term `X - w^{N-zk_rows-1}`.
211fn compute_row_polys<F: FftField, D: EvaluationDomain<F>>(
212    domain: D,
213    zk_rows: usize,
214) -> (DensePolynomial<F>, DensePolynomial<F>) {
215    assert!(domain.size() >= 1 + zk_rows, "0 domain");
216    let ws_rev_iter = elements_rev(domain); // `w^{N-1},...,0 = w^N`
217    let mut z_polys = ws_rev_iter.map(|wi| z_poly(wi));
218    let zk_rows_prod = z_polys.by_ref().take(zk_rows).fold(one(), |acc, x| acc * x);
219    let last_row = z_polys.next().unwrap();
220    (zk_rows_prod, last_row)
221}
222
223pub struct EvaluatedDomain<F: FftField> {
224    pub domain: GeneralEvaluationDomain<F>,
225    pub not_last_row: F,
226    pub l_first: F,
227    pub l_last: F,
228    pub vanishing_polynomial_inv: F,
229}
230
231impl<F: FftField> EvaluatedDomain<F> {
232    pub fn new(domain: GeneralEvaluationDomain<F>, z: F, zk_rows: usize) -> Self {
233        let mut z_n = z; // z^n, n=2^d - domain size, so squarings only
234        for _ in 0..domain.log_size_of_group() {
235            z_n.square_in_place();
236        }
237        let z_n_minus_one = z_n - F::one(); // vanishing polynomial of the full domain
238
239        // w^{n-1}
240        let mut wi = domain.group_gen_inv();
241        // Vanishing polynomial of zk rows: prod = (z - w^{n-1})...(z - w^{n-k})
242        let mut prod = F::one();
243        for _ in 0..zk_rows {
244            prod *= z - wi;
245            wi *= domain.group_gen_inv();
246        }
247        // z - w^{n-(k+1)}}
248        let not_last_row = z - wi;
249
250        // w^{k+1}
251        let wj = domain.group_gen().pow([(zk_rows + 1) as u64]);
252
253        let mut inv = [z_n_minus_one, z - F::one(), wj * z - F::one()];
254        batch_inversion(&mut inv);
255
256        let vanishing_polynomial_inv = prod * inv[0];
257        let z_n_minus_one_div_n = z_n_minus_one * domain.size_inv();
258        let l_first = z_n_minus_one_div_n * inv[1];
259        let l_last = z_n_minus_one_div_n * inv[2];
260
261        Self {
262            domain,
263            not_last_row,
264            l_first,
265            l_last,
266            vanishing_polynomial_inv,
267        }
268    }
269
270    pub(crate) fn divide_by_vanishing_poly_in_zeta(&self, poly_in_zeta: F) -> F {
271        poly_in_zeta * self.vanishing_polynomial_inv
272    }
273
274    pub fn omega(&self) -> F {
275        self.domain.group_gen()
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282    use ark_ed_on_bls12_381_bandersnatch::Fq;
283    use ark_ff::One;
284    use ark_poly::Radix2EvaluationDomain;
285    use ark_std::{test_rng, UniformRand};
286
287    fn _test_evaluated_domain(hiding: bool) {
288        let rng = &mut test_rng();
289
290        // let domain = GeneralEvaluationDomain::new(1024);
291        let n = 1024;
292        let domain = Domain::test_domain(n, hiding);
293        let z = Fq::rand(rng);
294        let domain_eval = domain.evaluate(z);
295        assert_eq!(domain.l_first.poly.evaluate(&z), domain_eval.l_first);
296        assert_eq!(domain.l_last.poly.evaluate(&z), domain_eval.l_last);
297        assert_eq!(
298            domain.not_last_row.poly.evaluate(&z),
299            domain_eval.not_last_row
300        );
301    }
302
303    #[test]
304    #[should_panic(expected = "0 domain")]
305    fn test_domain_zk_rows() {
306        let log_n = 4;
307        let n = 1 << log_n;
308        let domain = Radix2EvaluationDomain::<Fq>::new(n).unwrap();
309        let w = domain.group_gen();
310        let (zk_rows_prod, last_row) = compute_row_polys(domain, 0);
311        assert_eq!(zk_rows_prod, one());
312        assert_eq!(last_row, z_poly(domain.group_gen_inv()));
313
314        let zk_rows = 3;
315        let (zk_rows_prod, last_row) = compute_row_polys(domain, zk_rows);
316        assert_eq!(zk_rows_prod.degree(), zk_rows);
317        let last_row_index = n - (zk_rows + 1);
318        assert_eq!(last_row, z_poly(w.pow([last_row_index as u64])));
319
320        let zk_rows = n - 1;
321        let (zk_rows_prod, last_row) = compute_row_polys(domain, zk_rows);
322        assert_eq!(last_row, z_poly(Fq::one()));
323        assert_eq!(
324            zk_rows_prod * last_row,
325            domain.vanishing_polynomial().into()
326        );
327
328        let zk_rows = n;
329        compute_row_polys(domain, zk_rows);
330    }
331
332    #[test]
333    fn test_evaluated_domain() {
334        _test_evaluated_domain(false);
335        _test_evaluated_domain(true);
336    }
337
338    // Disabling blinding must yield reproducible columns (deterministic proofs, for test
339    // vectors generation) without altering the domain layout, so that the resulting proofs
340    // still match the parameters (e.g. max ring size) of the blinding-enabled configuration.
341    #[test]
342    fn column_blinding() {
343        let n = 16;
344        let values = vec![Fq::one(); 4];
345
346        let domain = Domain::test_domain(n, true);
347        let col_1 = domain.column(values.clone());
348        let col_2 = domain.column(values.clone());
349        assert_ne!(col_1.poly, col_2.poly);
350
351        let capacity = domain.capacity;
352        let domain = domain.without_blinding();
353        assert_eq!(domain.capacity, capacity);
354        let col_1 = domain.column(values.clone());
355        let col_2 = domain.column(values);
356        assert_eq!(col_1.poly, col_2.poly);
357    }
358}