1use std::fmt::{self, Display};
2use std::str::FromStr;
3
4use super::{convert_bits, expand_prefix, polymod};
5use super::{HashType, Payload};
6
7const SIZE_MASK: u8 = 0x07;
8const TYPE_MASK: u8 = 0x78;
9
10type Result<T> = std::result::Result<T, Error>;
11
12#[rustfmt::skip]
14const CHARSET_REV: [Option<u8>; 128] = [
15 None, None, None, None, None, None, None, None,
16 None, None, None, None, None, None, None, None,
17 None, None, None, None, None, None, None, None,
18 None, None, None, None, None, None, None, None,
19 None, None, None, None, None, None, None, None,
20 None, None, None, None, None, None, None, None,
21 Some(15), None, Some(10), Some(17), Some(21), Some(20), Some(26), Some(30),
22 Some(7), Some(5), None, None, None, None, None, None,
23 None, Some(29), None, Some(24), Some(13), Some(25), Some(9), Some(8),
24 Some(23), None, Some(18), Some(22), Some(31), Some(27), Some(19), None,
25 Some(1), Some(0), Some(3), Some(16), Some(11), Some(28), Some(12), Some(14),
26 Some(6), Some(4), Some(2), None, None, None, None, None,
27 None, Some(29), None, Some(24), Some(13), Some(25), Some(9), Some(8),
28 Some(23), None, Some(18), Some(22), Some(31), Some(27), Some(19), None,
29 Some(1), Some(0), Some(3), Some(16), Some(11), Some(28), Some(12), Some(14),
30 Some(6), Some(4), Some(2), None, None, None, None, None,
31];
32
33#[derive(Debug, PartialEq, Eq)]
35pub enum Error {
36 InvalidChar(char),
38 InvalidLength(usize),
40 ChecksumFailed(u64),
44 InvalidVersion(u8),
46}
47
48impl Display for Error {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::InvalidChar(c) => write!(f, "Invalid Character `{c}` encountered during decode."),
52 Self::InvalidLength(len) => write!(f, "Invalid hash length detected: {}", len),
53 Self::ChecksumFailed(cs) => write!(f, "Checksum failed validation: {}", cs),
54 Self::InvalidVersion(vbit) => write!(f, "Invalid version byte detected {:X}", vbit),
55 }
56 }
57}
58
59impl std::error::Error for Error {}
60
61impl FromStr for Payload {
62 type Err = Error;
63
64 fn from_str(addr_str: &str) -> Result<Self> {
65 if addr_str.is_empty() {
67 return Err(Error::InvalidLength(0));
68 }
69
70 let (prefix, payload_str) = addr_str
71 .split_once(":")
72 .unwrap_or(("bitcoincash", addr_str));
73
74 let payload_5_bits: Vec<u8> = payload_str
76 .chars()
77 .map(|c| match CHARSET_REV.get(c as usize) {
78 Some(Some(d)) => Ok(*d as u8),
79 _ => Err(Error::InvalidChar(c)),
80 })
81 .collect::<Result<_>>()?;
82
83 let checksum = polymod(&[&expand_prefix(prefix), &payload_5_bits[..]].concat());
85 if checksum != 0 {
86 return Err(Error::ChecksumFailed(checksum));
87 }
88 let checksum: u64 = payload_5_bits
89 .iter()
90 .rev()
91 .take(8)
92 .enumerate()
93 .map(|(i, &val)| (val as u64) << (5 * i))
94 .sum();
95
96 let len_5_bit = payload_5_bits.len();
98 let payload = convert_bits(&payload_5_bits[..(len_5_bit - 8)], 5, 8, false);
99
100 let version = payload[0];
102
103 let body = &payload[1..];
105 let body_len = body.len();
106 let version_size = version & SIZE_MASK;
107
108 match version_size {
109 0x00 if body_len != 20 => Err(Error::InvalidLength(body_len)),
110 0x01 if body_len != 24 => Err(Error::InvalidLength(body_len)),
111 0x02 if body_len != 28 => Err(Error::InvalidLength(body_len)),
112 0x03 if body_len != 32 => Err(Error::InvalidLength(body_len)),
113 0x04 if body_len != 40 => Err(Error::InvalidLength(body_len)),
114 0x05 if body_len != 48 => Err(Error::InvalidLength(body_len)),
115 0x06 if body_len != 56 => Err(Error::InvalidLength(body_len)),
116 0x07 if body_len != 64 => Err(Error::InvalidLength(body_len)),
117 _ => Ok(()),
118 }?;
119
120 let version_type = version & TYPE_MASK;
122 let hash_type = HashType::try_from(version_type >> 3)?;
123
124 Ok(Payload {
125 payload: body.to_vec(),
126 hash_type,
127 checksum,
128 })
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use hex_literal::hex;
136
137 use crate::test_vectors::{TestCase, TEST_VECTORS};
138
139 #[test]
140 fn decode() {
141 for tc in TEST_VECTORS
142 .lines()
143 .map(|s| TestCase::try_from(s).expect("Failed to parse test vector"))
144 {
145 let payload: Payload = tc.cashaddr.parse().expect("could not parse");
146 assert_eq!(payload.payload, tc.pl, "Incorrect payload parsed");
147 assert_eq!(payload.hash_type, tc.hashtype, "Incorrect Hash Type parsed")
148 }
149 }
150 #[test]
151 fn case_insensitive() {
152 let cashaddr = "bitcoincash:qr6m7j9njldWWzlg9v7v53unlr4JKmx6Eylep8ekg2";
153 let addr: Payload = cashaddr.parse().unwrap();
154 let payload = hex!("F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9");
155 assert_eq!(payload, addr.payload.as_ref());
156 assert_eq!(HashType::P2PKH, addr.hash_type);
157 }
158 #[test]
159 fn checksum() {
160 let cashaddr = "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr3jkmx6eylep8ekg2";
161 match cashaddr.parse::<Payload>() {
162 Err(Error::ChecksumFailed(_)) => (),
163 Err(e) => panic!("Expected ChecksumFailed but found {e:?}"),
164 Ok(_) => panic!(
165 "Payload successfully parsed from cashaddr with invalid checksum. cashaddr was {}",
166 cashaddr,
167 ),
168 }
169 }
170 #[test]
171 fn invalid_char() {
172 match "bitcoincash:qr6m7j9njlbWWzlg9v7v53unlr4JKmx6Eylep8ekg2".parse::<Payload>() {
173 Err(Error::InvalidChar('b')) => (),
174 Err(e) => panic!("Failed to detect invalid char, instead detected {:?}", e),
175 Ok(_) => panic!("Failed to detect invalid char"),
176 }
177 }
178}