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