From ab5087199b777925e5696531b913aefa5a0e677e Mon Sep 17 00:00:00 2001 From: seelabs Date: Wed, 19 Oct 2022 11:03:40 -0400 Subject: [PATCH] Fast base58 codec: This algorithm is about an order of magnitude faster than the existing algorithm (about 10x faster for encoding and about 15x faster for decoding - including the double hash for the checksum). The algorithms use gcc's int128 (fast MS version will have to wait, in the meantime MS falls back to the slow code). --- src/ripple/protocol/impl/b58_utils.h | 176 +++++++++++ src/ripple/protocol/impl/token_errors.h | 101 ++++++ src/ripple/protocol/impl/tokens.cpp | 399 +++++++++++++++++++++++- src/ripple/protocol/tokens.h | 56 +++- 4 files changed, 711 insertions(+), 21 deletions(-) create mode 100644 src/ripple/protocol/impl/b58_utils.h create mode 100644 src/ripple/protocol/impl/token_errors.h diff --git a/src/ripple/protocol/impl/b58_utils.h b/src/ripple/protocol/impl/b58_utils.h new file mode 100644 index 00000000000..bbbf09751f9 --- /dev/null +++ b/src/ripple/protocol/impl/b58_utils.h @@ -0,0 +1,176 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_B58_UTILS_H_INCLUDED +#define RIPPLE_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 x = a; + unsigned __int128 y = b; + unsigned __int128 c = x * y + carry; + return {c & 0xffffffffffffffff, c >> 64}; +} + +[[nodiscard]] inline std::tuple +carrying_add(std::uint64_t a, std::uint64_t b) +{ + unsigned __int128 x = a; + unsigned __int128 y = b; + unsigned __int128 c = x + y; + return {c & 0xffffffffffffffff, c >> 64}; +} + +// Add a u64 to a "big uint" value inplace. +// The bitint value is stored with the smallest coefficients first +// (i.e a[0] is the 2^0 coefficient, a[n] is the 2^n coefficient) +// panics if overflows (this is a specialized adder for b58 decoding. +// it should never overflow). +inline void +inplace_bigint_add(std::span a, std::uint64_t b) +{ + if (a.size() <= 1) + { + ripple::LogicError("Input span too small for inplace_bigint_add"); + } + + std::uint64_t carry; + std::tie(a[0], carry) = carrying_add(a[0], b); + + for (auto& v : a.subspan(1)) + { + if (!carry) + { + return; + } + std::tie(v, carry) = carrying_add(v, 1); + } + if (carry) + { + LogicError("Overflow in inplace_bigint_add"); + } +} + +inline void +inplace_bigint_mul(std::span a, std::uint64_t b) +{ + if (a.empty()) + { + LogicError("Empty span passed to inplace_bigint_mul"); + } + + auto const last_index = a.size() - 1; + if (a[last_index] != 0) + { + LogicError("Non-zero element in inplace_bigint_mul last index"); + } + + 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; +} +// 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) +{ + auto to_u128 = [](std::uint64_t high, + std::uint64_t low) -> unsigned __int128 { + unsigned __int128 result = high; + unsigned __int128 low128 = low; + return ((result << 64) | low128); + }; + auto div_rem_64 = + [](unsigned __int128 num, + std::uint64_t denom) -> std::tuple { + unsigned __int128 denom128 = denom; + unsigned __int128 d = num / denom128; + unsigned __int128 r = num % denom128; + return {static_cast(d), static_cast(r)}; + }; + + std::uint64_t prev_rem; + std::size_t 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 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 +[[nodiscard]] inline std::array +b58_10_to_b58_be(std::uint64_t 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; + } + assert(i <= 10); + + return result; +} +} // namespace detail +} // namespace b58_fast +#endif + +} // namespace ripple +#endif // B58_UTILS_H_ diff --git a/src/ripple/protocol/impl/token_errors.h b/src/ripple/protocol/impl/token_errors.h new file mode 100644 index 00000000000..e41e3b723e2 --- /dev/null +++ b/src/ripple/protocol/impl/token_errors.h @@ -0,0 +1,101 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2022 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PROTOCOL_TOKEN_ERRORS_H_INCLUDED +#define RIPPLE_PROTOCOL_TOKEN_ERRORS_H_INCLUDED + +#include + +namespace ripple { +enum class TokenCodecErrc { + Success = 0, + InputTooLarge, + InputTooSmall, + BadB58Character, + OutputTooSmall, + MismatchedTokenType, + MismatchedChecksum, + InvalidEncodingChar, + Unknown, +}; +} + +namespace std { +template <> +struct is_error_code_enum : true_type +{ +}; +} // namespace std + +namespace ripple { +namespace detail { +class TokenCodecErrcCategory : public std::error_category +{ +public: + // Return a short descriptive name for the category + virtual const char* + name() const noexcept override final + { + return "TokenCodecError"; + } + // Return what each enum means in text + virtual std::string + message(int c) const override final + { + switch (static_cast(c)) + { + case TokenCodecErrc::Success: + return "conversion successful"; + case TokenCodecErrc::InputTooLarge: + return "input too large"; + case TokenCodecErrc::InputTooSmall: + return "input too small"; + case TokenCodecErrc::BadB58Character: + return "bad base 58 character"; + case TokenCodecErrc::OutputTooSmall: + return "output too small"; + case TokenCodecErrc::MismatchedTokenType: + return "mismatched token type"; + case TokenCodecErrc::MismatchedChecksum: + return "mismatched checksum"; + case TokenCodecErrc::InvalidEncodingChar: + return "invalid encoding char"; + case TokenCodecErrc::Unknown: + return "unknown"; + default: + return "unknown"; + } + } +}; +} // namespace detail + +inline const ripple::detail::TokenCodecErrcCategory& +TokenCodecErrcCategory() +{ + static ripple::detail::TokenCodecErrcCategory c; + return c; +} + +inline std::error_code +make_error_code(ripple::TokenCodecErrc e) +{ + return {static_cast(e), TokenCodecErrcCategory()}; +} +} // namespace ripple +#endif // TOKEN_ERRORS_H_ diff --git a/src/ripple/protocol/impl/tokens.cpp b/src/ripple/protocol/impl/tokens.cpp index 816d49e40df..0ea0a68e45b 100644 --- a/src/ripple/protocol/impl/tokens.cpp +++ b/src/ripple/protocol/impl/tokens.cpp @@ -17,10 +17,15 @@ */ //============================================================================== -#include -#include #include + +#include +#include + #include +#include +#include + #include #include #include @@ -88,13 +93,6 @@ checksum(void* out, void const* message, std::size_t size) namespace detail { -/* The base58 encoding & decoding routines in this namespace are taken from - * Bitcoin but have been modified from the original. - * - * Copyright (c) 2014 The Bitcoin Core developers - * Distributed under the MIT software license, see the accompanying - * file COPYING or http://www.opensource.org/licenses/mit-license.php. - */ static std::string encodeBase58( void const* message, @@ -195,6 +193,27 @@ decodeBase58(std::string const& s) } // namespace detail +[[nodiscard]] std::string +encodeBase58Token(TokenType type, void const* token, std::size_t size) +{ +#ifndef _MSC_VER + return b58_fast::encodeBase58Token(type, token, size); +#else + return b58_ref::encodeBase58Token(type, token, size); +#endif +} + +[[nodiscard]] std::string +decodeBase58Token(std::string const& s, TokenType type) +{ +#ifndef _MSC_VER + return b58_fast::decodeBase58Token(s, type); +#else + return b58_ref::decodeBase58Token_ms(s, type); +#endif +} + +namespace b58_ref { std::string encodeBase58Token(TokenType type, void const* token, std::size_t size) { @@ -210,7 +229,7 @@ encodeBase58Token(TokenType type, void const* token, std::size_t size) // Lay the data out as // - buf[0] = safe_cast>(type); + buf[0] = static_cast>(type); if (size) std::memcpy(buf.data() + 1, token, size); checksum(buf.data() + 1 + size, buf.data(), 1 + size); @@ -229,7 +248,7 @@ decodeBase58Token(std::string const& s, TokenType type) return {}; // The type must match. - if (type != safe_cast(static_cast(ret[0]))) + if (type != static_cast(static_cast(ret[0]))) return {}; // And the checksum must as well. @@ -241,5 +260,363 @@ decodeBase58Token(std::string const& s, TokenType type) // Skip the leading type byte and the trailing checksum. return ret.substr(1, ret.size() - 1 - guard.size()); } +} // namespace b58_ref + +#ifndef _MSC_VER +namespace b58_fast { +namespace detail { +B58Result> +b256_to_b58(std::span input, std::span out) +{ + // Allocate enough base 58^10 coeff for encoding 38 bytes + // (33 bytes for nodepublic + 1 byte token + 4 bytes checksum) + // log(2^(38*8),58^10)) ~= 5.18. So 6 coeff are enough + if (input.size() > 38) + { + return Unexpected(TokenCodecErrc::InputTooLarge); + }; + + auto count_leading_zeros = [&](auto const& col) -> std::size_t { + std::size_t count = 0; + for (auto const& c : col) + { + if (c != 0) + { + return count; + } + count += 1; + } + return count; + }; + + auto const input_zeros = count_leading_zeros(input); + input = input.subspan(input_zeros); + + std::array base_58_10_coeff{}; + std::array base_2_64_coeff_buf{}; + std::span const base_2_64_coeff = + [&]() -> std::span { + // convert from input from big endian to native u64, lowest coeff first + std::size_t num_coeff = 0; + for (int i = 0; i < 5; ++i) + { + if (i * 8 > input.size()) + { + break; + } + auto const src_i_end = input.size() - i * 8; + if (src_i_end >= 8) + { + std::memcpy( + &base_2_64_coeff_buf[num_coeff], &input[src_i_end - 8], 8); + boost::endian::big_to_native_inplace( + base_2_64_coeff_buf[num_coeff]); + } + else + { + std::uint64_t be = 0; + for (int bi = 0; bi < src_i_end; ++bi) + { + be <<= 8; + be |= input[bi]; + } + base_2_64_coeff_buf[num_coeff] = be; + }; + num_coeff += 1; + } + return std::span(base_2_64_coeff_buf.data(), num_coeff); + }(); + + constexpr std::uint64_t B_58_10 = 430804206899405824; // 58^10; + std::size_t num_58_10_coeffs = 0; + std::size_t cur_2_64_end = base_2_64_coeff.size(); + // compute the base 58 10 coeffs + while (cur_2_64_end > 0) + { + base_58_10_coeff[num_58_10_coeffs] = + ripple::b58_fast::detail::inplace_bigint_div_rem( + base_2_64_coeff.subspan(0, cur_2_64_end), B_58_10); + num_58_10_coeffs += 1; + if (base_2_64_coeff[cur_2_64_end - 1] == 0) + { + cur_2_64_end -= 1; + } + } + + // Translate the result into the alphabet + // Put all the zeros at the beginning, then all the values from the output + std::fill( + out.begin(), out.begin() + input_zeros, ::ripple::alphabetForward[0]); + + // iterate through the base 58^10 coeff + // convert to base 58 little endian then + // convert to alphabet big endian + bool skip_zeros = true; + auto out_index = input_zeros; + for (int i = num_58_10_coeffs - 1; i >= 0; --i) + { + if (skip_zeros && base_58_10_coeff[i] == 0) + { + continue; + } + auto const b58_be = + ripple::b58_fast::detail::b58_10_to_b58_be(base_58_10_coeff[i]); + std::size_t to_skip = 0; + std::span b58_be_s{b58_be.data(), b58_be.size()}; + if (skip_zeros) + { + to_skip = count_leading_zeros(b58_be_s); + skip_zeros = false; + if (out.size() < (i + 1) * 10 - to_skip) + { + return Unexpected(TokenCodecErrc::OutputTooSmall); + } + } + for (auto b58_coeff : b58_be_s.subspan(to_skip)) + { + out[out_index] = ::ripple::alphabetForward[b58_coeff]; + out_index += 1; + } + } + + return out.subspan(0, out_index); +} + +// Note the input is in BIG ENDIAN form (some fn in this module use little +// endian) +B58Result> +b58_to_b256(std::string_view input, std::span out) +{ + // Convert from b58 to b 58^10 + + // Max encoded value is 38 bytes + // log(2^(38*8),58) ~= 51.9 + if (input.size() > 52) + { + return Unexpected(TokenCodecErrc::InputTooLarge); + }; + if (out.size() < 8) + { + return Unexpected(TokenCodecErrc::OutputTooSmall); + } + + auto count_leading_zeros = [&](auto const& col) -> std::size_t { + std::size_t count = 0; + for (auto const& c : col) + { + if (c != ::ripple::alphabetForward[0]) + { + return count; + } + count += 1; + } + return count; + }; + + auto const input_zeros = count_leading_zeros(input); + + // Allocate enough base 58^10 coeff for encoding 38 bytes + // (33 bytes for nodepublic + 1 byte token + 4 bytes checksum) + // log(2^(38*8),58^10)) ~= 5.18. So 6 coeff are enough + std::array b_58_10_coeff{}; + auto [num_full_coeffs, partial_coeff_len] = + ripple::b58_fast::detail::div_rem(input.size(), 10); + auto const num_partial_coeffs = partial_coeff_len ? 1 : 0; + auto const num_b_58_10_coeffs = num_full_coeffs + num_partial_coeffs; + assert(num_b_58_10_coeffs <= b_58_10_coeff.size()); + for (auto c : input.substr(0, partial_coeff_len)) + { + auto cur_val = ::ripple::alphabetReverse[c]; + if (cur_val < 0) + { + return Unexpected(TokenCodecErrc::InvalidEncodingChar); + } + b_58_10_coeff[0] *= 58; + b_58_10_coeff[0] += cur_val; + } + for (int i = 0; i < 10; ++i) + { + for (int j = 0; j < num_full_coeffs; ++j) + { + auto c = input[partial_coeff_len + j * 10 + i]; + auto cur_val = ::ripple::alphabetReverse[c]; + if (cur_val < 0) + { + return Unexpected(TokenCodecErrc::InvalidEncodingChar); + } + b_58_10_coeff[num_partial_coeffs + j] *= 58; + b_58_10_coeff[num_partial_coeffs + j] += cur_val; + } + } + + constexpr std::uint64_t B_58_10 = 430804206899405824; // 58^10; + + // log(2^(38*8),2^64) ~= 4.75) + std::array result{}; + result[0] = b_58_10_coeff[0]; + std::size_t cur_result_size = 1; + for (int i = 1; i < num_b_58_10_coeffs; ++i) + { + auto c = b_58_10_coeff[i]; + ripple::b58_fast::detail::inplace_bigint_mul( + std::span(&result[0], cur_result_size + 1), B_58_10); + ripple::b58_fast::detail::inplace_bigint_add( + std::span(&result[0], cur_result_size + 1), c); + if (result[cur_result_size] != 0) + { + cur_result_size += 1; + } + } + std::fill(out.begin(), out.begin() + input_zeros, 0); + auto cur_out_i = input_zeros; + // Don't write leading zeros to the output for the most significant + // coeff + { + std::uint64_t const c = result[cur_result_size - 1]; + auto skip_zero = true; + // start and end of output range + for (int i = 0; i < 8; ++i) + { + std::uint8_t const b = (c >> (8 * (7 - i))) & 0xff; + if (i == 7) + { + // handle the all zero case - write the last zero + skip_zero = false; + } + if (skip_zero) + { + if (b == 0) + { + continue; + } + skip_zero = false; + } + out[cur_out_i] = b; + cur_out_i += 1; + } + } + if ((cur_out_i + 8 * (cur_result_size - 1)) > out.size()) + { + return Unexpected(TokenCodecErrc::OutputTooSmall); + } + + for (int i = cur_result_size - 2; i >= 0; --i) + { + auto c = result[i]; + boost::endian::native_to_big_inplace(c); + memcpy(&out[cur_out_i], &c, 8); + cur_out_i += 8; + } + + return out.subspan(0, cur_out_i); +} +} // namespace detail + +B58Result> +encodeBase58Token( + TokenType token_type, + std::span input, + std::span out) +{ + constexpr std::size_t tmpBufSize = 128; + std::array buf; + if (input.size() > tmpBufSize - 5) + { + return Unexpected(TokenCodecErrc::InputTooLarge); + } + if (input.size() == 0) + { + return Unexpected(TokenCodecErrc::InputTooSmall); + } + // + buf[0] = static_cast(token_type); + // buf[1..=input.len()] = input; + memcpy(&buf[1], input.data(), input.size()); + size_t const checksum_i = input.size() + 1; + // buf[checksum_i..checksum_i + 4] = checksum + checksum(buf.data() + checksum_i, buf.data(), checksum_i); + std::span b58Span(buf.data(), input.size() + 5); + return detail::b256_to_b58(b58Span, out); +} +// Convert from base 58 to base 256, largest coefficients first +// The input is encoded in XPRL format, with the token in the first +// byte and the checksum in the last four bytes. +// The decoded base 256 value does not include the token type or checksum. +// It is an error if the token type or checksum does not match. +B58Result> +decodeBase58Token( + TokenType type, + std::string_view s, + std::span outBuf) +{ + std::array tmpBuf; + auto const decodeResult = + detail::b58_to_b256(s, std::span(tmpBuf.data(), tmpBuf.size())); + + if (!decodeResult) + return decodeResult; + + auto const ret = decodeResult.value(); + + // Reject zero length tokens + if (ret.size() < 6) + return Unexpected(TokenCodecErrc::InputTooSmall); + + // The type must match. + if (type != static_cast(static_cast(ret[0]))) + return Unexpected(TokenCodecErrc::MismatchedTokenType); + + // And the checksum must as well. + std::array guard; + checksum(guard.data(), ret.data(), ret.size() - guard.size()); + if (!std::equal(guard.rbegin(), guard.rend(), ret.rbegin())) + { + return Unexpected(TokenCodecErrc::MismatchedChecksum); + } + + std::size_t const outSize = ret.size() - 1 - guard.size(); + if (outBuf.size() < outSize) + return Unexpected(TokenCodecErrc::OutputTooSmall); + // Skip the leading type byte and the trailing checksum. + std::copy(ret.begin() + 1, ret.begin() + outSize + 1, outBuf.begin()); + return outBuf.subspan(0, outSize); +} + +[[nodiscard]] std::string +encodeBase58Token(TokenType type, void const* token, std::size_t size) +{ + std::string sr; + // The largest object encoded as base58 is 33 bytes; This will be encoded in + // at most ceil(log(2^256,58)) bytes, or 46 bytes. 64 is plenty (and there's + // not real benefit making it smaller) + sr.resize(128); + std::span outSp( + reinterpret_cast(sr.data()), sr.size()); + std::span inSp( + reinterpret_cast(token), size); + auto r = b58_fast::encodeBase58Token(type, inSp, outSp); + if (!r) + return {}; + sr.resize(r.value().size()); + return sr; +} + +[[nodiscard]] std::string +decodeBase58Token(std::string const& s, TokenType type) +{ + std::string sr; + // The largest object encoded as base58 is 33 bytes; 64 is plenty (and + // there's no benefit making it smaller) + sr.resize(64); + std::span outSp( + reinterpret_cast(sr.data()), sr.size()); + auto r = b58_fast::decodeBase58Token(type, s, outSp); + if (!r) + return {}; + sr.resize(r.value().size()); + return sr; +} +} // namespace b58_fast +#endif } // namespace ripple diff --git a/src/ripple/protocol/tokens.h b/src/ripple/protocol/tokens.h index 0afb4509f41..1f4e865ec24 100644 --- a/src/ripple/protocol/tokens.h +++ b/src/ripple/protocol/tokens.h @@ -20,12 +20,21 @@ #ifndef RIPPLE_PROTOCOL_TOKENS_H_INCLUDED #define RIPPLE_PROTOCOL_TOKENS_H_INCLUDED +#include +#include +#include + #include #include +#include #include +#include namespace ripple { +template +using B58Result = Expected; + enum class TokenType : std::uint8_t { None = 1, // unused NodePublic = 28, @@ -38,11 +47,11 @@ enum class TokenType : std::uint8_t { }; template -std::optional +[[nodiscard]] std::optional parseBase58(std::string const& s); template -std::optional +[[nodiscard]] std::optional parseBase58(TokenType type, std::string const& s); /** Encode data in Base58Check format using XRPL alphabet @@ -56,20 +65,47 @@ parseBase58(TokenType type, std::string const& s); @return the encoded token. */ -std::string +[[nodiscard]] std::string encodeBase58Token(TokenType type, void const* token, std::size_t size); -/** Decode a token of given type encoded using Base58Check and the XRPL alphabet +[[nodiscard]] std::string +decodeBase58Token(std::string const& s, TokenType type); + +namespace b58_ref { +// Use the reference version is not using gcc extensions (int128 in particular) +[[nodiscard]] std::string +encodeBase58Token(TokenType type, void const* token, std::size_t size); - @param s The encoded token - @param type The type expected for this token. +[[nodiscard]] std::string +decodeBase58Token(std::string const& s, TokenType type); +} // namespace b58_ref + +#ifndef _MSC_VER +namespace b58_fast { +// Use the fast version (10-15x faster) is using gcc extensions (int128 in +// particular) +[[nodiscard]] B58Result> +encodeBase58Token( + TokenType token_type, + std::span input, + std::span out); + +[[nodiscard]] B58Result> +decodeBase58Token( + TokenType type, + std::string_view s, + std::span outBuf); + +// This interface matches the old interface, but requires additional allocation +[[nodiscard]] std::string +encodeBase58Token(TokenType type, void const* token, std::size_t size); - @return If the encoded token decodes correctly, the token data without - the type or checksum. And empty string otherwise. -*/ -std::string +// This interface matches the old interface, but requires additional allocation +[[nodiscard]] std::string decodeBase58Token(std::string const& s, TokenType type); +} // namespace b58_fast +#endif } // namespace ripple #endif