stacks_core/
c32.rs

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)]
58/// C32 error type
59pub enum C32Error {
60	/// Invalid C32 string.
61	#[error("Invalid C32 string")]
62	InvalidC32,
63	/// Invalid character.
64	#[error("Invalid C32 character: {0}")]
65	InvalidChar(char),
66	/// Invalid checksum.
67	#[error("Invalid C32 checksum - expected {0:?}, got {1:?}")]
68	InvalidChecksum([u8; 4], Vec<u8>),
69	/// Invalid C32 address.
70	#[error("Invalid C32 address: {0}")]
71	InvalidAddress(String),
72	/// Invalid C32 address.
73	#[error("Invalid C32 address version: {0}")]
74	InvalidVersion(u8),
75	/// Conversion error, from utf8.
76	#[error(transparent)]
77	FromUtf8Error(#[from] std::string::FromUtf8Error),
78	/// Integer conversion error.
79	#[error(transparent)]
80	IntConversionError(#[from] std::num::TryFromIntError),
81}
82/// C32 encode the given data
83pub 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
125/// C32 decode the given data
126pub 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
176/// C32 encode the given data with a version check
177pub 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
195/// C32 decode the given data with a version check
196pub 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
238/// C32 encode the given data as an address
239pub 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
249/// C32 decode the given address string
250pub 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}