Created
December 17, 2018 22:14
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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