#include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include #include #ifndef _MSC_VER #include #include #include #include #include #include #include namespace xrpl { namespace test { namespace { [[nodiscard]] inline auto randEngine() -> std::mt19937& { static std::mt19937 r = [] { std::random_device rd; return std::mt19937{rd()}; }(); return r; } constexpr int numTokenTypeIndexes = 9; [[nodiscard]] inline auto tokenTypeAndSize(int i) -> std::tuple { assert(i < numTokenTypeIndexes); switch (i) { using enum xrpl::TokenType; case 0: return {None, 20}; case 1: return {NodePublic, 32}; case 2: return {NodePublic, 33}; case 3: return {NodePrivate, 32}; case 4: return {AccountID, 20}; case 5: return {AccountPublic, 32}; case 6: return {AccountPublic, 33}; case 7: return {AccountSecret, 32}; case 8: return {FamilySeed, 16}; default: throw std::invalid_argument( "Invalid token selection passed to tokenTypeAndSize() " "in " __FILE__); } } [[nodiscard]] inline auto randomTokenTypeAndSize() -> std::tuple { using namespace xrpl; auto& rng = randEngine(); std::uniform_int_distribution<> d(0, 8); return tokenTypeAndSize(d(rng)); } // Return the token type and subspan of `d` to use as test data. [[nodiscard]] inline auto randomB256TestData(std::span d) -> std::tuple> { auto& rng = randEngine(); std::uniform_int_distribution dist(0, 255); auto [tokType, tokSize] = randomTokenTypeAndSize(); std::generate(d.begin(), d.begin() + tokSize, [&] { return dist(rng); }); return {tokType, d.subspan(0, tokSize)}; } inline void printAsChar(std::span a, std::span b) { auto asString = [](std::span s) { std::string r; r.resize(s.size()); std::copy(s.begin(), s.end(), r.begin()); return r; }; auto sa = asString(a); auto sb = asString(b); std::cerr << "\n\n" << sa << "\n" << sb << "\n"; } inline void printAsInt(std::span a, std::span b) { auto asString = [](std::span s) -> std::string { std::stringstream sstr; for (auto i : s) { sstr << std::setw(3) << int(i) << ','; } return sstr.str(); }; auto sa = asString(a); auto sb = asString(b); std::cerr << "\n\n" << sa << "\n" << sb << "\n"; } } // namespace namespace multiprecision_utils { boost::multiprecision::checked_uint512_t toBoostMP(std::span in) { boost::multiprecision::checked_uint512_t mbp = 0; for (auto i = in.rbegin(); i != in.rend(); ++i) { mbp <<= 64; mbp += *i; } return mbp; } std::vector randomBigInt(std::uint8_t minSize = 1, std::uint8_t maxSize = 5) { auto eng = randEngine(); std::uniform_int_distribution numCoeffDist(minSize, maxSize); std::uniform_int_distribution dist; auto const numCoeff = numCoeffDist(eng); std::vector coeffs; coeffs.reserve(numCoeff); for (int i = 0; i < numCoeff; ++i) { coeffs.push_back(dist(eng)); } return coeffs; } } // namespace multiprecision_utils class base58_test : public beast::unit_test::suite { void testMultiprecision() { testcase("b58_multiprecision"); using namespace boost::multiprecision; constexpr std::size_t iters = 100000; auto eng = randEngine(); std::uniform_int_distribution dist; std::uniform_int_distribution dist1(1); for (int i = 0; i < iters; ++i) { std::uint64_t const d = dist(eng); if (d == 0u) continue; auto bigInt = multiprecision_utils::randomBigInt(); auto const boostBigInt = multiprecision_utils::toBoostMP( std::span(bigInt.data(), bigInt.size())); auto const refDiv = boostBigInt / d; auto const refMod = boostBigInt % d; auto const mod = b58_fast::detail::inplace_bigint_div_rem( std::span(bigInt.data(), bigInt.size()), d); auto const foundDiv = multiprecision_utils::toBoostMP(bigInt); BEAST_EXPECT(refMod.convert_to() == mod); BEAST_EXPECT(foundDiv == refDiv); } for (int i = 0; i < iters; ++i) { std::uint64_t const d = dist(eng); auto bigInt = multiprecision_utils::randomBigInt(/*minSize*/ 2); if (bigInt[bigInt.size() - 1] == std::numeric_limits::max()) { bigInt[bigInt.size() - 1] -= 1; // Prevent overflow } auto const boostBigInt = multiprecision_utils::toBoostMP( std::span(bigInt.data(), bigInt.size())); auto const refAdd = boostBigInt + d; auto const result = b58_fast::detail::inplace_bigint_add( std::span(bigInt.data(), bigInt.size()), d); BEAST_EXPECT(result == TokenCodecErrc::success); auto const foundAdd = multiprecision_utils::toBoostMP(bigInt); BEAST_EXPECT(refAdd == foundAdd); } for (int i = 0; i < iters; ++i) { std::uint64_t const d = dist1(eng); // Force overflow std::vector bigInt(5, std::numeric_limits::max()); auto const boostBigInt = multiprecision_utils::toBoostMP( std::span(bigInt.data(), bigInt.size())); auto const refAdd = boostBigInt + d; auto const result = b58_fast::detail::inplace_bigint_add( std::span(bigInt.data(), bigInt.size()), d); BEAST_EXPECT(result == TokenCodecErrc::overflowAdd); auto const foundAdd = multiprecision_utils::toBoostMP(bigInt); BEAST_EXPECT(refAdd != foundAdd); } for (int i = 0; i < iters; ++i) { std::uint64_t const d = dist(eng); auto bigInt = multiprecision_utils::randomBigInt(/* minSize */ 2); // inplace mul requires the most significant coeff to be zero to // hold the result. bigInt[bigInt.size() - 1] = 0; auto const boostBigInt = multiprecision_utils::toBoostMP( std::span(bigInt.data(), bigInt.size())); auto const refMul = boostBigInt * d; auto const result = b58_fast::detail::inplace_bigint_mul( std::span(bigInt.data(), bigInt.size()), d); BEAST_EXPECT(result == TokenCodecErrc::success); auto const foundMul = multiprecision_utils::toBoostMP(bigInt); BEAST_EXPECT(refMul == foundMul); } for (int i = 0; i < iters; ++i) { std::uint64_t const d = dist1(eng); // Force overflow std::vector bigInt(5, std::numeric_limits::max()); auto const boostBigInt = multiprecision_utils::toBoostMP( std::span(bigInt.data(), bigInt.size())); auto const refMul = boostBigInt * d; auto const result = b58_fast::detail::inplace_bigint_mul( std::span(bigInt.data(), bigInt.size()), d); BEAST_EXPECT(result == TokenCodecErrc::inputTooLarge); auto const foundMul = multiprecision_utils::toBoostMP(bigInt); BEAST_EXPECT(refMul != foundMul); } } void testFastMatchesRef() { testcase("fast_matches_ref"); auto testRawEncode = [&](std::span const& b256Data) { std::array b58ResultBuf[2]; std::array, 2> b58Result; std::array b256ResultBuf[2]; std::array, 2> b256Result; for (int i = 0; i < 2; ++i) { std::span const outBuf{b58ResultBuf[i]}; if (i == 0) { auto const r = xrpl::b58_fast::detail::b256_to_b58_be(b256Data, outBuf); BEAST_EXPECT(r); b58Result[i] = r.value(); } else { std::array tmpBuf{}; std::string const s = xrpl::b58_ref::detail::encodeBase58( b256Data.data(), b256Data.size(), tmpBuf.data(), tmpBuf.size()); BEAST_EXPECT(s.size()); b58Result[i] = outBuf.subspan(0, s.size()); std::copy(s.begin(), s.end(), b58Result[i].begin()); } } if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size())) { if (!BEAST_EXPECT( memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0)) { printAsChar(b58Result[0], b58Result[1]); } } for (int i = 0; i < 2; ++i) { std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()}; if (i == 0) { std::string const in( b58Result[i].data(), b58Result[i].data() + b58Result[i].size()); auto const r = xrpl::b58_fast::detail::b58_to_b256_be(in, outBuf); BEAST_EXPECT(r); b256Result[i] = r.value(); } else { std::string const st(b58Result[i].begin(), b58Result[i].end()); std::string const s = xrpl::b58_ref::detail::decodeBase58(st); BEAST_EXPECT(s.size()); b256Result[i] = outBuf.subspan(0, s.size()); std::copy(s.begin(), s.end(), b256Result[i].begin()); } } if (BEAST_EXPECT(b256Result[0].size() == b256Result[1].size())) { if (!BEAST_EXPECT( memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) == 0)) { printAsInt(b256Result[0], b256Result[1]); } } }; auto testTokenEncode = [&](xrpl::TokenType const tokType, std::span const& b256Data) { std::array b58ResultBuf[2]; std::array, 2> b58Result; std::array b256ResultBuf[2]; std::array, 2> b256Result; for (int i = 0; i < 2; ++i) { std::span const outBuf{b58ResultBuf[i].data(), b58ResultBuf[i].size()}; if (i == 0) { auto const r = xrpl::b58_fast::encodeBase58Token(tokType, b256Data, outBuf); BEAST_EXPECT(r); b58Result[i] = r.value(); } else { std::string const s = xrpl::b58_ref::encodeBase58Token(tokType, b256Data.data(), b256Data.size()); BEAST_EXPECT(s.size()); b58Result[i] = outBuf.subspan(0, s.size()); std::copy(s.begin(), s.end(), b58Result[i].begin()); } } if (BEAST_EXPECT(b58Result[0].size() == b58Result[1].size())) { if (!BEAST_EXPECT( memcmp(b58Result[0].data(), b58Result[1].data(), b58Result[0].size()) == 0)) { printAsChar(b58Result[0], b58Result[1]); } } for (int i = 0; i < 2; ++i) { std::span const outBuf{b256ResultBuf[i].data(), b256ResultBuf[i].size()}; if (i == 0) { std::string const in( b58Result[i].data(), b58Result[i].data() + b58Result[i].size()); auto const r = xrpl::b58_fast::decodeBase58Token(tokType, in, outBuf); BEAST_EXPECT(r); b256Result[i] = r.value(); } else { std::string const st(b58Result[i].begin(), b58Result[i].end()); std::string const s = xrpl::b58_ref::decodeBase58Token(st, tokType); BEAST_EXPECT(s.size()); b256Result[i] = outBuf.subspan(0, s.size()); std::copy(s.begin(), s.end(), b256Result[i].begin()); } } if (BEAST_EXPECT(b256Result[0].size() == b256Result[1].size())) { if (!BEAST_EXPECT( memcmp(b256Result[0].data(), b256Result[1].data(), b256Result[0].size()) == 0)) { printAsInt(b256Result[0], b256Result[1]); } } }; auto testIt = [&](xrpl::TokenType const tokType, std::span const& b256Data) { testRawEncode(b256Data); testTokenEncode(tokType, b256Data); }; // test every token type with data where every byte is the same and the // bytes range from 0-255 for (int i = 0; i < numTokenTypeIndexes; ++i) { std::array b256DataBuf{}; auto const [tokType, tokSize] = tokenTypeAndSize(i); for (int d = 0; d <= 255; ++d) { memset(b256DataBuf.data(), d, tokSize); testIt(tokType, std::span(b256DataBuf.data(), tokSize)); } } // test with random data constexpr std::size_t iters = 100000; for (int i = 0; i < iters; ++i) { std::array b256DataBuf{}; auto const [tokType, b256Data] = randomB256TestData(b256DataBuf); testIt(tokType, b256Data); } } void run() override { testMultiprecision(); testFastMatchesRef(); } }; BEAST_DEFINE_TESTSUITE(base58, basics, xrpl); } // namespace test } // namespace xrpl #endif // _MSC_VER