Skip to content

Commit 383f788

Browse files
committed
correction: support erasures
1 parent 2e1b7be commit 383f788

File tree

2 files changed

+149
-35
lines changed

2 files changed

+149
-35
lines changed

src/primitives/correction.rs

Lines changed: 121 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ pub trait CorrectableError {
7676
return None;
7777
}
7878

79-
self.residue_error().map(|e| Corrector { residue: e.residue(), phantom: PhantomData })
79+
self.residue_error().map(|e| Corrector {
80+
erasures: FieldVec::new(),
81+
residue: e.residue(),
82+
phantom: PhantomData,
83+
})
8084
}
8185
}
8286

@@ -127,12 +131,40 @@ impl CorrectableError for DecodeError {
127131
}
128132

129133
/// An error-correction context.
130-
pub struct Corrector<Ck> {
134+
pub struct Corrector<Ck: Checksum> {
135+
erasures: FieldVec<usize>,
131136
residue: Polynomial<Fe32>,
132137
phantom: PhantomData<Ck>,
133138
}
134139

135140
impl<Ck: Checksum> Corrector<Ck> {
141+
/// A bound on the number of errors and erasures (errors with known location)
142+
/// can be corrected by this corrector.
143+
///
144+
/// Returns N such that, given E errors and X erasures, corection is possible
145+
/// iff 2E + X <= N.
146+
pub fn singleton_bound(&self) -> usize {
147+
// d - 1, where d = [number of consecutive roots] + 2
148+
Ck::ROOT_EXPONENTS.end() - Ck::ROOT_EXPONENTS.start() + 1
149+
}
150+
151+
/// TODO
152+
pub fn add_erasures(&mut self, locs: &[usize]) {
153+
for loc in locs {
154+
// If the user tries to add too many erasures, just ignore them. In
155+
// this case error correction is guaranteed to fail anyway, because
156+
// they will have exceeded the singleton bound. (Otherwise, the
157+
// singleton bound, which is always <= the checksum length, must be
158+
// greater than NO_ALLOC_MAX_LENGTH. So the checksum length must be
159+
// greater than NO_ALLOC_MAX_LENGTH. Then correction will still fail.)
160+
#[cfg(not(feature = "alloc"))]
161+
if self.erasures.len() == NO_ALLOC_MAX_LENGTH {
162+
break;
163+
}
164+
self.erasures.push(*loc);
165+
}
166+
}
167+
136168
/// Returns an iterator over the errors in the string.
137169
///
138170
/// Returns `None` if it can be determined that there are too many errors to be
@@ -145,29 +177,44 @@ impl<Ck: Checksum> Corrector<Ck> {
145177
/// string may not actually be the intended string.
146178
pub fn bch_errors(&self) -> Option<ErrorIterator<Ck>> {
147179
// 1. Compute all syndromes by evaluating the residue at each power of the generator.
148-
let syndromes: FieldVec<_> = Ck::ROOT_GENERATOR
180+
let syndromes: Polynomial<_> = Ck::ROOT_GENERATOR
149181
.powers_range(Ck::ROOT_EXPONENTS)
150182
.map(|rt| self.residue.evaluate(&rt))
151183
.collect();
152184

185+
// 1a. Compute the "Forney syndrome polynomial" which is the product of the syndrome
186+
// polynomial and the erasure locator. This "erases the erasures" so that B-M
187+
// can find only the errors.
188+
let mut erasure_locator = Polynomial::with_monic_leading_term(&[]); // 1
189+
for loc in &self.erasures {
190+
let factor: Polynomial<_> =
191+
[Ck::CorrectionField::ONE, -Ck::ROOT_GENERATOR.powi(*loc as i64)]
192+
.iter()
193+
.cloned()
194+
.collect(); // alpha^-ix - 1
195+
erasure_locator = erasure_locator.mul_mod_x_d(&factor, usize::MAX);
196+
}
197+
let forney_syndromes = erasure_locator.convolution(&syndromes);
198+
153199
// 2. Use the Berlekamp-Massey algorithm to find the connection polynomial of the
154200
// LFSR that generates these syndromes. For magical reasons this will be equal
155201
// to the error locator polynomial for the syndrome.
156-
let lfsr = LfsrIter::berlekamp_massey(&syndromes[..]);
202+
let lfsr = LfsrIter::berlekamp_massey(&forney_syndromes.as_inner()[..]);
157203
let conn = lfsr.coefficient_polynomial();
158204

159205
// 3. The connection polynomial is the error locator polynomial. Use this to get
160206
// the errors.
161-
let max_correctable_errors =
162-
(Ck::ROOT_EXPONENTS.end() - Ck::ROOT_EXPONENTS.start() + 1) / 2;
163-
if conn.degree() <= max_correctable_errors {
207+
if erasure_locator.degree() + 2 * conn.degree() <= self.singleton_bound() {
208+
// 3a. Compute the "errata locator" which is the product of the error locator
209+
// and the erasure locator. Note that while we used the Forney syndromes
210+
// when calling the BM algorithm, in all other cases we use the ordinary
211+
// unmodified syndromes.
212+
let errata_locator = conn.mul_mod_x_d(&erasure_locator, usize::MAX);
164213
Some(ErrorIterator {
165-
evaluator: conn.mul_mod_x_d(
166-
&Polynomial::from(syndromes),
167-
Ck::ROOT_EXPONENTS.end() - Ck::ROOT_EXPONENTS.start() + 1,
168-
),
169-
locator_derivative: conn.formal_derivative(),
170-
inner: conn.find_nonzero_distinct_roots(Ck::ROOT_GENERATOR),
214+
evaluator: errata_locator.mul_mod_x_d(&syndromes, self.singleton_bound()),
215+
locator_derivative: errata_locator.formal_derivative(),
216+
erasures: &self.erasures[..],
217+
errors: conn.find_nonzero_distinct_roots(Ck::ROOT_GENERATOR),
171218
a: Ck::ROOT_GENERATOR,
172219
c: *Ck::ROOT_EXPONENTS.start(),
173220
})
@@ -206,32 +253,39 @@ impl<Ck: Checksum> Corrector<Ck> {
206253
/// caller should fix this before attempting error correction. If it is unknown,
207254
/// the caller cannot assume anything about the intended checksum, and should not
208255
/// attempt error correction.
209-
pub struct ErrorIterator<Ck: Checksum> {
256+
pub struct ErrorIterator<'c, Ck: Checksum> {
210257
evaluator: Polynomial<Ck::CorrectionField>,
211258
locator_derivative: Polynomial<Ck::CorrectionField>,
212-
inner: super::polynomial::RootIter<Ck::CorrectionField>,
259+
erasures: &'c [usize],
260+
errors: super::polynomial::RootIter<Ck::CorrectionField>,
213261
a: Ck::CorrectionField,
214262
c: usize,
215263
}
216264

217-
impl<Ck: Checksum> Iterator for ErrorIterator<Ck> {
265+
impl<'c, Ck: Checksum> Iterator for ErrorIterator<'c, Ck> {
218266
type Item = (usize, Fe32);
219267

220268
fn next(&mut self) -> Option<Self::Item> {
221269
// Compute -i, which is the location we will return to the user.
222-
let neg_i = match self.inner.next() {
223-
None => return None,
224-
Some(0) => 0,
225-
Some(x) => Ck::ROOT_GENERATOR.multiplicative_order() - x,
270+
let neg_i = if self.erasures.is_empty() {
271+
match self.errors.next() {
272+
None => return None,
273+
Some(0) => 0,
274+
Some(x) => Ck::ROOT_GENERATOR.multiplicative_order() - x,
275+
}
276+
} else {
277+
let pop = self.erasures[0];
278+
self.erasures = &self.erasures[1..];
279+
pop
226280
};
227281

228282
// Forney's equation, as described in https://en.wikipedia.org/wiki/BCH_code#Forney_algorithm
229283
//
230284
// It is rendered as
231285
//
232-
// a^i evaluator(a^-i)
233-
// e_k = - ---------------------------------
234-
// a^(ci) locator_derivative(a^-i)
286+
// evaluator(a^-i)
287+
// e_k = - -----------------------------------------
288+
// (a^i)^(c - 1)) locator_derivative(a^-i)
235289
//
236290
// where here a is `Ck::ROOT_GENERATOR`, c is the first element of the range
237291
// `Ck::ROOT_EXPONENTS`, and both evalutor and locator_derivative are polynomials
@@ -240,8 +294,8 @@ impl<Ck: Checksum> Iterator for ErrorIterator<Ck> {
240294
let a_i = self.a.powi(neg_i as i64);
241295
let a_neg_i = a_i.clone().multiplicative_inverse();
242296

243-
let num = self.evaluator.evaluate(&a_neg_i) * &a_i;
244-
let den = a_i.powi(self.c as i64) * self.locator_derivative.evaluate(&a_neg_i);
297+
let num = self.evaluator.evaluate(&a_neg_i);
298+
let den = a_i.powi(self.c as i64 - 1) * self.locator_derivative.evaluate(&a_neg_i);
245299
let ret = -num / den;
246300
match ret.try_into() {
247301
Ok(ret) => Some((neg_i, ret)),
@@ -263,9 +317,13 @@ mod tests {
263317
match SegwitHrpstring::new(s) {
264318
Ok(_) => panic!("{} successfully, and wrongly, parsed", s),
265319
Err(e) => {
266-
let ctx = e.correction_context::<Bech32>().unwrap();
320+
let mut ctx = e.correction_context::<Bech32>().unwrap();
267321
let mut iter = ctx.bch_errors().unwrap();
322+
assert_eq!(iter.next(), Some((0, Fe32::X)));
323+
assert_eq!(iter.next(), None);
268324

325+
ctx.add_erasures(&[0]);
326+
let mut iter = ctx.bch_errors().unwrap();
269327
assert_eq!(iter.next(), Some((0, Fe32::X)));
270328
assert_eq!(iter.next(), None);
271329
}
@@ -276,9 +334,13 @@ mod tests {
276334
match SegwitHrpstring::new(s) {
277335
Ok(_) => panic!("{} successfully, and wrongly, parsed", s),
278336
Err(e) => {
279-
let ctx = e.correction_context::<Bech32>().unwrap();
337+
let mut ctx = e.correction_context::<Bech32>().unwrap();
280338
let mut iter = ctx.bch_errors().unwrap();
339+
assert_eq!(iter.next(), Some((6, Fe32::T)));
340+
assert_eq!(iter.next(), None);
281341

342+
ctx.add_erasures(&[6]);
343+
let mut iter = ctx.bch_errors().unwrap();
282344
assert_eq!(iter.next(), Some((6, Fe32::T)));
283345
assert_eq!(iter.next(), None);
284346
}
@@ -297,13 +359,42 @@ mod tests {
297359
}
298360
}
299361

300-
// Two errors.
301-
let s = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mxx";
362+
// Two errors; cannot correct.
363+
let s = "bc1qar0srrr7xfkvy5l64qlydnw9re59gtzzwf5mdx";
302364
match SegwitHrpstring::new(s) {
303365
Ok(_) => panic!("{} successfully, and wrongly, parsed", s),
304366
Err(e) => {
305-
let ctx = e.correction_context::<Bech32>().unwrap();
367+
let mut ctx = e.correction_context::<Bech32>().unwrap();
306368
assert!(ctx.bch_errors().is_none());
369+
370+
// But we can correct it if we inform where an error is.
371+
ctx.add_erasures(&[0]);
372+
let mut iter = ctx.bch_errors().unwrap();
373+
assert_eq!(iter.next(), Some((0, Fe32::X)));
374+
assert_eq!(iter.next(), Some((20, Fe32::_3)));
375+
assert_eq!(iter.next(), None);
376+
377+
ctx.add_erasures(&[20]);
378+
let mut iter = ctx.bch_errors().unwrap();
379+
assert_eq!(iter.next(), Some((0, Fe32::X)));
380+
assert_eq!(iter.next(), Some((20, Fe32::_3)));
381+
assert_eq!(iter.next(), None);
382+
}
383+
}
384+
385+
// In fact, if we know the locations, we can correct up to 3 errors.
386+
let s = "bc1q9r0srrr7xfkvy5l64qlydnw9re59gtzzwf5mdx";
387+
match SegwitHrpstring::new(s) {
388+
Ok(_) => panic!("{} successfully, and wrongly, parsed", s),
389+
Err(e) => {
390+
let mut ctx = e.correction_context::<Bech32>().unwrap();
391+
ctx.add_erasures(&[37, 0, 20]);
392+
let mut iter = ctx.bch_errors().unwrap();
393+
394+
assert_eq!(iter.next(), Some((37, Fe32::C)));
395+
assert_eq!(iter.next(), Some((0, Fe32::X)));
396+
assert_eq!(iter.next(), Some((20, Fe32::_3)));
397+
assert_eq!(iter.next(), None);
307398
}
308399
}
309400
}

src/primitives/polynomial.rs

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ pub struct Polynomial<F> {
1717
}
1818

1919
impl<F: Field> PartialEq for Polynomial<F> {
20-
fn eq(&self, other: &Self) -> bool {
21-
self.inner[..self.degree()] == other.inner[..other.degree()]
22-
}
20+
fn eq(&self, other: &Self) -> bool { self.coefficients() == other.coefficients() }
2321
}
2422

2523
impl<F: Field> Eq for Polynomial<F> {}
@@ -58,9 +56,16 @@ impl<F: Field> Polynomial<F> {
5856
debug_assert_ne!(self.inner.len(), 0, "polynomials never have no terms");
5957
let degree_without_leading_zeros = self.inner.len() - 1;
6058
let leading_zeros = self.inner.iter().rev().take_while(|el| **el == F::ZERO).count();
61-
degree_without_leading_zeros - leading_zeros
59+
degree_without_leading_zeros.saturating_sub(leading_zeros)
6260
}
6361

62+
/// Accessor for the coefficients of the polynomial, in "little endian" order.
63+
///
64+
/// # Panics
65+
///
66+
/// Panics if [`Self::has_data`] is false.
67+
pub fn coefficients(&self) -> &[F] { &self.inner[..self.degree() + 1] }
68+
6469
/// An iterator over the coefficients of the polynomial.
6570
///
6671
/// Yields value in "little endian" order; that is, the constant term is returned first.
@@ -70,7 +75,7 @@ impl<F: Field> Polynomial<F> {
7075
/// Panics if [`Self::has_data`] is false.
7176
pub fn iter(&self) -> slice::Iter<F> {
7277
self.assert_has_data();
73-
self.inner[..self.degree() + 1].iter()
78+
self.coefficients().iter()
7479
}
7580

7681
/// The leading term of the polynomial.
@@ -143,6 +148,24 @@ impl<F: Field> Polynomial<F> {
143148
res
144149
}
145150

151+
/// TODO
152+
pub fn convolution(&self, syndromes: &Self) -> Self {
153+
let mut ret = FieldVec::new();
154+
let terms = (1 + syndromes.inner.len()).saturating_sub(1 + self.degree());
155+
if terms == 0 {
156+
ret.push(F::ZERO);
157+
return Self::from(ret);
158+
}
159+
160+
let n = 1 + self.degree();
161+
for idx in 0..terms {
162+
ret.push(
163+
(0..n).map(|i| self.inner[n - i - 1].clone() * &syndromes.inner[idx + i]).sum(),
164+
);
165+
}
166+
Self::from(ret)
167+
}
168+
146169
/// Multiplies two polynomials modulo x^d, for some given `d`.
147170
///
148171
/// Can be used to simply multiply two polynomials, by passing `usize::MAX` or

0 commit comments

Comments
 (0)