Botan 3.11.0
Crypto and TLS for C&
base58.cpp
Go to the documentation of this file.
1/*
2* (C) 2018,2020,2026 Jack Lloyd
3*
4* Botan is released under the Simplified BSD License (see license.txt)
5*/
6
7#include <botan/base58.h>
8
9#include <botan/bigint.h>
10#include <botan/exceptn.h>
11#include <botan/hash.h>
12#include <botan/internal/ct_utils.h>
13#include <botan/internal/divide.h>
14#include <botan/internal/int_utils.h>
15#include <botan/internal/loadstor.h>
16#include <botan/internal/mul128.h>
17
18namespace Botan {
19
20namespace {
21
22uint32_t sha256_d_checksum(const uint8_t input[], size_t input_length) {
23 auto sha256 = HashFunction::create_or_throw("SHA-256");
24
25 std::vector<uint8_t> checksum(32);
26
27 sha256->update(input, input_length);
28 sha256->final(checksum);
29
30 sha256->update(checksum);
31 sha256->final(checksum);
32
33 return load_be<uint32_t>(checksum.data(), 0);
34}
35
36char lookup_base58_char(uint8_t x) {
37 // "123456789 ABCDEFGH JKLMN PQRSTUVWXYZ abcdefghijk mnopqrstuvwxyz"
38 BOTAN_DEBUG_ASSERT(x < 58);
39
40 // This works by computing offset(x) such that x + offset(x) is equal to the
41 // desired character
42
43 size_t offset = 49;
44
45 offset += CT::Mask<uint8_t>::is_gt(x, 8).if_set_return(7);
46 offset += CT::Mask<uint8_t>::is_gt(x, 16).if_set_return(1);
47 offset += CT::Mask<uint8_t>::is_gt(x, 21).if_set_return(1);
48 offset += CT::Mask<uint8_t>::is_gt(x, 32).if_set_return(6);
49 offset += CT::Mask<uint8_t>::is_gt(x, 43).if_set_return(1);
50 return static_cast<char>(x + offset);
51}
52
53consteval word base58_conversion_radix() {
54 if constexpr(sizeof(word) == 8) {
55 // 58^10 largest that fits into a 64 bit word
56 return 430804206899405824U;
57 } else {
58 // 58^5 largest that fits into a 32 bit word
59 return 656356768U;
60 }
61}
62
63consteval size_t base58_conversion_radix_digits() {
64 if constexpr(sizeof(word) == 8) {
65 return 10;
66 } else {
67 return 5;
68 }
69}
70
71constexpr std::pair<uint8_t, word> divmod_58(word x) {
72 BOTAN_DEBUG_ASSERT(x < base58_conversion_radix());
73
74 word q = 0;
75
76 // Division by constant 58
77 //
78 // Compilers will *usually* convert an expression like `x / 58` into
79 // exactly this kind of operation, but not necessarily always...
80 if constexpr(sizeof(word) == 4) {
81 const uint64_t magic = 2369637129; // ceil(2**36 / 29)
82 const uint64_t z = magic * x;
83 q = z >> 37;
84 } else {
85 const uint64_t magic = 5088756985850910791; // ceil(2**67 / 29)
86 uint64_t lo = 0; // unused
87 uint64_t hi = 0;
88 mul64x64_128(magic, x >> 1, &lo, &hi);
89 q = static_cast<word>(hi >> 3);
90 }
91
92 const uint8_t r = static_cast<uint8_t>(x - q * 58);
93 return std::make_pair(r, q);
94}
95
96std::string base58_encode(BigInt v, size_t leading_zeros) {
97 constexpr word radix = base58_conversion_radix();
98 constexpr size_t radix_digits = base58_conversion_radix_digits();
99
100 BigInt q;
101 std::vector<uint8_t> digits;
102
103 while(v.is_nonzero()) {
104 word r = 0;
105 ct_divide_word(v, radix, q, r);
106
107 for(size_t i = 0; i != radix_digits; ++i) {
108 const auto [r58, q58] = divmod_58(r);
109 digits.push_back(r58);
110 r = q58;
111 }
112 v.swap(q);
113 }
114
115 // remove leading zeros
116 while(!digits.empty() && digits.back() == 0) {
117 digits.pop_back();
118 }
119
120 std::string result;
121
122 for(const uint8_t d : digits) {
123 result.push_back(lookup_base58_char(d));
124 }
125
126 for(size_t i = 0; i != leading_zeros; ++i) {
127 result.push_back('1'); // 'zero' byte
128 }
129
130 return std::string(result.rbegin(), result.rend());
131}
132
133template <typename T, typename Z>
134size_t count_leading_zeros(const T input[], size_t input_length, Z zero) {
135 size_t leading_zeros = 0;
136
137 while(leading_zeros < input_length && input[leading_zeros] == zero) {
138 leading_zeros += 1;
139 }
140
141 return leading_zeros;
142}
143
144uint8_t base58_value_of(char input) {
145 /*
146 * Alphabet: "123456789 ABCDEFGH JKLMN PQRSTUVWXYZ abcdefghijk mnopqrstuvwxyz"
147 *
148 * Valid input ranges are:
149 *
150 * '1'-'9' (length 9)
151 * 'A'-'H' (length 8)
152 * 'J'-'N' (length 5)
153 * 'P'-'Z' (length 11)
154 * 'a'-'k' (length 11)
155 * 'm'-'z' (length 14)
156 */
157 constexpr uint64_t v_lo = make_uint64(0, '1', 'A', 'J', 'P', 'a', 'm', 0);
158 constexpr uint64_t v_range = make_uint64(0, 9, 8, 5, 11, 11, 14, 0);
159
160 const uint8_t x = static_cast<uint8_t>(input);
161 const uint64_t x8 = x * 0x0101010101010101; // replicate x to each byte
162
163 // is x8 in any of the ranges?
164 const uint64_t v_mask = swar_in_range<uint64_t>(x8, v_lo, v_range) ^ 0x8000000000000000;
165
166 /*
167 * Offsets mapping from the character code x to the base58 value of x in each range
168 *
169 * For example '2' (50) + 0xCF == 1
170 *
171 * Fallback byte 7 is set to 0xFF - x so that if used it results in 0xFF to indicate invalid.
172 */
173 constexpr uint64_t val_v_const = make_uint64(0, 0xCF, 0xC8, 0xC7, 0xC6, 0xC0, 0xBF, 0);
174 const uint64_t val_v = val_v_const ^ (static_cast<uint64_t>(0xFF - x) << 56);
175
176 return x + static_cast<uint8_t>(val_v >> (8 * index_of_first_set_byte(v_mask)));
177}
178
179} // namespace
180
181std::string base58_encode(const uint8_t input[], size_t input_length) {
182 const BigInt v(input, input_length);
183 return base58_encode(v, count_leading_zeros(input, input_length, 0));
184}
185
186std::string base58_check_encode(const uint8_t input[], size_t input_length) {
187 BigInt v(input, input_length);
188 v <<= 32;
189 v += sha256_d_checksum(input, input_length);
190 return base58_encode(v, count_leading_zeros(input, input_length, 0));
191}
192
193std::vector<uint8_t> base58_decode(const char input[], size_t input_length) {
194 const size_t leading_zeros = count_leading_zeros(input, input_length, '1');
195
196 std::vector<uint8_t> digits;
197
198 for(size_t i = leading_zeros; i != input_length; ++i) {
199 const char c = input[i];
200
201 if(c == ' ' || c == '\n') {
202 continue;
203 }
204
205 const uint8_t idx = base58_value_of(c);
206
207 if(idx == 0xFF) {
208 throw Decoding_Error("Invalid base58");
209 }
210
211 digits.push_back(idx);
212 }
213
214 BigInt v;
215
216 constexpr word radix1 = 58;
217 constexpr word radix2 = 58 * 58;
218 constexpr word radix3 = 58 * 58 * 58;
219 constexpr word radix4 = 58 * 58 * 58 * 58;
220
221 std::span<uint8_t> remaining{digits};
222
223 while(remaining.size() >= 4) {
224 const word accum = radix3 * remaining[0] + radix2 * remaining[1] + radix1 * remaining[2] + remaining[3];
225 v *= radix4;
226 v += accum;
227 remaining = remaining.subspan(4);
228 }
229
230 while(!remaining.empty()) {
231 v *= 58;
232 v += remaining[0];
233 remaining = remaining.subspan(1);
234 }
235
236 return v.serialize(v.bytes() + leading_zeros);
237}
238
239std::vector<uint8_t> base58_check_decode(const char input[], size_t input_length) {
240 std::vector<uint8_t> dec = base58_decode(input, input_length);
241
242 if(dec.size() < 4) {
243 throw Decoding_Error("Invalid base58 too short for checksum");
244 }
245
246 const uint32_t computed_checksum = sha256_d_checksum(dec.data(), dec.size() - 4);
247 const uint32_t checksum = load_be<uint32_t>(&dec[dec.size() - 4], 0);
248
249 if(checksum != computed_checksum) {
250 throw Decoding_Error("Invalid base58 checksum");
251 }
252
253 dec.resize(dec.size() - 4);
254
255 return dec;
256}
257
258} // namespace Botan
#define BOTAN_DEBUG_ASSERT(expr)
Definition assert.h:129
size_t bytes() const
Definition bigint.cpp:294
T serialize(size_t len) const
Definition bigint.h:727
static constexpr Mask< T > is_gt(T x, T y)
Definition ct_utils.h:458
static std::unique_ptr< HashFunction > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition hash.cpp:308
constexpr uint64_t make_uint64(uint8_t i0, uint8_t i1, uint8_t i2, uint8_t i3, uint8_t i4, uint8_t i5, uint8_t i6, uint8_t i7)
Definition loadstor.h:121
std::vector< uint8_t > base58_check_decode(const char input[], size_t input_length)
Definition base58.cpp:239
constexpr T swar_in_range(T v, T lower, T upper)
Definition int_utils.h:114
std::string base58_encode(const uint8_t input[], size_t input_length)
Definition base58.cpp:181
constexpr size_t index_of_first_set_byte(T v)
Definition int_utils.h:130
std::string base58_check_encode(const uint8_t input[], size_t input_length)
Definition base58.cpp:186
void ct_divide_word(const BigInt &x, word y, BigInt &q_out, word &r_out)
Definition divide.cpp:123
std::vector< uint8_t > base58_decode(const char input[], size_t input_length)
Definition base58.cpp:193
std::conditional_t< HasNative64BitRegisters, std::uint64_t, uint32_t > word
Definition types.h:119
constexpr void mul64x64_128(uint64_t a, uint64_t b, uint64_t *lo, uint64_t *hi)
Definition mul128.h:24
constexpr auto load_be(ParamTs &&... params)
Definition loadstor.h:504