rippled
Loading...
Searching...
No Matches
base58_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2022 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#ifndef _MSC_VER
21
22#include <xrpl/beast/unit_test.h>
23#include <xrpl/protocol/detail/b58_utils.h>
24#include <xrpl/protocol/tokens.h>
25
26#include <boost/multiprecision/cpp_int.hpp>
27#include <boost/random.hpp>
28
29#include <array>
30#include <cstddef>
31#include <random>
32#include <span>
33#include <sstream>
34
35namespace ripple {
36namespace test {
37namespace {
38
39[[nodiscard]] inline auto
40randEngine() -> std::mt19937&
41{
42 static std::mt19937 r = [] {
44 return std::mt19937{rd()};
45 }();
46 return r;
47}
48
49constexpr int numTokenTypeIndexes = 9;
50
51[[nodiscard]] inline auto
52tokenTypeAndSize(int i) -> std::tuple<ripple::TokenType, std::size_t>
53{
54 assert(i < numTokenTypeIndexes);
55
56 switch (i)
57 {
58 using enum ripple::TokenType;
59 case 0:
60 return {None, 20};
61 case 1:
62 return {NodePublic, 32};
63 case 2:
64 return {NodePublic, 33};
65 case 3:
66 return {NodePrivate, 32};
67 case 4:
68 return {AccountID, 20};
69 case 5:
70 return {AccountPublic, 32};
71 case 6:
72 return {AccountPublic, 33};
73 case 7:
74 return {AccountSecret, 32};
75 case 8:
76 return {FamilySeed, 16};
77 default:
79 "Invalid token selection passed to tokenTypeAndSize() "
80 "in " __FILE__);
81 }
82}
83
84[[nodiscard]] inline auto
85randomTokenTypeAndSize() -> std::tuple<ripple::TokenType, std::size_t>
86{
87 using namespace ripple;
88 auto& rng = randEngine();
90 return tokenTypeAndSize(d(rng));
91}
92
93// Return the token type and subspan of `d` to use as test data.
94[[nodiscard]] inline auto
95randomB256TestData(std::span<std::uint8_t> d)
97{
98 auto& rng = randEngine();
100 auto [tokType, tokSize] = randomTokenTypeAndSize();
101 std::generate(d.begin(), d.begin() + tokSize, [&] { return dist(rng); });
102 return {tokType, d.subspan(0, tokSize)};
103}
104
105inline void
107{
108 auto asString = [](std::span<std::uint8_t> s) {
109 std::string r;
110 r.resize(s.size());
111 std::copy(s.begin(), s.end(), r.begin());
112 return r;
113 };
114 auto sa = asString(a);
115 auto sb = asString(b);
116 std::cerr << "\n\n" << sa << "\n" << sb << "\n";
117}
118
119inline void
121{
122 auto asString = [](std::span<std::uint8_t> s) -> std::string {
124 for (auto i : s)
125 {
126 sstr << std::setw(3) << int(i) << ',';
127 }
128 return sstr.str();
129 };
130 auto sa = asString(a);
131 auto sb = asString(b);
132 std::cerr << "\n\n" << sa << "\n" << sb << "\n";
133}
134
135} // namespace
136
137namespace multiprecision_utils {
138
139boost::multiprecision::checked_uint512_t
140toBoostMP(std::span<std::uint64_t> in)
141{
142 boost::multiprecision::checked_uint512_t mbp = 0;
143 for (auto i = in.rbegin(); i != in.rend(); ++i)
144 {
145 mbp <<= 64;
146 mbp += *i;
147 }
148 return mbp;
149}
150
152randomBigInt(std::uint8_t minSize = 1, std::uint8_t maxSize = 5)
153{
154 auto eng = randEngine();
155 std::uniform_int_distribution<std::uint8_t> numCoeffDist(minSize, maxSize);
157 auto const numCoeff = numCoeffDist(eng);
159 coeffs.reserve(numCoeff);
160 for (int i = 0; i < numCoeff; ++i)
161 {
162 coeffs.push_back(dist(eng));
163 }
164 return coeffs;
165}
166} // namespace multiprecision_utils
167
168class base58_test : public beast::unit_test::suite
169{
170 void
171 testMultiprecision()
172 {
173 testcase("b58_multiprecision");
174
175 using namespace boost::multiprecision;
176
177 constexpr std::size_t iters = 100000;
178 auto eng = randEngine();
181 for (int i = 0; i < iters; ++i)
182 {
183 std::uint64_t const d = dist(eng);
184 if (!d)
185 continue;
186 auto bigInt = multiprecision_utils::randomBigInt();
187 auto const boostBigInt = multiprecision_utils::toBoostMP(
188 std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
189
190 auto const refDiv = boostBigInt / d;
191 auto const refMod = boostBigInt % d;
192
193 auto const mod = b58_fast::detail::inplace_bigint_div_rem(
194 std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
195 auto const foundDiv = multiprecision_utils::toBoostMP(bigInt);
196 BEAST_EXPECT(refMod.convert_to<std::uint64_t>() == mod);
197 BEAST_EXPECT(foundDiv == refDiv);
198 }
199 for (int i = 0; i < iters; ++i)
200 {
201 std::uint64_t const d = dist(eng);
202 auto bigInt = multiprecision_utils::randomBigInt(/*minSize*/ 2);
203 if (bigInt[bigInt.size() - 1] ==
205 {
206 bigInt[bigInt.size() - 1] -= 1; // Prevent overflow
207 }
208 auto const boostBigInt = multiprecision_utils::toBoostMP(
209 std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
210
211 auto const refAdd = boostBigInt + d;
212
213 auto const result = b58_fast::detail::inplace_bigint_add(
214 std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
215 BEAST_EXPECT(result == TokenCodecErrc::success);
216 auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
217 BEAST_EXPECT(refAdd == foundAdd);
218 }
219 for (int i = 0; i < iters; ++i)
220 {
221 std::uint64_t const d = dist1(eng);
222 // Force overflow
225
226 auto const boostBigInt = multiprecision_utils::toBoostMP(
227 std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
228
229 auto const refAdd = boostBigInt + d;
230
231 auto const result = b58_fast::detail::inplace_bigint_add(
232 std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
233 BEAST_EXPECT(result == TokenCodecErrc::overflowAdd);
234 auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
235 BEAST_EXPECT(refAdd != foundAdd);
236 }
237 for (int i = 0; i < iters; ++i)
238 {
239 std::uint64_t const d = dist(eng);
240 auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2);
241 // inplace mul requires the most significant coeff to be zero to
242 // hold the result.
243 bigInt[bigInt.size() - 1] = 0;
244 auto const boostBigInt = multiprecision_utils::toBoostMP(
245 std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
246
247 auto const refMul = boostBigInt * d;
248
249 auto const result = b58_fast::detail::inplace_bigint_mul(
250 std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
251 BEAST_EXPECT(result == TokenCodecErrc::success);
252 auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
253 BEAST_EXPECT(refMul == foundMul);
254 }
255 for (int i = 0; i < iters; ++i)
256 {
257 std::uint64_t const d = dist1(eng);
258 // Force overflow
261 auto const boostBigInt = multiprecision_utils::toBoostMP(
262 std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
263
264 auto const refMul = boostBigInt * d;
265
266 auto const result = b58_fast::detail::inplace_bigint_mul(
267 std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
268 BEAST_EXPECT(result == TokenCodecErrc::inputTooLarge);
269 auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
270 BEAST_EXPECT(refMul != foundMul);
271 }
272 }
273
274 void
275 testFastMatchesRef()
276 {
277 testcase("fast_matches_ref");
278 auto testRawEncode = [&](std::span<std::uint8_t> const& b256Data) {
279 std::array<std::uint8_t, 64> b58ResultBuf[2];
281
282 std::array<std::uint8_t, 64> b256ResultBuf[2];
284 for (int i = 0; i < 2; ++i)
285 {
286 std::span const outBuf{b58ResultBuf[i]};
287 if (i == 0)
288 {
289 auto const r = ripple::b58_fast::detail::b256_to_b58_be(
290 b256Data, outBuf);
291 BEAST_EXPECT(r);
292 b58Result[i] = r.value();
293 }
294 else
295 {
298 b256Data.data(),
299 b256Data.size(),
300 tmpBuf.data(),
301 tmpBuf.size());
302 BEAST_EXPECT(s.size());
303 b58Result[i] = outBuf.subspan(0, s.size());
304 std::copy(s.begin(), s.end(), b58Result[i].begin());
305 }
306 }
307 if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size()))
308 {
309 if (!BEAST_EXPECT(
310 memcmp(
311 b58Result[0].data(),
312 b58Result[1].data(),
313 b58Result[0].size()) == 0))
314 {
315 printAsChar(b58Result[0], b58Result[1]);
316 }
317 }
318
319 for (int i = 0; i < 2; ++i)
320 {
321 std::span const outBuf{
322 b256ResultBuf[i].data(), b256ResultBuf[i].size()};
323 if (i == 0)
324 {
325 std::string const in(
326 b58Result[i].data(),
327 b58Result[i].data() + b58Result[i].size());
328 auto const r =
329 ripple::b58_fast::detail::b58_to_b256_be(in, outBuf);
330 BEAST_EXPECT(r);
331 b256Result[i] = r.value();
332 }
333 else
334 {
335 std::string const st(
336 b58Result[i].begin(), b58Result[i].end());
337 std::string const s =
339 BEAST_EXPECT(s.size());
340 b256Result[i] = outBuf.subspan(0, s.size());
341 std::copy(s.begin(), s.end(), b256Result[i].begin());
342 }
343 }
344
345 if (BEAST_EXPECT(b256Result[0].size() == b256Result[1].size()))
346 {
347 if (!BEAST_EXPECT(
348 memcmp(
349 b256Result[0].data(),
350 b256Result[1].data(),
351 b256Result[0].size()) == 0))
352 {
353 printAsInt(b256Result[0], b256Result[1]);
354 }
355 }
356 };
357
358 auto testTokenEncode = [&](ripple::TokenType const tokType,
359 std::span<std::uint8_t> const& b256Data) {
360 std::array<std::uint8_t, 64> b58ResultBuf[2];
362
363 std::array<std::uint8_t, 64> b256ResultBuf[2];
365 for (int i = 0; i < 2; ++i)
366 {
367 std::span const outBuf{
368 b58ResultBuf[i].data(), b58ResultBuf[i].size()};
369 if (i == 0)
370 {
371 auto const r = ripple::b58_fast::encodeBase58Token(
372 tokType, b256Data, outBuf);
373 BEAST_EXPECT(r);
374 b58Result[i] = r.value();
375 }
376 else
377 {
379 tokType, b256Data.data(), b256Data.size());
380 BEAST_EXPECT(s.size());
381 b58Result[i] = outBuf.subspan(0, s.size());
382 std::copy(s.begin(), s.end(), b58Result[i].begin());
383 }
384 }
385 if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size()))
386 {
387 if (!BEAST_EXPECT(
388 memcmp(
389 b58Result[0].data(),
390 b58Result[1].data(),
391 b58Result[0].size()) == 0))
392 {
393 printAsChar(b58Result[0], b58Result[1]);
394 }
395 }
396
397 for (int i = 0; i < 2; ++i)
398 {
399 std::span const outBuf{
400 b256ResultBuf[i].data(), b256ResultBuf[i].size()};
401 if (i == 0)
402 {
403 std::string const in(
404 b58Result[i].data(),
405 b58Result[i].data() + b58Result[i].size());
406 auto const r = ripple::b58_fast::decodeBase58Token(
407 tokType, in, outBuf);
408 BEAST_EXPECT(r);
409 b256Result[i] = r.value();
410 }
411 else
412 {
413 std::string const st(
414 b58Result[i].begin(), b58Result[i].end());
415 std::string const s =
417 BEAST_EXPECT(s.size());
418 b256Result[i] = outBuf.subspan(0, s.size());
419 std::copy(s.begin(), s.end(), b256Result[i].begin());
420 }
421 }
422
423 if (BEAST_EXPECT(b256Result[0].size() == b256Result[1].size()))
424 {
425 if (!BEAST_EXPECT(
426 memcmp(
427 b256Result[0].data(),
428 b256Result[1].data(),
429 b256Result[0].size()) == 0))
430 {
431 printAsInt(b256Result[0], b256Result[1]);
432 }
433 }
434 };
435
436 auto testIt = [&](ripple::TokenType const tokType,
437 std::span<std::uint8_t> const& b256Data) {
438 testRawEncode(b256Data);
439 testTokenEncode(tokType, b256Data);
440 };
441
442 // test every token type with data where every byte is the same and the
443 // bytes range from 0-255
444 for (int i = 0; i < numTokenTypeIndexes; ++i)
445 {
447 auto const [tokType, tokSize] = tokenTypeAndSize(i);
448 for (int d = 0; d <= 255; ++d)
449 {
450 memset(b256DataBuf.data(), d, tokSize);
451 testIt(tokType, std::span(b256DataBuf.data(), tokSize));
452 }
453 }
454
455 // test with random data
456 constexpr std::size_t iters = 100000;
457 for (int i = 0; i < iters; ++i)
458 {
460 auto const [tokType, b256Data] = randomB256TestData(b256DataBuf);
461 testIt(tokType, b256Data);
462 }
463 }
464
465 void
466 run() override
467 {
468 testMultiprecision();
469 testFastMatchesRef();
470 }
471};
472
473BEAST_DEFINE_TESTSUITE(base58, basics, ripple);
474
475} // namespace test
476} // namespace ripple
477#endif // _MSC_VER
T begin(T... args)
A testsuite class.
Definition suite.h:55
T copy(T... args)
T data(T... args)
T end(T... args)
T generate(T... args)
T memcmp(T... args)
T memset(T... args)
std::string decodeBase58(std::string const &s)
Definition tokens.cpp:275
std::string encodeBase58(void const *message, std::size_t size, void *temp, std::size_t temp_size)
Definition tokens.cpp:223
std::string encodeBase58Token(TokenType type, void const *token, std::size_t size)
Definition tokens.cpp:325
std::string decodeBase58Token(std::string const &s, TokenType type)
Definition tokens.cpp:349
auto const data
General field definitions, or fields used in multiple transaction namespaces.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:48
TokenType
Definition tokens.h:38
int run(int argc, char **argv)
Definition Main.cpp:349
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)