rippled
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 <ripple/beast/unit_test.h>
23 #include <ripple/protocol/impl/b58_utils.h>
24 #include <ripple/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 
35 namespace ripple {
36 namespace test {
37 namespace {
38 
39 [[nodiscard]] inline auto
40 randEngine() -> std::mt19937&
41 {
42  static std::mt19937 r = [] {
44  return std::mt19937{rd()};
45  }();
46  return r;
47 }
48 
49 constexpr int numTokenTypeIndexes = 9;
50 
51 [[nodiscard]] inline auto
52 tokenTypeAndSize(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
85 randomTokenTypeAndSize() -> 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
95 randomB256TestData(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 
105 inline 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 
119 inline void
121 {
122  auto asString = [](std::span<std::uint8_t> s) -> std::string {
123  std::stringstream sstr;
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 
137 namespace multiprecision_utils {
138 
139 boost::multiprecision::checked_uint512_t
140 toBoostMP(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 
152 randomBigInt(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 
168 class 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();
180  for (int i = 0; i < iters; ++i)
181  {
182  std::uint64_t const d = dist(eng);
183  if (!d)
184  continue;
185  auto bigInt = multiprecision_utils::randomBigInt();
186  auto const boostBigInt = multiprecision_utils::toBoostMP(
187  std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
188 
189  auto const refDiv = boostBigInt / d;
190  auto const refMod = boostBigInt % d;
191 
192  auto const mod = b58_fast::detail::inplace_bigint_div_rem(
193  std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
194  auto const foundDiv = multiprecision_utils::toBoostMP(bigInt);
195  BEAST_EXPECT(refMod.convert_to<std::uint64_t>() == mod);
196  BEAST_EXPECT(foundDiv == refDiv);
197  }
198  for (int i = 0; i < iters; ++i)
199  {
200  std::uint64_t const d = dist(eng);
201  auto bigInt = multiprecision_utils::randomBigInt(/*minSize*/ 2);
202  if (bigInt[bigInt.size() - 1] ==
204  {
205  bigInt[bigInt.size() - 1] -= 1; // Prevent overflow
206  }
207  auto const boostBigInt = multiprecision_utils::toBoostMP(
208  std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
209 
210  auto const refAdd = boostBigInt + d;
211 
212  b58_fast::detail::inplace_bigint_add(
213  std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
214  auto const foundAdd = multiprecision_utils::toBoostMP(bigInt);
215  BEAST_EXPECT(refAdd == foundAdd);
216  }
217  for (int i = 0; i < iters; ++i)
218  {
219  std::uint64_t const d = dist(eng);
220  auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2);
221  // inplace mul requires the most significant coeff to be zero to
222  // hold the result.
223  bigInt[bigInt.size() - 1] = 0;
224  auto const boostBigInt = multiprecision_utils::toBoostMP(
225  std::span<std::uint64_t>(bigInt.data(), bigInt.size()));
226 
227  auto const refMul = boostBigInt * d;
228 
229  b58_fast::detail::inplace_bigint_mul(
230  std::span<uint64_t>(bigInt.data(), bigInt.size()), d);
231  auto const foundMul = multiprecision_utils::toBoostMP(bigInt);
232  BEAST_EXPECT(refMul == foundMul);
233  }
234  }
235 
236  void
237  testFastMatchesRef()
238  {
239  testcase("fast_matches_ref");
240  auto testRawEncode = [&](std::span<std::uint8_t> const& b256Data) {
241  std::array<std::uint8_t, 64> b58ResultBuf[2];
243 
244  std::array<std::uint8_t, 64> b256ResultBuf[2];
245  std::array<std::span<std::uint8_t>, 2> b256Result;
246  for (int i = 0; i < 2; ++i)
247  {
248  std::span const outBuf{b58ResultBuf[i]};
249  if (i == 0)
250  {
251  auto const r = ripple::b58_fast::detail::b256_to_b58_be(
252  b256Data, outBuf);
253  BEAST_EXPECT(r);
254  b58Result[i] = r.value();
255  }
256  else
257  {
260  b256Data.data(),
261  b256Data.size(),
262  tmpBuf.data(),
263  tmpBuf.size());
264  BEAST_EXPECT(s.size());
265  b58Result[i] = outBuf.subspan(0, s.size());
266  std::copy(s.begin(), s.end(), b58Result[i].begin());
267  }
268  }
269  if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size()))
270  {
271  if (!BEAST_EXPECT(
272  memcmp(
273  b58Result[0].data(),
274  b58Result[1].data(),
275  b58Result[0].size()) == 0))
276  {
277  printAsChar(b58Result[0], b58Result[1]);
278  }
279  }
280 
281  for (int i = 0; i < 2; ++i)
282  {
283  std::span const outBuf{
284  b256ResultBuf[i].data(), b256ResultBuf[i].size()};
285  if (i == 0)
286  {
287  std::string const in(
288  b58Result[i].data(),
289  b58Result[i].data() + b58Result[i].size());
290  auto const r =
291  ripple::b58_fast::detail::b58_to_b256_be(in, outBuf);
292  BEAST_EXPECT(r);
293  b256Result[i] = r.value();
294  }
295  else
296  {
297  std::string const st(
298  b58Result[i].begin(), b58Result[i].end());
299  std::string const s =
301  BEAST_EXPECT(s.size());
302  b256Result[i] = outBuf.subspan(0, s.size());
303  std::copy(s.begin(), s.end(), b256Result[i].begin());
304  }
305  }
306 
307  if (BEAST_EXPECT(b256Result[0].size() == b256Result[1].size()))
308  {
309  if (!BEAST_EXPECT(
310  memcmp(
311  b256Result[0].data(),
312  b256Result[1].data(),
313  b256Result[0].size()) == 0))
314  {
315  printAsInt(b256Result[0], b256Result[1]);
316  }
317  }
318  };
319 
320  auto testTokenEncode = [&](ripple::TokenType const tokType,
321  std::span<std::uint8_t> const& b256Data) {
322  std::array<std::uint8_t, 64> b58ResultBuf[2];
324 
325  std::array<std::uint8_t, 64> b256ResultBuf[2];
326  std::array<std::span<std::uint8_t>, 2> b256Result;
327  for (int i = 0; i < 2; ++i)
328  {
329  std::span const outBuf{
330  b58ResultBuf[i].data(), b58ResultBuf[i].size()};
331  if (i == 0)
332  {
333  auto const r = ripple::b58_fast::encodeBase58Token(
334  tokType, b256Data, outBuf);
335  BEAST_EXPECT(r);
336  b58Result[i] = r.value();
337  }
338  else
339  {
341  tokType, b256Data.data(), b256Data.size());
342  BEAST_EXPECT(s.size());
343  b58Result[i] = outBuf.subspan(0, s.size());
344  std::copy(s.begin(), s.end(), b58Result[i].begin());
345  }
346  }
347  if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size()))
348  {
349  if (!BEAST_EXPECT(
350  memcmp(
351  b58Result[0].data(),
352  b58Result[1].data(),
353  b58Result[0].size()) == 0))
354  {
355  printAsChar(b58Result[0], b58Result[1]);
356  }
357  }
358 
359  for (int i = 0; i < 2; ++i)
360  {
361  std::span const outBuf{
362  b256ResultBuf[i].data(), b256ResultBuf[i].size()};
363  if (i == 0)
364  {
365  std::string const in(
366  b58Result[i].data(),
367  b58Result[i].data() + b58Result[i].size());
368  auto const r = ripple::b58_fast::decodeBase58Token(
369  tokType, in, outBuf);
370  BEAST_EXPECT(r);
371  b256Result[i] = r.value();
372  }
373  else
374  {
375  std::string const st(
376  b58Result[i].begin(), b58Result[i].end());
377  std::string const s =
379  BEAST_EXPECT(s.size());
380  b256Result[i] = outBuf.subspan(0, s.size());
381  std::copy(s.begin(), s.end(), b256Result[i].begin());
382  }
383  }
384 
385  if (BEAST_EXPECT(b256Result[0].size() == b256Result[1].size()))
386  {
387  if (!BEAST_EXPECT(
388  memcmp(
389  b256Result[0].data(),
390  b256Result[1].data(),
391  b256Result[0].size()) == 0))
392  {
393  printAsInt(b256Result[0], b256Result[1]);
394  }
395  }
396  };
397 
398  auto testIt = [&](ripple::TokenType const tokType,
399  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 < numTokenTypeIndexes; ++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  constexpr std::size_t iters = 100000;
419  for (int i = 0; i < iters; ++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 
435 BEAST_DEFINE_TESTSUITE(base58, ripple_basics, ripple);
436 
437 } // namespace test
438 } // namespace ripple
439 #endif // _MSC_VER
sstream
std::string::resize
T resize(T... args)
span
std::string
STL class.
std::uniform_int_distribution
std::vector::reserve
T reserve(T... args)
std::vector
STL class.
std::array::size
T size(T... args)
random
std::stringstream
STL class.
ripple::b58_ref::decodeBase58Token
std::string decodeBase58Token(std::string const &s, TokenType type)
Definition: tokens.cpp:343
ripple::QualityDirection::in
@ in
std::generate
T generate(T... args)
std::tuple
std::cerr
ripple::b58_ref::encodeBase58Token
std::string encodeBase58Token(TokenType type, void const *token, std::size_t size)
Definition: tokens.cpp:319
std::mt19937
std::vector::push_back
T push_back(T... args)
std::random_device
ripple::TokenType::FamilySeed
@ FamilySeed
ripple::TokenType
TokenType
Definition: tokens.h:38
cstddef
array
std::copy
T copy(T... args)
std::invalid_argument
STL class.
std::uint8_t
ripple::b58_ref::detail::decodeBase58
std::string decodeBase58(std::string const &s)
Definition: tokens.cpp:270
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::b58_ref::detail::encodeBase58
std::string encodeBase58(void const *message, std::size_t size, void *temp, std::size_t temp_size)
Definition: tokens.cpp:219
ripple::TokenType::AccountSecret
@ AccountSecret
std::string::begin
T begin(T... args)
ripple::TokenType::AccountPublic
@ AccountPublic
ripple::TokenType::NodePublic
@ NodePublic
std::stringstream::str
T str(T... args)
std::size_t
std::string::end
T end(T... args)
std::setw
T setw(T... args)
std::memcmp
T memcmp(T... args)
std::numeric_limits
std::array::data
T data(T... args)
ripple::TokenType::NodePrivate
@ NodePrivate
ripple::AccountID
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition: AccountID.h:49
std::memset
T memset(T... args)
ripple::compression::Algorithm::None
@ None
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(DeliverMin, app, ripple)