five8_const/
lib.rs

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        /* If c<'1', this will underflow and idx will be huge */
40        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        /* too long */
53        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    /* Make sure the encoded version has the same number of leading '1's
105    as the decoded version has leading 0s. The check doesn't read past
106    the end of encoded, because '\0' != '1', so it will return NULL. */
107    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        // take the first four bytes of each 8-byte block and reverse them:
138        // 3 2 1 0 11 10 9 8 19 18 17 16 27 26 25 24 etc
139        // or if on a BE machine, just take the last four bytes of each 8-byte block:
140        // 4 5 6 7 12 13 14 15 20 21 22 23 etc
141        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    /* Convert each term to big endian for the final output */
182    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
205/// Try decode into a 32-byte array.
206pub 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
213/// Decode into a 32-byte array. Panic on error.
214pub 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
224/// Try decode into a 64-byte array.
225pub 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
232/// Decode into a 64-byte array. Panic on error.
233pub 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}