const_crypto/
bs58.rs

1pub const fn try_from_str(input: &str) -> Result<[u8; 32], &'static str> {
2    match decode_pubkey_internal(input.as_bytes()) {
3        Ok(bytes) => Ok(bytes),
4        Err(e) => Err(e),
5    }
6}
7
8pub const fn decode_pubkey(input: &str) -> [u8; 32] {
9    match try_from_str(input) {
10        Ok(pubkey) => pubkey,
11        Err(_) => {
12            panic!("Invalid base58 Pubkey (Solana & Bitcoin Alphabet)")
13        }
14    }
15}
16
17/// This is const-ified from base58 crate
18const fn new(base: &[u8; 58]) -> ([u8; 58], [u8; 128]) {
19    let mut encode = [0x00; 58];
20    let mut decode = [0xFF; 128];
21
22    let mut i = 0;
23    while i < encode.len() {
24        encode[i] = base[i];
25        decode[base[i] as usize] = i as u8;
26        i += 1;
27    }
28
29    (encode, decode)
30}
31
32/// This is const-ified from base58 crate
33///
34/// TODO: still need to handle oob w/o panic but like cmon just provide
35/// a valid pubkey str
36const fn decode_pubkey_internal(input: &[u8]) -> Result<[u8; 32], &'static str> {
37    let mut output = [0; 32];
38
39    const ENCODE_DECODE: ([u8; 58], [u8; 128]) = new(&SOLANA_ALPHABET);
40    const ENCODE: [u8; 58] = ENCODE_DECODE.0;
41    const DECODE: [u8; 128] = ENCODE_DECODE.1;
42    const ZERO: u8 = ENCODE[0];
43
44    let mut index = 0;
45
46    let len = input.len();
47    let mut i = 0;
48    while i < len {
49        let c = &input[i];
50
51        if *c > 127 {
52            return Err("Input contains non-ASCII");
53        }
54
55        let mut val = DECODE[*c as usize] as usize;
56        if val == 0xFF {
57            return Err("Input contains invalid char");
58        }
59
60        let mut inner_idx = 0;
61        while inner_idx < index {
62            val += (output[inner_idx] as usize) * 58;
63            output[inner_idx] = (val & 0xFF) as u8;
64            val >>= 8;
65            inner_idx += 1;
66        }
67
68        while val > 0 {
69            output[index] = (val & 0xFF) as u8;
70            index += 1;
71            val >>= 8;
72        }
73
74        i += 1;
75    }
76
77    let mut idx = 0;
78    while idx < input.len() && input[idx] == ZERO {
79        output[index] = 0;
80        index += 1;
81        idx += 1;
82    }
83
84    let mut rev_output = [0; 32];
85    let mut idx = 0;
86    while idx < 32 {
87        rev_output[idx] = output[31 - idx];
88        idx += 1;
89    }
90    Ok(rev_output)
91}
92
93#[repr(C)]
94#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
95pub struct Base58Str {
96    bytes: [u8; 44],
97    len: usize,
98}
99
100impl Base58Str {
101    pub const fn str(&self) -> &str {
102        unsafe {
103            core::str::from_utf8_unchecked(core::slice::from_raw_parts(
104                self.bytes.as_ptr(),
105                self.len,
106            ))
107        }
108    }
109}
110
111pub const fn encode_pubkey(input: &[u8; 32]) -> Base58Str {
112    // Count leading zeros
113    let mut in_leading_0s = 0;
114    while in_leading_0s < 32 {
115        if input[in_leading_0s] != 0 {
116            break;
117        }
118        in_leading_0s += 1;
119    }
120
121    let mut binary: [u32; 8] = [0; 8];
122    let bytes_as_u32: &[u32] = unsafe {
123        // Cast a reference to bytes as a reference to u32
124        core::slice::from_raw_parts(
125            input.as_ptr() as *const u32,
126            input.len() / core::mem::size_of::<u32>(),
127        )
128    };
129
130    let mut i = 0;
131    while i < 8 {
132        binary[i] = unsafe { core::ptr::read_unaligned(&bytes_as_u32[i]).to_be() };
133        i += 1;
134    }
135
136    let mut intermediate: [u64; 9] = [0; 9];
137
138    let mut i = 0;
139    while i < 8 {
140        let mut j = 0;
141        while j < 8 {
142            intermediate[j + 1] += binary[i] as u64 * ENC_TABLE_32[i][j];
143            j += 1;
144        }
145        i += 1;
146    }
147
148    let mut i = 8;
149    while i != 0 {
150        intermediate[i - 1] += intermediate[i] / 656_356_768;
151        intermediate[i] %= 656_356_768;
152        i -= 1
153    }
154
155    let mut raw_base58: [u8; 45] = [0; 45];
156
157    let mut i = 0;
158    while i < 9 {
159        let v = intermediate[i] as u32;
160        raw_base58[5 * i + 4] = (v % 58) as u8;
161        raw_base58[5 * i + 3] = (v / 58 % 58) as u8;
162        raw_base58[5 * i + 2] = (v / 3364 % 58) as u8;
163        raw_base58[5 * i + 1] = (v / 195112 % 58) as u8;
164        raw_base58[5 * i] = (v / 11316496) as u8;
165        i += 1;
166    }
167
168    let mut raw_leading_0s = 0;
169    while raw_leading_0s < 45 {
170        if raw_base58[raw_leading_0s] != 0 {
171            break;
172        }
173        raw_leading_0s += 1;
174    }
175
176    let mut out = [0_u8; 44];
177
178    let skip = raw_leading_0s - in_leading_0s;
179    let end = 45 - skip;
180    let mut i = 0;
181    while i != end {
182        let idx = raw_base58[skip + i];
183        out[i] = SOLANA_ALPHABET[idx as usize];
184        i += 1;
185    }
186
187    Base58Str {
188        bytes: out,
189        len: end,
190    }
191}
192
193const SOLANA_ALPHABET: [u8; 58] = *b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
194
195pub const ENC_TABLE_32: [[u64; 9 - 1]; 8] = [
196    [
197        513_735,
198        77_223_048,
199        437_087_610,
200        300_156_666,
201        605_448_490,
202        214_625_350,
203        141_436_834,
204        379_377_856,
205    ],
206    [
207        0,
208        78_508,
209        646_269_101,
210        118_408_823,
211        91_512_303,
212        209_184_527,
213        413_102_373,
214        153_715_680,
215    ],
216    [
217        0,
218        0,
219        11_997,
220        486_083_817,
221        3_737_691,
222        294_005_210,
223        247_894_721,
224        289_024_608,
225    ],
226    [
227        0,
228        0,
229        0,
230        1_833,
231        324_463_681,
232        385_795_061,
233        551_597_588,
234        21_339_008,
235    ],
236    [0, 0, 0, 0, 280, 127_692_781, 389_432_875, 357_132_832],
237    [0, 0, 0, 0, 0, 42, 537_767_569, 410_450_016],
238    [0, 0, 0, 0, 0, 0, 6, 356_826_688],
239    [0, 0, 0, 0, 0, 0, 0, 1],
240];
241
242/// This case is tested explicitly because the initial implementation
243/// had an out-of-bounds access that panicked for this case (system_program ID)
244#[test]
245fn test_null_case_round_trip() {
246    let bytes = [0; 32];
247    let encoded = bs58::encode(bytes).into_string();
248    assert_eq!(decode_pubkey(&encoded), bytes);
249}
250
251#[test]
252fn test_many_random() {
253    for _ in 0..100_000 {
254        // Generate random bytes
255        let bytes = rand::random::<[u8; 32]>();
256
257        // Encode using bs58 crate
258        let encoded = bs58::encode(bytes).into_string();
259
260        // Check our decoded impl matches original bytes
261        assert_eq!(decode_pubkey(&encoded), bytes);
262    }
263}
264
265#[test]
266fn test_encode() {
267    for _ in 0..100_000 {
268        // Generate random bytes
269        let bytes = rand::random::<[u8; 32]>();
270
271        // Encode using bs58 crate
272        let encoded = bs58::encode(bytes).into_string();
273
274        // Check our encoded impl matches original bytes
275        assert_eq!(encoded, encode_pubkey(&bytes).str());
276    }
277}