|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | + |
| 3 | +//! Polynomials over Finite Fields |
| 4 | +
|
| 5 | +#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))] |
| 6 | +use alloc::vec::Vec; |
| 7 | +use core::{iter, ops}; |
| 8 | + |
| 9 | +use super::Field; |
| 10 | + |
| 11 | +/// A polynomial over some field. |
| 12 | +#[derive(PartialEq, Eq, Clone, Debug, Hash)] |
| 13 | +pub struct Polynomial<F> { |
| 14 | + inner: Vec<F>, |
| 15 | +} |
| 16 | + |
| 17 | +impl<F> Polynomial<F> { |
| 18 | + /// Constructor for a polynomial from a vector of coefficients. |
| 19 | + /// |
| 20 | + /// These coefficients are in "little endian" order. That is, the ith |
| 21 | + /// coefficient is the one multiplied by x^i. |
| 22 | + pub fn new(f: Vec<F>) -> Self { Self { inner: f } } |
| 23 | +} |
| 24 | + |
| 25 | +impl<F: Field> Polynomial<F> { |
| 26 | + /// The degree of the polynomial. |
| 27 | + /// |
| 28 | + /// For constants it will return zero, including for the constant zero. |
| 29 | + pub fn degree(&self) -> usize { |
| 30 | + debug_assert_ne!(self.inner.len(), 0, "polynomials never have no terms"); |
| 31 | + let degree_without_leading_zeros = self.inner.len() - 1; |
| 32 | + let leading_zeros = self.inner.iter().rev().take_while(|el| **el == F::ZERO).count(); |
| 33 | + degree_without_leading_zeros - leading_zeros |
| 34 | + } |
| 35 | + |
| 36 | + /// The leading term of the polynomial. |
| 37 | + /// |
| 38 | + /// For the constant 0 polynomial, will return 0. |
| 39 | + pub fn leading_term(&self) -> F { |
| 40 | + for term in self.inner.iter().rev() { |
| 41 | + if *term != F::ZERO { |
| 42 | + return term.clone(); |
| 43 | + } |
| 44 | + } |
| 45 | + F::ZERO |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +impl<F: Field> Polynomial<F> { |
| 50 | + /// Finds all roots of the polynomial in the given field, in |
| 51 | + /// no particular order. |
| 52 | + /// |
| 53 | + /// Does not consider multiplicity; it assumes there are no |
| 54 | + /// repeated roots. (FIXME we really ought to do so, and |
| 55 | + /// definitely should before exposing this function in the |
| 56 | + /// public API.) |
| 57 | + /// |
| 58 | + /// If the polynomial does not split, then the returned vector |
| 59 | + /// will have length strictly less than [`Self::degree`]. If |
| 60 | + /// the polynomial _does_ split then the length will be equal. |
| 61 | + /// |
| 62 | + /// For constants, will return vec![0] for the constant 0 and the |
| 63 | + /// empty vector for any other constant. Probably the caller wants |
| 64 | + /// to check if they have a constant and special-case this. |
| 65 | + pub fn find_distinct_roots(&self) -> Vec<F> { |
| 66 | + // Implements Chien search |
| 67 | + |
| 68 | + let mut ret = Vec::with_capacity(self.degree()); |
| 69 | + // Check if zero is a root |
| 70 | + if self.inner.is_empty() || self.leading_term() == F::ZERO { |
| 71 | + ret.push(F::ZERO); |
| 72 | + } |
| 73 | + // Special-case constants, which have 0 as a root iff they are the constant 0. |
| 74 | + if self.degree() == 1 { |
| 75 | + return ret; |
| 76 | + // from here on out we know self.inner[0] won't panic |
| 77 | + } |
| 78 | + |
| 79 | + // Vector of [1, gen, gen^2, ...] up to the degree d. |
| 80 | + debug_assert_eq!(F::GENERATOR.multiplicative_order(), F::MULTIPLICATIVE_ORDER); |
| 81 | + let gen_power = iter::successors(Some(F::ONE), |gen| Some(F::GENERATOR * gen)) |
| 82 | + .take(self.degree() + 1) |
| 83 | + .collect::<Vec<F>>(); |
| 84 | + |
| 85 | + // We special-cased 0 above. So now we can check every nonzero element |
| 86 | + // to see if it is a root. We brute-force this using Chein's algorithm, |
| 87 | + // which exploits the fact that we can go from f(alpha^i) to f(alpha^{i+1}) |
| 88 | + // pretty efficiently. So iterate through all the powers of the generator |
| 89 | + // in this way. |
| 90 | + let mut cand = F::ONE; |
| 91 | + let mut eval = self.clone(); |
| 92 | + for _ in 0..F::MULTIPLICATIVE_ORDER { |
| 93 | + let sum = eval.inner.iter().cloned().fold(F::ZERO, F::add); |
| 94 | + if sum == F::ZERO { |
| 95 | + ret.push(cand.clone()); |
| 96 | + } |
| 97 | + |
| 98 | + for (i, gen_power) in gen_power.iter().enumerate() { |
| 99 | + eval.inner[i] *= gen_power; |
| 100 | + } |
| 101 | + cand *= F::GENERATOR; |
| 102 | + } |
| 103 | + |
| 104 | + ret |
| 105 | + } |
| 106 | + |
| 107 | + /// Given a BCH generator polynomial, find an element alpha that maximizes the |
| 108 | + /// consecutive range i..j such that `alpha^i `through `alpha^j` are all roots |
| 109 | + /// of the polynomial. |
| 110 | + /// |
| 111 | + /// (Despite the name, the returned element might not actually be a primitive |
| 112 | + /// element. For a "primitive BCH code" it will be, but in general not. But |
| 113 | + /// there is no standard name for the element this function returns, and |
| 114 | + /// "primitive element" is suggestive.) |
| 115 | + /// |
| 116 | + /// # Panics |
| 117 | + /// |
| 118 | + /// Panics if there are fewer roots than the degree of the polynomial, or if |
| 119 | + /// the longest geometric series in the roots appears to be of the form |
| 120 | + /// alpha*beta^i where alpha is not 1. Either situation indicates that your |
| 121 | + /// BCH generator polynomial is weird in some way, and you should file a bug |
| 122 | + /// or (more likely) fix your polynomial. |
| 123 | + /// |
| 124 | + /// # Returns |
| 125 | + /// |
| 126 | + /// Returns a primitive element, its order (which is the length of the code), |
| 127 | + /// and the longest range of exponents of the element which are roots of the |
| 128 | + /// polynomial. For the avoidance of doubt it returns a [`ops::RangeInclusive`] |
| 129 | + /// (syntax `a..=b`) rather than the more-common [`ops::Range`] (syntax `a..b`). |
| 130 | + /// Both endpoints are included in the set of values. |
| 131 | + /// |
| 132 | + /// Internally this function analyzes the roots in an arbitrary (randomized) |
| 133 | + /// order, and therefore may return different values on consecutive runs. If |
| 134 | + /// there is a particular "elegant" value you are looking for, it may be |
| 135 | + /// worthwhile to run the function multiple times. |
| 136 | + pub fn bch_generator_primitive_element(&self) -> (F, usize, ops::RangeInclusive<usize>) { |
| 137 | + let roots = self.find_distinct_roots(); |
| 138 | + debug_assert!(roots.len() <= self.degree(),); |
| 139 | + assert_eq!( |
| 140 | + self.degree(), |
| 141 | + roots.len(), |
| 142 | + "Found {} roots ({:?}) for a polynomial of degree {}; polynomial appears not to split.", |
| 143 | + roots.len(), |
| 144 | + roots, |
| 145 | + self.degree(), |
| 146 | + ); |
| 147 | + |
| 148 | + // Brute-force (worst case n^3 in the length of the polynomial) the longest |
| 149 | + // geometric series within the set of roots. The common ratio between these |
| 150 | + // roots will be our primitive element. |
| 151 | + // |
| 152 | + // We also learn the length of the series and the first root in the series. |
| 153 | + let mut max_length = 0; |
| 154 | + let mut max_start = F::ZERO; |
| 155 | + let mut max_ratio = F::ZERO; |
| 156 | + for r1 in &roots { |
| 157 | + for r2 in &roots { |
| 158 | + if r1 == r2 { |
| 159 | + continue; |
| 160 | + } |
| 161 | + let ratio = r2.clone() / r1; |
| 162 | + |
| 163 | + let mut len = 1; |
| 164 | + let mut elem = r1.clone(); |
| 165 | + while roots.contains(&(elem.clone() * &ratio)) { |
| 166 | + len += 1; |
| 167 | + elem *= ∶ |
| 168 | + } |
| 169 | + if len > max_length { |
| 170 | + max_length = len; |
| 171 | + max_start = r2.clone(); |
| 172 | + max_ratio = ratio; |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + // We have the primitive element (max_ratio) and the first element in the |
| 178 | + // series with that ratio (max_start). To get the actual exponents of the |
| 179 | + // series, we need i such that max_start = max_ratio^i. |
| 180 | + // |
| 181 | + // It may occur that no such i exists, if the entire series is in a coset |
| 182 | + // of the group generated by max_ratio. In *theory* this means that we |
| 183 | + // should go back and find the second-longest geometric series and try |
| 184 | + // that, because for a real-life BCH code this situation indicates that |
| 185 | + // something is wrong and we should just panic. |
| 186 | + let code_len = max_ratio.multiplicative_order(); |
| 187 | + |
| 188 | + let mut min_index = None; |
| 189 | + let mut base = F::ONE; |
| 190 | + for i in 0..code_len { |
| 191 | + base *= &max_ratio; |
| 192 | + if base == max_start { |
| 193 | + min_index = Some(i); |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + let min_index = match min_index { |
| 198 | + Some(idx) => idx, |
| 199 | + None => panic!("Found geometric series within roots starting from {} (ratio {} length {}), but this series does not consist of powers of any generator.", max_start, max_ratio, max_length), |
| 200 | + }; |
| 201 | + |
| 202 | + // We write `a..=b - 1` instead of `a..b` because RangeInclusive is actually |
| 203 | + // a different type than Range, so the two syntaxes are not equivalent here. |
| 204 | + (max_ratio, code_len, min_index..=min_index + max_length - 1) |
| 205 | + } |
| 206 | +} |
| 207 | + |
| 208 | +#[cfg(test)] |
| 209 | +mod tests { |
| 210 | + use super::*; |
| 211 | + use crate::{Fe1024, Fe32}; |
| 212 | + |
| 213 | + #[test] |
| 214 | + fn roots() { |
| 215 | + let bip93_poly = Polynomial::<Fe1024>::new( |
| 216 | + [ |
| 217 | + Fe32::S, |
| 218 | + Fe32::S, |
| 219 | + Fe32::C, |
| 220 | + Fe32::M, |
| 221 | + Fe32::L, |
| 222 | + Fe32::E, |
| 223 | + Fe32::E, |
| 224 | + Fe32::E, |
| 225 | + Fe32::Q, |
| 226 | + Fe32::G, |
| 227 | + Fe32::_3, |
| 228 | + Fe32::M, |
| 229 | + Fe32::E, |
| 230 | + Fe32::P, |
| 231 | + ] |
| 232 | + .iter() |
| 233 | + .copied() |
| 234 | + .map(Fe1024::from) |
| 235 | + .collect(), |
| 236 | + ); |
| 237 | + |
| 238 | + assert_eq!(bip93_poly.degree(), 13); |
| 239 | + |
| 240 | + let (elem, order, root_indices) = bip93_poly.bch_generator_primitive_element(); |
| 241 | + // Basically, only the order and the length of the `root_indices` range are |
| 242 | + // guaranteed to be consistent across runs. There will be two possible ranges, |
| 243 | + // a "low" one (in this case 9..16) and a "high one" (77..84) whose generator |
| 244 | + // is the multiplicative inverse of the small one. These correspond to the |
| 245 | + // fact that for any order-93 element, e^x = (e^-1)^(93 - x). |
| 246 | + // |
| 247 | + // Then in addition to the element/inverse symmetry, for this polynomial there |
| 248 | + // is also an entire second generator which can produce the same set of roots. |
| 249 | + // So we get that one and _its_ inverse. |
| 250 | + // |
| 251 | + // Also, BTW, the range 77..84 appears in the Appendix to BIP93 so you can |
| 252 | + // verify its correctness there. |
| 253 | + assert_eq!(order, 93); |
| 254 | + // These next three assertions just illustrate the above comment... |
| 255 | + assert_eq!( |
| 256 | + Fe1024::new([Fe32::Q, Fe32::_9]).multiplicative_inverse(), |
| 257 | + Fe1024::new([Fe32::G, Fe32::G]), |
| 258 | + ); |
| 259 | + assert_eq!( |
| 260 | + Fe1024::new([Fe32::Q, Fe32::_9]).powi(9), |
| 261 | + Fe1024::new([Fe32::G, Fe32::G]).powi(84), |
| 262 | + ); |
| 263 | + assert_eq!( |
| 264 | + Fe1024::new([Fe32::Q, Fe32::_9]).powi(16), |
| 265 | + Fe1024::new([Fe32::G, Fe32::G]).powi(77), |
| 266 | + ); |
| 267 | + // ...and these ones are actual unit tests. |
| 268 | + if elem == Fe1024::new([Fe32::_9, Fe32::_9]) { |
| 269 | + assert_eq!(root_indices, 9..=16); |
| 270 | + } else if elem == Fe1024::new([Fe32::Q, Fe32::G]) { |
| 271 | + assert_eq!(root_indices, 77..=84); |
| 272 | + } else if elem == Fe1024::new([Fe32::Q, Fe32::_9]) { |
| 273 | + assert_eq!(root_indices, 9..=16); |
| 274 | + } else if elem == Fe1024::new([Fe32::G, Fe32::G]) { |
| 275 | + assert_eq!(root_indices, 77..=84); |
| 276 | + } else { |
| 277 | + panic!("Unexpected generator {}", elem); |
| 278 | + } |
| 279 | + } |
| 280 | +} |
0 commit comments