cfx_addr/
lib.rs

1// Copyright 2021 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4//
5// Modification based on https://github.com/hlb8122/rust-bitcoincash-addr in MIT License.
6// A copy of the original license is included in LICENSE.rust-bitcoincash-addr.
7
8#![cfg_attr(not(feature = "std"), no_std)]
9
10mod consts;
11mod types;
12mod utils;
13
14pub use consts::*;
15pub use types::*;
16pub use utils::*;
17
18#[cfg(not(feature = "std"))]
19extern crate alloc;
20
21#[cfg(not(feature = "std"))]
22use alloc::{string::String, vec::Vec};
23
24use cfx_types::Address;
25
26pub fn cfx_addr_encode(
27    raw: &[u8], network: Network, encoding_options: EncodingOptions,
28) -> Result<String, EncodingError> {
29    // Calculate version byte
30    let length = raw.len();
31    let version_byte = match length {
32        20 => consts::SIZE_160,
33        // Conflux does not have other hash sizes. We don't use the sizes below
34        // but we kept these for unit tests.
35        24 => consts::SIZE_192,
36        28 => consts::SIZE_224,
37        32 => consts::SIZE_256,
38        40 => consts::SIZE_320,
39        48 => consts::SIZE_384,
40        56 => consts::SIZE_448,
41        64 => consts::SIZE_512,
42        _ => return Err(EncodingError::InvalidLength(length)),
43    };
44
45    // Get prefix
46    let prefix = network.to_prefix()?;
47
48    // Convert payload to 5 bit array
49    let mut payload = Vec::with_capacity(1 + raw.len());
50    payload.push(version_byte);
51    payload.extend(raw);
52    let payload_5_bits = convert_bits(&payload, 8, 5, true)
53        .expect("no error is possible for encoding");
54
55    // Construct payload string using CHARSET
56    let payload_str: String = payload_5_bits
57        .iter()
58        .map(|b| CHARSET[*b as usize] as char)
59        .collect();
60
61    // Create checksum
62    let expanded_prefix = expand_prefix(&prefix);
63    let checksum_input =
64        [&expanded_prefix[..], &payload_5_bits, &[0; 8][..]].concat();
65    let checksum = polymod(&checksum_input);
66
67    // Convert checksum to string
68    let checksum_str: String = (0..8)
69        .rev()
70        .map(|i| CHARSET[((checksum >> (i * 5)) & 31) as usize] as char)
71        .collect();
72
73    // Concatenate all parts
74    let cfx_base32_addr = match encoding_options {
75        EncodingOptions::Simple => {
76            [&prefix, ":", &payload_str, &checksum_str].concat()
77        }
78        EncodingOptions::QrCode => {
79            let addr_type_str = AddressType::from_address(&raw)?.to_str();
80            [
81                &prefix,
82                ":type.",
83                addr_type_str,
84                ":",
85                &payload_str,
86                &checksum_str,
87            ]
88            .concat()
89            .to_uppercase()
90        }
91    };
92    Ok(cfx_base32_addr)
93}
94
95pub fn cfx_addr_decode(
96    addr_str: &str,
97) -> Result<DecodedRawAddress, DecodingError> {
98    let has_lowercase = addr_str.chars().any(|c| c.is_lowercase());
99    let has_uppercase = addr_str.chars().any(|c| c.is_uppercase());
100    if has_lowercase && has_uppercase {
101        return Err(DecodingError::MixedCase);
102    }
103    let lowercase = addr_str.to_lowercase();
104
105    // Delimit and extract prefix
106    let parts: Vec<&str> = lowercase.split(':').collect();
107    if parts.len() < 2 {
108        return Err(DecodingError::NoPrefix);
109    }
110    let prefix = parts[0];
111    // Match network
112    let network = Network::from_prefix(prefix)?;
113
114    let mut address_type = None;
115    // Parse optional parts. We will ignore everything we can't understand.
116    for option_str in &parts[1..parts.len() - 1] {
117        let key_value: Vec<&str> = option_str.split('.').collect();
118        if key_value.len() != 2 {
119            return Err(DecodingError::InvalidOption(OptionError::ParseError(
120                (*option_str).into(),
121            )));
122        }
123        // Address type.
124        if key_value[0] == "type" {
125            address_type = Some(AddressType::parse(key_value[1])?);
126        }
127    }
128
129    // Do some sanity checks on the payload string
130    let payload_str = parts[parts.len() - 1];
131    if payload_str.len() == 0 {
132        return Err(DecodingError::InvalidLength(0));
133    }
134    let has_lowercase = payload_str.chars().any(|c| c.is_lowercase());
135    let has_uppercase = payload_str.chars().any(|c| c.is_uppercase());
136    if has_lowercase && has_uppercase {
137        return Err(DecodingError::MixedCase);
138    }
139
140    // Decode payload to 5 bit array
141    let payload_chars = payload_str.chars();
142    let payload_5_bits: Result<Vec<u8>, DecodingError> = payload_chars
143        .map(|c| {
144            let i = c as usize;
145            if let Some(Some(d)) = CHAR_INDEX.get(i) {
146                Ok(*d as u8)
147            } else {
148                Err(DecodingError::InvalidChar(c))
149            }
150        })
151        .collect();
152    let payload_5_bits = payload_5_bits?;
153
154    // Verify the checksum
155    let checksum =
156        polymod(&[&expand_prefix(prefix), &payload_5_bits[..]].concat());
157    if checksum != 0 {
158        // TODO: according to the spec it is possible to do correction based on
159        // the checksum,  we shouldn't do it automatically but we could
160        // include the corrected address in  the error.
161        return Err(DecodingError::ChecksumFailed(checksum));
162    }
163
164    // Convert from 5 bit array to byte array
165    let len_5_bit = payload_5_bits.len();
166    let payload =
167        convert_bits(&payload_5_bits[..(len_5_bit - 8)], 5, 8, false)?;
168
169    // Verify the version byte
170    let version = payload[0];
171
172    // Check length
173    let body = &payload[1..];
174    let body_len = body.len();
175    let version_size = version & consts::SIZE_MASK;
176    if (version_size == consts::SIZE_160 && body_len != 20)
177        // Conflux does not have other hash sizes. We don't use the sizes below
178        // but we kept these for unit tests.
179        || (version_size == consts::SIZE_192 && body_len != 24)
180        || (version_size == consts::SIZE_224 && body_len != 28)
181        || (version_size == consts::SIZE_256 && body_len != 32)
182        || (version_size == consts::SIZE_320 && body_len != 40)
183        || (version_size == consts::SIZE_384 && body_len != 48)
184        || (version_size == consts::SIZE_448 && body_len != 56)
185        || (version_size == consts::SIZE_512 && body_len != 64)
186    {
187        return Err(DecodingError::InvalidLength(body_len));
188    }
189    // Check reserved bits
190    if version & consts::RESERVED_BITS_MASK != 0 {
191        return Err(DecodingError::VersionNotRecognized(version));
192    }
193
194    let hex_address;
195    // Check address type for parsed H160 address.
196    if version_size == consts::SIZE_160 {
197        hex_address = Some(Address::from_slice(body));
198        match address_type {
199            Some(expected) => {
200                let got =
201                    AddressType::from_address(hex_address.as_ref().unwrap())
202                        .or(Err(()));
203                if got.as_ref() != Ok(&expected) {
204                    return Err(DecodingError::InvalidOption(
205                        OptionError::AddressTypeMismatch { expected, got },
206                    ));
207                }
208            }
209            None => {}
210        }
211    } else {
212        hex_address = None;
213    }
214
215    Ok(DecodedRawAddress {
216        input_base32_address: addr_str.into(),
217        parsed_address_bytes: body.to_vec(),
218        hex_address,
219        network,
220    })
221}