1use once_cell::sync::Lazy;
2
3use crate::{
4 address::AddressVersion,
5 crypto::{sha256::DoubleSha256Hasher, Hashing},
6};
7
8const C32_ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
9
10static C32_BYTE_MAP: Lazy<[Option<u8>; 128]> = Lazy::new(|| {
11 let mut table: [Option<u8>; 128] = [None; 128];
12
13 let alphabet: [char; 32] = C32_ALPHABET
14 .iter()
15 .map(|byte| *byte as char)
16 .collect::<Vec<_>>()
17 .try_into()
18 .unwrap();
19
20 alphabet.iter().enumerate().for_each(|(i, x)| {
21 table[*x as usize] = Some(i as u8);
22 });
23
24 alphabet
25 .iter()
26 .map(|c| c.to_ascii_lowercase())
27 .enumerate()
28 .for_each(|(i, x)| {
29 table[x as usize] = Some(i as u8);
30 });
31
32 [('O', '0'), ('L', '1'), ('I', '1')]
33 .into_iter()
34 .for_each(|special_pair| {
35 let i = alphabet
36 .iter()
37 .enumerate()
38 .find(|(_, a)| **a == special_pair.1)
39 .unwrap()
40 .0;
41
42 table[special_pair.0 as usize] = Some(i as u8);
43 table[special_pair.0.to_ascii_lowercase() as usize] = Some(i as u8);
44 });
45
46 table
47});
48
49fn encode_overhead(len: usize) -> usize {
50 (len * 8 + 4) / 5
51}
52
53fn decode_underhead(len: usize) -> usize {
54 len / (8f64 / 5f64).ceil() as usize
55}
56
57#[derive(thiserror::Error, Clone, Debug, Eq, PartialEq)]
58pub enum C32Error {
60 #[error("Invalid C32 string")]
62 InvalidC32,
63 #[error("Invalid C32 character: {0}")]
65 InvalidChar(char),
66 #[error("Invalid C32 checksum - expected {0:?}, got {1:?}")]
68 InvalidChecksum([u8; 4], Vec<u8>),
69 #[error("Invalid C32 address: {0}")]
71 InvalidAddress(String),
72 #[error("Invalid C32 address version: {0}")]
74 InvalidVersion(u8),
75 #[error(transparent)]
77 FromUtf8Error(#[from] std::string::FromUtf8Error),
78 #[error(transparent)]
80 IntConversionError(#[from] std::num::TryFromIntError),
81}
82pub fn encode(data: impl AsRef<[u8]>) -> String {
84 let data = data.as_ref();
85
86 let mut encoded = Vec::with_capacity(encode_overhead(data.len()));
87 let mut buffer = 0u32;
88 let mut bits = 0;
89
90 for byte in data.iter().rev() {
91 buffer |= (*byte as u32) << bits;
92 bits += 8;
93
94 while bits >= 5 {
95 encoded.push(C32_ALPHABET[(buffer & 0x1F) as usize]);
96 buffer >>= 5;
97 bits -= 5;
98 }
99 }
100
101 if bits > 0 {
102 encoded.push(C32_ALPHABET[(buffer & 0x1F) as usize]);
103 }
104
105 while let Some(i) = encoded.pop() {
106 if i != C32_ALPHABET[0] {
107 encoded.push(i);
108 break;
109 }
110 }
111
112 for i in data {
113 if *i == 0 {
114 encoded.push(C32_ALPHABET[0]);
115 } else {
116 break;
117 }
118 }
119
120 encoded.reverse();
121
122 String::from_utf8(encoded).unwrap()
123}
124
125pub fn decode(input: impl AsRef<str>) -> Result<Vec<u8>, C32Error> {
127 let input = input.as_ref().as_bytes();
128
129 if !input.is_ascii() {
130 return Err(C32Error::InvalidC32);
131 }
132
133 let mut decoded = Vec::with_capacity(decode_underhead(input.len()));
134 let mut carry = 0u16;
135 let mut carry_bits = 0;
136
137 for byte in input.iter().rev() {
138 let Some(bits) = C32_BYTE_MAP.get(*byte as usize).unwrap() else {
139 return Err(C32Error::InvalidChar(*byte as char));
140 };
141
142 carry |= (u16::from(*bits)) << carry_bits;
143 carry_bits += 5;
144
145 if carry_bits >= 8 {
146 decoded.push((carry & 0xFF) as u8);
147 carry >>= 8;
148 carry_bits -= 8;
149 }
150 }
151
152 if carry_bits > 0 {
153 decoded.push(u8::try_from(carry)?);
154 }
155
156 while let Some(i) = decoded.pop() {
157 if i != 0 {
158 decoded.push(i);
159 break;
160 }
161 }
162
163 for byte in input.iter() {
164 if *byte == b'0' {
165 decoded.push(0);
166 } else {
167 break;
168 }
169 }
170
171 decoded.reverse();
172
173 Ok(decoded)
174}
175
176pub fn version_check_encode(
178 version: AddressVersion,
179 data: impl AsRef<[u8]>,
180) -> String {
181 let data = data.as_ref();
182
183 let mut buffer = vec![version as u8];
184 buffer.extend_from_slice(data);
185
186 let checksum = DoubleSha256Hasher::new(&buffer).checksum();
187 buffer.extend_from_slice(&checksum);
188
189 let mut encoded = encode(&buffer[1..]);
190 encoded.insert(0, C32_ALPHABET[version as usize] as char);
191
192 encoded
193}
194
195pub fn version_check_decode(
197 input: impl AsRef<str>,
198) -> Result<(AddressVersion, Vec<u8>), C32Error> {
199 let input = input.as_ref();
200
201 if !input.is_ascii() {
202 return Err(C32Error::InvalidC32);
203 }
204
205 let (encoded_version_bytes, encoded_data_bytes) = input.split_at(1);
206
207 let decoded_version_bytes = decode(encoded_version_bytes)?;
208 let decoded_version_byte = *decoded_version_bytes.first().unwrap();
209 let decoded_data_bytes = decode(encoded_data_bytes)?;
210
211 if decoded_data_bytes.len() < 4 {
212 return Err(C32Error::InvalidC32);
213 }
214
215 let (data_bytes, expected_checksum) =
216 decoded_data_bytes.split_at(decoded_data_bytes.len() - 4);
217
218 let mut buffer_to_check = vec![decoded_version_byte];
219 buffer_to_check.extend_from_slice(data_bytes);
220
221 let computed_checksum = DoubleSha256Hasher::new(buffer_to_check).checksum();
222
223 if computed_checksum != expected_checksum {
224 return Err(C32Error::InvalidChecksum(
225 computed_checksum,
226 expected_checksum.to_vec(),
227 ));
228 }
229
230 Ok((
231 decoded_version_byte
232 .try_into()
233 .map_err(|_| C32Error::InvalidVersion(decoded_version_byte))?,
234 data_bytes.to_vec(),
235 ))
236}
237
238pub fn encode_address(
240 version: AddressVersion,
241 data: impl AsRef<[u8]>,
242) -> String {
243 let encoded = version_check_encode(version, data);
244 let address = format!("S{}", encoded);
245
246 address
247}
248
249pub fn decode_address(
251 address: impl AsRef<str>,
252) -> Result<(AddressVersion, Vec<u8>), C32Error> {
253 let address = address.as_ref();
254
255 if !address.starts_with('S') || address.len() <= 5 {
256 return Err(C32Error::InvalidAddress(address.to_string()));
257 }
258
259 version_check_decode(&address[1..])
260}
261
262#[cfg(test)]
263mod tests {
264 use rand::{thread_rng, Rng, RngCore};
265 use strum::IntoEnumIterator;
266
267 use super::{decode_address, encode, encode_address};
268 use crate::address::AddressVersion;
269
270 #[test]
271 fn test_c32_encode() {
272 let input = vec![1, 2, 3, 4, 6, 1, 2, 6, 2, 3, 6, 9, 4, 0, 0];
273 let encoded = encode(input);
274
275 assert_eq!(encoded, "41061060410C0G30R4G8000");
276 }
277
278 #[test]
279 fn test_c32_decode() {
280 let input = vec![1, 2, 3, 4, 6, 1, 2, 6, 2, 3, 6, 9, 4, 0, 0];
281 let encoded = encode(&input);
282 let decoded = super::decode(encoded).unwrap();
283
284 assert_eq!(input, decoded);
285 }
286
287 #[test]
288 fn test_c32_check() {
289 let version = AddressVersion::MainnetSingleSig;
290 let data = hex::encode("8a4d3f2e55c87f964bae8b2963b3a824a2e9c9ab")
291 .into_bytes();
292
293 let encoded = encode_address(version, &data);
294 let (decoded_version, decoded) = decode_address(encoded).unwrap();
295
296 assert_eq!(decoded, data);
297 assert_eq!(decoded_version, version);
298 }
299
300 #[test]
301 fn test_c32_randomized_input() {
302 let mut rng = thread_rng();
303
304 for _ in 0..1000 {
305 let len = rng.gen_range(10..=10);
306 let mut input = vec![0u8; len];
307 rng.fill_bytes(&mut input);
308
309 let encoded_bytes = encode(&input);
310 let encoded = encoded_bytes.clone();
311 let decoded = super::decode(encoded.clone()).unwrap();
312
313 assert_eq!(decoded, input);
314 }
315 }
316
317 #[test]
318 fn test_c32_check_randomized_input() {
319 let mut rng = thread_rng();
320
321 for _ in 0..1000 {
322 let bytes = rng.gen::<[u8; 20]>();
323
324 for version in AddressVersion::iter() {
325 let encoded = encode_address(version, bytes);
326 let (decoded_version, decoded) =
327 decode_address(encoded).unwrap();
328
329 assert_eq!(decoded, bytes);
330 assert_eq!(decoded_version, version);
331 }
332 }
333 }
334}