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
17const 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
32const 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 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 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#[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 let bytes = rand::random::<[u8; 32]>();
256
257 let encoded = bs58::encode(bytes).into_string();
259
260 assert_eq!(decode_pubkey(&encoded), bytes);
262 }
263}
264
265#[test]
266fn test_encode() {
267 for _ in 0..100_000 {
268 let bytes = rand::random::<[u8; 32]>();
270
271 let encoded = bs58::encode(bytes).into_string();
273
274 assert_eq!(encoded, encode_pubkey(&bytes).str());
276 }
277}