bitcoincash_addr/cashaddr/
mod.rs

1pub mod errors;
2
3use super::*;
4pub use errors::{DecodingError, EncodingError};
5
6// Prefixes
7const MAINNET_PREFIX: &str = "bitcoincash";
8const TESTNET_PREFIX: &str = "bchtest";
9const REGNET_PREFIX: &str = "bchreg";
10
11// The cashaddr character set for encoding
12const CHARSET: &[u8; 32] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
13
14// The cashaddr character set for decoding
15#[rustfmt::skip]
16const CHARSET_REV: [Option<u8>; 128] = [
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    None,     None,     None,     None,     None,     None,     None,     None,
22    None,     None,     None,     None,     None,     None,     None,     None,
23    Some(15), None,     Some(10), Some(17), Some(21), Some(20), Some(26), Some(30),
24    Some(7),  Some(5),  None,     None,     None,     None,     None,     None,
25    None,     Some(29), None,     Some(24), Some(13), Some(25), Some(9),  Some(8),
26    Some(23), None,     Some(18), Some(22), Some(31), Some(27), Some(19), None,
27    Some(1),  Some(0),  Some(3),  Some(16), Some(11), Some(28), Some(12), Some(14),
28    Some(6),  Some(4),  Some(2),  None,     None,     None,     None,     None,
29    None,     Some(29),  None,    Some(24), Some(13), Some(25), Some(9),  Some(8),
30    Some(23), None,     Some(18), Some(22), Some(31), Some(27), Some(19), None,
31    Some(1),  Some(0),  Some(3),  Some(16), Some(11), Some(28), Some(12), Some(14),
32    Some(6),  Some(4),  Some(2),  None,     None,     None,     None,     None,
33];
34
35// Version byte flags
36#[allow(dead_code)]
37mod version_byte_flags {
38    pub const TYPE_MASK: u8 = 0x78;
39    pub const TYPE_P2PKH: u8 = 0x00;
40    pub const TYPE_P2SH: u8 = 0x08;
41
42    pub const SIZE_MASK: u8 = 0x07;
43    pub const SIZE_160: u8 = 0x00;
44    pub const SIZE_192: u8 = 0x01;
45    pub const SIZE_224: u8 = 0x02;
46    pub const SIZE_256: u8 = 0x03;
47    pub const SIZE_320: u8 = 0x04;
48    pub const SIZE_384: u8 = 0x05;
49    pub const SIZE_448: u8 = 0x06;
50    pub const SIZE_512: u8 = 0x07;
51}
52
53// https://github.com/Bitcoin-ABC/bitcoin-abc/blob/2804a49bfc0764ba02ce2999809c52b3b9bb501e/src/cashaddr.cpp#L42
54fn polymod(v: &[u8]) -> u64 {
55    let mut c: u64 = 1;
56    for d in v.iter() {
57        let c0: u8 = (c >> 35) as u8;
58        c = ((c & 0x0007_ffff_ffff) << 5) ^ u64::from(*d);
59        if c0 & 0x01 != 0 {
60            c ^= 0x0098_f2bc_8e61;
61        }
62        if c0 & 0x02 != 0 {
63            c ^= 0x0079_b76d_99e2;
64        }
65        if c0 & 0x04 != 0 {
66            c ^= 0x00f3_3e5f_b3c4;
67        }
68        if c0 & 0x08 != 0 {
69            c ^= 0x00ae_2eab_e2a8;
70        }
71        if c0 & 0x10 != 0 {
72            c ^= 0x001e_4f43_e470;
73        }
74    }
75    c ^ 1
76}
77
78// Expand the address prefix for the checksum operation.
79fn expand_prefix(prefix: &str) -> Vec<u8> {
80    let mut ret: Vec<u8> = prefix.chars().map(|c| (c as u8) & 0x1f).collect();
81    ret.push(0);
82    ret
83}
84
85fn convert_bits(data: &[u8], inbits: u8, outbits: u8, pad: bool) -> Vec<u8> {
86    assert!(inbits <= 8 && outbits <= 8);
87    let num_bytes = (data.len() * inbits as usize + outbits as usize - 1) / outbits as usize;
88    let mut ret = Vec::with_capacity(num_bytes);
89    let mut acc: u16 = 0; // accumulator of bits
90    let mut num: u8 = 0; // num bits in acc
91    let groupmask = (1 << outbits) - 1;
92    for d in data.iter() {
93        // We push each input chunk into a 16-bit accumulator
94        acc = (acc << inbits) | u16::from(*d);
95        num += inbits;
96        // Then we extract all the output groups we can
97        while num > outbits {
98            ret.push((acc >> (num - outbits)) as u8);
99            acc &= !(groupmask << (num - outbits));
100            num -= outbits;
101        }
102    }
103    if pad {
104        // If there's some bits left, pad and add it
105        if num > 0 {
106            ret.push((acc << (outbits - num)) as u8);
107        }
108    } else {
109        // If there's some bits left, figure out if we need to remove padding and add it
110        let padding = (data.len() * inbits as usize) % outbits as usize;
111        if num as usize > padding {
112            ret.push((acc >> padding) as u8);
113        }
114    }
115    ret
116}
117
118/// Codec allowing the encoding and decoding of CashAddrs.
119pub struct CashAddrCodec;
120
121impl AddressCodec for CashAddrCodec {
122    type EncodingError = EncodingError;
123    type DecodingError = DecodingError;
124
125    fn encode(
126        raw: &[u8],
127        hash_type: HashType,
128        network: Network,
129    ) -> Result<String, Self::EncodingError> {
130        // Calculate version byte
131        let hash_flag = match hash_type {
132            HashType::Key => version_byte_flags::TYPE_P2PKH,
133            HashType::Script => version_byte_flags::TYPE_P2SH,
134        };
135        let length = raw.len();
136        let version_byte = match length {
137            20 => version_byte_flags::SIZE_160,
138            24 => version_byte_flags::SIZE_192,
139            28 => version_byte_flags::SIZE_224,
140            32 => version_byte_flags::SIZE_256,
141            40 => version_byte_flags::SIZE_320,
142            48 => version_byte_flags::SIZE_384,
143            56 => version_byte_flags::SIZE_448,
144            64 => version_byte_flags::SIZE_512,
145            _ => return Err(EncodingError(length)),
146        } | hash_flag;
147
148        // Get prefix
149        let prefix = match network {
150            Network::Main => MAINNET_PREFIX,
151            Network::Test => TESTNET_PREFIX,
152            Network::Regtest => REGNET_PREFIX,
153        };
154
155        // Convert payload to 5 bit array
156        let mut payload = Vec::with_capacity(1 + raw.len());
157        payload.push(version_byte);
158        payload.extend(raw);
159        let payload_5_bits = convert_bits(&payload, 8, 5, true);
160
161        // Construct payload string using CHARSET
162        let payload_str: String = payload_5_bits
163            .iter()
164            .map(|b| CHARSET[*b as usize] as char)
165            .collect();
166
167        // Create checksum
168        let expanded_prefix = expand_prefix(prefix);
169        let checksum_input = [&expanded_prefix[..], &payload_5_bits, &[0; 8][..]].concat();
170        let checksum = polymod(&checksum_input);
171
172        // Convert checksum to string
173        let checksum_str: String = (0..8)
174            .rev()
175            .map(|i| CHARSET[((checksum >> (i * 5)) & 31) as usize] as char)
176            .collect();
177
178        // Concatentate all parts
179        let cashaddr = [prefix, ":", &payload_str, &checksum_str].concat();
180        Ok(cashaddr)
181    }
182
183    fn decode(addr_str: &str) -> Result<Address, Self::DecodingError> {
184        // Delimit and extract prefix
185        let parts: Vec<&str> = addr_str.split(':').collect();
186        if parts.len() != 2 {
187            return Err(DecodingError::NoPrefix);
188        }
189        let prefix = parts[0];
190        let payload_str = parts[1];
191
192        // Match network
193        let network = match prefix {
194            MAINNET_PREFIX => Network::Main,
195            TESTNET_PREFIX => Network::Test,
196            REGNET_PREFIX => Network::Regtest,
197            _ => return Err(DecodingError::InvalidPrefix(prefix.to_string())),
198        };
199
200        // Do some sanity checks on the string
201        let mut payload_chars = payload_str.chars();
202        if let Some(first_char) = payload_chars.next() {
203            if first_char.is_lowercase() {
204                if payload_chars.any(|c| c.is_uppercase()) {
205                    return Err(DecodingError::MixedCase);
206                }
207            } else if payload_chars.any(|c| c.is_lowercase()) {
208                return Err(DecodingError::MixedCase);
209            }
210        } else {
211            return Err(DecodingError::InvalidLength(0));
212        }
213
214        // Decode payload to 5 bit array
215        let payload_chars = payload_str.chars(); // Reintialize iterator here
216        let payload_5_bits: Result<Vec<u8>, DecodingError> = payload_chars
217            .map(|c| {
218                let i = c as usize;
219                if let Some(Some(d)) = CHARSET_REV.get(i) {
220                    Ok(*d as u8)
221                } else {
222                    Err(DecodingError::InvalidChar(c))
223                }
224            })
225            .collect();
226        let payload_5_bits = payload_5_bits?;
227
228        // Verify the checksum
229        let checksum = polymod(&[&expand_prefix(prefix), &payload_5_bits[..]].concat());
230        if checksum != 0 {
231            return Err(DecodingError::ChecksumFailed(checksum));
232        }
233
234        // Convert from 5 bit array to byte array
235        let len_5_bit = payload_5_bits.len();
236        let payload = convert_bits(&payload_5_bits[..(len_5_bit - 8)], 5, 8, false);
237
238        // Verify the version byte
239        let version = payload[0];
240
241        // Check length
242        let body = &payload[1..];
243        let body_len = body.len();
244        let version_size = version & version_byte_flags::SIZE_MASK;
245        if (version_size == version_byte_flags::SIZE_160 && body_len != 20)
246            || (version_size == version_byte_flags::SIZE_192 && body_len != 24)
247            || (version_size == version_byte_flags::SIZE_224 && body_len != 28)
248            || (version_size == version_byte_flags::SIZE_256 && body_len != 32)
249            || (version_size == version_byte_flags::SIZE_320 && body_len != 40)
250            || (version_size == version_byte_flags::SIZE_384 && body_len != 48)
251            || (version_size == version_byte_flags::SIZE_448 && body_len != 56)
252            || (version_size == version_byte_flags::SIZE_512 && body_len != 64)
253        {
254            return Err(DecodingError::InvalidLength(body_len));
255        }
256
257        // Extract the hash type and return
258        let version_type = version & version_byte_flags::TYPE_MASK;
259        let hash_type = if version_type == version_byte_flags::TYPE_P2PKH {
260            HashType::Key
261        } else if version_type == version_byte_flags::TYPE_P2SH {
262            HashType::Script
263        } else {
264            return Err(DecodingError::InvalidVersion(version));
265        };
266
267        Ok(Address {
268            scheme: Scheme::CashAddr,
269            body: body.to_vec(),
270            hash_type,
271            network,
272        })
273    }
274}
275
276#[cfg(test)]
277mod tests {
278    use super::*;
279    use hex;
280
281    #[test]
282    fn mainnet_20byte() {
283        // 20-byte public key hash on mainnet
284        verify(
285            Network::Main,
286            &hex::decode("F5BF48B397DAE70BE82B3CCA4793F8EB2B6CDAC9").unwrap(),
287            "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2",
288        );
289    }
290
291    #[test]
292    fn mainnet_24byte() {
293        // 24-byte public key hash on mainnet
294        verify(
295            Network::Main,
296            &hex::decode("7ADBF6C17084BC86C1706827B41A56F5CA32865925E946EA").unwrap(),
297            "bitcoincash:q9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2ws4mr9g0",
298        );
299    }
300
301    #[test]
302    fn mainnet_28byte() {
303        // 28-byte public key hash on mainnet
304        verify(
305            Network::Main,
306            &hex::decode("3A84F9CF51AAE98A3BB3A78BF16A6183790B18719126325BFC0C075B").unwrap(),
307            "bitcoincash:qgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcw59jxxuz",
308        );
309    }
310
311    #[test]
312    fn mainnet_32byte() {
313        // 32-byte public key hash on mainnet
314        verify(
315            Network::Main,
316            &hex::decode("3173EF6623C6B48FFD1A3DCC0CC6489B0A07BB47A37F47CFEF4FE69DE825C060")
317                .unwrap(),
318            "bitcoincash:qvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq5nlegake",
319        );
320    }
321
322    #[test]
323    fn mainnet_40byte() {
324        // 40-byte public key hash on mainnet
325        verify(
326            Network::Main,
327            &hex::decode("C07138323E00FA4FC122D3B85B9628EA810B3F381706385E289B0B25631197D194B5C238BEB136FB").unwrap(),
328            "bitcoincash:qnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv39gr3uvz",
329        );
330    }
331
332    #[test]
333    fn mainnet_48byte() {
334        // 48-byte public key hash on mainnet
335        verify(
336            Network::Main,
337            &hex::decode("E361CA9A7F99107C17A622E047E3745D3E19CF804ED63C5C40C6BA763696B98241223D8CE62AD48D863F4CB18C930E4C").unwrap(),
338            "bitcoincash:qh3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqex2w82sl",
339        );
340    }
341
342    #[test]
343    fn mainnet_56byte() {
344        // 56-byte public key hash on mainnet
345        verify(
346            Network::Main,
347            &hex::decode("D9FA7C4C6EF56DC4FF423BAAE6D495DBFF663D034A72D1DC7D52CBFE7D1E6858F9D523AC0A7A5C34077638E4DD1A701BD017842789982041").unwrap(),
348            "bitcoincash:qmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqscw8jd03f",
349        );
350    }
351    #[test]
352    fn mainnet_64byte() {
353        // 64-byte public key hash on mainnet
354        verify(
355            Network::Main,
356            &hex::decode("D0F346310D5513D9E01E299978624BA883E6BDA8F4C60883C10F28C2967E67EC77ECC7EEEAEAFC6DA89FAD72D11AC961E164678B868AEEEC5F2C1DA08884175B").unwrap(),
357            "bitcoincash:qlg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mtky5sv5w",
358        );
359    }
360
361    fn verify(network: Network, data: &Vec<u8>, cashaddr: &str) {
362        let hash_type = HashType::Key;
363        let output = CashAddrCodec::encode(data, hash_type, network).unwrap();
364        assert!(output == cashaddr.to_ascii_lowercase());
365        let decoded = CashAddrCodec::decode(cashaddr).unwrap();
366        assert!(decoded.into_body() == *data);
367    }
368}