Skip to main content

primitives/algebra/
multivariate_ring.rs

1use std::{
2    iter::Sum,
3    marker::PhantomData,
4    ops::{Add, AddAssign, Mul},
5};
6
7use ff::Field;
8use typenum::PowerOfTwo;
9
10// This module defines a multivariate polynomial ring $$ F_q[X_1, ..., X_n] / (X_1^2 - 1, ...,
11// X_n^2 - 1) $$ together with basic operations of this ring.
12use crate::{
13    algebra::field::{FieldExtension, SubfieldElement},
14    izip_eq,
15    random::{CryptoRngCore, Random},
16    types::{heap_array::SubfieldElements, Positive},
17};
18
19/// The form of multivariate polynomial
20#[derive(Default, Copy, Clone, Debug, PartialEq)]
21pub struct Coefficient;
22#[derive(Default, Copy, Clone, Debug, PartialEq)]
23pub struct Evaluation;
24
25/// An element `P(X_0, ..., X_{N-1})` of the multivariate polynomial ring with `N` variables.
26///
27/// The polynomial can be either in coefficient or in evaluation form, given by `Form`
28/// parameter. The number of coefficients/evaluations is `M = 2 ^ N`.
29///
30/// In _coefficient_ representation `data` are the coefficients of the polynomial:
31///     `\sum_{i=0..2^N-1} c_i * \Prod_{j=0..N-1} X_j ^ bin(i)_j`
32/// `bin(i)` is the  binary-decomposition of `i` (LSB first).
33/// For example for `N=2` the 2-variate polynomial is `c_0 + c_1 . X_0 + c_2 . X_1 + c_3 . X_0 .
34/// X_1` and `data = [c_0, c_1, c_2, c_3]`.
35///
36/// In _evaluation_ representation `data` are the evaluations:
37///     `e_i = P(bin_signed(i))`
38/// `bin_signed(i)` is the signed binary-decomposition of `i` (LSB first).
39/// For example for `N=2` the evaluations are:
40///     - `e_0 = P(-1, -1)`
41///     - `e_1 = P(-1,  1)`
42///     - `e_2 = P( 1, -1)`
43///     - `e_3 = P( 1,  1)`
44/// and `data = [e_0, e_1, e_2, e_3]`
45#[derive(Clone, Default, Debug, PartialEq)]
46pub struct MultivariateRing<F: FieldExtension, M: Positive, Form> {
47    data: SubfieldElements<F, M>,
48    _form: PhantomData<Form>,
49}
50
51pub type MultivariateRingCoefForm<F, M> = MultivariateRing<F, M, Coefficient>;
52pub type MultivariateRingEvalForm<F, M> = MultivariateRing<F, M, Evaluation>;
53
54impl<F: FieldExtension, M: Positive + PowerOfTwo, Form> MultivariateRing<F, M, Form> {
55    pub fn new(data: SubfieldElements<F, M>) -> Self {
56        Self {
57            data,
58            _form: PhantomData,
59        }
60    }
61
62    pub fn random(rng: impl CryptoRngCore) -> Self {
63        Self::new(SubfieldElements::<F, M>::random(rng))
64    }
65
66    pub fn data(&self) -> &SubfieldElements<F, M> {
67        &self.data
68    }
69
70    pub fn into_data(self) -> SubfieldElements<F, M> {
71        self.data
72    }
73}
74
75impl<F: FieldExtension, M: Positive + PowerOfTwo> MultivariateRing<F, M, Coefficient> {
76    /// Transform polynomial from coefficient representation to evaluation representation using the
77    /// Walsh-Hadamard transform.
78    pub fn to_eval_repr(self) -> MultivariateRing<F, M, Evaluation> {
79        let mut data: SubfieldElements<F, M> = self.data.clone();
80        walsh_hadamard::walsh_hadamard_inplace(&mut data);
81        MultivariateRing::<_, _, Evaluation>::new(data)
82    }
83
84    pub fn nb_coefs(&self) -> usize {
85        self.data.len()
86    }
87}
88
89impl<F: FieldExtension, M: Positive + PowerOfTwo> MultivariateRing<F, M, Evaluation> {
90    pub fn new_from_sparse_coef(
91        coefs: impl IntoIterator<Item = (usize, SubfieldElement<F>)>,
92    ) -> Self {
93        let mut data = SubfieldElements::<F, M>::default();
94        for (mut i, c_i) in coefs {
95            debug_assert!(i < M::to_usize());
96            i &= M::to_usize() - 1; // Reduce `i` modulo `M`
97            data[i] += c_i;
98        }
99        MultivariateRing::<_, _, Coefficient>::new(data).to_eval_repr()
100    }
101
102    pub fn from_coeffs(coefs: SubfieldElements<F, M>) -> Self {
103        MultivariateRing::new(coefs).to_eval_repr()
104    }
105
106    pub fn square(&self) -> Self {
107        Self {
108            data: self.data.iter().map(SubfieldElement::<F>::square).collect(),
109            _form: PhantomData,
110        }
111    }
112
113    pub fn nb_evals(&self) -> usize {
114        self.data.len()
115    }
116}
117
118#[macros::op_variants(owned, borrowed, flipped)]
119impl<F: FieldExtension, M: Positive + PowerOfTwo> Mul<&MultivariateRing<F, M, Evaluation>>
120    for MultivariateRing<F, M, Evaluation>
121{
122    type Output = MultivariateRing<F, M, Evaluation>;
123
124    fn mul(self, rhs: &MultivariateRing<F, M, Evaluation>) -> Self::Output {
125        Self::new(izip_eq!(self.data, &rhs.data).map(|(a, b)| a * b).collect())
126    }
127}
128
129impl<F: FieldExtension, M: Positive> AddAssign for MultivariateRing<F, M, Evaluation> {
130    fn add_assign(&mut self, rhs: Self) {
131        izip_eq!(&mut self.data, &rhs.data).for_each(|(a, b)| *a += b);
132    }
133}
134
135impl<F: FieldExtension, M: Positive + PowerOfTwo> Add for MultivariateRing<F, M, Evaluation> {
136    type Output = Self;
137
138    fn add(mut self, rhs: Self) -> Self::Output {
139        self += rhs;
140        self
141    }
142}
143
144impl<F: FieldExtension, M: Positive + PowerOfTwo> Sum for MultivariateRing<F, M, Evaluation> {
145    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
146        iter.fold(Self::default(), |acc, x| acc + x)
147    }
148}
149
150mod walsh_hadamard {
151    use typenum::PowerOfTwo;
152
153    use crate::{
154        algebra::field::FieldExtension,
155        types::{heap_array::SubfieldElements, Positive},
156    };
157
158    // In-place Walsh-Hadamard transformation
159    pub(super) fn walsh_hadamard_inplace<F: FieldExtension, M: Positive + PowerOfTwo>(
160        data: &mut SubfieldElements<F, M>,
161    ) {
162        let m = M::to_usize();
163        let mut step = 1;
164        while step < m {
165            let mut i = 0;
166            let step2 = step << 1;
167            while i < m {
168                for j in i..i + step {
169                    let t = data[j];
170                    data[j] = t - data[j + step];
171                    data[j + step] = t + data[j + step];
172                }
173                i += step2;
174            }
175            step = step2;
176        }
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use itertools::izip;
183    use itybity::IntoBits;
184    use typenum::{PowerOfTwo, Shleft, Unsigned};
185
186    use super::{
187        walsh_hadamard::walsh_hadamard_inplace,
188        Coefficient,
189        Evaluation,
190        MultivariateRing,
191    };
192    use crate::{
193        algebra::{
194            elliptic_curve::{Curve25519Ristretto as C, ScalarField},
195            field::{mersenne::Mersenne107, FieldExtension, SubfieldElement},
196        },
197        izip_eq,
198        random::{test_rng, Random},
199        types::{heap_array::SubfieldElements, Positive},
200    };
201
202    type Fq = ScalarField<C>;
203
204    impl<F: FieldExtension, M: Positive + PowerOfTwo> MultivariateRing<F, M, Coefficient> {
205        fn eval(&self, x_vals: Vec<SubfieldElement<F>>) -> SubfieldElement<F> {
206            use num_traits::identities::One;
207            debug_assert_eq!(1usize << x_vals.len(), self.data.len());
208            self.data
209                .iter()
210                .enumerate()
211                .map(|(i, c_i)| {
212                    let monomial_i = izip!(x_vals.iter(), i.into_iter_lsb0())
213                        .map(|(x_i, b_i)| {
214                            if b_i {
215                                *x_i
216                            } else {
217                                SubfieldElement::<F>::one()
218                            }
219                        })
220                        .product::<SubfieldElement<F>>();
221                    *c_i * monomial_i
222                })
223                .sum()
224        }
225
226        /// Multiply 2 polynomials in coefficient representation.
227        ///
228        /// __Remark:__ Use only in tests as it has O(M^2) complexity
229        fn mul_slow(&self, other: &Self) -> Self {
230            let mut out = Self::default();
231
232            let n = self.nb_coefs();
233            for i in 0..n {
234                for j in 0..n {
235                    let k = i ^ j;
236                    out.data[k] += self.data[i] * other.data[j];
237                }
238            }
239
240            out
241        }
242    }
243
244    fn signed_binary_decomposition<F: FieldExtension>(
245        x: usize,
246        n: usize,
247    ) -> Vec<SubfieldElement<F>> {
248        use num_traits::identities::One;
249        x.into_iter_lsb0()
250            .take(n)
251            .map(|b_i| {
252                if b_i {
253                    SubfieldElement::<F>::one()
254                } else {
255                    -SubfieldElement::<F>::one()
256                }
257            })
258            .collect()
259    }
260
261    fn test_multivariate_ring_walsh_hadamard_impl<F: FieldExtension, M: Positive + PowerOfTwo>(
262        walsh_hadamard: fn(&mut SubfieldElements<F, M>),
263    ) {
264        let rng = test_rng();
265        let poly_coef = MultivariateRing::<F, M, Coefficient>::random(rng);
266        let mut data = poly_coef.data.clone();
267        walsh_hadamard(&mut data);
268        let poly_eval = MultivariateRing::<_, _, Evaluation>::new(data);
269
270        // Check each evaluation values by manually evaluating the input polynomial at each
271        // point
272        let log2m = M::to_usize().ilog2() as usize;
273        for i in 0..poly_eval.nb_evals() {
274            // Signed binary-decomposition of `i`
275            let x_vals: Vec<_> = signed_binary_decomposition::<F>(i, log2m);
276            let e_i = poly_coef.eval(x_vals);
277            assert_eq!(e_i, poly_eval.data[i])
278        }
279    }
280
281    #[test]
282    fn test_multivariate_ring_walsh_hadamard() {
283        type M = Shleft<typenum::U1, typenum::U8>;
284
285        test_multivariate_ring_walsh_hadamard_impl::<Mersenne107, M>(walsh_hadamard_inplace);
286        test_multivariate_ring_walsh_hadamard_impl::<Fq, M>(walsh_hadamard_inplace);
287    }
288
289    fn test_multivariate_ring_new_from_sparse_coef_impl<
290        F: FieldExtension,
291        M: Positive + PowerOfTwo,
292        T: Positive,
293    >() {
294        use num_traits::identities::One;
295
296        let mut rng = test_rng();
297        let coefs = SubfieldElements::<F, T>::random(&mut rng);
298        let coefs_sparse: Vec<_> = izip!(
299            (0..T::to_usize()).map(|_| usize::random(&mut rng) % M::to_usize()),
300            coefs
301        )
302        .collect();
303
304        let poly_eval_exp =
305            MultivariateRing::<_, M, Evaluation>::new_from_sparse_coef(coefs_sparse.clone());
306
307        let log2m = M::to_usize().ilog2() as usize;
308        let coef_sparse_bin: Vec<(Vec<_>, SubfieldElement<F>)> = coefs_sparse
309            .into_iter()
310            .map(|(i, c_i)| (i.into_iter_lsb0().take(log2m).collect(), c_i))
311            .collect();
312
313        for i in 0..poly_eval_exp.nb_evals() {
314            let x_vals: Vec<_> = signed_binary_decomposition::<F>(i, log2m);
315            let e_i = coef_sparse_bin
316                .iter()
317                .map(|(i_bin, c_i)| {
318                    izip_eq!(&x_vals, i_bin)
319                        .map(|(x_j, i_bin_j)| {
320                            if *i_bin_j {
321                                *x_j
322                            } else {
323                                SubfieldElement::<F>::one()
324                            }
325                        })
326                        .product::<SubfieldElement<F>>()
327                        * *c_i
328                })
329                .sum::<SubfieldElement<F>>();
330
331            assert_eq!(e_i, poly_eval_exp.data[i]);
332        }
333    }
334
335    #[test]
336    fn test_multivariate_ring_new_from_sparse_coef() {
337        type M = Shleft<typenum::U1, typenum::U8>;
338        test_multivariate_ring_new_from_sparse_coef_impl::<Mersenne107, M, typenum::U1>();
339        test_multivariate_ring_new_from_sparse_coef_impl::<Mersenne107, M, typenum::U13>();
340        test_multivariate_ring_new_from_sparse_coef_impl::<Fq, M, typenum::U1>();
341        test_multivariate_ring_new_from_sparse_coef_impl::<Fq, M, typenum::U13>();
342    }
343
344    fn test_multivariate_ring_mul_impl<F: FieldExtension, M: Positive + PowerOfTwo>() {
345        let mut rng = test_rng();
346        let a = MultivariateRing::<F, M, Coefficient>::random(&mut rng);
347        let b = MultivariateRing::<F, M, Coefficient>::random(&mut rng);
348
349        // Transform to evaluation representation
350        let a_eval = a.clone().to_eval_repr();
351        let b_eval = b.clone().to_eval_repr();
352
353        // Multiplication in the evaluation domain
354        let c_eval_exp = a_eval * b_eval;
355
356        // Schoolbook multiplication in the coefficient representation
357        let c = a.mul_slow(&b);
358        let c_eval_act = c.to_eval_repr();
359
360        assert_eq!(c_eval_act, c_eval_exp);
361    }
362
363    #[test]
364    fn test_multivariate_ring_mul() {
365        type M = Shleft<typenum::U1, typenum::U8>;
366
367        test_multivariate_ring_mul_impl::<Mersenne107, M>();
368        test_multivariate_ring_mul_impl::<Fq, M>();
369    }
370
371    #[test]
372    #[ignore]
373    fn test_multivariate_ring_to_eval_repr_large() {
374        type F = Mersenne107;
375        type N = typenum::U25;
376        type M = Shleft<typenum::U1, N>;
377        const NB_ITER: usize = 1;
378
379        let data_orig = SubfieldElements::<F, M>::random(test_rng());
380
381        let mut data = data_orig.clone();
382        let start = std::time::Instant::now();
383        for _ in 0..NB_ITER {
384            walsh_hadamard_inplace(&mut data);
385            std::hint::black_box(&mut data);
386        }
387        let duration = start.elapsed();
388        println!(
389            "Walsh-Hadamard transformation of size 2^{} execution time: {:?} sec",
390            N::to_usize(),
391            duration.as_secs_f32() / NB_ITER as f32
392        );
393    }
394}