use core::convert::TryInto;
use core::marker::PhantomData;
use crate::primitives::decode::{
CheckedHrpstringError, ChecksumError, InvalidResidueError, SegwitHrpstringError,
};
use crate::primitives::{Field as _, FieldVec, LfsrIter, Polynomial};
#[cfg(feature = "alloc")]
use crate::DecodeError;
use crate::{Checksum, Fe32};
pub const NO_ALLOC_MAX_LENGTH: usize = 7;
pub trait CorrectableError {
fn residue_error(&self) -> Option<&InvalidResidueError>;
fn correction_context<Ck: Checksum>(&self) -> Option<Corrector<Ck>> {
#[cfg(not(feature = "alloc"))]
if Ck::CHECKSUM_LENGTH >= NO_ALLOC_MAX_LENGTH {
return None;
}
self.residue_error().map(|e| Corrector {
erasures: FieldVec::new(),
residue: e.residue(),
phantom: PhantomData,
})
}
}
impl CorrectableError for InvalidResidueError {
fn residue_error(&self) -> Option<&InvalidResidueError> { Some(self) }
}
impl CorrectableError for ChecksumError {
fn residue_error(&self) -> Option<&InvalidResidueError> {
match self {
ChecksumError::InvalidResidue(ref e) => Some(e),
_ => None,
}
}
}
impl CorrectableError for SegwitHrpstringError {
fn residue_error(&self) -> Option<&InvalidResidueError> {
match self {
SegwitHrpstringError::Checksum(ref e) => e.residue_error(),
_ => None,
}
}
}
impl CorrectableError for CheckedHrpstringError {
fn residue_error(&self) -> Option<&InvalidResidueError> {
match self {
CheckedHrpstringError::Checksum(ref e) => e.residue_error(),
_ => None,
}
}
}
#[cfg(feature = "alloc")]
impl CorrectableError for crate::segwit::DecodeError {
fn residue_error(&self) -> Option<&InvalidResidueError> { self.0.residue_error() }
}
#[cfg(feature = "alloc")]
impl CorrectableError for DecodeError {
fn residue_error(&self) -> Option<&InvalidResidueError> {
match self {
DecodeError::Checksum(ref e) => e.residue_error(),
_ => None,
}
}
}
pub struct Corrector<Ck: Checksum> {
erasures: FieldVec<usize>,
residue: Polynomial<Fe32>,
phantom: PhantomData<Ck>,
}
impl<Ck: Checksum> Corrector<Ck> {
pub fn singleton_bound(&self) -> usize {
Ck::ROOT_EXPONENTS.end() - Ck::ROOT_EXPONENTS.start() + 1
}
pub fn add_erasures(&mut self, locs: &[usize]) {
for loc in locs {
#[cfg(not(feature = "alloc"))]
if self.erasures.len() == NO_ALLOC_MAX_LENGTH {
break;
}
self.erasures.push(*loc);
}
}
pub fn bch_errors(&self) -> Option<ErrorIterator<'_, Ck>> {
let syndromes: Polynomial<_> = Ck::ROOT_GENERATOR
.powers_range(Ck::ROOT_EXPONENTS)
.map(|rt| self.residue.evaluate(&rt))
.collect();
let mut erasure_locator = Polynomial::with_monic_leading_term(&[]); for loc in &self.erasures {
let factor: Polynomial<_> =
[Ck::CorrectionField::ONE, -Ck::ROOT_GENERATOR.powi(*loc as i64)]
.iter()
.cloned()
.collect(); erasure_locator = erasure_locator.mul_mod_x_d(&factor, usize::MAX);
}
let forney_syndromes = erasure_locator.convolution(&syndromes);
let lfsr = LfsrIter::berlekamp_massey(&forney_syndromes.as_inner()[..]);
let conn = lfsr.coefficient_polynomial();
if erasure_locator.degree() + 2 * conn.degree() <= self.singleton_bound() {
let errata_locator = conn.mul_mod_x_d(&erasure_locator, usize::MAX);
Some(ErrorIterator {
evaluator: errata_locator.mul_mod_x_d(&syndromes, self.singleton_bound()),
locator_derivative: errata_locator.formal_derivative(),
erasures: &self.erasures[..],
errors: conn.find_nonzero_distinct_roots(Ck::ROOT_GENERATOR),
a: Ck::ROOT_GENERATOR,
c: *Ck::ROOT_EXPONENTS.start(),
})
} else {
None
}
}
}
pub struct ErrorIterator<'c, Ck: Checksum> {
evaluator: Polynomial<Ck::CorrectionField>,
locator_derivative: Polynomial<Ck::CorrectionField>,
erasures: &'c [usize],
errors: super::polynomial::RootIter<Ck::CorrectionField>,
a: Ck::CorrectionField,
c: usize,
}
impl<Ck: Checksum> Iterator for ErrorIterator<'_, Ck> {
type Item = (usize, Fe32);
fn next(&mut self) -> Option<Self::Item> {
let neg_i = if self.erasures.is_empty() {
match self.errors.next() {
None => return None,
Some(0) => 0,
Some(x) => Ck::ROOT_GENERATOR.multiplicative_order() - x,
}
} else {
let pop = self.erasures[0];
self.erasures = &self.erasures[1..];
pop
};
let a_i = self.a.powi(neg_i as i64);
let a_neg_i = a_i.clone().multiplicative_inverse();
let num = self.evaluator.evaluate(&a_neg_i);
let den = a_i.powi(self.c as i64 - 1) * self.locator_derivative.evaluate(&a_neg_i);
let ret = -num / den;
match ret.try_into() {
Ok(ret) => Some((neg_i, ret)),
Err(_) => unreachable!("error guaranteed to lie in base field"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::primitives::decode::{
CheckedHrpstringError, SegwitHrpstring, SegwitHrpstringError, UncheckedHrpstring,
};
use crate::Bech32;
#[test]
fn bech32() {
let s = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdx";
match SegwitHrpstring::new(s) {
Ok(_) => panic!("{} successfully, and wrongly, parsed", s),
Err(e) => {
let mut ctx = e.correction_context::<Bech32>().unwrap();
let mut iter = ctx.bch_errors().unwrap();
assert_eq!(iter.next(), Some((0, Fe32::X)));
assert_eq!(iter.next(), None);
ctx.add_erasures(&[0]);
let mut iter = ctx.bch_errors().unwrap();
assert_eq!(iter.next(), Some((0, Fe32::X)));
assert_eq!(iter.next(), None);
}
}
let s = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzfwf5mdq";
match SegwitHrpstring::new(s) {
Ok(_) => panic!("{} successfully, and wrongly, parsed", s),
Err(e) => {
let mut ctx = e.correction_context::<Bech32>().unwrap();
let mut iter = ctx.bch_errors().unwrap();
assert_eq!(iter.next(), Some((6, Fe32::T)));
assert_eq!(iter.next(), None);
ctx.add_erasures(&[6]);
let mut iter = ctx.bch_errors().unwrap();
assert_eq!(iter.next(), Some((6, Fe32::T)));
assert_eq!(iter.next(), None);
}
}
let s = "bc1qar0srrr7xfkvy5l64qlydnw9re59gtzzwf5mdq";
match SegwitHrpstring::new(s) {
Ok(_) => panic!("{} successfully, and wrongly, parsed", s),
Err(e) => {
let ctx = e.correction_context::<Bech32>().unwrap();
let mut iter = ctx.bch_errors().unwrap();
assert_eq!(iter.next(), Some((20, Fe32::_3)));
assert_eq!(iter.next(), None);
}
}
let s = "bc1qar0srrr7xfkvy5l64qlydnw9re59gtzzwf5mdx";
match SegwitHrpstring::new(s) {
Ok(_) => panic!("{} successfully, and wrongly, parsed", s),
Err(e) => {
let mut ctx = e.correction_context::<Bech32>().unwrap();
assert!(ctx.bch_errors().is_none());
ctx.add_erasures(&[0]);
let mut iter = ctx.bch_errors().unwrap();
assert_eq!(iter.next(), Some((0, Fe32::X)));
assert_eq!(iter.next(), Some((20, Fe32::_3)));
assert_eq!(iter.next(), None);
ctx.add_erasures(&[20]);
let mut iter = ctx.bch_errors().unwrap();
assert_eq!(iter.next(), Some((0, Fe32::X)));
assert_eq!(iter.next(), Some((20, Fe32::_3)));
assert_eq!(iter.next(), None);
}
}
let s = "bc1q9r0srrr7xfkvy5l64qlydnw9re59gtzzwf5mdx";
match SegwitHrpstring::new(s) {
Ok(_) => panic!("{} successfully, and wrongly, parsed", s),
Err(e) => {
let mut ctx = e.correction_context::<Bech32>().unwrap();
ctx.add_erasures(&[37, 0, 20]);
let mut iter = ctx.bch_errors().unwrap();
assert_eq!(iter.next(), Some((37, Fe32::C)));
assert_eq!(iter.next(), Some((0, Fe32::X)));
assert_eq!(iter.next(), Some((20, Fe32::_3)));
assert_eq!(iter.next(), None);
}
}
}
#[test]
fn residue_error() {
let checksum_error = UncheckedHrpstring::new("A1G7SGD8")
.expect("vector should parse")
.validate_checksum::<Bech32>()
.expect_err("vector should have invalid checksum residue");
let residue =
checksum_error.residue_error().expect("checksum error should expose invalid residue");
assert!(residue.residue_error().is_some(), "invalid residue error should expose itself");
let checked_checksum = CheckedHrpstringError::Checksum(checksum_error.clone());
assert!(
checked_checksum.residue_error().is_some(),
"checked checksum errors should expose invalid residue"
);
let segwit_no_data = SegwitHrpstringError::NoData;
assert!(segwit_no_data.residue_error().is_none(), "no-data errors are not correctable");
#[cfg(feature = "alloc")]
{
let segwit_checksum = SegwitHrpstringError::Checksum(checksum_error.clone());
let segwit_decode = crate::segwit::DecodeError(segwit_checksum.clone());
assert!(
segwit_decode.residue_error().is_some(),
"segwit decode wrapper should expose invalid residue"
);
let decode_checksum = crate::DecodeError::Checksum(checksum_error.clone());
assert!(
decode_checksum.residue_error().is_some(),
"top-level decode checksum errors should expose invalid residue"
);
}
}
}