| 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 | |