|
6 | 6 | //! equation to identify the error values, in a BCH-encoded string.
|
7 | 7 | //!
|
8 | 8 |
|
| 9 | +use core::convert::TryInto; |
| 10 | +use core::marker::PhantomData; |
| 11 | + |
9 | 12 | use crate::primitives::decode::{
|
10 | 13 | CheckedHrpstringError, ChecksumError, InvalidResidueError, SegwitHrpstringError,
|
11 | 14 | };
|
| 15 | +use crate::primitives::{Field as _, FieldVec, LfsrIter, Polynomial}; |
12 | 16 | #[cfg(feature = "alloc")]
|
13 | 17 | use crate::DecodeError;
|
| 18 | +use crate::{Checksum, Fe32}; |
14 | 19 |
|
15 | 20 | /// **One more than** the maximum length (in characters) of a checksum which
|
16 | 21 | /// can be error-corrected without an allocator.
|
@@ -57,6 +62,22 @@ pub trait CorrectableError {
|
57 | 62 | ///
|
58 | 63 | /// This is the function that implementors should implement.
|
59 | 64 | fn residue_error(&self) -> Option<&InvalidResidueError>;
|
| 65 | + |
| 66 | + /// Wrapper around [`Self::residue_error`] that outputs a correction context. |
| 67 | + /// |
| 68 | + /// Will return None if the error is not a correctable one, or if the **alloc** |
| 69 | + /// feature is disabled and the checksum is too large. See the documentation |
| 70 | + /// for [`NO_ALLOC_MAX_LENGTH`] for more information. |
| 71 | + /// |
| 72 | + /// This is the function that users should call. |
| 73 | + fn correction_context<Ck: Checksum>(&self) -> Option<Corrector<Ck>> { |
| 74 | + #[cfg(not(feature = "alloc"))] |
| 75 | + if Ck::CHECKSUM_LENGTH >= NO_ALLOC_MAX_LENGTH { |
| 76 | + return None; |
| 77 | + } |
| 78 | + |
| 79 | + self.residue_error().map(|e| Corrector { residue: e.residue(), phantom: PhantomData }) |
| 80 | + } |
60 | 81 | }
|
61 | 82 |
|
62 | 83 | impl CorrectableError for InvalidResidueError {
|
@@ -104,3 +125,186 @@ impl CorrectableError for DecodeError {
|
104 | 125 | }
|
105 | 126 | }
|
106 | 127 | }
|
| 128 | + |
| 129 | +/// An error-correction context. |
| 130 | +pub struct Corrector<Ck> { |
| 131 | + residue: Polynomial<Fe32>, |
| 132 | + phantom: PhantomData<Ck>, |
| 133 | +} |
| 134 | + |
| 135 | +impl<Ck: Checksum> Corrector<Ck> { |
| 136 | + /// Returns an iterator over the errors in the string. |
| 137 | + /// |
| 138 | + /// Returns `None` if it can be determined that there are too many errors to be |
| 139 | + /// corrected. However, returning an iterator from this function does **not** |
| 140 | + /// imply that the intended string can be determined. It only implies that there |
| 141 | + /// is a unique closest correct string to the erroneous string, and gives |
| 142 | + /// instructions for finding it. |
| 143 | + /// |
| 144 | + /// If the input string has sufficiently many errors, this unique closest correct |
| 145 | + /// string may not actually be the intended string. |
| 146 | + pub fn bch_errors(&self) -> Option<ErrorIterator<Ck>> { |
| 147 | + // 1. Compute all syndromes by evaluating the residue at each power of the generator. |
| 148 | + let syndromes: FieldVec<_> = Ck::ROOT_GENERATOR |
| 149 | + .powers_range(Ck::ROOT_EXPONENTS) |
| 150 | + .map(|rt| self.residue.evaluate(&rt)) |
| 151 | + .collect(); |
| 152 | + |
| 153 | + // 2. Use the Berlekamp-Massey algorithm to find the connection polynomial of the |
| 154 | + // LFSR that generates these syndromes. For magical reasons this will be equal |
| 155 | + // to the error locator polynomial for the syndrome. |
| 156 | + let lfsr = LfsrIter::berlekamp_massey(&syndromes[..]); |
| 157 | + let conn = lfsr.coefficient_polynomial(); |
| 158 | + |
| 159 | + // 3. The connection polynomial is the error locator polynomial. Use this to get |
| 160 | + // 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 { |
| 164 | + 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), |
| 171 | + a: Ck::ROOT_GENERATOR, |
| 172 | + c: *Ck::ROOT_EXPONENTS.start(), |
| 173 | + }) |
| 174 | + } else { |
| 175 | + None |
| 176 | + } |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +/// An iterator over the errors in a string. |
| 181 | +/// |
| 182 | +/// The errors will be yielded as `(usize, Fe32)` tuples. |
| 183 | +/// |
| 184 | +/// The first component is a **negative index** into the string. So 0 represents |
| 185 | +/// the last element, 1 the second-to-last, and so on. |
| 186 | +/// |
| 187 | +/// The second component is an element to **add to** the element at the given |
| 188 | +/// location in the string. |
| 189 | +/// |
| 190 | +/// The maximum index is one less than [`Checksum::CODE_LENGTH`], regardless of the |
| 191 | +/// actual length of the string. Therefore it is not safe to simply subtract the |
| 192 | +/// length of the string from the returned index; you must first check that the |
| 193 | +/// index makes sense. If the index exceeds the length of the string or implies that |
| 194 | +/// an error occurred in the HRP, the string should simply be rejected as uncorrectable. |
| 195 | +/// |
| 196 | +/// Out-of-bound error locations will not occur "naturally", in the sense that they |
| 197 | +/// will happen with extremely low probability for a string with a valid HRP and a |
| 198 | +/// uniform error pattern. (The probability is 32^-n, where n is the size of the |
| 199 | +/// range [`Checksum::ROOT_EXPONENTS`], so it is not neglible but is very small for |
| 200 | +/// most checksums.) However, it is easy to construct adversarial inputs that will |
| 201 | +/// exhibit this behavior, so you must take it into account. |
| 202 | +/// |
| 203 | +/// Out-of-bound error locations may occur naturally in the case of a string with a |
| 204 | +/// corrupted HRP, because for checksumming purposes the HRP is treated as twice as |
| 205 | +/// many field elements as characters, plus one. If the correct HRP is known, the |
| 206 | +/// caller should fix this before attempting error correction. If it is unknown, |
| 207 | +/// the caller cannot assume anything about the intended checksum, and should not |
| 208 | +/// attempt error correction. |
| 209 | +pub struct ErrorIterator<Ck: Checksum> { |
| 210 | + evaluator: Polynomial<Ck::CorrectionField>, |
| 211 | + locator_derivative: Polynomial<Ck::CorrectionField>, |
| 212 | + inner: super::polynomial::RootIter<Ck::CorrectionField>, |
| 213 | + a: Ck::CorrectionField, |
| 214 | + c: usize, |
| 215 | +} |
| 216 | + |
| 217 | +impl<Ck: Checksum> Iterator for ErrorIterator<Ck> { |
| 218 | + type Item = (usize, Fe32); |
| 219 | + |
| 220 | + fn next(&mut self) -> Option<Self::Item> { |
| 221 | + // 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, |
| 226 | + }; |
| 227 | + |
| 228 | + // Forney's equation, as described in https://en.wikipedia.org/wiki/BCH_code#Forney_algorithm |
| 229 | + // |
| 230 | + // It is rendered as |
| 231 | + // |
| 232 | + // a^i evaluator(a^-i) |
| 233 | + // e_k = - --------------------------------- |
| 234 | + // a^(ci) locator_derivative(a^-i) |
| 235 | + // |
| 236 | + // where here a is `Ck::ROOT_GENERATOR`, c is the first element of the range |
| 237 | + // `Ck::ROOT_EXPONENTS`, and both evalutor and locator_derivative are polynomials |
| 238 | + // which are computed when constructing the ErrorIterator. |
| 239 | + |
| 240 | + let a_i = self.a.powi(neg_i as i64); |
| 241 | + let a_neg_i = a_i.clone().multiplicative_inverse(); |
| 242 | + |
| 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); |
| 245 | + let ret = -num / den; |
| 246 | + match ret.try_into() { |
| 247 | + Ok(ret) => Some((neg_i, ret)), |
| 248 | + Err(_) => unreachable!("error guaranteed to lie in base field"), |
| 249 | + } |
| 250 | + } |
| 251 | +} |
| 252 | + |
| 253 | +#[cfg(test)] |
| 254 | +mod tests { |
| 255 | + use super::*; |
| 256 | + use crate::primitives::decode::SegwitHrpstring; |
| 257 | + use crate::Bech32; |
| 258 | + |
| 259 | + #[test] |
| 260 | + fn bech32() { |
| 261 | + // Last x should be q |
| 262 | + let s = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdx"; |
| 263 | + match SegwitHrpstring::new(s) { |
| 264 | + Ok(_) => panic!("{} successfully, and wrongly, parsed", s), |
| 265 | + Err(e) => { |
| 266 | + let ctx = e.correction_context::<Bech32>().unwrap(); |
| 267 | + let mut iter = ctx.bch_errors().unwrap(); |
| 268 | + |
| 269 | + assert_eq!(iter.next(), Some((0, Fe32::X))); |
| 270 | + assert_eq!(iter.next(), None); |
| 271 | + } |
| 272 | + } |
| 273 | + |
| 274 | + // f should be z, 6 chars from the back. |
| 275 | + let s = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzfwf5mdq"; |
| 276 | + match SegwitHrpstring::new(s) { |
| 277 | + Ok(_) => panic!("{} successfully, and wrongly, parsed", s), |
| 278 | + Err(e) => { |
| 279 | + let ctx = e.correction_context::<Bech32>().unwrap(); |
| 280 | + let mut iter = ctx.bch_errors().unwrap(); |
| 281 | + |
| 282 | + assert_eq!(iter.next(), Some((6, Fe32::T))); |
| 283 | + assert_eq!(iter.next(), None); |
| 284 | + } |
| 285 | + } |
| 286 | + |
| 287 | + // 20 characters from the end there is a q which should be 3 |
| 288 | + let s = "bc1qar0srrr7xfkvy5l64qlydnw9re59gtzzwf5mdq"; |
| 289 | + match SegwitHrpstring::new(s) { |
| 290 | + Ok(_) => panic!("{} successfully, and wrongly, parsed", s), |
| 291 | + Err(e) => { |
| 292 | + let ctx = e.correction_context::<Bech32>().unwrap(); |
| 293 | + let mut iter = ctx.bch_errors().unwrap(); |
| 294 | + |
| 295 | + assert_eq!(iter.next(), Some((20, Fe32::_3))); |
| 296 | + assert_eq!(iter.next(), None); |
| 297 | + } |
| 298 | + } |
| 299 | + |
| 300 | + // Two errors. |
| 301 | + let s = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mxx"; |
| 302 | + match SegwitHrpstring::new(s) { |
| 303 | + Ok(_) => panic!("{} successfully, and wrongly, parsed", s), |
| 304 | + Err(e) => { |
| 305 | + let ctx = e.correction_context::<Bech32>().unwrap(); |
| 306 | + assert!(ctx.bch_errors().is_none()); |
| 307 | + } |
| 308 | + } |
| 309 | + } |
| 310 | +} |
0 commit comments