#[cfg(test)]
use super::bch::{GEN_LONG, GEN_REGULAR};
type Gf32 = u8;
const GF32_REDUCE: u8 = 0b0_1001;
const fn gf32_mul(a: Gf32, b: Gf32) -> Gf32 {
let mut result: u8 = 0;
let mut a = a;
let mut i = 0;
while i < 5 {
if (b >> i) & 1 != 0 {
result ^= a;
}
let carry = (a >> 4) & 1;
a = (a << 1) & 0x1F;
if carry != 0 {
a ^= GF32_REDUCE;
}
i += 1;
}
result
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
struct Gf1024 {
lo: Gf32,
hi: Gf32,
}
impl Gf1024 {
const ZERO: Gf1024 = Gf1024 { lo: 0, hi: 0 };
const ONE: Gf1024 = Gf1024 { lo: 1, hi: 0 };
const fn from_gf32(v: Gf32) -> Self {
Gf1024 { lo: v, hi: 0 }
}
fn add(self, other: Self) -> Self {
Gf1024 {
lo: self.lo ^ other.lo,
hi: self.hi ^ other.hi,
}
}
fn is_zero(self) -> bool {
self.lo == 0 && self.hi == 0
}
fn mul(self, other: Self) -> Self {
let ll = gf32_mul(self.lo, other.lo);
let lh = gf32_mul(self.lo, other.hi);
let hl = gf32_mul(self.hi, other.lo);
let hh = gf32_mul(self.hi, other.hi);
Gf1024 {
lo: ll ^ hh,
hi: lh ^ hl ^ hh,
}
}
fn pow(self, mut exp: u32) -> Self {
let mut base = self;
let mut result = Gf1024::ONE;
while exp > 0 {
if exp & 1 == 1 {
result = result.mul(base);
}
base = base.mul(base);
exp >>= 1;
}
result
}
fn inv(self) -> Self {
debug_assert!(!self.is_zero(), "inv of zero in GF(1024)");
self.pow(1022)
}
}
#[cfg(test)]
const ZETA: Gf1024 = Gf1024 { lo: 0, hi: 1 };
const BETA: Gf1024 = Gf1024 { lo: 0, hi: 8 };
const GAMMA: Gf1024 = Gf1024 { lo: 25, hi: 6 };
const REGULAR_J_START: u32 = 77;
const LONG_J_START: u32 = 1019;
#[cfg(test)]
fn generator_polynomial_regular() -> [Gf32; 14] {
let mut g = [0u8; 14];
g[13] = 1;
for (i, slot) in g.iter_mut().enumerate().take(13) {
*slot = ((GEN_REGULAR[0] >> (5 * i)) & 0x1F) as u8;
}
g
}
#[cfg(test)]
fn generator_polynomial_long() -> [Gf32; 16] {
let mut g = [0u8; 16];
g[15] = 1;
for (i, slot) in g.iter_mut().enumerate().take(15) {
*slot = ((GEN_LONG[0] >> (5 * i)) & 0x1F) as u8;
}
g
}
fn horner(coeffs: &[Gf32], x: Gf1024) -> Gf1024 {
let mut acc = Gf1024::ZERO;
for &c in coeffs.iter().rev() {
acc = acc.mul(x).add(Gf1024::from_gf32(c));
}
acc
}
fn horner_ext(coeffs: &[Gf1024], x: Gf1024) -> Gf1024 {
let mut acc = Gf1024::ZERO;
for &c in coeffs.iter().rev() {
acc = acc.mul(x).add(c);
}
acc
}
fn compute_syndromes(
residue_xor_const: u128,
checksum_len: u32,
alpha: Gf1024,
j_start: u32,
) -> [Gf1024; 8] {
let mut coeffs = [0u8; 15];
let len = checksum_len as usize;
for i in 0..checksum_len {
coeffs[i as usize] = ((residue_xor_const >> (5 * i)) & 0x1F) as u8;
}
let coeffs = &coeffs[..len];
let mut syndromes = [Gf1024::ZERO; 8];
let alpha_j_start = alpha.pow(j_start);
let mut alpha_j = alpha_j_start;
for s in &mut syndromes {
*s = horner(coeffs, alpha_j);
alpha_j = alpha_j.mul(alpha);
}
syndromes
}
fn berlekamp_massey(syndromes: &[Gf1024; 8]) -> Vec<Gf1024> {
let n = syndromes.len();
let mut lam: Vec<Gf1024> = vec![Gf1024::ONE]; let mut prev: Vec<Gf1024> = vec![Gf1024::ONE]; let mut l: usize = 0; let mut m: usize = 1; let mut b = Gf1024::ONE;
for k in 0..n {
let mut d = syndromes[k];
for i in 1..=l {
if i <= k && i < lam.len() {
d = d.add(lam[i].mul(syndromes[k - i]));
}
}
if d.is_zero() {
m += 1;
} else if 2 * l <= k {
let t = lam.clone();
let scale = d.mul(b.inv());
let new_len = (lam.len()).max(prev.len() + m);
lam.resize(new_len, Gf1024::ZERO);
for (i, &p) in prev.iter().enumerate() {
let idx = i + m;
lam[idx] = lam[idx].add(scale.mul(p));
}
l = k + 1 - l;
prev = t;
b = d;
m = 1;
} else {
let scale = d.mul(b.inv());
let new_len = (lam.len()).max(prev.len() + m);
lam.resize(new_len, Gf1024::ZERO);
for (i, &p) in prev.iter().enumerate() {
let idx = i + m;
lam[idx] = lam[idx].add(scale.mul(p));
}
m += 1;
}
}
while lam.len() > 1 && lam.last().is_some_and(|x| x.is_zero()) {
lam.pop();
}
lam
}
fn chien_search(
lambda: &[Gf1024],
data_with_checksum_len: usize,
alpha: Gf1024,
) -> Option<Vec<usize>> {
let deg = lambda.len() - 1;
if deg == 0 {
return Some(Vec::new());
}
let mut error_degrees = Vec::with_capacity(deg);
let alpha_inv = alpha.inv();
let mut current = Gf1024::ONE; for d in 0..data_with_checksum_len {
if horner_ext(lambda, current).is_zero() {
error_degrees.push(d);
}
current = current.mul(alpha_inv);
}
if error_degrees.len() != deg {
return None;
}
Some(error_degrees)
}
fn forney(
syndromes: &[Gf1024; 8],
lambda: &[Gf1024],
error_degrees: &[usize],
alpha: Gf1024,
j_start: u32,
) -> Option<Vec<Gf32>> {
let s_poly: Vec<Gf1024> = syndromes.to_vec();
let mut omega = vec![Gf1024::ZERO; 8];
for i in 0..s_poly.len().min(8) {
for j in 0..lambda.len() {
if i + j < 8 {
omega[i + j] = omega[i + j].add(s_poly[i].mul(lambda[j]));
}
}
}
let mut lambda_prime = vec![Gf1024::ZERO; lambda.len().saturating_sub(1)];
for i in 1..lambda.len() {
if i % 2 == 1 {
lambda_prime[i - 1] = lambda[i];
}
}
let mut magnitudes = Vec::with_capacity(error_degrees.len());
for &d in error_degrees {
let x_k = alpha.pow(d as u32);
let x_k_inv = x_k.inv();
let omega_val = horner_ext(&omega, x_k_inv);
let lam_p_val = horner_ext(&lambda_prime, x_k_inv);
if lam_p_val.is_zero() {
return None;
}
let shift = j_start.saturating_sub(1);
let x_k_shift = x_k_inv.pow(shift);
let mag = x_k_shift.mul(omega_val.mul(lam_p_val.inv()));
if mag.hi != 0 {
return None;
}
if mag.lo == 0 {
return None;
}
magnitudes.push(mag.lo);
}
Some(magnitudes)
}
pub(super) fn decode_regular_errors(
residue_xor_const: u128,
data_with_checksum_len: usize,
) -> Option<(Vec<usize>, Vec<Gf32>)> {
decode_errors(
residue_xor_const,
data_with_checksum_len,
13,
BETA,
REGULAR_J_START,
)
}
pub(super) fn decode_long_errors(
residue_xor_const: u128,
data_with_checksum_len: usize,
) -> Option<(Vec<usize>, Vec<Gf32>)> {
decode_errors(
residue_xor_const,
data_with_checksum_len,
15,
GAMMA,
LONG_J_START,
)
}
fn decode_errors(
residue_xor_const: u128,
data_with_checksum_len: usize,
checksum_len: u32,
alpha: Gf1024,
j_start: u32,
) -> Option<(Vec<usize>, Vec<Gf32>)> {
let syndromes = compute_syndromes(residue_xor_const, checksum_len, alpha, j_start);
if syndromes.iter().all(|s| s.is_zero()) {
return Some((Vec::new(), Vec::new()));
}
let lambda = berlekamp_massey(&syndromes);
let deg = lambda.len() - 1;
if deg == 0 || deg > 4 {
return None;
}
let error_degrees = chien_search(&lambda, data_with_checksum_len, alpha)?;
if error_degrees.len() != deg {
return None;
}
let magnitudes = forney(&syndromes, &lambda, &error_degrees, alpha, j_start)?;
let mut positions = Vec::with_capacity(error_degrees.len());
for &d in &error_degrees {
if d >= data_with_checksum_len {
return None;
}
let k = data_with_checksum_len - 1 - d;
positions.push(k);
}
let mut paired: Vec<(usize, Gf32)> = positions.into_iter().zip(magnitudes).collect();
paired.sort_by_key(|p| p.0);
let positions: Vec<usize> = paired.iter().map(|p| p.0).collect();
let magnitudes: Vec<Gf32> = paired.iter().map(|p| p.1).collect();
Some((positions, magnitudes))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::consts::{MK_LONG_CONST, MK_REGULAR_CONST};
use crate::string_layer::bch::{
GEN_LONG, GEN_REGULAR, LONG_MASK, LONG_SHIFT, REGULAR_MASK, REGULAR_SHIFT,
bch_create_checksum_long, bch_create_checksum_regular, hrp_expand,
};
#[test]
fn gf32_mul_identity() {
for v in 0..32u8 {
assert_eq!(gf32_mul(v, 1), v);
assert_eq!(gf32_mul(1, v), v);
}
}
#[test]
fn gf32_mul_zero() {
for v in 0..32u8 {
assert_eq!(gf32_mul(v, 0), 0);
assert_eq!(gf32_mul(0, v), 0);
}
}
#[test]
fn gf32_alpha_powers_match_bech32_log_inv_table() {
let mut a: u8 = 1;
let expected: [u8; 31] = [
1, 2, 4, 8, 16, 9, 18, 13, 26, 29, 19, 15, 30, 21, 3, 6, 12, 24, 25, 27, 31, 23, 7, 14,
28, 17, 11, 22, 5, 10, 20,
];
for &exp in &expected {
assert_eq!(a, exp);
a = gf32_mul(a, 2);
}
assert_eq!(a, 1);
}
#[test]
fn zeta_is_primitive_cube_root_of_unity() {
let zeta_sq = ZETA.mul(ZETA);
assert_eq!(zeta_sq, ZETA.add(Gf1024::ONE), "ζ² should equal ζ + 1");
let zeta_cu = zeta_sq.mul(ZETA);
assert_eq!(zeta_cu, Gf1024::ONE, "ζ³ should equal 1");
}
#[test]
fn beta_has_order_93_regular() {
let mut p = Gf1024::ONE;
for j in 1..=93 {
p = p.mul(BETA);
if p == Gf1024::ONE {
assert_eq!(j, 93, "β prematurely returned to 1 at exponent {}", j);
}
}
assert_eq!(p, Gf1024::ONE, "β^93 should equal 1");
}
#[test]
fn gamma_has_order_1023_long() {
for &q in &[341u32, 93u32, 33u32] {
assert_ne!(GAMMA.pow(q), Gf1024::ONE, "γ^(1023/p) = 1 for some p");
}
assert_eq!(GAMMA.pow(1023), Gf1024::ONE, "γ^1023 should equal 1");
}
#[test]
fn generator_polynomial_evaluates_to_zero_at_specified_roots() {
let g_reg = generator_polynomial_regular();
let g_long = generator_polynomial_long();
let regular_roots: [u32; 13] = [17, 20, 46, 49, 52, 77, 78, 79, 80, 81, 82, 83, 84];
for &i in ®ular_roots {
assert!(
horner(&g_reg, BETA.pow(i)).is_zero(),
"g_regular(β^{}) != 0",
i
);
}
let long_roots: [u32; 15] = [
32, 64, 96, 895, 927, 959, 991, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026,
];
for &i in &long_roots {
assert!(
horner(&g_long, GAMMA.pow(i)).is_zero(),
"g_long(γ^{}) != 0",
i
);
}
}
use crate::string_layer::bch::polymod_run;
#[test]
fn one_error_decodes_correctly_regular() {
let hrp = "mk";
let data: Vec<u8> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let checksum = bch_create_checksum_regular(hrp, &data);
let mut codeword = data.clone();
codeword.extend_from_slice(&checksum);
let original = codeword.clone();
let err_pos = 5;
let err_mag: u8 = 0b10101;
codeword[err_pos] ^= err_mag;
let mut input = hrp_expand(hrp);
input.extend_from_slice(&codeword);
let polymod = polymod_run(&input, &GEN_REGULAR, REGULAR_SHIFT, REGULAR_MASK);
let residue = polymod ^ MK_REGULAR_CONST;
let (positions, magnitudes) =
decode_regular_errors(residue, codeword.len()).expect("1-error must decode");
assert_eq!(positions, vec![err_pos]);
assert_eq!(magnitudes, vec![err_mag]);
let mut corrected = codeword.clone();
for (p, m) in positions.iter().zip(&magnitudes) {
corrected[*p] ^= m;
}
assert_eq!(corrected, original);
}
#[test]
fn two_errors_decode_correctly_regular() {
let hrp = "mk";
let data: Vec<u8> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
let checksum = bch_create_checksum_regular(hrp, &data);
let mut codeword = data.clone();
codeword.extend_from_slice(&checksum);
let original = codeword.clone();
let positions_in: [usize; 2] = [3, 17];
let mags_in: [u8; 2] = [0b11001, 0b00111];
for (&p, &m) in positions_in.iter().zip(&mags_in) {
codeword[p] ^= m;
}
let mut input = hrp_expand(hrp);
input.extend_from_slice(&codeword);
let polymod = polymod_run(&input, &GEN_REGULAR, REGULAR_SHIFT, REGULAR_MASK);
let residue = polymod ^ MK_REGULAR_CONST;
let (positions, magnitudes) =
decode_regular_errors(residue, codeword.len()).expect("2-error must decode");
assert_eq!(positions, vec![3, 17]);
assert_eq!(magnitudes, vec![mags_in[0], mags_in[1]]);
let mut corrected = codeword.clone();
for (p, m) in positions.iter().zip(&magnitudes) {
corrected[*p] ^= m;
}
assert_eq!(corrected, original);
}
#[test]
fn four_errors_decode_correctly_long() {
let hrp = "mk";
let data: Vec<u8> = (0..16).collect();
let checksum = bch_create_checksum_long(hrp, &data);
let mut codeword = data.clone();
codeword.extend_from_slice(&checksum);
let original = codeword.clone();
let positions_in: [usize; 4] = [0, 5, 18, 28];
let mags_in: [u8; 4] = [0b00001, 0b10000, 0b11111, 0b01010];
for (&p, &m) in positions_in.iter().zip(&mags_in) {
codeword[p] ^= m;
}
let mut input = hrp_expand(hrp);
input.extend_from_slice(&codeword);
let polymod = polymod_run(&input, &GEN_LONG, LONG_SHIFT, LONG_MASK);
let residue = polymod ^ MK_LONG_CONST;
let (positions, magnitudes) =
decode_long_errors(residue, codeword.len()).expect("4-error must decode");
assert_eq!(positions, vec![0, 5, 18, 28]);
assert_eq!(magnitudes, mags_in.to_vec());
let mut corrected = codeword.clone();
for (p, m) in positions.iter().zip(&magnitudes) {
corrected[*p] ^= m;
}
assert_eq!(corrected, original);
}
#[test]
fn five_errors_either_rejects_or_returns_bogus_recovery() {
let hrp = "mk";
let data: Vec<u8> = (0..16).collect();
let checksum = bch_create_checksum_long(hrp, &data);
let mut codeword = data.clone();
codeword.extend_from_slice(&checksum);
let positions_in: [usize; 5] = [0, 5, 10, 15, 20];
let mags_in: [u8; 5] = [1, 2, 3, 4, 5];
for (&p, &m) in positions_in.iter().zip(&mags_in) {
codeword[p] ^= m;
}
let mut input = hrp_expand(hrp);
input.extend_from_slice(&codeword);
let polymod = polymod_run(&input, &GEN_LONG, LONG_SHIFT, LONG_MASK);
let residue = polymod ^ MK_LONG_CONST;
if let Some((positions, magnitudes)) = decode_long_errors(residue, codeword.len()) {
let original = {
let mut o = data.clone();
o.extend_from_slice(&checksum);
o
};
let mut corrected = codeword.clone();
for (p, m) in positions.iter().zip(&magnitudes) {
corrected[*p] ^= m;
}
assert_ne!(
corrected, original,
"5-error decode should not produce the original codeword"
);
}
}
}