Skip to main content

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}