#ifndef XRPL_PROTOCOL_B58_UTILS_H_INCLUDED #define XRPL_PROTOCOL_B58_UTILS_H_INCLUDED #include #include #include #include #include #include #include #include namespace ripple { template using Result = boost::outcome_v2::result; #ifndef _MSC_VER namespace b58_fast { namespace detail { // This optimizes to what hand written asm would do (single divide) [[nodiscard]] inline std::tuple div_rem(std::uint64_t a, std::uint64_t b) { return {a / b, a % b}; } // This optimizes to what hand written asm would do (single multiply) [[nodiscard]] inline std::tuple carrying_mul(std::uint64_t a, std::uint64_t b, std::uint64_t carry) { unsigned __int128 const x = a; unsigned __int128 const y = b; unsigned __int128 const c = x * y + carry; return {c & 0xffff'ffff'ffff'ffff, c >> 64}; } [[nodiscard]] inline std::tuple carrying_add(std::uint64_t a, std::uint64_t b) { unsigned __int128 const x = a; unsigned __int128 const y = b; unsigned __int128 const c = x + y; return {c & 0xffff'ffff'ffff'ffff, c >> 64}; } // Add a u64 to a "big uint" value inplace. // The bigint value is stored with the smallest coefficients first // (i.e a[0] is the 2^0 coefficient, a[n] is the 2^(64*n) coefficient) // panics if overflows (this is a specialized adder for b58 decoding. // it should never overflow). [[nodiscard]] inline TokenCodecErrc inplace_bigint_add(std::span a, std::uint64_t b) { if (a.size() <= 1) { return TokenCodecErrc::inputTooSmall; } std::uint64_t carry; std::tie(a[0], carry) = carrying_add(a[0], b); for (auto& v : a.subspan(1)) { if (!carry) { return TokenCodecErrc::success; } std::tie(v, carry) = carrying_add(v, 1); } if (carry) { return TokenCodecErrc::overflowAdd; } return TokenCodecErrc::success; } [[nodiscard]] inline TokenCodecErrc inplace_bigint_mul(std::span a, std::uint64_t b) { if (a.empty()) { return TokenCodecErrc::inputTooSmall; } auto const last_index = a.size() - 1; if (a[last_index] != 0) { return TokenCodecErrc::inputTooLarge; } std::uint64_t carry = 0; for (auto& coeff : a.subspan(0, last_index)) { std::tie(coeff, carry) = carrying_mul(coeff, b, carry); } a[last_index] = carry; return TokenCodecErrc::success; } // divide a "big uint" value inplace and return the mod // numerator is stored so smallest coefficients come first [[nodiscard]] inline std::uint64_t inplace_bigint_div_rem(std::span numerator, std::uint64_t divisor) { if (numerator.size() == 0) { // should never happen, but if it does then it seems natural to define // the a null set of numbers to be zero, so the remainder is also zero. // LCOV_EXCL_START UNREACHABLE( "ripple::b58_fast::detail::inplace_bigint_div_rem : empty " "numerator"); return 0; // LCOV_EXCL_STOP } auto to_u128 = [](std::uint64_t high, std::uint64_t low) -> unsigned __int128 { unsigned __int128 const high128 = high; unsigned __int128 const low128 = low; return ((high128 << 64) | low128); }; auto div_rem_64 = [](unsigned __int128 num, std::uint64_t denom) -> std::tuple { unsigned __int128 const denom128 = denom; unsigned __int128 const d = num / denom128; unsigned __int128 const r = num - (denom128 * d); XRPL_ASSERT( d >> 64 == 0, "ripple::b58_fast::detail::inplace_bigint_div_rem::div_rem_64 : " "valid division result"); XRPL_ASSERT( r >> 64 == 0, "ripple::b58_fast::detail::inplace_bigint_div_rem::div_rem_64 : " "valid remainder"); return {static_cast(d), static_cast(r)}; }; std::uint64_t prev_rem = 0; int const last_index = numerator.size() - 1; std::tie(numerator[last_index], prev_rem) = div_rem(numerator[last_index], divisor); for (int i = last_index - 1; i >= 0; --i) { unsigned __int128 const cur_num = to_u128(prev_rem, numerator[i]); std::tie(numerator[i], prev_rem) = div_rem_64(cur_num, divisor); } return prev_rem; } // convert from base 58^10 to base 58 // put largest coeffs first // the `_be` suffix stands for "big endian" [[nodiscard]] inline std::array b58_10_to_b58_be(std::uint64_t input) { [[maybe_unused]] static constexpr std::uint64_t B_58_10 = 430804206899405824; // 58^10; XRPL_ASSERT( input < B_58_10, "ripple::b58_fast::detail::b58_10_to_b58_be : valid input"); constexpr std::size_t resultSize = 10; std::array result{}; int i = 0; while (input > 0) { std::uint64_t rem; std::tie(input, rem) = div_rem(input, 58); result[resultSize - 1 - i] = rem; i += 1; } return result; } } // namespace detail } // namespace b58_fast #endif } // namespace ripple #endif // XRPL_PROTOCOL_B58_UTILS_H_INCLUDED