mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-20 18:45:55 +00:00
751 lines
24 KiB
C++
751 lines
24 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2012, 2013 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.
|
|
*/
|
|
//==============================================================================
|
|
//
|
|
/* The base58 encoding & decoding routines in the b58_ref 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.
|
|
*/
|
|
|
|
#include <ripple/protocol/tokens.h>
|
|
|
|
#include <ripple/basics/safe_cast.h>
|
|
#include <ripple/protocol/digest.h>
|
|
#include <ripple/protocol/impl/b58_utils.h>
|
|
|
|
#include <boost/container/small_vector.hpp>
|
|
#include <boost/endian.hpp>
|
|
#include <boost/endian/conversion.hpp>
|
|
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
/*
|
|
Converting between bases is straight forward. First, some background:
|
|
|
|
Given the coefficients C[m], ... ,C[0] and base B, those coefficients represent
|
|
the number C[m]*B^m + ... + C[0]*B^0; The following pseudo-code converts the
|
|
coefficients to the (infinite precision) integer N:
|
|
|
|
```
|
|
N = 0;
|
|
i = m ;; N.B. m is the index of the largest coefficient
|
|
while (i>=0)
|
|
N = N + C[i]*B^i
|
|
i = i - 1
|
|
```
|
|
|
|
For example, in base 10, the number 437 represents the integer 4*10^2 + 3*10^1 +
|
|
7*10^0. In base 16, 437 is the same as 4*16^2 + 3*16^1 + 7*16^0.
|
|
|
|
To find the coefficients that represent the integer N in base B, we start by
|
|
computing the lowest order coefficients and work up to the highest order
|
|
coefficients. The following pseudo-code converts the (infinite precision)
|
|
integer N to the correct coefficients:
|
|
|
|
```
|
|
i = 0
|
|
while(N)
|
|
C[i] = N mod B
|
|
N = floor(N/B)
|
|
i = i + 1
|
|
```
|
|
|
|
For example, to find the coefficients of the integer 437 in base 10:
|
|
|
|
C[0] is 437 mod 10; C[0] = 7;
|
|
N is floor(437/10); N = 43;
|
|
C[1] is 43 mod 10; C[1] = 3;
|
|
N is floor(43/10); N = 4;
|
|
C[2] is 4 mod 10; C[2] = 4;
|
|
N is floor(4/10); N = 0;
|
|
Since N is 0, the algorithm stops.
|
|
|
|
|
|
To convert between a number represented with coefficients from base B1 to that
|
|
same number represented with coefficients from base B2, we can use the algorithm
|
|
that converts coefficients from base B1 to an integer, and then use the
|
|
algorithm that converts a number to coefficients from base B2.
|
|
|
|
There is a useful shortcut that can be used if one of the bases is a power of
|
|
the other base. If B1 == B2^G, then each coefficient from base B1 can be
|
|
converted to base B2 independently to create a a group of "G" B2 coefficient.
|
|
These coefficients can be simply concatenated together. Since 16 == 2^4, this
|
|
property is what makes base 16 useful when dealing with binary numbers. For
|
|
example consider converting the base 16 number "93" to binary. The base 16
|
|
coefficient 9 is represented in base 2 with the coefficients 1,0,0,1. The base
|
|
16 coefficient 3 is represented in base 2 with the coefficients 0,0,1,1. To get
|
|
the final answer, just concatenate those two independent conversions together.
|
|
The base 16 number "93" is the binary number "10010011".
|
|
|
|
The original (now reference) algorithm to convert from base 58 to a binary
|
|
number used the
|
|
|
|
```
|
|
N = 0;
|
|
for i in m to 0 inclusive
|
|
N = N + C[i]*B^i
|
|
```
|
|
|
|
algorithm.
|
|
|
|
However, the algorithm above is pseudo-code. In particular, the variable "N" is
|
|
an infinite precision integer in that pseudo-code. Real computers do
|
|
computations on registers, and these registers have limited length. Modern
|
|
computers use 64-bit general purpose registers, and can multiply two 64 bit
|
|
numbers and obtain a 128 bit result (in two registers).
|
|
|
|
The original algorithm in essence converted from base 58 to base 256 (base
|
|
2^8). The new, faster algorithm converts from base 58 to base 58^10 (this is
|
|
fast using the shortcut described above), then from base 58^10 to base 2^64
|
|
(this is slow, and requires multi-precision arithmetic), and then from base 2^64
|
|
to base 2^8 (this is fast, using the shortcut described above). Base 58^10 is
|
|
chosen because it is the largest power of 58 that will fit into a 64-bit
|
|
register.
|
|
|
|
While it may seem counter-intuitive that converting from base 58 -> base 58^10
|
|
-> base 2^64 -> base 2^8 is faster than directly converting from base 58 -> base
|
|
2^8, it is actually 10x-15x faster. The reason for the speed increase is two of
|
|
the conversions are trivial (converting between bases where one base is a power
|
|
of another base), and doing the multi-precision computations with larger
|
|
coefficients sizes greatly speeds up the multi-precision computations.
|
|
*/
|
|
|
|
namespace ripple {
|
|
|
|
static constexpr char const* alphabetForward =
|
|
"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz";
|
|
|
|
static constexpr std::array<int, 256> const alphabetReverse = []() {
|
|
std::array<int, 256> map{};
|
|
for (auto& m : map)
|
|
m = -1;
|
|
for (int i = 0, j = 0; alphabetForward[i] != 0; ++i)
|
|
map[static_cast<unsigned char>(alphabetForward[i])] = j++;
|
|
return map;
|
|
}();
|
|
|
|
template <class Hasher>
|
|
static typename Hasher::result_type
|
|
digest(void const* data, std::size_t size) noexcept
|
|
{
|
|
Hasher h;
|
|
h(data, size);
|
|
return static_cast<typename Hasher::result_type>(h);
|
|
}
|
|
|
|
template <
|
|
class Hasher,
|
|
class T,
|
|
std::size_t N,
|
|
class = std::enable_if_t<sizeof(T) == 1>>
|
|
static typename Hasher::result_type
|
|
digest(std::array<T, N> const& v)
|
|
{
|
|
return digest<Hasher>(v.data(), v.size());
|
|
}
|
|
|
|
// Computes a double digest (e.g. digest of the digest)
|
|
template <class Hasher, class... Args>
|
|
static typename Hasher::result_type
|
|
digest2(Args const&... args)
|
|
{
|
|
return digest<Hasher>(digest<Hasher>(args...));
|
|
}
|
|
|
|
/** Calculate a 4-byte checksum of the data
|
|
|
|
The checksum is calculated as the first 4 bytes
|
|
of the SHA256 digest of the message. This is added
|
|
to the base58 encoding of identifiers to detect
|
|
user error in data entry.
|
|
|
|
@note This checksum algorithm is part of the client API
|
|
*/
|
|
static void
|
|
checksum(void* out, void const* message, std::size_t size)
|
|
{
|
|
auto const h = digest2<sha256_hasher>(message, size);
|
|
std::memcpy(out, h.data(), 4);
|
|
}
|
|
|
|
[[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(s, type);
|
|
#endif
|
|
}
|
|
|
|
namespace b58_ref {
|
|
|
|
namespace detail {
|
|
|
|
std::string
|
|
encodeBase58(
|
|
void const* message,
|
|
std::size_t size,
|
|
void* temp,
|
|
std::size_t temp_size)
|
|
{
|
|
auto pbegin = reinterpret_cast<unsigned char const*>(message);
|
|
auto const pend = pbegin + size;
|
|
|
|
// Skip & count leading zeroes.
|
|
int zeroes = 0;
|
|
while (pbegin != pend && *pbegin == 0)
|
|
{
|
|
pbegin++;
|
|
zeroes++;
|
|
}
|
|
|
|
auto const b58begin = reinterpret_cast<unsigned char*>(temp);
|
|
auto const b58end = b58begin + temp_size;
|
|
|
|
std::fill(b58begin, b58end, 0);
|
|
|
|
while (pbegin != pend)
|
|
{
|
|
int carry = *pbegin;
|
|
// Apply "b58 = b58 * 256 + ch".
|
|
for (auto iter = b58end; iter != b58begin; --iter)
|
|
{
|
|
carry += 256 * (iter[-1]);
|
|
iter[-1] = carry % 58;
|
|
carry /= 58;
|
|
}
|
|
assert(carry == 0);
|
|
pbegin++;
|
|
}
|
|
|
|
// Skip leading zeroes in base58 result.
|
|
auto iter = b58begin;
|
|
while (iter != b58end && *iter == 0)
|
|
++iter;
|
|
|
|
// Translate the result into a string.
|
|
std::string str;
|
|
str.reserve(zeroes + (b58end - iter));
|
|
str.assign(zeroes, alphabetForward[0]);
|
|
while (iter != b58end)
|
|
str += alphabetForward[*(iter++)];
|
|
return str;
|
|
}
|
|
|
|
std::string
|
|
decodeBase58(std::string const& s)
|
|
{
|
|
auto psz = reinterpret_cast<unsigned char const*>(s.c_str());
|
|
auto remain = s.size();
|
|
// Skip and count leading zeroes
|
|
int zeroes = 0;
|
|
while (remain > 0 && alphabetReverse[*psz] == 0)
|
|
{
|
|
++zeroes;
|
|
++psz;
|
|
--remain;
|
|
}
|
|
|
|
if (remain > 64)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
// Allocate enough space in big-endian base256 representation.
|
|
// log(58) / log(256), rounded up.
|
|
std::vector<unsigned char> b256(remain * 733 / 1000 + 1);
|
|
while (remain > 0)
|
|
{
|
|
auto carry = alphabetReverse[*psz];
|
|
if (carry == -1)
|
|
{
|
|
return {};
|
|
}
|
|
// Apply "b256 = b256 * 58 + carry".
|
|
for (auto iter = b256.rbegin(); iter != b256.rend(); ++iter)
|
|
{
|
|
carry += 58 * *iter;
|
|
*iter = carry % 256;
|
|
carry /= 256;
|
|
}
|
|
assert(carry == 0);
|
|
++psz;
|
|
--remain;
|
|
}
|
|
// Skip leading zeroes in b256.
|
|
auto iter = std::find_if(
|
|
b256.begin(), b256.end(), [](unsigned char c) { return c != 0; });
|
|
std::string result;
|
|
result.reserve(zeroes + (b256.end() - iter));
|
|
result.assign(zeroes, 0x00);
|
|
while (iter != b256.end())
|
|
result.push_back(*(iter++));
|
|
|
|
return result;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
std::string
|
|
encodeBase58Token(TokenType type, void const* token, std::size_t size)
|
|
{
|
|
// expanded token includes type + 4 byte checksum
|
|
auto const expanded = 1 + size + 4;
|
|
|
|
// We need expanded + expanded * (log(256) / log(58)) which is
|
|
// bounded by expanded + expanded * (138 / 100 + 1) which works
|
|
// out to expanded * 3:
|
|
auto const bufsize = expanded * 3;
|
|
|
|
boost::container::small_vector<std::uint8_t, 1024> buf(bufsize);
|
|
|
|
// Lay the data out as
|
|
// <type><token><checksum>
|
|
buf[0] = safe_cast<std::underlying_type_t<TokenType>>(type);
|
|
if (size)
|
|
std::memcpy(buf.data() + 1, token, size);
|
|
checksum(buf.data() + 1 + size, buf.data(), 1 + size);
|
|
|
|
return detail::encodeBase58(
|
|
buf.data(), expanded, buf.data() + expanded, bufsize - expanded);
|
|
}
|
|
|
|
std::string
|
|
decodeBase58Token(std::string const& s, TokenType type)
|
|
{
|
|
std::string const ret = detail::decodeBase58(s);
|
|
|
|
// Reject zero length tokens
|
|
if (ret.size() < 6)
|
|
return {};
|
|
|
|
// The type must match.
|
|
if (type != safe_cast<TokenType>(static_cast<std::uint8_t>(ret[0])))
|
|
return {};
|
|
|
|
// And the checksum must as well.
|
|
std::array<char, 4> guard;
|
|
checksum(guard.data(), ret.data(), ret.size() - guard.size());
|
|
if (!std::equal(guard.rbegin(), guard.rend(), ret.rbegin()))
|
|
return {};
|
|
|
|
// Skip the leading type byte and the trailing checksum.
|
|
return ret.substr(1, ret.size() - 1 - guard.size());
|
|
}
|
|
} // namespace b58_ref
|
|
|
|
#ifndef _MSC_VER
|
|
// The algorithms use gcc's int128 (fast MS version will have to wait, in the
|
|
// meantime MS falls back to the slower reference implementation)
|
|
namespace b58_fast {
|
|
namespace detail {
|
|
// Note: both the input and output will be BIG ENDIAN
|
|
B58Result<std::span<std::uint8_t>>
|
|
b256_to_b58_be(std::span<std::uint8_t const> input, std::span<std::uint8_t> out)
|
|
{
|
|
// Max valid input is 38 bytes:
|
|
// (33 bytes for nodepublic + 1 byte token + 4 bytes checksum)
|
|
if (input.size() > 38)
|
|
{
|
|
return Unexpected(TokenCodecErrc::inputTooLarge);
|
|
};
|
|
|
|
auto count_leading_zeros =
|
|
[](std::span<std::uint8_t const> 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);
|
|
|
|
// Allocate enough base 2^64 coeff for encoding 38 bytes
|
|
// log(2^(38*8),2^64)) ~= 4.75. So 5 coeff are enough
|
|
std::array<std::uint64_t, 5> base_2_64_coeff_buf{};
|
|
std::span<std::uint64_t> const base_2_64_coeff =
|
|
[&]() -> std::span<std::uint64_t> {
|
|
// convert input from big endian to native u64, lowest coeff first
|
|
std::size_t num_coeff = 0;
|
|
for (int i = 0; i < base_2_64_coeff_buf.size(); ++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);
|
|
}();
|
|
|
|
// Allocate enough base 58^10 coeff for encoding 38 bytes
|
|
// log(2^(38*8),58^10)) ~= 5.18. So 6 coeff are enough
|
|
std::array<std::uint64_t, 6> base_58_10_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 big 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;
|
|
}
|
|
static constexpr std::uint64_t B_58_10 = 430804206899405824; // 58^10;
|
|
if (base_58_10_coeff[i] >= B_58_10)
|
|
{
|
|
return Unexpected(TokenCodecErrc::inputTooLarge);
|
|
}
|
|
std::array<std::uint8_t, 10> 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<std::uint8_t const> 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 BIG ENDIAN (some fn in this module use little endian)
|
|
B58Result<std::span<std::uint8_t>>
|
|
b58_to_b256_be(std::string_view input, std::span<std::uint8_t> 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<std::uint64_t, 6> 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<std::uint64_t, 5> 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)
|
|
{
|
|
std::uint64_t const c = b_58_10_coeff[i];
|
|
|
|
{
|
|
auto code = ripple::b58_fast::detail::inplace_bigint_mul(
|
|
std::span(&result[0], cur_result_size + 1), B_58_10);
|
|
if (code != TokenCodecErrc::success)
|
|
{
|
|
return Unexpected(code);
|
|
}
|
|
}
|
|
{
|
|
auto code = ripple::b58_fast::detail::inplace_bigint_add(
|
|
std::span(&result[0], cur_result_size + 1), c);
|
|
if (code != TokenCodecErrc::success)
|
|
{
|
|
return Unexpected(code);
|
|
}
|
|
}
|
|
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 (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<std::span<std::uint8_t>>
|
|
encodeBase58Token(
|
|
TokenType token_type,
|
|
std::span<std::uint8_t const> input,
|
|
std::span<std::uint8_t> out)
|
|
{
|
|
constexpr std::size_t tmpBufSize = 128;
|
|
std::array<std::uint8_t, tmpBufSize> buf;
|
|
if (input.size() > tmpBufSize - 5)
|
|
{
|
|
return Unexpected(TokenCodecErrc::inputTooLarge);
|
|
}
|
|
if (input.size() == 0)
|
|
{
|
|
return Unexpected(TokenCodecErrc::inputTooSmall);
|
|
}
|
|
// <type (1 byte)><token (input len)><checksum (4 bytes)>
|
|
buf[0] = static_cast<std::uint8_t>(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<std::uint8_t const> b58Span(buf.data(), input.size() + 5);
|
|
return detail::b256_to_b58_be(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<std::span<std::uint8_t>>
|
|
decodeBase58Token(
|
|
TokenType type,
|
|
std::string_view s,
|
|
std::span<std::uint8_t> outBuf)
|
|
{
|
|
std::array<std::uint8_t, 64> tmpBuf;
|
|
auto const decodeResult =
|
|
detail::b58_to_b256_be(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<TokenType>(static_cast<std::uint8_t>(ret[0])))
|
|
return Unexpected(TokenCodecErrc::mismatchedTokenType);
|
|
|
|
// And the checksum must as well.
|
|
std::array<std::uint8_t, 4> 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. 128 is plenty (and
|
|
// there's not real benefit making it smaller). Note that 46 bytes may be
|
|
// encoded in more than 46 base58 chars. Since decode uses 64 as the
|
|
// over-allocation, this function uses 128 (again, over-allocation assuming
|
|
// 2 base 58 char per byte)
|
|
sr.resize(128);
|
|
std::span<std::uint8_t> outSp(
|
|
reinterpret_cast<std::uint8_t*>(sr.data()), sr.size());
|
|
std::span<std::uint8_t const> inSp(
|
|
reinterpret_cast<std::uint8_t const*>(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<std::uint8_t> outSp(
|
|
reinterpret_cast<std::uint8_t*>(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 // _MSC_VER
|
|
} // namespace ripple
|