xrpld
Loading...
Searching...
No Matches
base58.cpp
1#include <xrpl/protocol/detail/token_errors.h>
2
3#include <boost/multiprecision/cpp_int.hpp> // IWYU pragma: keep
4
5#include <gtest/gtest.h>
6
7#include <algorithm>
8#include <cassert>
9#include <cstdint>
10#include <cstring>
11#include <iomanip>
12#include <iostream>
13#include <limits>
14#include <ranges>
15#include <stdexcept>
16#include <string>
17#include <tuple>
18#include <vector>
19#ifndef _MSC_VER
20
21#include <xrpl/protocol/detail/b58_utils.h>
22#include <xrpl/protocol/tokens.h>
23
24#include <array>
25#include <cstddef>
26#include <random>
27#include <span>
28#include <sstream>
29
30namespace xrpl::test {
31namespace {
32
33[[nodiscard]] inline auto
34randEngine() -> std::mt19937&
35{
36 static std::mt19937 kR = [] {
37 std::random_device rd;
38 return std::mt19937{rd()};
39 }();
40 return kR;
41}
42
43constexpr int kNumTokenTypeIndexes = 9;
44
45[[nodiscard]] inline auto
46tokenTypeAndSize(int i) -> std::tuple<xrpl::TokenType, std::size_t>
47{
48 assert(i < kNumTokenTypeIndexes);
49
50 switch (i)
51 {
52 using enum xrpl::TokenType;
53 case 0:
54 return {None, 20};
55 case 1:
56 return {NodePublic, 32};
57 case 2:
58 return {NodePublic, 33};
59 case 3:
60 return {NodePrivate, 32};
61 case 4:
62 return {AccountID, 20};
63 case 5:
64 return {AccountPublic, 32};
65 case 6:
66 return {AccountPublic, 33};
67 case 7:
68 return {AccountSecret, 32};
69 case 8:
70 return {FamilySeed, 16};
71 default:
72 throw std::invalid_argument(
73 "Invalid token selection passed to tokenTypeAndSize() "
74 "in " __FILE__);
75 }
76}
77
78[[nodiscard]] inline auto
79randomTokenTypeAndSize() -> std::tuple<xrpl::TokenType, std::size_t>
80{
81 using namespace xrpl;
82 auto& rng = randEngine();
83 std::uniform_int_distribution<> d(0, 8);
84 return tokenTypeAndSize(d(rng));
85}
86
87// Return the token type and subspan of `d` to use as test data.
88[[nodiscard]] inline auto
89randomB256TestData(std::span<std::uint8_t> d)
90 -> std::tuple<xrpl::TokenType, std::span<std::uint8_t>>
91{
92 auto& rng = randEngine();
93 std::uniform_int_distribution<std::uint8_t> dist(0, 255);
94 auto [tokType, tokSize] = randomTokenTypeAndSize();
95 std::generate(d.begin(), d.begin() + tokSize, [&] { return dist(rng); });
96 return {tokType, d.subspan(0, tokSize)};
97}
98
99inline void
100printAsChar(std::span<std::uint8_t> a, std::span<std::uint8_t> b)
101{
102 auto asString = [](std::span<std::uint8_t> s) {
103 std::string r;
104 r.resize(s.size());
105 std::ranges::copy(s, r.begin());
106 return r;
107 };
108 auto sa = asString(a);
109 auto sb = asString(b);
110 std::cerr << "\n\n" << sa << "\n" << sb << "\n";
111}
112
113inline void
114printAsInt(std::span<std::uint8_t> a, std::span<std::uint8_t> b)
115{
116 auto asString = [](std::span<std::uint8_t> s) -> std::string {
117 std::stringstream sstr;
118 for (auto i : s)
119 {
120 sstr << std::setw(3) << int(i) << ',';
121 }
122 return sstr.str();
123 };
124 auto sa = asString(a);
125 auto sb = asString(b);
126 std::cerr << "\n\n" << sa << "\n" << sb << "\n";
127}
128
129} // namespace
130
131namespace multiprecision_utils {
132
133boost::multiprecision::checked_uint512_t
134toBoostMP(std::span<std::uint64_t> in)
135{
136 boost::multiprecision::checked_uint512_t mbp = 0;
137 for (auto const& i : std::views::reverse(in))
138 {
139 mbp <<= 64;
140 mbp += i;
141 }
142 return mbp;
143}
144
145std::vector<std::uint64_t>
146randomBigInt(std::uint8_t minSize = 1, std::uint8_t maxSize = 5)
147{
148 auto eng = randEngine();
149 std::uniform_int_distribution<std::uint8_t> numCoeffDist(minSize, maxSize);
150 std::uniform_int_distribution<std::uint64_t> dist;
151 auto const numCoeff = numCoeffDist(eng);
152 std::vector<std::uint64_t> coeffs;
153 coeffs.reserve(numCoeff);
154 for (auto i = 0uz; i < numCoeff; ++i)
155 {
156 coeffs.push_back(dist(eng));
157 }
158 return coeffs;
159}
160} // namespace multiprecision_utils
161
162TEST(Base58Test, multiprecision)
163{
164 using namespace boost::multiprecision;
165
166 constexpr std::size_t kIters = 100000;
167 auto eng = randEngine();
168 std::uniform_int_distribution<std::uint64_t> dist;
169 std::uniform_int_distribution<std::uint64_t> dist1(1);
170 for (auto i = 0uz; i < kIters; ++i)
171 {
172 std::uint64_t const d = dist(eng);
173 if (d == 0u)
174 continue;
175 auto bigInt = multiprecision_utils::randomBigInt();
176 auto const boostBigInt =
177 multiprecision_utils::toBoostMP(std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
178
179 auto const refDiv = boostBigInt / d;
180 auto const refMod = boostBigInt % d;
181
182 auto const mod = b58_fast::detail::inplaceBigintDivRem(
183 std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
184 auto const foundDiv = multiprecision_utils::toBoostMP(bigInt);
185 EXPECT_EQ(refMod.convert_to<std::uint64_t>(), mod);
186 EXPECT_EQ(foundDiv, refDiv);
187 }
188 for (auto i = 0uz; i < kIters; ++i)
189 {
190 std::uint64_t const d = dist(eng);
191 auto bigInt = multiprecision_utils::randomBigInt(/*minSize*/ 2);
192 if (bigInt[bigInt.size() - 1] == std::numeric_limits<std::uint64_t>::max())
193 {
194 bigInt[bigInt.size() - 1] -= 1; // Prevent overflow
195 }
196 auto const boostBigInt =
197 multiprecision_utils::toBoostMP(std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
198
199 auto const refAdd = boostBigInt + d;
200
201 auto const result = b58_fast::detail::inplaceBigintAdd(
202 std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
203 EXPECT_EQ(result, TokenCodecErrc::Success);
204 auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
205 EXPECT_EQ(refAdd, foundAdd);
206 }
207 for (auto i = 0uz; i < kIters; ++i)
208 {
209 std::uint64_t const d = dist1(eng);
210 // Force overflow
211 std::vector<std::uint64_t> bigInt(5, std::numeric_limits<std::uint64_t>::max());
212
213 auto const boostBigInt =
214 multiprecision_utils::toBoostMP(std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
215
216 auto const refAdd = boostBigInt + d;
217
218 auto const result = b58_fast::detail::inplaceBigintAdd(
219 std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
220 EXPECT_EQ(result, TokenCodecErrc::OverflowAdd);
221 auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
222 EXPECT_NE(refAdd, foundAdd);
223 }
224 for (auto i = 0uz; i < kIters; ++i)
225 {
226 std::uint64_t const d = dist(eng);
227 auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2);
228 // inplace mul requires the most significant coeff to be zero to
229 // hold the result.
230 bigInt[bigInt.size() - 1] = 0;
231 auto const boostBigInt =
232 multiprecision_utils::toBoostMP(std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
233
234 auto const refMul = boostBigInt * d;
235
236 auto const result = b58_fast::detail::inplaceBigintMul(
237 std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
238 EXPECT_EQ(result, TokenCodecErrc::Success);
239 auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
240 EXPECT_EQ(refMul, foundMul);
241 }
242 for (auto i = 0uz; i < kIters; ++i)
243 {
244 std::uint64_t const d = dist1(eng);
245 // Force overflow
246 std::vector<std::uint64_t> bigInt(5, std::numeric_limits<std::uint64_t>::max());
247 auto const boostBigInt =
248 multiprecision_utils::toBoostMP(std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
249
250 auto const refMul = boostBigInt * d;
251
252 auto const result = b58_fast::detail::inplaceBigintMul(
253 std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
254 EXPECT_EQ(result, TokenCodecErrc::InputTooLarge);
255 auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
256 EXPECT_NE(refMul, foundMul);
257 }
258}
259
260TEST(Base58Test, fast_matches_ref)
261{
262 auto testRawEncode = [&](std::span<std::uint8_t> const& b256Data) {
263 std::array<std::uint8_t, 64> b58ResultBuf[2];
264 std::array<std::span<std::uint8_t>, 2> b58Result;
265
266 std::array<std::uint8_t, 64> b256ResultBuf[2];
267 std::array<std::span<std::uint8_t>, 2> b256Result;
268 for (auto i = 0uz; i < 2; ++i)
269 {
270 std::span const outBuf{b58ResultBuf[i]};
271 if (i == 0)
272 {
273 auto const r = xrpl::b58_fast::detail::b256ToB58Be(b256Data, outBuf);
274 EXPECT_TRUE(r);
275 b58Result[i] = r.value();
276 }
277 else
278 {
279 std::array<std::uint8_t, 128> tmpBuf{};
280 std::string const s = xrpl::b58_ref::detail::encodeBase58(
281 b256Data.data(), b256Data.size(), tmpBuf.data(), tmpBuf.size());
282 EXPECT_TRUE(s.size());
283 b58Result[i] = outBuf.subspan(0, s.size());
284 std::ranges::copy(s, b58Result[i].begin());
285 }
286 }
287 auto const rawB58SameSize = b58Result[0].size() == b58Result[1].size();
288 EXPECT_TRUE(rawB58SameSize);
289 if (rawB58SameSize)
290 {
291 auto const rawB58SameData =
292 memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0;
293 EXPECT_TRUE(rawB58SameData);
294 if (!rawB58SameData)
295 {
296 printAsChar(b58Result[0], b58Result[1]);
297 }
298 }
299
300 for (auto i = 0uz; i < 2; ++i)
301 {
302 std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()};
303 if (i == 0)
304 {
305 std::string const in(
306 b58Result[i].data(), b58Result[i].data() + b58Result[i].size());
307 auto const r = xrpl::b58_fast::detail::b58ToB256Be(in, outBuf);
308 EXPECT_TRUE(r);
309 b256Result[i] = r.value();
310 }
311 else
312 {
313 std::string const st(b58Result[i].begin(), b58Result[i].end());
314 std::string const s = xrpl::b58_ref::detail::decodeBase58(st);
315 EXPECT_TRUE(s.size());
316 b256Result[i] = outBuf.subspan(0, s.size());
317 std::ranges::copy(s, b256Result[i].begin());
318 }
319 }
320
321 auto const rawB256SameSize = b256Result[0].size() == b256Result[1].size();
322 EXPECT_TRUE(rawB256SameSize);
323 if (rawB256SameSize)
324 {
325 auto const rawB256SameData =
326 memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) == 0;
327 EXPECT_TRUE(rawB256SameData);
328 if (!rawB256SameData)
329 {
330 printAsInt(b256Result[0], b256Result[1]);
331 }
332 }
333 };
334
335 auto testTokenEncode = [&](xrpl::TokenType const tokType,
336 std::span<std::uint8_t> const& b256Data) {
337 std::array<std::uint8_t, 64> b58ResultBuf[2];
338 std::array<std::span<std::uint8_t>, 2> b58Result;
339
340 std::array<std::uint8_t, 64> b256ResultBuf[2];
341 std::array<std::span<std::uint8_t>, 2> b256Result;
342 for (auto i = 0uz; i < 2; ++i)
343 {
344 std::span const outBuf{b58ResultBuf[i].data(), b58ResultBuf[i].size()};
345 if (i == 0)
346 {
347 auto const r = xrpl::b58_fast::encodeBase58Token(tokType, b256Data, outBuf);
348 EXPECT_TRUE(r);
349 b58Result[i] = r.value();
350 }
351 else
352 {
353 std::string const s =
354 xrpl::b58_ref::encodeBase58Token(tokType, b256Data.data(), b256Data.size());
355 EXPECT_TRUE(s.size());
356 b58Result[i] = outBuf.subspan(0, s.size());
357 std::ranges::copy(s, b58Result[i].begin());
358 }
359 }
360 auto const tokenB58SameSize = b58Result[0].size() == b58Result[1].size();
361 EXPECT_TRUE(tokenB58SameSize);
362 if (tokenB58SameSize)
363 {
364 auto const tokenB58SameData =
365 memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0;
366 EXPECT_TRUE(tokenB58SameData);
367 if (!tokenB58SameData)
368 {
369 printAsChar(b58Result[0], b58Result[1]);
370 }
371 }
372
373 for (auto i = 0uz; i < 2; ++i)
374 {
375 std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()};
376 if (i == 0)
377 {
378 std::string const in(
379 b58Result[i].data(), b58Result[i].data() + b58Result[i].size());
380 auto const r = xrpl::b58_fast::decodeBase58Token(tokType, in, outBuf);
381 EXPECT_TRUE(r);
382 b256Result[i] = r.value();
383 }
384 else
385 {
386 std::string const st(b58Result[i].begin(), b58Result[i].end());
387 std::string const s = xrpl::b58_ref::decodeBase58Token(st, tokType);
388 EXPECT_TRUE(s.size());
389 b256Result[i] = outBuf.subspan(0, s.size());
390 std::ranges::copy(s, b256Result[i].begin());
391 }
392 }
393
394 auto const tokenB256SameSize = b256Result[0].size() == b256Result[1].size();
395 EXPECT_TRUE(tokenB256SameSize);
396 if (tokenB256SameSize)
397 {
398 auto const tokenB256SameData =
399 memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) == 0;
400 EXPECT_TRUE(tokenB256SameData);
401 if (!tokenB256SameData)
402 {
403 printAsInt(b256Result[0], b256Result[1]);
404 }
405 }
406 };
407
408 auto testIt = [&](xrpl::TokenType const tokType, std::span<std::uint8_t> const& b256Data) {
409 testRawEncode(b256Data);
410 testTokenEncode(tokType, b256Data);
411 };
412
413 // test every token type with data where every byte is the same and the
414 // bytes range from 0-255
415 for (int i = 0; i < kNumTokenTypeIndexes; ++i)
416 {
417 std::array<std::uint8_t, 128> b256DataBuf{};
418 auto const [tokType, tokSize] = tokenTypeAndSize(i);
419 for (int d = 0; d <= 255; ++d)
420 {
421 memset(b256DataBuf.data(), d, tokSize);
422 testIt(tokType, std::span(b256DataBuf.data(), tokSize));
423 }
424 }
425
426 // test with random data
427 constexpr std::size_t kIters = 100000;
428 for (auto i = 0uz; i < kIters; ++i)
429 {
430 std::array<std::uint8_t, 128> b256DataBuf{};
431 auto const [tokType, b256Data] = randomB256TestData(b256DataBuf);
432 testIt(tokType, b256Data);
433 }
434}
435
436} // namespace xrpl::test
437
438#endif // _MSC_VER
T begin(T... args)
T copy(T... args)
T data(T... args)
T end(T... args)
T generate(T... args)
T max(T... args)
T memcmp(T... args)
T memset(T... args)
std::string decodeBase58(std::string const &s)
Definition tokens.cpp:252
std::string encodeBase58(void const *message, std::size_t size, void *temp, std::size_t tempSize)
Definition tokens.cpp:205
std::string encodeBase58Token(TokenType type, void const *token, std::size_t size)
Definition tokens.cpp:300
std::string decodeBase58Token(std::string const &s, TokenType type)
Definition tokens.cpp:323
TEST(UnitsTest, types)
Definition Units.cpp:16
TokenType
Definition tokens.h:19
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
T push_back(T... args)
T reserve(T... args)
T resize(T... args)
T setw(T... args)
T size(T... args)
T str(T... args)
T subspan(T... args)