1use std::{
2 iter::Sum,
3 marker::PhantomData,
4 ops::{Add, AddAssign, Mul},
5};
6
7use ff::Field;
8use typenum::PowerOfTwo;
9
10use crate::{
13 algebra::field::{FieldExtension, SubfieldElement},
14 izip_eq,
15 random::{CryptoRngCore, Random},
16 types::{heap_array::SubfieldElements, Positive},
17};
18
19#[derive(Default, Copy, Clone, Debug, PartialEq)]
21pub struct Coefficient;
22#[derive(Default, Copy, Clone, Debug, PartialEq)]
23pub struct Evaluation;
24
25#[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 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; 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 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 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 let log2m = M::to_usize().ilog2() as usize;
273 for i in 0..poly_eval.nb_evals() {
274 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 let a_eval = a.clone().to_eval_repr();
351 let b_eval = b.clone().to_eval_repr();
352
353 let c_eval_exp = a_eval * b_eval;
355
356 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}