commonware_math/ntt.rs
1use crate::algebra::{Additive, FieldNTT, Ring};
2#[cfg(not(feature = "std"))]
3use alloc::{vec, vec::Vec};
4use commonware_codec::{Buf, EncodeSize, RangeCfg, Read, Write};
5use commonware_utils::bitmap::BitMap;
6use core::{
7 num::NonZeroU32,
8 ops::{Index, IndexMut},
9};
10use rand_core::CryptoRng;
11#[cfg(feature = "std")]
12use std::vec::Vec;
13
14/// Determines the size of polynomials we compute naively in [`EvaluationColumn::vanishing`].
15///
16/// Benchmarked to be optimal, based on BLS12381 threshold recovery time.
17const LG_VANISHING_BASE: u32 = 8;
18
19/// Reverse the first `bit_width` bits of `i`.
20///
21/// Any bits beyond that width will be erased.
22fn reverse_bits(bit_width: u32, i: u64) -> u64 {
23 assert!(bit_width <= 64, "bit_width must be <= 64");
24 i.wrapping_shl(64 - bit_width).reverse_bits()
25}
26
27/// Turn a slice into reversed bit order in place.
28///
29/// `out` MUST have length `2^bit_width`.
30fn reverse_slice<T>(bit_width: u32, out: &mut [T]) {
31 assert_eq!(out.len(), 1 << bit_width);
32 for i in 0..out.len() {
33 let j = reverse_bits(bit_width, i as u64) as usize;
34 // Only swap once, and don't swap if the location is the same.
35 if i < j {
36 out.swap(i, j);
37 }
38 }
39}
40
41/// Calculate an NTT, or an inverse NTT (with FORWARD=false), in place.
42///
43/// We implement this generically over anything we can index into, which allows
44/// performing NTTs in place.
45fn ntt<const FORWARD: bool, F: FieldNTT, M: IndexMut<(usize, usize), Output = F>>(
46 rows: usize,
47 cols: usize,
48 matrix: &mut M,
49) {
50 let lg_rows = rows.ilog2() as usize;
51 assert_eq!(1 << lg_rows, rows, "rows should be a power of 2");
52 // A number w such that w^(2^lg_rows) = 1.
53 // (Or, in the inverse case, the inverse of that number, to undo the NTT).
54 let w = {
55 let w = F::root_of_unity(lg_rows as u8).expect("too many rows to perform NTT");
56 if FORWARD {
57 w
58 } else {
59 // since w^(2^lg_rows) = 1, w^(2^lg_rows - 1) * w = 1,
60 // making that left-hand term the inverse of w.
61 w.exp(&[(1 << lg_rows) - 1])
62 }
63 };
64 // The inverse algorithm consists of carefully undoing the work of the
65 // standard algorithm, so we describe that in detail.
66 //
67 // To understand the NTT algorithm, first consider the case of a single
68 // column. We have a polynomial f(X), and we want to turn that into:
69 //
70 // [f(w^0), f(w^1), ..., f(w^(2^lg_rows - 1))]
71 //
72 // Our polynomial can be written as:
73 //
74 // f+(X^2) + X f-(X^2)
75 //
76 // where f+ and f- are polynomials with half the degree.
77 // f+ is obtained by taking the coefficients at even indices,
78 // f- is obtained by taking the coefficients at odd indices.
79 //
80 // w^2 is also conveniently a 2^(lg_rows - 1) root of unity. Thus,
81 // we can recursively compute an NTT on f+, using w^2 as the root,
82 // and an NTT on f-, using w^2 as the root, each of which is a problem
83 // of half the size.
84 //
85 // We can then compute:
86 // f+((w^i)^2) + (w^i) f-((w^i)^2)
87 // f+((w^i)^2) - (w^i) f-((w^i)^2)
88 // for each i.
89 // (Note that (-w^i)^2 = ((-w)^2)^i = (w^i)^2))
90 //
91 // Our coefficients are conveniently laid out as [f+ f-], already
92 // in a neat order. When we recurse, the coefficients of f+ are, in
93 // turn, already laid out as [f++ f+-], and so on.
94 //
95 // We just need to transform this recursive algorithm, in top down form,
96 // into an iterative one, in bottom up form. For that, note that the NTT
97 // for the case of 1 row is trivial: do nothing.
98
99 // Will contain, in bottom up order, the power of w we need at that stage.
100 // At the last stage, we need w itself.
101 // At the stage before last, we need w^2.
102 // And so on.
103 // How many stages do we need? If we have 1 row, we need 0 stages.
104 // In general, with 2^n rows, we need n stages.
105 let stages = {
106 let mut out = vec![(0usize, F::zero()); lg_rows];
107 let mut w_i = w;
108 for i in (0..lg_rows).rev() {
109 out[i] = (i, w_i.clone());
110 w_i = w_i.clone() * &w_i;
111 }
112 // In the case of the reverse algorithm, we undo each stage of the
113 // forward algorithm, starting with the last stage.
114 if !FORWARD {
115 out.reverse();
116 }
117 out
118 };
119 for (stage, w) in stages.into_iter() {
120 // At stage i, we have polynomials with 2^i coefficients,
121 // which have already been evaluated to create 2^i entries.
122 // We need to combine these evaluations to create 2^(i + 1) entries,
123 // representing the evaluation of a polynomial with 2^(i + 1) coefficients.
124 // If we have two of these evaluations, laid out one after the other:
125 //
126 // [x_0, x_1, ...] [y_0, y_1, ...]
127 //
128 // Then the number of elements we need to skip to get the corresponding
129 // element in the other half is simply the number of elements in each half,
130 // i.e. 2^i.
131 let skip = 1 << stage;
132 let mut i = 0;
133 while i < rows {
134 // In the case of a backwards NTT, skew should be the inverse of the skew
135 // in the forwards direction.
136 let mut w_j = F::one();
137 for j in 0..skip {
138 let index_a = i + j;
139 let index_b = index_a + skip;
140 for k in 0..cols {
141 let (a, b) = (matrix[(index_a, k)].clone(), matrix[(index_b, k)].clone());
142 if FORWARD {
143 let w_j_b = w_j.clone() * &b;
144 matrix[(index_a, k)] = a.clone() + &w_j_b;
145 matrix[(index_b, k)] = a - &w_j_b;
146 } else {
147 // To check the math, convince yourself that applying the forward
148 // transformation, and then this transformation, with w_j being the
149 // inverse of the value above, that you get (a, b).
150 // (a + w_j * b) + (a - w_j * b) = 2 * a
151 matrix[(index_a, k)] = (a.clone() + &b).div_2();
152 // (a + w_j * b) - (a - w_j * b) = 2 * w_j * b.
153 // w_j in this branch is the inverse of w_j in the other branch.
154 matrix[(index_b, k)] = ((a - &b) * &w_j).div_2();
155 }
156 }
157 w_j *= &w;
158 }
159 i += 2 * skip;
160 }
161 }
162}
163
164/// Columns of some larger piece of data.
165///
166/// This allows us to easily do NTTs over partial segments of some bigger matrix.
167struct Columns<'a, const N: usize, F> {
168 data: [&'a mut [F]; N],
169}
170
171impl<'a, const N: usize, F> Index<(usize, usize)> for Columns<'a, N, F> {
172 type Output = F;
173
174 fn index(&self, (i, j): (usize, usize)) -> &Self::Output {
175 &self.data[j][i]
176 }
177}
178
179impl<'a, const N: usize, F> IndexMut<(usize, usize)> for Columns<'a, N, F> {
180 fn index_mut(&mut self, (i, j): (usize, usize)) -> &mut Self::Output {
181 &mut self.data[j][i]
182 }
183}
184
185/// Used to keep track of the points at which a polynomial needs to vanish.
186///
187/// This takes care of subtle details like padding and bit ordering.
188///
189/// This struct is associated with a particular size, which is a power of two,
190/// and thus a particular root of unity.
191#[derive(Debug, PartialEq)]
192pub struct VanishingPoints {
193 lg_size: u32,
194 bits: BitMap,
195}
196
197impl VanishingPoints {
198 /// This will have size `2^lg_size`, and vanish everywhere.
199 ///
200 /// Be aware that this means all points are initially marked as vanishing.
201 pub fn new(lg_size: u32) -> Self {
202 Self {
203 lg_size,
204 bits: BitMap::zeroes(1 << lg_size),
205 }
206 }
207
208 /// This will have size `2^lg_size`, and vanish nowhere.
209 pub fn all_non_vanishing(lg_size: u32) -> Self {
210 Self {
211 lg_size,
212 bits: BitMap::ones(1 << lg_size),
213 }
214 }
215
216 pub const fn lg_size(&self) -> u32 {
217 self.lg_size
218 }
219
220 /// Set the root `w^index` to vanish, `value = false`, or not, `value = true`.
221 fn set(&mut self, index: u64, value: bool) {
222 self.bits.set(reverse_bits(self.lg_size, index), value);
223 }
224
225 /// Set the root `w^index` to not vanish.
226 ///
227 /// cf. `set`;
228 pub fn set_non_vanishing(&mut self, index: u64) {
229 self.set(index, true);
230 }
231
232 pub fn get(&self, index: u64) -> bool {
233 self.bits.get(reverse_bits(self.lg_size, index))
234 }
235
236 pub fn count_non_vanishing(&self) -> u64 {
237 self.bits.count_ones()
238 }
239
240 /// Check that a particular chunk of this set vanishes.
241 ///
242 /// `lg_chunk_size` determines the size of the chunk, which must be a power of two.
243 ///
244 /// `index` determines which chunk to use. After chunk 0, you have chunk 1, and so on.
245 ///
246 /// The chunk is taken from the set in reverse bit order. This is what methods
247 /// that create a vanishing polynomial recursively want. Take care when using
248 /// this naively.
249 fn chunk_vanishes_everywhere(&self, lg_chunk_size: u32, index: u64) -> bool {
250 assert!(lg_chunk_size <= self.lg_size);
251 let start = index << lg_chunk_size;
252 self.bits.is_unset(start..start + (1 << lg_chunk_size))
253 }
254
255 /// Yield the bits of a chunk, in reverse bit order.
256 ///
257 /// cf. `chunk_vanishes_everywhere`, which uses the same chunk indexing scheme.
258 fn get_chunk(&self, lg_chunk_size: u32, index: u64) -> impl Iterator<Item = bool> + '_ {
259 (index << lg_chunk_size..(index + 1) << lg_chunk_size).map(|i| self.bits.get(i))
260 }
261
262 #[cfg(any(test, feature = "fuzz"))]
263 fn iter_bits_in_order(&self) -> impl Iterator<Item = bool> + '_ {
264 (0..(1u64 << self.lg_size)).map(|i| self.get(i))
265 }
266}
267
268/// Represents the evaluation of a single polynomial over a full domain.
269#[derive(Debug)]
270struct EvaluationColumn<F> {
271 evaluations: Vec<F>,
272}
273
274impl<F: FieldNTT> EvaluationColumn<F> {
275 /// Evaluate the vanishing polynomial over `points` on the domain.
276 ///
277 /// This returns the evaluation of the polynomial at `0`, and then the evaluation
278 /// of the polynomial over the whole domain.
279 ///
280 /// This assumes that `points` has at least one non-vanishing point.
281 pub fn vanishing(points: &VanishingPoints) -> (F, Self) {
282 // The goal of this function is to produce a polynomial v such that
283 // v(w^j) = 0 for each index j where points.get(j) = false.
284 //
285 // The core idea is to split this up recursively. We split the possible
286 // roots into two groups, and figure out the vanishing polynomials
287 // v_L and v_R for the first and second groups, respectively. Then,
288 // multiplying v_L and v_R yields a polynomial with the appropriate roots.
289 //
290 // We can multiply the polynomials in O(N lg N) time, by performing an
291 // NTT on both of them, multiplying the evaluations point wise, and then
292 // using a reverse NTT to get a polynomial back.
293 //
294 // Naturally, we can extend this to construct each sub-polynomial recursively
295 // as well, giving an O(N lg^2 N) algorithm in total.
296 //
297 // This function doesn't return the polynomial directly, but rather an
298 // evaluation of the polynomial. This is because many consumers often
299 // need this anyways, and by providing them with this result, we avoid
300 // performing a reverse NTT that they then proceed to undo. However,
301 // they can also need the evaluation at 0, so we provide and calculate that
302 // as well. That can also be calculated recursively, and merged with the
303 // above calculation.
304 //
305 // One point we haven't clarified yet is how to split up the roots.
306 // Let's use an example. With size 8, the roots are:
307 //
308 // w^0 w^1 w^2 w^3 w^4 w^5 w^6 w^7
309 //
310 // or, writing down just the exponent
311 //
312 // 0 1 2 3 4 5 6 7
313 //
314 // We could build up our final polynomial by merging polynomials of size
315 // two, with roots chosen among the following possibilities:
316 //
317 // 0 1 2 3 4 5 6 7
318 //
319 // However, this requires using different roots for each polynomial.
320 //
321 // If we instead use reverse bit order, we can have things be:
322 //
323 // 0 4 2 6 1 5 3 7
324 //
325 // which is equal to:
326 //
327 // 0 4 2 + (0 4) 1 + (0 4 2 + (0 4))
328 //
329 // So, we can start by having polynomials with the same possible roots
330 // at the lowest level, and then merge by multiplying the roots by
331 // the right power, for the polynomial on the right.
332 //
333 // The roots of a polynomial can easily be multiplied by some factor
334 // by dividing its coefficients by powers of a factor.
335 // cf [`PolynomialColumn::divide_roots`].
336 //
337 // Another optimization we can do for the merges is to keep track
338 // of polynomials that vanish everywhere and nowhere. A polynomial
339 // vanishing nowhere has no effect when merging, so we can skip a multiplication.
340 // Similarly, a polynomial vanishing everywhere is of the form X^N - 1,
341 // with which multiplication is simple.
342
343 /// Used to keep track of special polynomial values.
344 #[derive(Clone, Copy)]
345 enum Where {
346 /// Vanishes at none of the roots; i.e. is f(X) = 1.
347 Nowhere,
348 /// Vanishes at at least one of the roots.
349 Somewhere,
350 /// Vanishes at every single one of the roots.
351 Everywhere,
352 }
353
354 use Where::*;
355
356 let lg_len = points.lg_size();
357 let len = 1usize << lg_len;
358 // This will store our in progress polynomials, and eventually,
359 // the final evaluations.
360 let mut out = vec![F::zero(); len];
361 // For small inputs, one chunk might more than cover it all, so we
362 // need to make the chunk size be too big.
363 let lg_chunk_size = LG_VANISHING_BASE.min(lg_len);
364 // We use this to keep track of the polynomial evaluated at 0.
365 let mut at_zero = F::one();
366
367 // Populate out with polynomials up to a low degree.
368 // We also get a vector with the status of each polymomial, letting
369 // us accelerate the merging step.
370 let mut vanishes = {
371 let chunk_size = 1usize << lg_chunk_size;
372 // The negation of each possible root vanishing polynomials can have.
373 // We have the roots in reverse bit order.
374 let minus_roots = {
375 // We can panic without worry here, because we require a smaller
376 // root of unity to exist elsewhere.
377 let w = u8::try_from(lg_chunk_size)
378 .ok()
379 .and_then(|s| F::root_of_unity(s))
380 .expect("sub-root of unity should exist");
381 // The powers of w we'll use as roots, pre-negated.
382 let mut out: Vec<_> = (0..)
383 .scan(F::one(), |state, _| {
384 let out = -state.clone();
385 *state *= &w;
386 Some(out)
387 })
388 .take(chunk_size)
389 .collect();
390 // Make sure the order is what the rest of this routine expects.
391 reverse_slice(lg_chunk_size, out.as_mut_slice());
392 out
393 };
394 // Instead of actually negating `at_zero` inside of the loop below,
395 // we instead keep track of whether or not it needs to be negated
396 // after the loop, to just perform that operation once.
397 let mut negate_at_zero = false;
398 // Populate each chunk with the initial polynomial,
399 let vanishing = out
400 .chunks_exact_mut(chunk_size)
401 .enumerate()
402 .map(|(i, poly)| {
403 let i_u64 = i as u64;
404 if points.chunk_vanishes_everywhere(lg_chunk_size, i_u64) {
405 // Implicitly, there's a 1 past the end of the polynomial,
406 // which we handle when merging.
407 poly[0] = -F::one();
408 negate_at_zero ^= true;
409 return Where::Everywhere;
410 }
411 poly[0] = F::one();
412 let mut coeffs = 1;
413 for (b_j, minus_root) in points
414 .get_chunk(lg_chunk_size, i_u64)
415 .zip(minus_roots.iter())
416 {
417 if b_j {
418 continue;
419 }
420 // Multiply the polynomial by (X - w^j).
421 poly[coeffs] = F::one();
422 for k in (1..coeffs).rev() {
423 let (chunk_head, chunk_tail) = poly.split_at_mut(k);
424 chunk_tail[0] *= minus_root;
425 chunk_tail[0] += &chunk_head[k - 1];
426 }
427 poly[0] *= minus_root;
428 coeffs += 1;
429 }
430 if coeffs > 1 {
431 reverse_slice(lg_chunk_size, poly);
432 at_zero *= &poly[0];
433 Where::Somewhere
434 } else {
435 Where::Nowhere
436 }
437 })
438 .collect::<Vec<_>>();
439 if negate_at_zero {
440 at_zero = -at_zero.clone();
441 }
442 vanishing
443 };
444 // Avoid doing any of the subsequent work if we've already covered this case.
445 if lg_chunk_size >= lg_len {
446 // We do, however, need to turn the coefficients into evaluations.
447 return (at_zero, PolynomialColumn { coefficients: out }.evaluate());
448 }
449 let w_invs = {
450 // since w^(2^lg_rows) = 1, w^(2^lg_rows - 1) * w = 1,
451 // making that left-hand term the inverse of w.
452 let mut w_inv = F::root_of_unity(lg_len as u8)
453 .expect("too many rows to create vanishing polynomial")
454 .exp(&[(1 << lg_len) - 1]);
455 let mut out = Vec::with_capacity((lg_len - lg_chunk_size) as usize);
456 for _ in lg_chunk_size..lg_len {
457 out.push(w_inv.clone());
458 w_inv = w_inv.clone() * &w_inv;
459 }
460 out.reverse();
461 out
462 };
463 let mut lg_chunk_size = lg_chunk_size;
464 let mut scratch = Vec::<F>::with_capacity(len);
465 let mut coeff_shifts = Vec::with_capacity(1 << lg_chunk_size);
466 for w_inv in w_invs.into_iter() {
467 let chunk_size = 1 << lg_chunk_size;
468 // Closure to shift coefficients by the current power.
469 // This lets us reuse the computation of the powers.
470 let mut shift_coeffs = |coeffs: &mut [F]| {
471 if coeff_shifts.len() != chunk_size {
472 coeff_shifts.clear();
473 let mut acc = F::one();
474 for _ in 0..chunk_size {
475 coeff_shifts.push(acc.clone());
476 acc *= &w_inv;
477 }
478 }
479 for (i, coeff_i) in coeffs.iter_mut().enumerate() {
480 *coeff_i *= &coeff_shifts[reverse_bits(lg_chunk_size, i as u64) as usize];
481 }
482 };
483 let next_lg_chunk_size = lg_chunk_size + 1;
484 let next_chunk_size = 1 << next_lg_chunk_size;
485 for (i, chunk) in out.chunks_exact_mut(1 << next_lg_chunk_size).enumerate() {
486 let (left, right) = chunk.split_at_mut(1 << lg_chunk_size);
487 let (vanishes_l, vanishes_r) = (vanishes[2 * i], vanishes[2 * i + 1]);
488 // We keep track of whether or not the polynomial resulting from
489 // the merge is evaluated or not.
490 let mut evaluated = false;
491 vanishes[i] = match (vanishes_l, vanishes_r) {
492 (Nowhere, Nowhere) => {
493 // Both polynomials consist of 1 0 0 0 ..., and we
494 // want the final result to be that, just with more zeroes,
495 // so we need to clear the 1 value on the right side.
496 right[0] = F::zero();
497 Nowhere
498 }
499 (Nowhere, Somewhere) => {
500 // Clear the one value on the left.
501 left[0] = F::zero();
502 // Adjust the roots on the right.
503 shift_coeffs(right);
504 // Make it take all of the left space.
505 for i in 0..chunk_size {
506 chunk.swap(chunk_size + i, 2 * i);
507 }
508 Somewhere
509 }
510 (Nowhere, Everywhere) => {
511 // (X^(N/2) - 1) is on the right.
512 // First, we multiply its roots by w_N, yielding:
513 //
514 // -X^(N/2) - 1
515 //
516 // in reverse bit order we get the following:
517 left[0] = -F::one();
518 left[1] = -F::one();
519 // And we remove the -1 on the right side.
520 right[0] = F::zero();
521 Somewhere
522 }
523 // These two cases mirror the two above.
524 (Somewhere, Nowhere) => {
525 // Clear the one on the right side.
526 right[0] = F::zero();
527 // Make it take all of the right space.
528 // We can skip moving index 0.
529 for i in (1..chunk_size).rev() {
530 chunk.swap(i, 2 * i);
531 }
532 Somewhere
533 }
534 (Everywhere, Nowhere) => {
535 // Like above, but with the polynomial on the left,
536 // there's no need to adjust the roots.
537 left[0] = -F::one();
538 left[1] = F::one();
539 right[0] = F::zero();
540 Somewhere
541 }
542 (Somewhere, Everywhere) => {
543 // We need to make the left side occupy the whole space.
544 // Shifting by one index has the effect of multiplying
545 // the polynomial by X^(chunk_size), which is what we want.
546 for i in (0..chunk_size).rev() {
547 chunk.swap(i, 2 * i + 1);
548 // We copy the value in i, negate it, and make it occupy
549 // both 2 * i + 1 and 2 * i, thus multiplying by -(X^chunk_size + 1).
550 chunk[2 * i + 1] = -chunk[2 * i + 1].clone();
551 chunk[2 * i] = chunk[2 * i + 1].clone();
552 }
553 Somewhere
554 }
555 (Everywhere, Somewhere) => {
556 // Adjust the roots on the right.
557 shift_coeffs(right);
558 // Like above, but moving the right side, and multiplying by
559 // (X^chunk_size - 1).
560 for i in 0..chunk_size {
561 chunk.swap(chunk_size + i, 2 * i + 1);
562 chunk[2 * i] = -chunk[2 * i + 1].clone();
563 }
564 Somewhere
565 }
566 (Everywhere, Everywhere) => {
567 // Make sure to clear the -1 on the right side.
568 right[0] = F::zero();
569 // By choosing to do things this way, we effectively
570 // negate the final polynomial, so we need to correct
571 // for this with the zero value.
572 at_zero = -at_zero.clone();
573 Everywhere
574 }
575 // In this case, we can assume nothing, and have to do
576 // the full logic for actually multiplying the polynomials.
577 (Somewhere, Somewhere) => {
578 // Adjust the roots on the right.
579 shift_coeffs(right);
580 // Populate the scratch buffer with the right side.
581 scratch.clear();
582 scratch.resize(next_chunk_size, F::zero());
583 for i in 0..chunk_size {
584 core::mem::swap(&mut right[i], &mut scratch[2 * i]);
585 }
586 // We can skip moving index 0.
587 for i in (1..chunk_size).rev() {
588 chunk.swap(i, 2 * i);
589 }
590 // Turn the polynomials into evaluations.
591 ntt::<true, _, _>(
592 next_chunk_size,
593 2,
594 &mut Columns {
595 data: [chunk, scratch.as_mut_slice()],
596 },
597 );
598 // Multiply them, into the chunk.
599 for (l, r) in chunk.iter_mut().zip(scratch.iter_mut()) {
600 *l *= r;
601 }
602 evaluated = true;
603 Somewhere
604 }
605 };
606 // If this isn't the last iteration, make sure to turn back into coefficients.
607 let should_be_evaluated = next_chunk_size >= len;
608 if should_be_evaluated != evaluated {
609 if evaluated {
610 ntt::<false, _, _>(next_chunk_size, 1, &mut Columns { data: [chunk] });
611 } else {
612 ntt::<true, _, _>(next_chunk_size, 1, &mut Columns { data: [chunk] });
613 }
614 }
615 }
616 lg_chunk_size = next_lg_chunk_size;
617 }
618 // We do, however, need to turn the coefficients into evaluations.
619 (at_zero, Self { evaluations: out })
620 }
621
622 pub fn interpolate(self) -> PolynomialColumn<F> {
623 let mut data = self.evaluations;
624 ntt::<false, _, _>(
625 data.len(),
626 1,
627 &mut Columns {
628 data: [data.as_mut_slice()],
629 },
630 );
631 PolynomialColumn { coefficients: data }
632 }
633}
634
635/// A column containing a single polynomial.
636#[derive(Debug)]
637struct PolynomialColumn<F> {
638 coefficients: Vec<F>,
639}
640
641impl<F: FieldNTT> PolynomialColumn<F> {
642 /// Evaluate this polynomial over the domain, returning
643 pub fn evaluate(self) -> EvaluationColumn<F> {
644 let mut data = self.coefficients;
645 ntt::<true, _, _>(
646 data.len(),
647 1,
648 &mut Columns {
649 data: [data.as_mut_slice()],
650 },
651 );
652 EvaluationColumn { evaluations: data }
653 }
654
655 #[cfg(any(test, feature = "fuzz"))]
656 fn evaluate_one(&self, point: F) -> F {
657 let mut out = F::zero();
658 let rows = self.coefficients.len();
659 let lg_rows = rows.ilog2();
660 for i in (0..rows).rev() {
661 out = out * &point + &self.coefficients[reverse_bits(lg_rows, i as u64) as usize];
662 }
663 out
664 }
665
666 #[cfg(any(test, feature = "fuzz"))]
667 fn degree(&self) -> usize {
668 let rows = self.coefficients.len();
669 let lg_rows = rows.ilog2();
670 for i in (0..rows).rev() {
671 if self.coefficients[reverse_bits(lg_rows, i as u64) as usize] != F::zero() {
672 return i;
673 }
674 }
675 0
676 }
677
678 /// Divide the roots of each polynomial by some factor.
679 ///
680 /// If f(x) = 0, then after this transformation, f(x / z) = 0 instead.
681 ///
682 /// The number of roots does not change.
683 ///
684 /// c.f. [`EvaluationColumn::vanishing`] for how this is used.
685 fn divide_roots(&mut self, factor: F) {
686 let mut factor_i = F::one();
687 let lg_rows = self.coefficients.len().ilog2();
688 for i in 0..self.coefficients.len() {
689 let index = reverse_bits(lg_rows, i as u64) as usize;
690 self.coefficients[index] *= &factor_i;
691 factor_i *= &factor;
692 }
693 }
694}
695
696/// Represents a matrix of field elements, of arbitrary dimensions
697///
698/// This is in row major order, so consider processing elements in the same
699/// row first, for locality.
700#[derive(Clone, PartialEq)]
701pub struct Matrix<F> {
702 rows: usize,
703 cols: usize,
704 data: Vec<F>,
705}
706
707impl<F: EncodeSize> EncodeSize for Matrix<F> {
708 fn encode_size(&self) -> usize {
709 self.rows.encode_size() + self.cols.encode_size() + self.data.encode_size()
710 }
711}
712
713impl<F: Write> Write for Matrix<F> {
714 fn write(&self, buf: &mut impl bytes::BufMut) {
715 self.rows.write(buf);
716 self.cols.write(buf);
717 self.data.write(buf);
718 }
719}
720
721impl<F: Read> Read for Matrix<F> {
722 type Cfg = (usize, <F as Read>::Cfg);
723
724 fn read_cfg(
725 buf: &mut impl Buf,
726 (max_els, f_cfg): &Self::Cfg,
727 ) -> Result<Self, commonware_codec::Error> {
728 let cfg = RangeCfg::from(..=*max_els);
729 let rows = usize::read_cfg(buf, &cfg)?;
730 let cols = usize::read_cfg(buf, &cfg)?;
731 let data = Vec::<F>::read_cfg(buf, &(cfg, f_cfg.clone()))?;
732 let expected_len = rows
733 .checked_mul(cols)
734 .ok_or(commonware_codec::Error::Invalid(
735 "Matrix",
736 "matrix dimensions overflow",
737 ))?;
738 if data.len() != expected_len {
739 return Err(commonware_codec::Error::Invalid(
740 "Matrix",
741 "matrix element count does not match dimensions",
742 ));
743 }
744 Ok(Self { rows, cols, data })
745 }
746}
747
748impl<F: core::fmt::Debug> core::fmt::Debug for Matrix<F> {
749 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
750 for i in 0..self.rows {
751 let row_i = &self[i];
752 for row_i_j in row_i {
753 write!(f, "{row_i_j:?} ")?;
754 }
755 writeln!(f)?;
756 }
757 Ok(())
758 }
759}
760
761impl<F: Additive> Matrix<F> {
762 /// Create a zero matrix, with a certain number of rows and columns
763 fn zero(rows: usize, cols: usize) -> Self {
764 Self {
765 rows,
766 cols,
767 data: vec![F::zero(); rows * cols],
768 }
769 }
770
771 /// Initialize a matrix, with dimensions, and data to pull from.
772 ///
773 /// Any extra data is ignored, any data not supplied is treated as 0.
774 pub fn init(rows: usize, cols: usize, mut data: impl Iterator<Item = F>) -> Self {
775 let mut out = Self::zero(rows, cols);
776 'outer: for i in 0..rows {
777 for row_i in &mut out[i] {
778 let Some(x) = data.next() else {
779 break 'outer;
780 };
781 *row_i = x;
782 }
783 }
784 out
785 }
786
787 /// Interpret the columns of this matrix as polynomials, with at least `min_coefficients`.
788 ///
789 /// This will, in fact, produce a matrix padded to the next power of 2 of that number.
790 ///
791 /// This will return `None` if `min_coefficients < self.rows`, which would mean
792 /// discarding data, instead of padding it.
793 pub fn as_polynomials(&self, min_coefficients: usize) -> Option<PolynomialVector<F>> {
794 if min_coefficients < self.rows {
795 return None;
796 }
797 Some(PolynomialVector::new(
798 min_coefficients,
799 self.cols,
800 (0..self.rows).flat_map(|i| self[i].iter().cloned()),
801 ))
802 }
803
804 /// Multiply this matrix by another.
805 ///
806 /// This assumes that the number of columns in this matrix match the number
807 /// of rows in the other matrix.
808 pub fn mul(&self, other: &Self) -> Self
809 where
810 F: Ring,
811 {
812 assert_eq!(self.cols, other.rows);
813 let mut out = Self::zero(self.rows, other.cols);
814 for i in 0..self.rows {
815 for j in 0..self.cols {
816 let c = self[(i, j)].clone();
817 let other_j = &other[j];
818 for k in 0..other.cols {
819 out[(i, k)] += &(c.clone() * &other_j[k])
820 }
821 }
822 }
823 out
824 }
825}
826
827impl<F: FieldNTT> Matrix<F> {
828 fn ntt<const FORWARD: bool>(&mut self) {
829 ntt::<FORWARD, F, Self>(self.rows, self.cols, self)
830 }
831}
832
833impl<F> Matrix<F> {
834 pub const fn rows(&self) -> usize {
835 self.rows
836 }
837
838 pub const fn cols(&self) -> usize {
839 self.cols
840 }
841
842 /// Iterate over the rows of this matrix.
843 pub fn iter(&self) -> impl Iterator<Item = &[F]> {
844 (0..self.rows).map(|i| &self[i])
845 }
846}
847
848impl<F: crate::algebra::Random> Matrix<F> {
849 /// Create a random matrix with certain dimensions.
850 pub fn rand(mut rng: impl CryptoRng, rows: usize, cols: usize) -> Self
851 where
852 F: Additive,
853 {
854 Self::init(rows, cols, (0..rows * cols).map(|_| F::random(&mut rng)))
855 }
856}
857
858impl<F> Index<usize> for Matrix<F> {
859 type Output = [F];
860
861 fn index(&self, index: usize) -> &Self::Output {
862 &self.data[self.cols * index..self.cols * (index + 1)]
863 }
864}
865
866impl<F> IndexMut<usize> for Matrix<F> {
867 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
868 &mut self.data[self.cols * index..self.cols * (index + 1)]
869 }
870}
871
872impl<F> Index<(usize, usize)> for Matrix<F> {
873 type Output = F;
874
875 fn index(&self, (i, j): (usize, usize)) -> &Self::Output {
876 &self.data[self.cols * i + j]
877 }
878}
879
880impl<F> IndexMut<(usize, usize)> for Matrix<F> {
881 fn index_mut(&mut self, (i, j): (usize, usize)) -> &mut Self::Output {
882 &mut self.data[self.cols * i + j]
883 }
884}
885
886#[cfg(any(test, feature = "arbitrary"))]
887impl<'a, F: arbitrary::Arbitrary<'a>> arbitrary::Arbitrary<'a> for Matrix<F> {
888 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
889 let rows = u.int_in_range(1..=16)?;
890 let cols = u.int_in_range(1..=16)?;
891 let data = (0..rows * cols)
892 .map(|_| F::arbitrary(u))
893 .collect::<arbitrary::Result<Vec<F>>>()?;
894 Ok(Self { rows, cols, data })
895 }
896}
897
898#[derive(Clone, Debug, PartialEq)]
899pub struct PolynomialVector<F> {
900 // Each column of this matrix contains the coefficients of a polynomial,
901 // in reverse bit order. So, the ith coefficient appears at index i.reverse_bits().
902 //
903 // For example, a polynomial a0 + a1 X + a2 X^2 + a3 X^3 is stored as:
904 //
905 // a0 a2 a1 a3
906 //
907 // This is convenient because the even coefficients and the odd coefficients
908 // split nicely into halves. The first half of the rows have the property
909 // that the first bit of their coefficient index is 0, then in that subset
910 // the first half has the second bit set to 0, and the second half set to 1,
911 // and so on, recursively.
912 data: Matrix<F>,
913}
914
915impl<F: Additive> PolynomialVector<F> {
916 /// Construct a new vector of polynomials, from dimensions, and coefficients.
917 ///
918 /// The coefficients should be supplied in order of increasing index,
919 /// and then for each polynomial.
920 ///
921 /// In other words, if you have 3 polynomials:
922 ///
923 /// a0 + a1 X + ...
924 /// b0 + b1 X + ...
925 /// c0 + c1 X + ...
926 ///
927 /// The iterator should yield:
928 ///
929 /// a0 b0 c0
930 /// a1 b1 c1
931 /// ...
932 ///
933 /// Any coefficients not supplied are treated as being equal to 0.
934 fn new(rows: usize, cols: usize, mut coefficients: impl Iterator<Item = F>) -> Self {
935 assert!(rows > 0);
936 let rows = rows.next_power_of_two();
937 let lg_rows = rows.ilog2();
938 let mut data = Matrix::zero(rows, cols);
939 'outer: for i in 0..rows {
940 let row_i = &mut data[reverse_bits(lg_rows, i as u64) as usize];
941 for row_i_j in row_i {
942 let Some(c) = coefficients.next() else {
943 break 'outer;
944 };
945 *row_i_j = c;
946 }
947 }
948 Self { data }
949 }
950}
951
952impl<F: FieldNTT> PolynomialVector<F> {
953 /// Evaluate each polynomial in this vector over all points in an interpolation domain.
954 pub fn evaluate(mut self) -> EvaluationVector<F> {
955 self.data.ntt::<true>();
956 let active_rows = VanishingPoints::all_non_vanishing(self.data.rows().ilog2());
957 EvaluationVector {
958 data: self.data,
959 active_rows,
960 }
961 }
962
963 /// Like [Self::evaluate], but with a simpler algorithm that's much less efficient.
964 ///
965 /// Exists as a useful tool for testing
966 #[cfg(any(test, feature = "fuzz"))]
967 fn evaluate_naive(self) -> EvaluationVector<F> {
968 let rows = self.data.rows;
969 let lg_rows = rows.ilog2();
970 let w = F::root_of_unity(lg_rows as u8).expect("too much data to calculate NTT");
971 // entry (i, j) of this matrix will contain w^ij. Thus, multiplying it
972 // with the coefficients of a polynomial, in column order, will evaluate it.
973 // We also need to re-arrange the columns of the matrix to match the same
974 // order we have for polynomial coefficients.
975 let mut vandermonde_matrix = Matrix::zero(rows, rows);
976 let mut w_i = F::one();
977 for i in 0..rows {
978 let row_i = &mut vandermonde_matrix[i];
979 let mut w_ij = F::one();
980 for j in 0..rows {
981 // Remember, the coeffients of the polynomial are in reverse bit order!
982 row_i[reverse_bits(lg_rows, j as u64) as usize] = w_ij.clone();
983 w_ij *= &w_i;
984 }
985 w_i *= &w;
986 }
987
988 EvaluationVector {
989 data: vandermonde_matrix.mul(&self.data),
990 active_rows: VanishingPoints::all_non_vanishing(lg_rows),
991 }
992 }
993
994 /// Divide the roots of each polynomial by some factor.
995 ///
996 /// c.f. [`PolynomialColumn::divide_roots`]. This performs the same operation on
997 /// each polynomial in this vector.
998 fn divide_roots(&mut self, factor: F) {
999 let mut factor_i = F::one();
1000 let lg_rows = self.data.rows.ilog2();
1001 for i in 0..self.data.rows {
1002 for p_i in &mut self.data[reverse_bits(lg_rows, i as u64) as usize] {
1003 *p_i *= &factor_i;
1004 }
1005 factor_i *= &factor;
1006 }
1007 }
1008
1009 /// For each polynomial P_i in this vector compute the evaluation of P_i / Q.
1010 ///
1011 /// Naturally, you can call [EvaluationVector::interpolate]. The reason we don't
1012 /// do this is that the algorithm naturally yields an [EvaluationVector], and
1013 /// some use-cases may want access to that data as well.
1014 ///
1015 /// This assumes that the number of coefficients in the polynomials of this vector
1016 /// matches that of `q` (the coefficients can be 0, but need to be padded to the right size).
1017 ///
1018 /// This assumes that `q` has no zeroes over `coset_shift() * root_of_unity()^i`,
1019 /// for any i. This will be the case for a vanishing polynomial produced by
1020 /// [EvaluationColumn::vanishing] and then interpolated.
1021 /// If this isn't the case, the result may be junk.
1022 ///
1023 /// If `q` doesn't divide a partiular polynomial in this vector, the result
1024 /// for that polynomial is not guaranteed to be anything meaningful.
1025 fn divide(&mut self, mut q: PolynomialColumn<F>) {
1026 // The algorithm operates column wise.
1027 //
1028 // You can compute P(X) / Q(X) by evaluating each polynomial, then computing
1029 //
1030 // P(w^i) / Q(w^i)
1031 //
1032 // for each evaluation point. Then, you can interpolate back.
1033 //
1034 // But wait! What if Q(w^i) = 0? In particular, for the case of recovering
1035 // a polynomial from data with missing rows, we *expect* P(w^i) = 0 = Q(w^i)
1036 // for the indicies we're missing, so this doesn't work.
1037 //
1038 // What we can do is to instead multiply each of the roots by some factor z,
1039 // such that z w^i != w^j, for any i, j. In other words, we change the roots
1040 // such that they're not in the evaluation domain anymore, allowing us to
1041 // divide. We can then interpolate the result back into a polynomial,
1042 // and divide back the roots to where they should be.
1043 //
1044 // c.f. [PolynomialVector::divide_roots]
1045 assert_eq!(
1046 self.data.rows,
1047 q.coefficients.len(),
1048 "cannot divide by polynomial of the wrong size"
1049 );
1050 let skew = F::coset_shift();
1051 let skew_inv = F::coset_shift_inv();
1052 self.divide_roots(skew.clone());
1053 q.divide_roots(skew);
1054 ntt::<true, F, _>(self.data.rows, self.data.cols, &mut self.data);
1055 ntt::<true, F, _>(
1056 q.coefficients.len(),
1057 1,
1058 &mut Columns {
1059 data: [&mut q.coefficients],
1060 },
1061 );
1062 // Batch invert the denominators using prefix products of nonzero entries.
1063 // Zero denominators retain their inverse of zero.
1064 let zero = F::zero();
1065 let mut prefix = Vec::with_capacity(q.coefficients.len());
1066 let mut product = F::one();
1067 for q_i in &q.coefficients {
1068 prefix.push(product.clone());
1069 if q_i != &zero {
1070 product *= q_i;
1071 }
1072 }
1073 let mut inverse = product.inv();
1074 for (q_i, prefix) in q.coefficients.iter_mut().zip(prefix).rev() {
1075 if q_i != &zero {
1076 let next = inverse.clone() * &*q_i;
1077 *q_i = inverse * &prefix;
1078 inverse = next;
1079 }
1080 }
1081 // Do a point wise division.
1082 for (i, q_i_inv) in q.coefficients.iter().enumerate() {
1083 for d_i_j in &mut self.data[i] {
1084 *d_i_j *= q_i_inv;
1085 }
1086 }
1087 // Interpolate back, using the inverse skew
1088 ntt::<false, F, _>(self.data.rows, self.data.cols, &mut self.data);
1089 self.divide_roots(skew_inv);
1090 }
1091}
1092
1093impl<F> PolynomialVector<F> {
1094 /// Iterate over up to n rows of this vector.
1095 ///
1096 /// For example, given polynomials:
1097 ///
1098 /// a0 + a1 X + a2 X^2 + ...
1099 /// b0 + b1 X + b2 X^2 + ...
1100 ///
1101 /// This will return:
1102 ///
1103 /// a0 b0
1104 /// a1 b1
1105 /// ...
1106 ///
1107 /// up to n times.
1108 pub fn coefficients_up_to(&self, n: usize) -> impl Iterator<Item = &[F]> {
1109 let n = n.min(self.data.rows);
1110 let lg_rows = self.data.rows().ilog2();
1111 (0..n).map(move |i| &self.data[reverse_bits(lg_rows, i as u64) as usize])
1112 }
1113}
1114
1115/// The result of evaluating a vector of polynomials over all points in an interpolation domain.
1116///
1117/// This struct also remembers which rows have ever been filled with [Self::fill_row].
1118/// This is used in [Self::recover], which can use the rows that are present to fill in the missing
1119/// rows.
1120#[derive(Debug, PartialEq)]
1121pub struct EvaluationVector<F> {
1122 data: Matrix<F>,
1123 active_rows: VanishingPoints,
1124}
1125
1126impl<F: FieldNTT> EvaluationVector<F> {
1127 /// Figure out the polynomial which evaluates to this vector.
1128 ///
1129 /// i.e. the inverse of [PolynomialVector::evaluate].
1130 ///
1131 /// (This makes all the rows count as filled).
1132 fn interpolate(mut self) -> PolynomialVector<F> {
1133 self.data.ntt::<false>();
1134 PolynomialVector { data: self.data }
1135 }
1136
1137 /// Erase a particular row.
1138 ///
1139 /// Useful for testing the recovery procedure.
1140 #[cfg(any(test, feature = "fuzz"))]
1141 fn remove_row(&mut self, row: usize) {
1142 self.data[row].fill(F::zero());
1143 self.active_rows.set(row as u64, false);
1144 }
1145
1146 fn multiply(&mut self, evaluation: &EvaluationColumn<F>) {
1147 for (i, e_i) in evaluation.evaluations.iter().enumerate() {
1148 for self_j in &mut self.data[i] {
1149 *self_j = self_j.clone() * e_i;
1150 }
1151 }
1152 }
1153
1154 /// Attempt to recover the missing rows in this data.
1155 pub fn recover(mut self) -> PolynomialVector<F> {
1156 let non_vanishing = self.active_rows.count_non_vanishing();
1157 if non_vanishing == 0 || non_vanishing == self.data.rows as u64 {
1158 return self.interpolate();
1159 }
1160
1161 // If we had all of the rows, we could simply call [Self::interpolate],
1162 // in order to recover the original polynomial. If we do this while missing some
1163 // rows, what we get is D(X) * V(X) where D is the original polynomial,
1164 // and V(X) is a polynomial which vanishes at all the rows we're missing.
1165 //
1166 // As long as the degree of D is low enough, compared to the number of evaluations
1167 // we *do* have, then we can recover it by performing:
1168 //
1169 // (D(X) * V(X)) / V(X)
1170 //
1171 // If we have multiple columns, then this procedure can be done column by column,
1172 // with the same vanishing polynomial.
1173 let (_, vanishing) = EvaluationColumn::vanishing(&self.active_rows);
1174 self.multiply(&vanishing);
1175 let mut out = self.interpolate();
1176 out.divide(vanishing.interpolate());
1177 out
1178 }
1179}
1180
1181impl<F: Additive> EvaluationVector<F> {
1182 /// Create an empty element of this struct, with no filled rows.
1183 ///
1184 /// `2^lg_rows` must be a valid `usize`.
1185 pub fn empty(lg_rows: usize, cols: usize) -> Self {
1186 assert!(
1187 lg_rows < usize::BITS as usize,
1188 "2^lg_rows must be a valid usize"
1189 );
1190 let data = Matrix::zero(1 << lg_rows, cols);
1191 let active = VanishingPoints::new(lg_rows as u32);
1192 Self {
1193 data,
1194 active_rows: active,
1195 }
1196 }
1197
1198 /// Fill a specific row.
1199 pub fn fill_row(&mut self, row: usize, data: &[F]) {
1200 assert!(data.len() <= self.data.cols);
1201 self.data[row][..data.len()].clone_from_slice(data);
1202 self.active_rows.set(row as u64, true);
1203 }
1204}
1205
1206impl<F> EvaluationVector<F> {
1207 /// Get the underlying data, as a Matrix.
1208 pub fn data(self) -> Matrix<F> {
1209 self.data
1210 }
1211
1212 /// Return how many distinct rows have been filled.
1213 pub fn filled_rows(&self) -> usize {
1214 self.active_rows.count_non_vanishing() as usize
1215 }
1216}
1217
1218/// Compute Lagrange coefficients for interpolating a polynomial at 0 from evaluations
1219/// at roots of unity.
1220///
1221/// Given a subset S of indices where we have evaluations, this computes the Lagrange
1222/// coefficients needed to interpolate to 0. For each index `j` in S, the coefficient
1223/// is `L_j(0)` where `L_j` is the Lagrange basis polynomial.
1224///
1225/// The key formula is: `L_j(0) = P_Sbar(w^j) / (N * P_Sbar(0))`
1226///
1227/// where `P_Sbar` is the (possibly scaled) vanishing polynomial over the complement
1228/// (missing points), and N is the domain size. This follows from
1229/// `V_S(X) * V_Sbar(X) = X^N - 1`, which gives `V_S(0) = -1/V_Sbar(0)`.
1230/// The scaling factor of `P_Sbar` cancels in the ratio.
1231///
1232/// Building `P_Sbar` as the vanishing polynomial over missing points is cheaper than building `V_S`
1233/// when most points are present (the typical erasure-coding case), since `|Sbar| << |S|`.
1234///
1235/// # Arguments
1236/// * `total` - The total number of points in the domain (rounded up to power of 2)
1237/// * `iter` - Iterator of indices where we have evaluations (duplicates ignored, indices >= total ignored)
1238///
1239/// # Returns
1240/// A vector of `(index, coefficient)` pairs for each unique index in the input set.
1241pub fn lagrange_coefficients<F: FieldNTT>(
1242 total: NonZeroU32,
1243 iter: impl IntoIterator<Item = u32>,
1244) -> Vec<(u32, F)> {
1245 let total_u64 = u64::from(total.get());
1246 let size = total_u64.next_power_of_two();
1247 let lg_size = size.ilog2();
1248
1249 let mut present = VanishingPoints::new(lg_size);
1250 for i in iter {
1251 let i_u64 = u64::from(i);
1252 if i_u64 < total_u64 {
1253 present.set_non_vanishing(i_u64);
1254 }
1255 }
1256
1257 let num_present = present.count_non_vanishing();
1258
1259 if num_present == 0 {
1260 return Vec::new();
1261 }
1262
1263 let n_f = F::one().scale(&[size]);
1264 if num_present == size {
1265 let n_inv = n_f.inv();
1266 return (0..size).map(|i| (i as u32, n_inv.clone())).collect();
1267 }
1268
1269 // Build P_Sbar (vanishes at indices NOT in present) and evaluate at all
1270 // roots of unity via NTT. Note: vanishing() may produce a scaled polynomial
1271 // P_Sbar = c * V_Sbar, but the scaling cancels in the ratio below.
1272 let (p_sbar_at_zero, complement_evals) = EvaluationColumn::vanishing(&present);
1273
1274 // From V_S(0) * V_Sbar(0) = -1 (since V_S * V_Sbar = X^N - 1), we get:
1275 // L_j(0) = -V_S(0) * V_Sbar(w^j) / N = V_Sbar(w^j) / (N * V_Sbar(0))
1276 // Since P_Sbar = c * V_Sbar, the scaling c cancels:
1277 // L_j(0) = P_Sbar(w^j) / (N * P_Sbar(0))
1278 let factor = (n_f * &p_sbar_at_zero).inv();
1279
1280 let mut out = Vec::with_capacity(num_present as usize);
1281 for j in 0..size {
1282 if present.get(j) {
1283 let coeff = factor.clone() * &complement_evals.evaluations[j as usize];
1284 out.push((j as u32, coeff));
1285 }
1286 }
1287 out
1288}
1289
1290#[cfg(any(test, feature = "fuzz"))]
1291pub mod fuzz {
1292 use super::*;
1293 use crate::{algebra::Ring, fields::goldilocks::F};
1294 use arbitrary::{Arbitrary, Unstructured};
1295
1296 fn arb_polynomial_vector(
1297 u: &mut Unstructured<'_>,
1298 max_log_rows: u32,
1299 max_cols: usize,
1300 ) -> arbitrary::Result<PolynomialVector<F>> {
1301 let lg_rows = u.int_in_range(0..=max_log_rows)?;
1302 let cols = u.int_in_range(1..=max_cols)?;
1303 let rows = 1usize << lg_rows;
1304 let coefficients: Vec<F> = (0..rows * cols)
1305 .map(|_| Ok(F::from(u.arbitrary::<u64>()?)))
1306 .collect::<arbitrary::Result<_>>()?;
1307 Ok(PolynomialVector::new(rows, cols, coefficients.into_iter()))
1308 }
1309
1310 fn arb_bit_vec_not_all_0(
1311 u: &mut Unstructured<'_>,
1312 max_log_rows: u32,
1313 ) -> arbitrary::Result<VanishingPoints> {
1314 let lg_rows = u.int_in_range(0..=max_log_rows)?;
1315 let rows = 1usize << lg_rows;
1316 let set_row = u.int_in_range(0..=rows - 1)?;
1317 let mut bools: Vec<bool> = (0..rows)
1318 .map(|_| u.arbitrary())
1319 .collect::<arbitrary::Result<_>>()?;
1320 bools[set_row] = true;
1321 let mut out = VanishingPoints::new(lg_rows);
1322 for (i, b) in bools.into_iter().enumerate() {
1323 out.set(i as u64, b);
1324 }
1325 Ok(out)
1326 }
1327
1328 fn arb_recovery_setup(
1329 u: &mut Unstructured<'_>,
1330 max_n: usize,
1331 max_k: usize,
1332 max_cols: usize,
1333 ) -> arbitrary::Result<RecoverySetup> {
1334 let n = u.int_in_range(1..=max_n)?;
1335 let k = u.int_in_range(0..=max_k)?;
1336 let cols = u.int_in_range(1..=max_cols)?;
1337 let data: Vec<F> = (0..n * cols)
1338 .map(|_| Ok(F::from(u.arbitrary::<u64>()?)))
1339 .collect::<arbitrary::Result<_>>()?;
1340 let padded_rows = (n + k).next_power_of_two();
1341 let num_present = u.int_in_range(n..=padded_rows)?;
1342 let mut indices: Vec<usize> = (0..padded_rows).collect();
1343 for i in 0..num_present {
1344 let j = u.int_in_range(i..=padded_rows - 1)?;
1345 indices.swap(i, j);
1346 }
1347 let mut present = VanishingPoints::new(padded_rows.ilog2());
1348 for &i in &indices[..num_present] {
1349 present.set(i as u64, true);
1350 }
1351 Ok(RecoverySetup {
1352 n,
1353 k,
1354 cols,
1355 data,
1356 present,
1357 })
1358 }
1359
1360 #[derive(Debug)]
1361 pub struct RecoverySetup {
1362 n: usize,
1363 k: usize,
1364 cols: usize,
1365 data: Vec<F>,
1366 present: VanishingPoints,
1367 }
1368
1369 impl RecoverySetup {
1370 #[cfg(test)]
1371 pub(crate) const fn new(
1372 n: usize,
1373 k: usize,
1374 cols: usize,
1375 data: Vec<F>,
1376 present: VanishingPoints,
1377 ) -> Self {
1378 Self {
1379 n,
1380 k,
1381 cols,
1382 data,
1383 present,
1384 }
1385 }
1386
1387 pub fn test(self) {
1388 let data = PolynomialVector::new(self.n + self.k, self.cols, self.data.into_iter());
1389 let mut encoded = data.clone().evaluate();
1390 for (i, b_i) in self.present.iter_bits_in_order().enumerate() {
1391 if !b_i {
1392 encoded.remove_row(i);
1393 }
1394 }
1395 let recovered_data = encoded.recover();
1396 assert_eq!(data, recovered_data);
1397 }
1398 }
1399
1400 #[derive(Debug)]
1401 pub enum Plan {
1402 NttEqNaive(PolynomialVector<F>),
1403 EvaluationThenInverse(PolynomialVector<F>),
1404 VanishingPolynomial(VanishingPoints),
1405 Recovery(RecoverySetup),
1406 }
1407
1408 impl<'a> Arbitrary<'a> for Plan {
1409 fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
1410 match u.int_in_range(0..=3)? {
1411 0 => Ok(Self::NttEqNaive(arb_polynomial_vector(u, 6, 4)?)),
1412 1 => Ok(Self::EvaluationThenInverse(arb_polynomial_vector(u, 6, 4)?)),
1413 2 => Ok(Self::VanishingPolynomial(arb_bit_vec_not_all_0(u, 8)?)),
1414 _ => Ok(Self::Recovery(arb_recovery_setup(u, 128, 128, 4)?)),
1415 }
1416 }
1417 }
1418
1419 impl Plan {
1420 pub fn run(self, _u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
1421 match self {
1422 Self::NttEqNaive(p) => {
1423 let ntt = p.clone().evaluate();
1424 let ntt_naive = p.evaluate_naive();
1425 assert_eq!(ntt, ntt_naive);
1426 }
1427 Self::EvaluationThenInverse(p) => {
1428 assert_eq!(p.clone(), p.evaluate().interpolate());
1429 }
1430 Self::VanishingPolynomial(bv) => {
1431 let total = 1u64 << bv.lg_size();
1432 let expected_degree = total - bv.count_non_vanishing();
1433 let (at_zero, evals) = EvaluationColumn::<F>::vanishing(&bv);
1434 let v = evals.interpolate();
1435 assert_eq!(
1436 v.degree(),
1437 expected_degree as usize,
1438 "expected v to have degree {}",
1439 expected_degree
1440 );
1441 assert_eq!(
1442 at_zero, v.coefficients[0],
1443 "at_zero should be the 0th coefficient"
1444 );
1445 let w = F::root_of_unity(bv.lg_size() as u8).unwrap();
1446 let mut w_i = F::one();
1447 for b_i in bv.iter_bits_in_order() {
1448 let v_at_w_i = v.evaluate_one(w_i);
1449 if !b_i {
1450 assert_eq!(v_at_w_i, F::zero(), "v should evaluate to 0 at {:?}", w_i);
1451 } else {
1452 assert_ne!(v_at_w_i, F::zero());
1453 }
1454 w_i = w_i * w;
1455 }
1456 }
1457 Self::Recovery(setup) => {
1458 setup.test();
1459 }
1460 }
1461 Ok(())
1462 }
1463 }
1464
1465 #[test]
1466 fn test_fuzz() {
1467 use commonware_invariants::minifuzz;
1468 minifuzz::test(|u| u.arbitrary::<Plan>()?.run(u));
1469 }
1470}
1471
1472#[cfg(test)]
1473mod test {
1474 use super::*;
1475 use crate::{
1476 algebra::{Random, Ring},
1477 fields::goldilocks::F,
1478 };
1479 use commonware_utils::test_rng;
1480
1481 #[test]
1482 fn test_divide_matches_pointwise_inversion() {
1483 let mut rng = test_rng();
1484 for lg_rows in [0, 1, 2, 3, 5, 8] {
1485 let rows = 1 << lg_rows;
1486 for cols in [1, 3] {
1487 for zeros in 0..6 {
1488 let data = Matrix::<F>::rand(&mut rng, rows, cols);
1489 let mut expected = data.clone();
1490 // Include zeros at the ends and in the middle, plus an all-zero batch.
1491 let denominators: Vec<_> = (0..rows)
1492 .map(|i| {
1493 if match zeros {
1494 0 => false,
1495 1 => true,
1496 2 => i == 0,
1497 3 => i == rows - 1,
1498 4 => i % 2 == 0,
1499 _ => i != rows / 2,
1500 } {
1501 F::zero()
1502 } else {
1503 F::random(&mut rng)
1504 }
1505 })
1506 .collect();
1507 // Use separate inversions as the reference, including 0.inv() == 0.
1508 for (i, denominator) in denominators.iter().enumerate() {
1509 let inverse = denominator.inv();
1510 for value in &mut expected[i] {
1511 *value *= &inverse;
1512 }
1513 }
1514
1515 let mut p = EvaluationVector {
1516 data,
1517 active_rows: VanishingPoints::all_non_vanishing(lg_rows),
1518 }
1519 .interpolate();
1520 let mut q = EvaluationColumn {
1521 evaluations: denominators,
1522 }
1523 .interpolate();
1524 // Cancel divide's coset shift so it sees the values we chose above.
1525 p.divide_roots(F::coset_shift_inv());
1526 q.divide_roots(F::coset_shift_inv());
1527
1528 p.divide(q);
1529 // Evaluate on that same coset to compare the quotient row by row.
1530 p.divide_roots(F::coset_shift());
1531 assert_eq!(
1532 p.evaluate().data,
1533 expected,
1534 "rows={rows} cols={cols} zeros={zeros}"
1535 );
1536 }
1537 }
1538 }
1539 }
1540
1541 #[test]
1542 fn test_reverse_bits() {
1543 assert_eq!(reverse_bits(4, 0b1000), 0b0001);
1544 assert_eq!(reverse_bits(4, 0b0100), 0b0010);
1545 assert_eq!(reverse_bits(4, 0b0010), 0b0100);
1546 assert_eq!(reverse_bits(4, 0b0001), 0b1000);
1547 }
1548
1549 #[test]
1550 fn matrix_read_rejects_length_mismatch() {
1551 use bytes::BytesMut;
1552 use commonware_codec::{Read as _, Write as _};
1553
1554 let mut buf = BytesMut::new();
1555 (2usize).write(&mut buf);
1556 (2usize).write(&mut buf);
1557 vec![F::one(); 3].write(&mut buf);
1558
1559 let mut bytes = buf.freeze();
1560 let result = Matrix::<F>::read_cfg(&mut bytes, &(8, ()));
1561 assert!(matches!(
1562 result,
1563 Err(commonware_codec::Error::Invalid(
1564 "Matrix",
1565 "matrix element count does not match dimensions"
1566 ))
1567 ));
1568 }
1569
1570 fn assert_vanishing_points_correct(points: &VanishingPoints) {
1571 let expected_degree = (1 << points.lg_size()) - points.count_non_vanishing();
1572 let (at_zero, evaluations) = EvaluationColumn::<F>::vanishing(points);
1573 if points.count_non_vanishing() == 0 {
1574 // EvaluationColumn::vanishing assumes at least one non-vanishing point.
1575 // We still invoke it so callers can exercise internal branch coverage.
1576 return;
1577 }
1578 let polynomial = evaluations.interpolate();
1579 assert_eq!(
1580 polynomial.degree(),
1581 expected_degree as usize,
1582 "expected v to have degree {expected_degree}"
1583 );
1584 assert_eq!(
1585 at_zero, polynomial.coefficients[0],
1586 "at_zero should be the 0th coefficient"
1587 );
1588 let w = F::root_of_unity(points.lg_size() as u8).unwrap();
1589 let mut w_i = F::one();
1590 for (i, point_is_non_vanishing) in points.iter_bits_in_order().enumerate() {
1591 let value = polynomial.evaluate_one(w_i);
1592 if point_is_non_vanishing {
1593 assert_ne!(value, F::zero(), "expected non-zero at i={i}");
1594 } else {
1595 assert_eq!(value, F::zero(), "expected zero at i={i}");
1596 }
1597 w_i = w_i * w;
1598 }
1599 }
1600
1601 #[test]
1602 fn test_recovery_000() {
1603 let present = {
1604 let mut out = VanishingPoints::new(1);
1605 out.set_non_vanishing(1);
1606 out
1607 };
1608 fuzz::RecoverySetup::new(1, 1, 1, vec![F::one()], present).test()
1609 }
1610
1611 #[test]
1612 fn test_recovery_empty_vector() {
1613 let recovered = EvaluationVector::<F>::empty(4, 3).recover();
1614 let expected = EvaluationVector::<F>::empty(4, 3).interpolate();
1615 assert_eq!(recovered, expected);
1616 }
1617
1618 #[test]
1619 fn test_vanishing_polynomial_all_two_chunk_combinations() {
1620 fn fill_half(points: &mut VanishingPoints, half: usize, values: [bool; 2]) {
1621 let chunk_size = 1usize << LG_VANISHING_BASE;
1622 let start = half * chunk_size;
1623 let lg_size = points.lg_size();
1624 for i in 0..chunk_size {
1625 let value = values[i % 2];
1626 let raw_index = (start + i) as u64;
1627 points.set(reverse_bits(lg_size, raw_index), value);
1628 }
1629 }
1630
1631 let lg_size = LG_VANISHING_BASE + 1;
1632 // (0,0) => Everywhere, (0,1) => Somewhere, (1,1) => Nowhere.
1633 let states = [[false, false], [false, true], [true, true]];
1634 for left in states {
1635 for right in states {
1636 let mut points = VanishingPoints::new(lg_size);
1637 // VanishingPoints stores roots in reverse bit order. Writing raw halves
1638 // directly makes chunk 0/1 align exactly with the implementation's chunks.
1639 fill_half(&mut points, 0, left);
1640 fill_half(&mut points, 1, right);
1641 assert_vanishing_points_correct(&points);
1642 }
1643 }
1644 }
1645
1646 #[cfg(feature = "arbitrary")]
1647 mod conformance {
1648 use super::*;
1649 use commonware_codec::conformance::CodecConformance;
1650
1651 commonware_conformance::conformance_tests! {
1652 CodecConformance<Matrix<F>>,
1653 }
1654 }
1655}