Skip to content

Instantly share code, notes, and snippets.

@movitto
Created December 17, 2018 22:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save movitto/95881a3b190dd3149724949f77d8dee2 to your computer and use it in GitHub Desktop.
Save movitto/95881a3b190dd3149724949f77d8dee2 to your computer and use it in GitHub Desktop.
XRP Base64 -> Base58 Convertor: Converts Base64 addresses output by various rippled requests to the Standard Base58 Representation (in C++ and Ruby)
// Converts a Base64 XRP address to standard Base58 representation
// Compile with:
// g++ b64.cpp -lssl -lcrypto
#include <stdlib.h>
#include <utility>
#include <string>
#include <iostream>
#include <boost/optional.hpp>
#include <boost/container/small_vector.hpp>
#include <vector>
#include <cstring>
#include <openssl/sha.h>
inline
char const*
get_alphabet()
{
static char constexpr tab[] = {
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
};
return &tab[0];
}
inline
signed char const*
get_inverse()
{
static signed char constexpr tab[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0-15
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16-31
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 32-47
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 48-63
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 64-79
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 80-95
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, // 112-127
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128-143
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 144-159
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160-175
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 176-191
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 192-207
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 208-223
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 224-239
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // 240-255
};
return &tab[0];
}
/// Returns max chars needed to encode a base64 string
inline
std::size_t constexpr
encoded_size(std::size_t n)
{
return 4 * ((n + 2) / 3);
}
/// Returns max bytes needed to decode a base64 string
inline
std::size_t constexpr
decoded_size(std::size_t n)
{
return ((n / 4) * 3) + 2;
}
/** Encode a series of octets as a padded, base64 string.
The resulting string will not be null terminated.
@par Requires
The memory pointed to by `out` points to valid memory
of at least `encoded_size(len)` bytes.
@return The number of characters written to `out`. This
will exclude any null termination.
*/
std::size_t
encode(void* dest, void const* src, std::size_t len)
{
char* out = static_cast<char*>(dest);
char const* in = static_cast<char const*>(src);
auto const tab = get_alphabet();
for(auto n = len / 3; n--;)
{
*out++ = tab[ (in[0] & 0xfc) >> 2];
*out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)];
*out++ = tab[((in[2] & 0xc0) >> 6) + ((in[1] & 0x0f) << 2)];
*out++ = tab[ in[2] & 0x3f];
in += 3;
}
switch(len % 3)
{
case 2:
*out++ = tab[ (in[0] & 0xfc) >> 2];
*out++ = tab[((in[0] & 0x03) << 4) + ((in[1] & 0xf0) >> 4)];
*out++ = tab[ (in[1] & 0x0f) << 2];
*out++ = '=';
break;
case 1:
*out++ = tab[ (in[0] & 0xfc) >> 2];
*out++ = tab[((in[0] & 0x03) << 4)];
*out++ = '=';
*out++ = '=';
break;
case 0:
break;
}
return out - static_cast<char*>(dest);
}
/** Decode a padded base64 string into a series of octets.
@par Requires
The memory pointed to by `out` points to valid memory
of at least `decoded_size(len)` bytes.
@return The number of octets written to `out`, and
the number of characters read from the input string,
expressed as a pair.
*/
std::pair<std::size_t, std::size_t>
decode(void* dest, char const* src, std::size_t len)
{
char* out = static_cast<char*>(dest);
auto in = reinterpret_cast<unsigned char const*>(src);
unsigned char c3[3], c4[4];
int i = 0;
int j = 0;
auto const inverse = get_inverse();
while(len-- && *in != '=')
{
auto const v = inverse[*in];
if(v == -1)
break;
++in;
c4[i] = v;
if(++i == 4)
{
c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4);
c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2);
c3[2] = ((c4[2] & 0x3) << 6) + c4[3];
for(i = 0; i < 3; i++)
*out++ = c3[i];
i = 0;
}
}
if(i)
{
c3[0] = ( c4[0] << 2) + ((c4[1] & 0x30) >> 4);
c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2);
c3[2] = ((c4[2] & 0x3) << 6) + c4[3];
for(j = 0; j < i - 1; j++)
*out++ = c3[j];
}
return {out - static_cast<char*>(dest),
in - reinterpret_cast<unsigned char const*>(src)};
}
std::string
base64_encode (std::uint8_t const* data,
std::size_t len)
{
std::string dest;
dest.resize(encoded_size(len));
dest.resize(encode(&dest[0], data, len));
return dest;
}
std::string
base64_decode(std::string const& data)
{
std::string dest;
dest.resize(decoded_size(data.size()));
auto const result = decode(
&dest[0], data.data(), data.size());
dest.resize(result.first);
return dest;
}
/////////////////////////////////////////////////////
struct openssl_sha256_hasher
{
public:
using result_type =
std::array<std::uint8_t, 32>;
openssl_sha256_hasher();
void
operator()(void const* data,
std::size_t size) noexcept;
explicit
operator result_type() noexcept;
private:
char ctx_[112];
};
openssl_sha256_hasher::openssl_sha256_hasher()
{
static_assert(sizeof(decltype(
openssl_sha256_hasher::ctx_)) ==
sizeof(SHA256_CTX), "");
auto const ctx = reinterpret_cast<
SHA256_CTX*>(ctx_);
SHA256_Init(ctx);
}
void
openssl_sha256_hasher::operator()(void const* data,
std::size_t size) noexcept
{
auto const ctx = reinterpret_cast<
SHA256_CTX*>(ctx_);
SHA256_Update(ctx, data, size);
}
openssl_sha256_hasher::operator result_type() noexcept
{
auto const ctx = reinterpret_cast<
SHA256_CTX*>(ctx_);
result_type digest;
SHA256_Final(digest.data(), ctx);
return digest;
}
/////////////////////////////////////////////////////
//
//
enum class TokenType : std::uint8_t
{
None = 1, // unused
NodePublic = 28,
NodePrivate = 32,
AccountID = 0,
AccountPublic = 35,
AccountSecret = 34,
FamilyGenerator = 41, // unused
FamilySeed = 33
};
template <class T>
boost::optional<T>
parseBase58 (std::string const& s);
template<class T>
boost::optional<T>
parseBase58 (TokenType type, std::string const& s);
template <class T>
boost::optional<T>
parseHex (std::string const& s);
template <class T>
boost::optional<T>
parseHexOrBase58 (std::string const& s);
// Facilities for converting Ripple tokens
// to and from their human readable strings
/* Base-58 encode a Ripple Token
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.
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
*/
std::string
base58EncodeToken (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 Base58 token
The type and checksum must match or an
empty string is returned.
*/
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);
static char rippleAlphabet[] =
"rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz";
static char bitcoinAlphabet[] =
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
//------------------------------------------------------------------------------
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
*/
void
checksum (void* out,
void const* message,
std::size_t size)
{
auto const h =
digest2<openssl_sha256_hasher>(message, size);
std::memcpy(out, h.data(), 4);
}
//------------------------------------------------------------------------------
// 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.
static
std::string
encodeBase58(
void const* message, std::size_t size,
void *temp, std::size_t temp_size,
char const* const alphabet)
{
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, alphabet[0]);
while (iter != b58end)
str += alphabet[*(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] = static_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)
{
auto psz = s.c_str();
auto remain = s.size();
// Skip and count leading zeroes
int zeroes = 0;
while (remain > 0 && inv[*psz] == 0)
{
++zeroes;
++psz;
--remain;
}
// 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 = inv[*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;
}
/* Base58 decode a Ripple token
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)
{
auto ret = decodeBase58(s, inv);
// Reject zero length tokens
if (ret.size() < 6)
return {};
// The type must match.
if (type != static_cast<TokenType>(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());
}
//------------------------------------------------------------------------------
// 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);
}
int main(){
std::string base64 = base64_decode("AmQgZGrN0mII1lnAFCfFHUe/A9gn7ApTNEgEt406omfu");
std::string base58 = base58EncodeToken(TokenType::NodePublic, base64.c_str(), base64.size());
std::cout << base64 << std::endl << base58 << std::endl;
return 0;
}
# Converts a Base64 XRP address
# (such as that returned by validator crawl:
# https://developers.ripple.com/peer-protocol.html#peer-crawler)
# To standard Base58 representation
require 'base64'
require 'base58'
require 'openssl'
INPUT_ADDRESS = "AmQgZGrN0mII1lnAFCfFHUe/A9gn7ApTNEgEt406omfu"
TOKEN_TYPES = {
:None => 1, # unused
:NodePublic => 28,
:NodePrivate => 32,
:AccountID => 0,
:AccountPublic => 35,
:AccountSecret => 34,
:FamilyGenerator => 41, # unused
:FamilySeed => 33
}
TOKEN_TYPE = TOKEN_TYPES[:NodePublic]
b64 = Base64.decode64(INPUT_ADDRESS)
i64 = TOKEN_TYPE.chr + b64
chk = OpenSSL::Digest::SHA256.digest(OpenSSL::Digest::SHA256.digest(i64))[0..3]
b58 = Base58.binary_to_base58(i64 + chk, :ripple)
puts b58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment