|
| 1 | +use std::collections::HashMap; |
| 2 | + |
| 3 | +use bech32::primitives::correction::CorrectableError as _; |
| 4 | +use bech32::primitives::decode::CheckedHrpstring; |
| 5 | +use bech32::{Checksum, Fe1024, Fe32}; |
| 6 | +use honggfuzz::fuzz; |
| 7 | + |
| 8 | +/// The codex32 checksum algorithm, defined in BIP-93. |
| 9 | +/// |
| 10 | +/// Used in this fuzztest because it can correct up to 4 errors, vs bech32 which |
| 11 | +/// can correct only 1. Should exhibit more interesting behavior. |
| 12 | +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| 13 | +pub enum Codex32 {} |
| 14 | + |
| 15 | +impl Checksum for Codex32 { |
| 16 | + type MidstateRepr = u128; |
| 17 | + type CorrectionField = Fe1024; |
| 18 | + const ROOT_GENERATOR: Self::CorrectionField = Fe1024::new([Fe32::_9, Fe32::_9]); |
| 19 | + const ROOT_EXPONENTS: core::ops::RangeInclusive<usize> = 9..=16; |
| 20 | + |
| 21 | + const CHECKSUM_LENGTH: usize = 13; |
| 22 | + const CODE_LENGTH: usize = 93; |
| 23 | + // Copied from BIP-93 |
| 24 | + const GENERATOR_SH: [u128; 5] = [ |
| 25 | + 0x19dc500ce73fde210, |
| 26 | + 0x1bfae00def77fe529, |
| 27 | + 0x1fbd920fffe7bee52, |
| 28 | + 0x1739640bdeee3fdad, |
| 29 | + 0x07729a039cfc75f5a, |
| 30 | + ]; |
| 31 | + const TARGET_RESIDUE: u128 = 0x10ce0795c2fd1e62a; |
| 32 | +} |
| 33 | + |
| 34 | +static CORRECT: &[u8; 48] = b"ms10testsxxxxxxxxxxxxxxxxxxxxxxxxxx4nzvca9cmczlw"; |
| 35 | + |
| 36 | +fn do_test(data: &[u8]) { |
| 37 | + if data.is_empty() || data.len() % 2 == 1 { |
| 38 | + return; |
| 39 | + } |
| 40 | + |
| 41 | + let mut any_actual_errors = false; |
| 42 | + let mut e2t = 0; |
| 43 | + let mut erasures = Vec::with_capacity(CORRECT.len()); |
| 44 | + // Start with a correct string |
| 45 | + let mut hrpstring = *CORRECT; |
| 46 | + // ..then mangle it |
| 47 | + let mut errors = HashMap::with_capacity(data.len() / 2); |
| 48 | + for sl in data.chunks_exact(2) { |
| 49 | + let idx = usize::from(sl[0]) & 0x7f; |
| 50 | + if idx >= CORRECT.len() - 3 { |
| 51 | + return; |
| 52 | + } |
| 53 | + let offs = match Fe32::try_from(sl[1]) { |
| 54 | + Ok(fe) => fe, |
| 55 | + Err(_) => return, |
| 56 | + }; |
| 57 | + |
| 58 | + hrpstring[idx + 3] = |
| 59 | + (Fe32::from_char(hrpstring[idx + 3].into()).unwrap() + offs).to_char() as u8; |
| 60 | + |
| 61 | + if errors.insert(CORRECT.len() - (idx + 3) - 1, offs).is_some() { |
| 62 | + return; |
| 63 | + } |
| 64 | + if sl[0] & 0x80 == 0x80 { |
| 65 | + // We might push "dummy" errors which are erasures that aren't actually wrong. |
| 66 | + // If we do this too many times, we'll exceed the singleton bound so correction |
| 67 | + // will fail, but as long as we're within the bound everything should "work", |
| 68 | + // in the sense that there will be no crashes and the error corrector will |
| 69 | + // just yield an error with value Q. |
| 70 | + erasures.push(CORRECT.len() - (idx + 3) - 1); |
| 71 | + e2t += 1; |
| 72 | + if offs != Fe32::Q { |
| 73 | + any_actual_errors = true; |
| 74 | + } |
| 75 | + } else if offs != Fe32::Q { |
| 76 | + any_actual_errors = true; |
| 77 | + e2t += 2; |
| 78 | + } |
| 79 | + } |
| 80 | + // We need _some_ errors. |
| 81 | + if !any_actual_errors { |
| 82 | + return; |
| 83 | + } |
| 84 | + |
| 85 | + let s = unsafe { core::str::from_utf8_unchecked(&hrpstring) }; |
| 86 | + let mut correct_ctx = CheckedHrpstring::new::<Codex32>(s) |
| 87 | + .unwrap_err() |
| 88 | + .correction_context::<Codex32>() |
| 89 | + .unwrap(); |
| 90 | + |
| 91 | + correct_ctx.add_erasures(&erasures); |
| 92 | + |
| 93 | + let iter = correct_ctx.bch_errors(); |
| 94 | + if e2t <= 8 { |
| 95 | + for (idx, fe) in iter.unwrap() { |
| 96 | + assert_eq!(errors.remove(&idx), Some(fe)); |
| 97 | + } |
| 98 | + for val in errors.values() { |
| 99 | + assert_eq!(*val, Fe32::Q); |
| 100 | + } |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +fn main() { |
| 105 | + loop { |
| 106 | + fuzz!(|data| { |
| 107 | + do_test(data); |
| 108 | + }); |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +#[cfg(test)] |
| 113 | +mod tests { |
| 114 | + fn extend_vec_from_hex(hex: &str, out: &mut Vec<u8>) { |
| 115 | + let mut b = 0; |
| 116 | + for (idx, c) in hex.as_bytes().iter().filter(|&&c| c != b'\n').enumerate() { |
| 117 | + b <<= 4; |
| 118 | + match *c { |
| 119 | + b'A'..=b'F' => b |= c - b'A' + 10, |
| 120 | + b'a'..=b'f' => b |= c - b'a' + 10, |
| 121 | + b'0'..=b'9' => b |= c - b'0', |
| 122 | + _ => panic!("Bad hex"), |
| 123 | + } |
| 124 | + if (idx & 1) == 1 { |
| 125 | + out.push(b); |
| 126 | + b = 0; |
| 127 | + } |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + #[test] |
| 132 | + fn duplicate_crash() { |
| 133 | + let mut a = Vec::new(); |
| 134 | + extend_vec_from_hex("8c00a10091039e0185008000831f8e0f", &mut a); |
| 135 | + super::do_test(&a); |
| 136 | + } |
| 137 | +} |
0 commit comments