Skip to content

Commit a24825d

Browse files
committed
primitives: introduce Polynomial type, generator polynomial analysis
This introduces a type `Polynomial` which wraps a vector of field elements which it treats as the coefficients of a polynomial. It exposes only an ad-hoc set of functionality needed for error correction and is not intended to be a general-purpose polynomial type, so it is not exposed in the public API. Because it is backed by a `Vec` the type and its module are only available when the alloc feature is available.
1 parent cf545e4 commit a24825d

File tree

2 files changed

+284
-0
lines changed

2 files changed

+284
-0
lines changed

src/primitives/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ pub mod gf32;
1010
pub mod gf32_ext;
1111
pub mod hrp;
1212
pub mod iter;
13+
#[cfg(feature = "alloc")]
14+
mod polynomial;
1315
pub mod segwit;
1416

1517
use checksum::{Checksum, PackedNull};
1618
use field::impl_ops_for_fe;
1719
pub use field::{ExtensionField, Field};
20+
#[cfg(feature = "alloc")]
21+
use polynomial::Polynomial;
1822

1923
/// The "null checksum" used on bech32 strings for which we want to do no checksum checking.
2024
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]

src/primitives/polynomial.rs

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
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 *= &ratio;
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

Comments
 (0)