Update Base58 codec and remove Bitcoin support:

Use C++17 constant expressions to calculate the inverse
alphabet map at compile time instead of at runtime.

Remove support for encoding & decoding tokens using the
Bitcoin alphabet.
This commit is contained in:
Crypto Brad Garlinghouse
2020-08-07 14:22:36 +00:00
committed by Nik Bougalis
parent ab9f3fa42a
commit 831e03ad2a
11 changed files with 72 additions and 212 deletions

View File

@@ -57,12 +57,6 @@ template <>
boost::optional<AccountID>
parseBase58(std::string const& s);
// Parses AccountID using Bitcoin's alphabet
// This is to catch user error. Likely not needed
// DEPRECATED
boost::optional<AccountID>
deprecatedParseBitcoinAccountID(std::string const& s);
// Compatibility with legacy code
bool
deprecatedParseBase58(AccountID& account, Json::Value const& jv);

View File

@@ -86,7 +86,7 @@ enum error_code_i {
rpcNO_PF_REQUEST = 33,
// Bad parameter
rpcACT_BITCOIN = 34,
// NOT USED DO NOT USE AGAIN rpcACT_BITCOIN = 34,
rpcACT_MALFORMED = 35,
rpcALREADY_MULTISIG = 36,
rpcALREADY_SINGLE_SIG = 37,

View File

@@ -185,7 +185,7 @@ struct STExchange<STBlob, PublicKey>
inline std::string
toBase58(TokenType type, PublicKey const& pk)
{
return base58EncodeToken(type, pk.data(), pk.size());
return encodeBase58Token(type, pk.data(), pk.size());
}
template <>

View File

@@ -119,7 +119,7 @@ parseBase58(TokenType type, std::string const& s);
inline std::string
toBase58(TokenType type, SecretKey const& sk)
{
return base58EncodeToken(type, sk.data(), sk.size());
return encodeBase58Token(type, sk.data(), sk.size());
}
/** Create a secret key using secure random numbers. */

View File

@@ -128,7 +128,7 @@ seedAs1751(Seed const& seed);
inline std::string
toBase58(Seed const& seed)
{
return base58EncodeToken(TokenType::FamilySeed, seed.data(), seed.size());
return encodeBase58Token(TokenType::FamilySeed, seed.data(), seed.size());
}
} // namespace ripple

View File

@@ -28,7 +28,7 @@ namespace ripple {
std::string
toBase58(AccountID const& v)
{
return base58EncodeToken(TokenType::AccountID, v.data(), v.size());
return encodeBase58Token(TokenType::AccountID, v.data(), v.size());
}
template <>
@@ -45,19 +45,6 @@ parseBase58(std::string const& s)
return id;
}
boost::optional<AccountID>
deprecatedParseBitcoinAccountID(std::string const& s)
{
auto const result = decodeBase58TokenBitcoin(s, TokenType::AccountID);
if (result.empty())
return boost::none;
AccountID id;
if (result.size() != id.size())
return boost::none;
std::memcpy(id.data(), result.data(), result.size());
return id;
}
bool
deprecatedParseBase58(AccountID& account, Json::Value const& jv)
{

View File

@@ -32,7 +32,6 @@ namespace detail {
// This array will be omitted from the object file; only the sorted version
// will remain in the object file. But the string literals will remain.
constexpr static ErrorInfo unorderedErrorInfos[]{
{rpcACT_BITCOIN, "actBitcoin", "Account is bitcoin address."},
{rpcACT_MALFORMED, "actMalformed", "Account malformed."},
{rpcACT_NOT_FOUND, "actNotFound", "Account not found."},
{rpcALREADY_MULTISIG, "alreadyMultisig", "Already multisigned."},

View File

@@ -30,13 +30,17 @@
namespace ripple {
static char rippleAlphabet[] =
static constexpr char const* alphabetForward =
"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz";
static char bitcoinAlphabet[] =
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
//------------------------------------------------------------------------------
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
@@ -66,7 +70,7 @@ digest2(Args const&... args)
return digest<Hasher>(digest<Hasher>(args...));
}
/* Calculate a 4-byte checksum of the data
/** 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
@@ -75,32 +79,28 @@ digest2(Args const&... args)
@note This checksum algorithm is part of the client API
*/
void
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);
}
//------------------------------------------------------------------------------
namespace detail {
// Code from Bitcoin: https://github.com/bitcoin/bitcoin
// 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.
//
// Modified from the original
//
// WARNING Do not call this directly, use
// encodeBase58Token instead since it
// calculates the size of buffer needed.
/* 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,
std::size_t size,
void* temp,
std::size_t temp_size,
char const* const alphabet)
std::size_t temp_size)
{
auto pbegin = reinterpret_cast<unsigned char const*>(message);
auto const pend = pbegin + size;
@@ -140,73 +140,20 @@ encodeBase58(
// Translate the result into a string.
std::string str;
str.reserve(zeroes + (b58end - iter));
str.assign(zeroes, alphabet[0]);
str.assign(zeroes, alphabetForward[0]);
while (iter != b58end)
str += alphabet[*(iter++)];
str += alphabetForward[*(iter++)];
return str;
}
static std::string
encodeToken(
TokenType type,
void const* token,
std::size_t size,
char const* const alphabet)
{
// 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 encodeBase58(
buf.data(),
expanded,
buf.data() + expanded,
bufsize - expanded,
alphabet);
}
std::string
base58EncodeToken(TokenType type, void const* token, std::size_t size)
{
return encodeToken(type, token, size, rippleAlphabet);
}
std::string
base58EncodeTokenBitcoin(TokenType type, void const* token, std::size_t size)
{
return encodeToken(type, token, size, bitcoinAlphabet);
}
//------------------------------------------------------------------------------
// Code from Bitcoin: https://github.com/bitcoin/bitcoin
// 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.
//
// Modified from the original
template <class InverseArray>
static std::string
decodeBase58(std::string const& s, InverseArray const& inv)
decodeBase58(std::string const& s)
{
auto psz = s.c_str();
auto remain = s.size();
// Skip and count leading zeroes
int zeroes = 0;
while (remain > 0 && inv[*psz] == 0)
while (remain > 0 && alphabetReverse[*psz] == 0)
{
++zeroes;
++psz;
@@ -221,7 +168,7 @@ decodeBase58(std::string const& s, InverseArray const& inv)
std::vector<unsigned char> b256(remain * 733 / 1000 + 1);
while (remain > 0)
{
auto carry = inv[*psz];
auto carry = alphabetReverse[*psz];
if (carry == -1)
return {};
// Apply "b256 = b256 * 58 + carry".
@@ -246,16 +193,36 @@ decodeBase58(std::string const& s, InverseArray const& inv)
return result;
}
/* Base58 decode a Ripple token
} // namespace detail
The type and checksum are are checked
and removed from the returned result.
*/
template <class InverseArray>
static std::string
decodeBase58Token(std::string const& s, TokenType type, InverseArray const& inv)
std::string
encodeBase58Token(TokenType type, void const* token, std::size_t size)
{
std::string const ret = decodeBase58(s, inv);
// 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)
@@ -275,44 +242,4 @@ decodeBase58Token(std::string const& s, TokenType type, InverseArray const& inv)
return ret.substr(1, ret.size() - 1 - guard.size());
}
//------------------------------------------------------------------------------
// Maps characters to their base58 digit
class InverseAlphabet
{
private:
std::array<int, 256> map_;
public:
explicit InverseAlphabet(std::string const& digits)
{
map_.fill(-1);
int i = 0;
for (auto const c : digits)
map_[static_cast<unsigned char>(c)] = i++;
}
int
operator[](char c) const
{
return map_[static_cast<unsigned char>(c)];
}
};
static InverseAlphabet rippleInverse(rippleAlphabet);
static InverseAlphabet bitcoinInverse(bitcoinAlphabet);
std::string
decodeBase58Token(std::string const& s, TokenType type)
{
return decodeBase58Token(s, type, rippleInverse);
}
std::string
decodeBase58TokenBitcoin(std::string const& s, TokenType type)
{
return decodeBase58Token(s, type, bitcoinInverse);
}
} // namespace ripple

View File

@@ -53,61 +53,31 @@ template <class T>
boost::optional<T>
parseHexOrBase58(std::string const& s);
// Facilities for converting Ripple tokens
// to and from their human readable strings
/** Encode data in Base58Check format using XRPL alphabet
/* Base-58 encode a Ripple Token
For details on the format see
https://xrpl.org/base58-encodings.html#base58-encodings
Ripple Tokens have a one-byte prefx indicating
the type of token, followed by the data for the
token, and finally a 4-byte checksum.
@param type The type of token to encode.
@param token Pointer to the data to encode.
@param size The size of the data to encode.
Tokens include the following:
Wallet Seed
Account Public Key
Account ID
@param type A single byte representing the TokenType
@param token A pointer to storage of not
less than 2*(size+6) bytes
@param size the size of the token buffer in bytes
@return the encoded token.
*/
std::string
base58EncodeToken(TokenType type, void const* token, std::size_t size);
encodeBase58Token(TokenType type, void const* token, std::size_t size);
/* Base-58 encode a Bitcoin Token
*
* provided here for symmetry, but should never be needed
* except for testing.
*
* @see base58EncodeToken for format description.
*
*/
std::string
base58EncodeTokenBitcoin(TokenType type, void const* token, std::size_t size);
/** Decode a token of given type encoded using Base58Check and the XRPL alphabet
/** Decode a Base58 token
@param s The encoded token
@param type The type expected for this token.
The type and checksum must match or an
empty string is returned.
@return If the encoded token decodes correctly, the token data without
the type or checksum. And empty string otherwise.
*/
std::string
decodeBase58Token(std::string const& s, TokenType type);
/** Decode a Base58 token using Bitcoin alphabet
The type and checksum must match or an
empty string is returned.
This is used to detect user error. Specifically,
when an AccountID is specified using the wrong
base58 alphabet, so that a better error message
may be returned.
*/
std::string
decodeBase58TokenBitcoin(std::string const& s, TokenType type);
} // namespace ripple
#endif

View File

@@ -63,10 +63,7 @@ accountFromStringWithCode(
}
if (bStrict)
{
auto id = deprecatedParseBitcoinAccountID(strIdent);
return id ? rpcACT_BITCOIN : rpcACT_MALFORMED;
}
return rpcACT_MALFORMED;
// We allow the use of the seeds which is poor practice
// and merely for debugging convenience.

View File

@@ -69,20 +69,6 @@ class AccountCurrencies_test : public beast::unit_test::suite
BEAST_EXPECT(result[jss::error_message] == "Account malformed.");
}
{ // strict mode, using properly formatted bitcoin token
Json::Value params;
params[jss::account] = base58EncodeTokenBitcoin(
TokenType::AccountID, alice.id().data(), alice.id().size());
params[jss::strict] = true;
auto const result = env.rpc(
"json",
"account_currencies",
boost::lexical_cast<std::string>(params))[jss::result];
BEAST_EXPECT(result[jss::error] == "actBitcoin");
BEAST_EXPECT(
result[jss::error_message] == "Account is bitcoin address.");
}
{ // ask for nonexistent account
Json::Value params;
params[jss::account] = Account{"bob"}.human();