use std::str::FromStr;
use super::HashType;
use super::{convert_bits, polymod, expand_prefix};
const SIZE_MASK: u8 = 0x07;
const TYPE_MASK: u8 = 0x78;
#[rustfmt::skip]
const CHARSET_REV: [Option<u8>; 128] = [
None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None,
Some(15), None, Some(10), Some(17), Some(21), Some(20), Some(26), Some(30),
Some(7), Some(5), None, None, None, None, None, None,
None, Some(29), None, Some(24), Some(13), Some(25), Some(9), Some(8),
Some(23), None, Some(18), Some(22), Some(31), Some(27), Some(19), None,
Some(1), Some(0), Some(3), Some(16), Some(11), Some(28), Some(12), Some(14),
Some(6), Some(4), Some(2), None, None, None, None, None,
None, Some(29), None, Some(24), Some(13), Some(25), Some(9), Some(8),
Some(23), None, Some(18), Some(22), Some(31), Some(27), Some(19), None,
Some(1), Some(0), Some(3), Some(16), Some(11), Some(28), Some(12), Some(14),
Some(6), Some(4), Some(2), None, None, None, None, None,
];
#[derive(Debug, PartialEq)]
pub struct Payload {
pub payload: Vec<u8>,
pub hash_type: HashType,
}
#[derive(Debug)]
pub enum DecodeError {
InvalidChar(char),
InvalidLength(usize),
ChecksumFailed(u64),
InvalidVersion(u8),
}
impl FromStr for Payload {
type Err = DecodeError;
fn from_str(addr_str: &str) -> Result<Self, DecodeError> {
if addr_str.is_empty() {
return Err(DecodeError::InvalidLength(0));
}
let parts: Vec<&str> = addr_str.split(':').collect();
if parts.len() != 2 {
panic!("TODO, handle this case")
}
let prefix = parts[0];
let payload_str = parts[1];
let payload_chars = payload_str.chars(); let payload_5_bits: Result<Vec<u8>, DecodeError> = payload_chars
.map(|c| match CHARSET_REV.get(c as usize) {
Some(Some(d)) => Ok(*d as u8),
_ => Err(DecodeError::InvalidChar(c))
})
.collect();
let payload_5_bits = payload_5_bits?;
let checksum = polymod(&[&expand_prefix(prefix), &payload_5_bits[..]].concat());
if checksum != 0 {
return Err(DecodeError::ChecksumFailed(checksum));
}
let len_5_bit = payload_5_bits.len();
let payload = convert_bits(&payload_5_bits[..(len_5_bit - 8)], 5, 8, false);
let version = payload[0];
let body = &payload[1..];
let body_len = body.len();
let version_size = version & SIZE_MASK;
match version_size {
0x00 if body_len != 20 => Err(DecodeError::InvalidLength(body_len)),
0x01 if body_len != 24 => Err(DecodeError::InvalidLength(body_len)),
0x02 if body_len != 28 => Err(DecodeError::InvalidLength(body_len)),
0x03 if body_len != 32 => Err(DecodeError::InvalidLength(body_len)),
0x04 if body_len != 40 => Err(DecodeError::InvalidLength(body_len)),
0x05 if body_len != 48 => Err(DecodeError::InvalidLength(body_len)),
0x06 if body_len != 56 => Err(DecodeError::InvalidLength(body_len)),
0x07 if body_len != 64 => Err(DecodeError::InvalidLength(body_len)),
_ => Ok(())
}?;
let version_type = version & TYPE_MASK;
let hash_type = HashType::try_from(version_type)?;
Ok(Payload {
payload: body.to_vec(),
hash_type,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keyhash_20_main() {
let cashaddr = "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2";
let addr: Payload = cashaddr.parse().unwrap();
let payload = hex::decode("F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9").unwrap();
assert_eq!(payload, addr.payload);
}
#[test]
#[should_panic]
fn checksum() {
let cashaddr = "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr3jkmx6eylep8ekg2";
if let Err(DecodeError::ChecksumFailed(_)) = cashaddr.parse::<Payload>() {
panic!("Checksum missed detection of error")
}
}
}