use zeroize::Zeroize;
mod base32;
mod checksum;
use self::checksum::{Checksum, CHECKSUM_SIZE};
use error::Error;
use prelude::*;
pub const DEFAULT_SEPARATOR: char = '1';
pub const DEFAULT_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',
];
pub const DEFAULT_CHARSET_UPCASE: [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',
];
pub const MAX_LENGTH: usize = 90;
pub fn encode<S, D>(hrp: S, data: D) -> String
where
S: AsRef<str>,
D: AsRef<[u8]>,
{
Bech32::lower_case().encode(hrp, data)
}
pub fn decode<S>(encoded: S) -> Result<(String, Vec<u8>), Error>
where
S: AsRef<str>,
{
Bech32::lower_case().decode(encoded)
}
pub fn encode_upper<S, D>(hrp: S, data: D) -> String
where
S: AsRef<str>,
D: AsRef<[u8]>,
{
Bech32::upper_case().encode(hrp, data)
}
pub fn decode_upper<S>(encoded: S) -> Result<(String, Vec<u8>), Error>
where
S: AsRef<str>,
{
Bech32::upper_case().decode(encoded)
}
pub struct Bech32 {
pub charset: [char; 32],
pub charset_inverse: [Option<u8>; 128],
pub separator: char,
}
impl Default for Bech32 {
fn default() -> Self {
Bech32::lower_case()
}
}
impl Bech32 {
pub fn lower_case() -> Self {
Self::new(DEFAULT_CHARSET, DEFAULT_SEPARATOR)
}
pub fn upper_case() -> Self {
Self::new(DEFAULT_CHARSET_UPCASE, DEFAULT_SEPARATOR)
}
pub fn new(charset: [char; 32], separator: char) -> Self {
match separator {
'1' | 'B' | 'I' | 'O' | 'b' | 'i' | 'o' => (),
'0'...'9' | 'A'...'Z' | 'a'...'z' => panic!("invalid separator: {:?}", separator),
_ => (),
}
let mut charset_inverse = [None; 128];
for (i, char) in charset.iter().enumerate() {
let mut byte = [0u8];
char.encode_utf8(byte.as_mut());
charset_inverse[byte[0] as usize] = Some(i as u8);
}
Self {
charset,
charset_inverse,
separator,
}
}
pub fn separator(&self) -> char {
self.separator
}
pub fn encode<S, D>(&self, hrp: S, data: D) -> String
where
S: AsRef<str>,
D: AsRef<[u8]>,
{
let mut base32_data = base32::encode(data.as_ref());
let mut result =
String::with_capacity(hrp.as_ref().len() + 1 + base32_data.len() + CHECKSUM_SIZE);
result.push_str(hrp.as_ref());
result.push(self.separator);
let checksum = Checksum::new(hrp.as_ref().as_bytes(), &base32_data);
for byte in base32_data.iter().chain(checksum.as_ref().iter()) {
let c = self
.charset
.get(*byte as usize)
.expect("out of range character for alphabet");
result.push(*c);
}
base32_data.as_mut_slice().zeroize();
result
}
pub fn decode<S>(&self, encoded: S) -> Result<(String, Vec<u8>), Error>
where
S: AsRef<str>,
{
let encoded_str = encoded.as_ref();
let encoded_len: usize = encoded_str.len();
if encoded_len > MAX_LENGTH {
return Err(Error::LengthInvalid);
}
let pos = encoded_str
.rfind(self.separator)
.ok_or_else(|| Error::EncodingInvalid)?;
if pos == encoded_str.len() {
return Err(Error::EncodingInvalid);
}
let hrp = encoded_str[..pos].to_lowercase();
if hrp.is_empty() {
return Err(Error::EncodingInvalid);
}
for c in hrp.chars() {
match c {
'!'...'@' | 'A'...'Z' | '['...'`' | 'a'...'z' | '{'...'~' => (),
_ => return Err(Error::EncodingInvalid),
}
}
let encoded_data = &encoded_str[(pos + 1)..];
if encoded_data.len() < CHECKSUM_SIZE {
return Err(Error::LengthInvalid);
}
let mut base32_data = Vec::with_capacity(encoded_data.len());
for encoded_byte in encoded_data.bytes() {
let decoded_byte = self
.charset_inverse
.get(encoded_byte as usize)
.and_then(|byte| *byte)
.ok_or_else(|| Error::EncodingInvalid)?;
base32_data.push(decoded_byte);
}
if let Err(e) = Checksum::verify(hrp.as_bytes(), &base32_data) {
base32_data.as_mut_slice().zeroize();
return Err(e);
}
let base32_len = base32_data.len() - CHECKSUM_SIZE;
let decode_result = base32::decode(&base32_data[..base32_len]);
base32_data.as_mut_slice().zeroize();
decode_result.map(|decoded| (hrp, decoded))
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestVector {
encoded: &'static str,
hrp: &'static str,
bytes: &'static [u8],
upper_case: bool,
}
const VALID_TEST_VECTORS: &[TestVector] = &[
TestVector {
encoded: "A12UEL5L",
hrp: "a",
bytes: &[],
upper_case: true
},
TestVector {
encoded: "a12uel5l",
hrp: "a",
bytes: &[],
upper_case: false
},
TestVector {
encoded: "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs",
hrp: "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio",
bytes: &[],
upper_case: false
},
TestVector {
hrp: "abcdef",
bytes: &[0, 68, 50, 20, 199, 66, 84, 182, 53, 207, 132, 101, 58, 86, 215, 198, 117, 190, 119, 223],
encoded: "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw",
upper_case: false
},
TestVector {
encoded: "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j",
hrp: "1",
bytes: &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
upper_case: false
},
TestVector {
encoded: "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w",
hrp: "split",
bytes: &[197, 243, 139, 112, 48, 95, 81, 155, 246, 109, 133, 251, 108, 240, 48, 88, 243, 221, 228, 99, 236, 215, 145, 143, 45, 199, 67, 145, 143, 45],
upper_case: false
},
TestVector {
encoded: "?1ezyfcl",
hrp: "?",
bytes: &[],
upper_case: false
},
];
#[test]
fn encode_valid_test_vectors() {
let bech32 = Bech32::default();
for vector in VALID_TEST_VECTORS {
let encoded = bech32.encode(vector.hrp, vector.bytes);
assert_eq!(encoded, vector.encoded.to_lowercase());
}
}
#[test]
fn decode_valid_test_vectors() {
for vector in VALID_TEST_VECTORS {
let bech32 = if vector.upper_case {
Bech32::upper_case()
} else {
Bech32::default()
};
println!("decoding vector: {:?}", vector.encoded);
let (hrp, data) = bech32.decode(vector.encoded).unwrap();
assert_eq!(hrp, vector.hrp.to_lowercase());
assert_eq!(data, vector.bytes);
}
}
#[test]
fn hrp_character_out_of_range() {
let bech32 = Bech32::default();
assert_eq!(bech32.decode("\x201nwldj5"), Err(Error::EncodingInvalid));
assert_eq!(bech32.decode("\x7F1axkwrx"), Err(Error::EncodingInvalid));
}
#[test]
fn overall_max_length_exceeded() {
let too_long: &str = "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx";
assert_eq!(
Bech32::default().decode(too_long),
Err(Error::LengthInvalid)
);
}
#[test]
fn no_separator_character() {
assert_eq!(
Bech32::default().decode("pzry9x0s0muk"),
Err(Error::EncodingInvalid)
);
}
#[test]
fn empty_hrp() {
for empty_hrp_str in &["1pzry9x0s0muk", "10a06t8", "1qzzfhee"] {
assert_eq!(
Bech32::default().decode(empty_hrp_str),
Err(Error::EncodingInvalid)
);
}
}
#[test]
fn invalid_data_character() {
assert_eq!(
Bech32::default().decode("x1b4n0q5v"),
Err(Error::EncodingInvalid)
);
}
#[test]
fn checksum_too_short() {
assert_eq!(
Bech32::default().decode("li1dgmt3"),
Err(Error::LengthInvalid)
);
}
#[test]
fn invalid_character_in_checksum() {
assert_eq!(
Bech32::default().decode("de1lg7wt\x7F"),
Err(Error::EncodingInvalid)
);
}
#[test]
fn checksum_calculated_with_uppercase_hrp() {
assert_eq!(
Bech32::upper_case().decode("A1G7SGD8"),
Err(Error::ChecksumInvalid)
);
}
#[test]
fn invalid_mixed_case() {
assert_eq!(
Bech32::default().decode("a12UEL5L"),
Err(Error::EncodingInvalid)
);
}
}