use std::fmt;
#[allow(unused_imports, deprecated)]
use std::ascii::AsciiExt;
use bitcoin::bech32::{u5, Error};
pub fn encode_to_fmt<T: AsRef<[u5]>>(fmt: &mut fmt::Formatter, hrp: &str, data: T) -> fmt::Result {
let hrp_bytes: &[u8] = hrp.as_bytes();
let checksum = create_checksum(hrp_bytes, data.as_ref());
let data_part = data.as_ref().iter().chain(checksum.iter());
write!(
fmt,
"{}{}{}",
hrp,
SEP,
data_part
.map(|p| CHARSET[*p.as_ref() as usize])
.collect::<String>()
)
}
pub fn decode(s: &str) -> Result<(&str, Vec<u5>), Error> {
let len: usize = s.len();
if len < 14 {
return Err(Error::InvalidLength);
}
let (raw_hrp, raw_data) = match s.rfind("1") {
None => return Err(Error::MissingSeparator),
Some(sep) => {
let (hrp, data) = s.split_at(sep);
(hrp, &data[1..])
}
};
if raw_hrp.len() < 1 || raw_data.len() < 12 || raw_hrp.len() > 83 {
return Err(Error::InvalidLength);
}
let mut has_lower: bool = false;
let mut has_upper: bool = false;
let mut hrp_bytes: Vec<u8> = Vec::new();
for b in raw_hrp.bytes() {
if b < 33 || b > 126 {
return Err(Error::InvalidChar(b as char));
}
let mut c = b;
if b >= b'a' && b <= b'z' {
has_lower = true;
}
if b >= b'A' && b <= b'Z' {
has_upper = true;
c = b + (b'a' - b'A');
}
hrp_bytes.push(c);
}
let mut data = raw_data
.chars()
.map(|c| {
if !c.is_ascii() {
return Err(Error::InvalidChar(c));
}
if c.is_lowercase() {
has_lower = true;
} else if c.is_uppercase() {
has_upper = true;
}
let num_value = CHARSET_REV[c as usize];
if num_value > 31 || num_value < 0 {
return Err(Error::InvalidChar(c));
}
Ok(u5::try_from_u8(num_value as u8).expect("range checked above, num_value <= 31"))
})
.collect::<Result<Vec<u5>, Error>>()?;
if has_lower && has_upper {
return Err(Error::MixedCase);
}
if !verify_checksum(&hrp_bytes, &data) {
return Err(Error::InvalidChecksum);
}
let dbl: usize = data.len();
data.truncate(dbl - 12);
Ok((raw_hrp, data))
}
fn create_checksum(hrp: &[u8], data: &[u5]) -> Vec<u5> {
let mut values: Vec<u5> = hrp_expand(hrp);
values.extend_from_slice(data);
values.extend_from_slice(&[u5::try_from_u8(0).unwrap(); 12]);
let plm: u64 = polymod(&values) ^ 1;
let mut checksum: Vec<u5> = Vec::new();
for p in 0..12 {
checksum.push(u5::try_from_u8(((plm >> (5 * (11 - p))) & 0x1f) as u8).unwrap());
}
checksum
}
fn verify_checksum(hrp: &[u8], data: &[u5]) -> bool {
let mut exp = hrp_expand(hrp);
exp.extend_from_slice(data);
polymod(&exp) == 1u64
}
fn hrp_expand(hrp: &[u8]) -> Vec<u5> {
let mut v: Vec<u5> = Vec::new();
for b in hrp {
v.push(u5::try_from_u8(*b >> 5).expect("can't be out of range, max. 7"));
}
v.push(u5::try_from_u8(0).unwrap());
for b in hrp {
v.push(u5::try_from_u8(*b & 0x1f).expect("can't be out of range, max. 31"));
}
v
}
fn polymod(values: &[u5]) -> u64 {
let mut chk: u64 = 1;
let mut b: u8;
for v in values {
b = (chk >> 55) as u8;
chk = (chk & 0x7fffffffffffff) << 5 ^ (u64::from(*v.as_ref()));
for i in 0..5 {
if (b >> i) & 1 == 1 {
chk ^= GEN[i]
}
}
}
chk
}
const SEP: char = '1';
const CHARSET: [char; 32] = [
'q', 'p', 'z', 'r', 'y', '9', 'x', '8', 'g', 'f', '2', 't', 'v', 'd', 'w', '0', 's', '3', 'j',
'n', '5', '4', 'k', 'h', 'c', 'e', '6', 'm', 'u', 'a', '7', 'l',
];
const CHARSET_REV: [i8; 128] = [
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23,
-1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, -1, 29,
-1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1,
-1, -1, -1, -1,
];
const GEN: [u64; 5] = [
0x7d52fba40bd886,
0x5e8dbf1a03950c,
0x1c3a3c74072a18,
0x385d72fa0e5139,
0x7093e5a608865b,
];
#[cfg(test)]
mod test {
use super::*;
use bitcoin::bech32::ToBase32;
use rand;
#[test]
fn test_polymod_sanity() {
let data: [u8; 32] = rand::random();
let data1 = data.to_vec();
let data1_b32 = data1.to_base32();
let polymod1 = polymod(&data1_b32);
let data2 = data.to_vec();
let mut data2_b32 = data2.to_base32();
data2_b32.extend(vec![u5::try_from_u8(0).unwrap(); 1023]);
let polymod2 = polymod(&data2_b32);
assert_eq!(polymod1, polymod2);
}
#[test]
fn test_checksum() {
let data = vec![7,2,3,4,5,6,7,8,9,234,123,213,16];
let cs = create_checksum("lq".as_bytes(), &data.to_base32());
let expected_cs = vec![22,13,13,5,4,4,23,7,28,21,30,12];
for i in 0..expected_cs.len() {
assert_eq!(expected_cs[i], *cs[i].as_ref());
}
}
}