1#![doc = include_str!("../README.md")]
2#![no_std]
3use five8_core::{
4 DecodeError, BASE58_ENCODED_32_MAX_LEN, BASE58_ENCODED_64_MAX_LEN, BASE58_INVALID_CHAR,
5 BASE58_INVERSE, BASE58_INVERSE_TABLE_OFFSET, BASE58_INVERSE_TABLE_SENTINEL, BINARY_SZ_32,
6 BINARY_SZ_64, DEC_TABLE_32, DEC_TABLE_64, INTERMEDIATE_SZ_32, INTERMEDIATE_SZ_64, N_32, N_64,
7 RAW58_SZ_32, RAW58_SZ_64,
8};
9
10const fn unwrap_const(err: DecodeError) -> ! {
11 match err {
12 DecodeError::InvalidChar(_) => panic!("Illegal base58 char"),
13 DecodeError::TooLong => panic!("Base58 string too long"),
14 DecodeError::TooShort => panic!("Base58 string too short"),
15 DecodeError::LargestTermTooHigh => panic!("Largest term greater than 2^32"),
16 DecodeError::OutputTooLong => panic!("Decoded output has too many bytes"),
17 }
18}
19
20const fn base58_decode_before_be_convert_const<
21 const ENCODED_LEN: usize,
22 const RAW58_SZ: usize,
23 const INTERMEDIATE_SZ: usize,
24 const BINARY_SZ: usize,
25>(
26 encoded: &[u8],
27 dec_table: &[[u32; BINARY_SZ]; INTERMEDIATE_SZ],
28) -> Result<[u64; BINARY_SZ], DecodeError> {
29 let mut char_cnt = 0usize;
30 let min_left = ENCODED_LEN + 1;
31 let min_right = encoded.len();
32 let num_iters = if min_left < min_right {
33 min_left
34 } else {
35 min_right
36 };
37 while char_cnt < num_iters {
38 let c = encoded[char_cnt];
39 let idx = (c as u64).wrapping_sub(BASE58_INVERSE_TABLE_OFFSET as u64);
41 let capped_idx = if idx < BASE58_INVERSE_TABLE_SENTINEL as u64 {
42 idx
43 } else {
44 BASE58_INVERSE_TABLE_SENTINEL as u64
45 };
46 char_cnt += 1;
47 if BASE58_INVERSE[capped_idx as usize] == BASE58_INVALID_CHAR {
48 return Err(DecodeError::InvalidChar(c));
49 }
50 }
51 if char_cnt == ENCODED_LEN + 1 {
52 return Err(DecodeError::TooLong);
54 }
55 let prepend_0 = RAW58_SZ - char_cnt;
56 let mut raw_base58 = [0u8; RAW58_SZ];
57 let mut j = 0;
58 while j < RAW58_SZ {
59 raw_base58[j] = if j < prepend_0 {
60 0
61 } else {
62 BASE58_INVERSE[(encoded[j - prepend_0] - BASE58_INVERSE_TABLE_OFFSET) as usize]
63 };
64 j += 1;
65 }
66 let mut intermediate = [0u64; INTERMEDIATE_SZ];
67 let mut i = 0;
68 while i < INTERMEDIATE_SZ {
69 intermediate[i] = raw_base58[5 * i] as u64 * 11316496
70 + raw_base58[5 * i + 1] as u64 * 195112
71 + raw_base58[5 * i + 2] as u64 * 3364
72 + raw_base58[5 * i + 3] as u64 * 58
73 + raw_base58[5 * i + 4] as u64;
74 i += 1;
75 }
76 let mut binary = [0u64; BINARY_SZ];
77 let mut k = 0;
78 while k < BINARY_SZ {
79 let mut acc = 0u64;
80 let mut l = 0;
81 while l < INTERMEDIATE_SZ {
82 acc += intermediate[l] * dec_table[l][k] as u64;
83 l += 1;
84 }
85 binary[k] = acc;
86 k += 1;
87 }
88 let mut m = BINARY_SZ - 1;
89 while m >= 1 {
90 binary[m - 1] += binary[m] >> 32;
91 binary[m] &= 0xFFFFFFFF;
92 m -= 1;
93 }
94 if binary[0] > 0xFFFFFFFF {
95 return Err(DecodeError::LargestTermTooHigh);
96 }
97 Ok(binary)
98}
99
100const fn base58_decode_after_be_convert_const<const N: usize>(
101 out: &[u8; N],
102 encoded: &[u8],
103) -> Result<(), DecodeError> {
104 let mut leading_zero_cnt = 0u64;
108 while leading_zero_cnt < N as u64 {
109 if leading_zero_cnt as usize >= encoded.len() {
110 return Err(DecodeError::TooShort);
111 }
112 let out_val = out[leading_zero_cnt as usize];
113 if out_val != 0 {
114 break;
115 }
116 if encoded[leading_zero_cnt as usize] != b'1' {
117 return Err(DecodeError::TooShort);
118 }
119 leading_zero_cnt += 1;
120 }
121 if leading_zero_cnt as usize > N {
122 return Err(DecodeError::OutputTooLong);
123 }
124 if (leading_zero_cnt as usize) < N && encoded[leading_zero_cnt as usize] == b'1' {
125 return Err(DecodeError::OutputTooLong);
126 }
127 Ok(())
128}
129
130const fn truncate_and_swap_u64s_const<const BINARY_SZ: usize, const N: usize>(
131 binary: &[u64; BINARY_SZ],
132) -> [u8; N] {
133 let mut out = [0u8; N];
134 let binary_u8 = binary.as_ptr() as *const u8;
135 let mut i = 0;
136 while i < BINARY_SZ {
137 let binary_u8_idx = i * 8;
142 let out_idx = i * 4;
143 #[cfg(target_endian = "little")]
144 unsafe {
145 out[out_idx] = *binary_u8.add(binary_u8_idx + 3);
146 out[out_idx + 1] = *binary_u8.add(binary_u8_idx + 2);
147 out[out_idx + 2] = *binary_u8.add(binary_u8_idx + 1);
148 out[out_idx + 3] = *binary_u8.add(binary_u8_idx);
149 }
150 #[cfg(target_endian = "big")]
151 unsafe {
152 out[out_idx] = *binary_u8.add(binary_u8_idx + 4);
153 out[out_idx + 1] = *binary_u8.add(binary_u8_idx + 5);
154 out[out_idx + 2] = *binary_u8.add(binary_u8_idx + 6);
155 out[out_idx + 3] = *binary_u8.add(binary_u8_idx + 7);
156 }
157 i += 1
158 }
159 out
160}
161
162const fn decode_const<
163 const N: usize,
164 const ENCODED_LEN: usize,
165 const RAW58_SZ: usize,
166 const INTERMEDIATE_SZ: usize,
167 const BINARY_SZ: usize,
168>(
169 encoded: &str,
170 dec_table: &[[u32; BINARY_SZ]; INTERMEDIATE_SZ],
171) -> Result<[u8; N], DecodeError> {
172 let as_ref = encoded.as_bytes();
173 let binary_res =
174 base58_decode_before_be_convert_const::<ENCODED_LEN, RAW58_SZ, INTERMEDIATE_SZ, BINARY_SZ>(
175 as_ref, dec_table,
176 );
177 let binary = match binary_res {
178 Ok(x) => x,
179 Err(e) => return Err(e),
180 };
181 let out: [u8; N] = truncate_and_swap_u64s_const(&binary);
183 match base58_decode_after_be_convert_const(&out, as_ref) {
184 Ok(()) => Ok(out),
185 Err(e) => Err(e),
186 }
187}
188
189const fn decode_const_unwrap<
190 const N: usize,
191 const ENCODED_LEN: usize,
192 const RAW58_SZ: usize,
193 const INTERMEDIATE_SZ: usize,
194 const BINARY_SZ: usize,
195>(
196 encoded: &str,
197 dec_table: &[[u32; BINARY_SZ]; INTERMEDIATE_SZ],
198) -> [u8; N] {
199 match decode_const::<N, ENCODED_LEN, RAW58_SZ, INTERMEDIATE_SZ, BINARY_SZ>(encoded, dec_table) {
200 Ok(x) => x,
201 Err(e) => unwrap_const(e),
202 }
203}
204
205pub const fn try_decode_32_const(encoded: &str) -> Result<[u8; N_32], DecodeError> {
207 decode_const::<N_32, BASE58_ENCODED_32_MAX_LEN, RAW58_SZ_32, INTERMEDIATE_SZ_32, BINARY_SZ_32>(
208 encoded,
209 &DEC_TABLE_32,
210 )
211}
212
213pub const fn decode_32_const(encoded: &str) -> [u8; N_32] {
215 decode_const_unwrap::<
216 N_32,
217 BASE58_ENCODED_32_MAX_LEN,
218 RAW58_SZ_32,
219 INTERMEDIATE_SZ_32,
220 BINARY_SZ_32,
221 >(encoded, &DEC_TABLE_32)
222}
223
224pub const fn try_decode_64_const(encoded: &str) -> Result<[u8; N_64], DecodeError> {
226 decode_const::<N_64, BASE58_ENCODED_64_MAX_LEN, RAW58_SZ_64, INTERMEDIATE_SZ_64, BINARY_SZ_64>(
227 encoded,
228 &DEC_TABLE_64,
229 )
230}
231
232pub const fn decode_64_const(encoded: &str) -> [u8; N_64] {
234 decode_const_unwrap::<
235 N_64,
236 BASE58_ENCODED_64_MAX_LEN,
237 RAW58_SZ_64,
238 INTERMEDIATE_SZ_64,
239 BINARY_SZ_64,
240 >(encoded, &DEC_TABLE_64)
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 const DECODE_32_CONST_EXAMPLE: [u8; N_32] =
248 decode_32_const("JEKNVnkbo3jma5nREBBJCDoXFVeKkD56V3xKrvRmWxFF");
249
250 #[test]
251 fn test_decode_const_ok() {
252 let mut expected = [255u8; 32];
253 expected[31] = 254;
254 assert_eq!(DECODE_32_CONST_EXAMPLE, expected);
255 }
256
257 #[test]
258 #[should_panic]
259 fn test_decode_const_small_buffer_panic() {
260 decode_32_const("a3gV");
261 }
262
263 #[test]
264 #[should_panic]
265 fn test_decode_const_invalid_char_panic() {
266 let sample = "123456789abcd!efghij";
267 decode_32_const(sample);
268 }
269}