1 | #include <algorithm> |
2 | #include <array> |
3 | #include <cassert> |
4 | #include <cstdint> |
5 | #include <string> |
6 | #include <vector> |
7 | |
8 | namespace { |
9 | template<std::size_t N, std::size_t... Is> |
10 | constexpr std::array<char, N - 1> |
11 | to_array(const char (&a)[N], std::index_sequence<Is...>) |
12 | { |
13 | return {{a[Is]...}}; |
14 | } |
15 | |
16 | template<std::size_t N> |
17 | constexpr std::array<char, N - 1> |
18 | to_array(const char (&a)[N]) |
19 | { |
20 | return to_array(a, std::make_index_sequence<N - 1>()); |
21 | } |
22 | |
23 | static constexpr const std::array base64_alphabet = |
24 | to_array(a: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ); |
25 | static constexpr const std::array base64_urlsafe_alphabet = |
26 | to_array(a: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" ); |
27 | static constexpr const std::array base58_alphabet = |
28 | to_array(a: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" ); |
29 | |
30 | static_assert(base64_alphabet.size() == 64); |
31 | static_assert(base58_alphabet.size() == 58); |
32 | |
33 | template<std::size_t N> |
34 | constexpr std::array<uint8_t, 256> |
35 | invert_alphabet(std::array<char, N> alphabet) |
36 | { |
37 | std::array<uint8_t, 256> inverted{}; |
38 | |
39 | for (auto &e : inverted) |
40 | e = 0xff; |
41 | |
42 | for (std::size_t i = 0; i < N; i++) { |
43 | inverted[static_cast<uint8_t>(alphabet[i])] = static_cast<uint8_t>(i); |
44 | } |
45 | |
46 | return inverted; |
47 | } |
48 | |
49 | static constexpr const std::array base64_to_int = invert_alphabet(alphabet: base64_alphabet); |
50 | static constexpr const std::array base64_urlsafe_to_int = invert_alphabet(alphabet: base64_urlsafe_alphabet); |
51 | static constexpr const std::array base58_to_int = invert_alphabet(alphabet: base58_alphabet); |
52 | |
53 | static_assert(base64_to_int['A'] == 0); |
54 | static_assert(base64_to_int['B'] == 1); |
55 | static_assert(base64_to_int['C'] == 2); |
56 | static_assert(base64_to_int[0] == 0xff); |
57 | |
58 | // algorithm from https://github.com/miguelmota/cpp-base58 MIT Licensed |
59 | inline std::string |
60 | encode_base58(const std::array<char, 58> &alphabet, const std::string &input) |
61 | { |
62 | if (input.empty()) |
63 | return "" ; |
64 | |
65 | std::vector<uint8_t> digits(input.size() * 138 / 100 + 1); |
66 | std::size_t digitslen = 1; |
67 | for (uint8_t carry_ : input) { |
68 | uint32_t carry = static_cast<uint32_t>(carry_); |
69 | for (size_t j = 0; j < digitslen; j++) { |
70 | carry += (uint32_t)(digits[j]) * 256; |
71 | digits[j] = static_cast<uint8_t>(carry % 58); |
72 | carry /= 58; |
73 | } |
74 | while (carry > 0) { |
75 | assert(digitslen < digits.size()); |
76 | digits[digitslen++] = static_cast<uint8_t>(carry % 58); |
77 | carry /= 58; |
78 | } |
79 | } |
80 | std::string result(digits.size(), ' '); |
81 | |
82 | // leading zero bytes |
83 | std::size_t resultlen = 0; |
84 | for (; resultlen < input.length() && input[resultlen] == 0;) |
85 | result[resultlen++] = '1'; |
86 | |
87 | // reverse |
88 | for (size_t i = 0; i < digitslen; i++) |
89 | result[resultlen + i] = alphabet[digits[digitslen - 1 - i]]; |
90 | result.resize(n: digitslen + resultlen); |
91 | |
92 | return result; |
93 | } |
94 | |
95 | inline std::string |
96 | decode_base58(const std::array<uint8_t, 256> &reverse_alphabet, const std::string &input) |
97 | { |
98 | std::string result; |
99 | if (input.empty()) |
100 | return result; |
101 | |
102 | result.reserve(res: input.size() * 733 / 1000 + 1); |
103 | |
104 | // result.push_back(0); |
105 | |
106 | for (uint8_t b : input) { |
107 | if (b == ' ') |
108 | continue; |
109 | |
110 | if (b == 0xff) |
111 | return "" ; |
112 | |
113 | uint32_t carry = reverse_alphabet[b]; |
114 | for (char &j : result) { |
115 | carry += static_cast<uint8_t>(j) * 58; |
116 | j = static_cast<char>(static_cast<uint8_t>(carry % 0x100)); |
117 | carry /= 0x100; |
118 | } |
119 | while (carry > 0) { |
120 | result.push_back(c: static_cast<char>(static_cast<uint8_t>(carry % 0x100))); |
121 | carry /= 0x100; |
122 | } |
123 | } |
124 | |
125 | for (size_t i = 0; i < input.length() && input[i] == '1'; i++) |
126 | result.push_back(c: 0); |
127 | |
128 | std::reverse(first: result.begin(), last: result.end()); |
129 | return result; |
130 | } |
131 | |
132 | template<bool pad> |
133 | inline std::string |
134 | encode_base64(const std::array<char, 64> &alphabet, std::string input) |
135 | { |
136 | std::string encoded; |
137 | |
138 | size_t missing = 0; |
139 | |
140 | while ((input.size() + missing) % 3) |
141 | missing++; |
142 | |
143 | encoded.reserve(res: (input.size() * 4 + 2) / 3); |
144 | |
145 | for (size_t i = 0; i < input.size(); i += 3) { |
146 | uint32_t bytes = static_cast<uint8_t>(input[i]) << 16; |
147 | if (i + 1 < input.size()) |
148 | bytes += static_cast<uint8_t>(input[i + 1]) << 8; |
149 | if (i + 2 < input.size()) |
150 | bytes += static_cast<uint8_t>(input[i + 2]); |
151 | encoded.push_back(c: alphabet[(bytes >> 18) & 0b11'1111]); |
152 | encoded.push_back(c: alphabet[(bytes >> 12) & 0b11'1111]); |
153 | encoded.push_back(c: alphabet[(bytes >> 6) & 0b11'1111]); |
154 | encoded.push_back(c: alphabet[bytes & 0b11'1111]); |
155 | } |
156 | |
157 | if constexpr (pad) { |
158 | while (missing) { |
159 | encoded[encoded.size() - missing] = '='; |
160 | missing--; |
161 | } |
162 | } |
163 | |
164 | encoded.resize(n: encoded.size() - missing); |
165 | return encoded; |
166 | } |
167 | |
168 | inline std::string |
169 | decode_base64(const std::array<uint8_t, 256> &reverse_alphabet, const std::string &input) |
170 | { |
171 | std::string decoded; |
172 | decoded.reserve(res: (input.size() * 3 + 2) / 4); |
173 | |
174 | int bit_index = 0; |
175 | uint8_t d = 0; |
176 | for (uint8_t b : input) { |
177 | if (b == '=') |
178 | break; |
179 | |
180 | d = reverse_alphabet[b]; |
181 | |
182 | if (d > 64) |
183 | break; |
184 | |
185 | switch (bit_index++) { |
186 | case 0: |
187 | decoded.push_back(c: static_cast<char>(d << 2)); |
188 | break; |
189 | case 1: |
190 | decoded.back() = static_cast<char>(decoded.back() + (d >> 4)); |
191 | decoded.push_back(c: static_cast<char>(d << 4)); |
192 | break; |
193 | case 2: |
194 | decoded.back() = static_cast<char>(decoded.back() + (d >> 2)); |
195 | decoded.push_back(c: static_cast<char>(d << 6)); |
196 | break; |
197 | case 3: |
198 | decoded.back() = static_cast<char>(decoded.back() + d); |
199 | bit_index = 0; |
200 | } |
201 | } |
202 | |
203 | if ((bit_index == 2 && static_cast<uint8_t>(d << 4) == 0) || |
204 | (bit_index == 3 && static_cast<uint8_t>(d << 6) == 0)) |
205 | decoded.pop_back(); |
206 | |
207 | return decoded; |
208 | } |
209 | } |
210 | |
211 | namespace mtx { |
212 | namespace crypto { |
213 | std::string |
214 | base642bin(const std::string &b64) |
215 | { |
216 | return decode_base64(reverse_alphabet: base64_to_int, input: b64); |
217 | } |
218 | |
219 | std::string |
220 | bin2base64(const std::string &bin) |
221 | { |
222 | return encode_base64<true>(alphabet: base64_alphabet, input: bin); |
223 | } |
224 | |
225 | std::string |
226 | base642bin_unpadded(const std::string &b64) |
227 | { |
228 | return decode_base64(reverse_alphabet: base64_to_int, input: b64); |
229 | } |
230 | |
231 | std::string |
232 | bin2base64_unpadded(const std::string &bin) |
233 | { |
234 | return encode_base64<false>(alphabet: base64_alphabet, input: bin); |
235 | } |
236 | |
237 | std::string |
238 | base642bin_urlsafe_unpadded(const std::string &b64) |
239 | { |
240 | return decode_base64(reverse_alphabet: base64_urlsafe_to_int, input: b64); |
241 | } |
242 | |
243 | std::string |
244 | bin2base64_urlsafe_unpadded(const std::string &bin) |
245 | { |
246 | return encode_base64<false>(alphabet: base64_urlsafe_alphabet, input: bin); |
247 | } |
248 | |
249 | std::string |
250 | bin2base58(const std::string &bin) |
251 | { |
252 | return encode_base58(alphabet: base58_alphabet, input: bin); |
253 | } |
254 | |
255 | std::string |
256 | base582bin(const std::string &bin) |
257 | { |
258 | return decode_base58(reverse_alphabet: base58_to_int, input: bin); |
259 | } |
260 | } |
261 | } |
262 | |