Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add better Base58 implementation #180

Merged
merged 2 commits into from Mar 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
186 changes: 186 additions & 0 deletions src/Base58.cpp
@@ -0,0 +1,186 @@
// Copyright © 2014-2018 The Bitcoin Core developers
// Copyright © 2017-2019 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#include "Base58.h"

#include "Hash.h"

#include <cctype>
#include <algorithm>

using namespace TW;

static const std::array<char, 58> bitcoinDigits = {
'1','2','3','4','5','6','7','8','9',
'A','B','C','D','E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','m','n','o','p','q','r','s','t','u','v','w','x','y','z'
};

static const std::array<signed char, 128> bitcoinCharacterMap = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8,-1,-1,-1,-1,-1,-1,
-1, 9,10,11,12,13,14,15,16,-1,17,18,19,20,21,-1,
22,23,24,25,26,27,28,29,30,31,32,-1,-1,-1,-1,-1,
-1,33,34,35,36,37,38,39,40,41,42,43,-1,44,45,46,
47,48,49,50,51,52,53,54,55,56,57,-1,-1,-1,-1,-1,
};

static const std::array<char, 58> rippleDigits = {
'r','p','s','h','n','a','f','3','9',
'w','B','U','D','N','E','G','H','J','K','L','M','4','P','Q','R','S','T','7','V','W','X','Y','Z',
'2','b','c','d','e','C','g','6','5','j','k','m','8','o','F','q','i','1','t','u','v','A','x','y','z'
};

static const std::array<signed char, 128> rippleCharacterMap = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,50,33,7,21,41,40,27,45,8,-1,-1,-1,-1,-1,-1,
-1,54,10,38,12,14,47,15,16,-1,17,18,19,20,13,-1,
22,23,24,25,26,11,28,29,30,31,32,-1,-1,-1,-1,-1,
-1,5,34,35,36,37,6,39,3,49,42,43,-1,44,4,46,
1,48,0,2,51,52,53,9,55,56,57,-1,-1,-1,-1,-1,
};

Base58 Base58::bitcoin = Base58(bitcoinDigits, bitcoinCharacterMap);

Base58 Base58::ripple = Base58(rippleDigits, rippleCharacterMap);

Data Base58::decodeCheck(const char* begin, const char* end) const {
auto result = decode(begin, end);
if (result.size() < 4) {
return {};
}

// re-calculate the checksum, ensure it matches the included 4-byte checksum
auto hash = Hash::sha256(Hash::sha256(result.data(), result.data() + result.size() - 4));
if (!std::equal(hash.begin(), hash.begin() + 4, result.end() - 4)) {
return {};
}

return Data(result.begin(), result.end() - 4);
}

Data Base58::decode(const char* begin, const char* end) const {
auto it = begin;

// Skip leading spaces.
it = std::find_if_not(it, end, std::isspace);

// Skip and count leading zeros.
std::size_t zeroes = 0;
std::size_t length = 0;
while (it != end && *it == digits[0]) {
zeroes += 1;
it += 1;
}

// Allocate enough space in big-endian base256 representation.
std::size_t base258Size = (end - it) * 733 /1000 + 1; // log(58) / log(256), rounded up.
Data b256(base258Size);

// Process the characters.
while (it != end && !std::isspace(*it)) {
if (static_cast<unsigned char>(*it) >= 128) {
// Invalid b58 character
return {};
}

// Decode base58 character
int carry = characterMap[static_cast<unsigned char>(*it)];
if (carry == -1) {
// Invalid b58 character
return {};
}

std::size_t i = 0;
for (auto b256it = b256.rbegin(); (carry != 0 || i < length) && (b256it != b256.rend()); ++b256it, ++i) {
carry += 58 * (*b256it);
*b256it = carry % 256;
carry /= 256;
}
assert(carry == 0);
length = i;
it += 1;
}

// Skip trailing spaces.
it = std::find_if_not(it, end, std::isspace);
if (it != end) {
// Extra charaters at the end
return {};
}

// Skip leading zeroes in b256.
auto b256it = b256.begin() + (base258Size - length);
while (b256it != b256.end() && *b256it == 0) {
b256it++;
}

// Copy result into output vector.
Data result;
result.reserve(zeroes + (b256.end() - b256it));
result.assign(zeroes, 0x00);
std::copy(b256it, b256.end(), std::back_inserter(result));

return result;
}

std::string Base58::encodeCheck(const byte* begin, const byte* end) const {
// add 4-byte hash check to the end
Data dataWithCheck(begin, end);
auto hash = Hash::sha256(Hash::sha256(begin, end));
dataWithCheck.insert(dataWithCheck.end(), hash.begin(), hash.begin() + 4);
return encode(dataWithCheck);
}

std::string Base58::encode(const byte* begin, const byte* end) const {
// Skip & count leading zeroes.
int zeroes = 0;
int length = 0;
while (begin != end && *begin == 0) {
begin += 1;
zeroes += 1;
}

// Allocate enough space in big-endian base58 representation.
int base58Size = (end - begin) * 138 / 100 + 1; // log(256) / log(58), rounded up.
Data b58(base58Size);

while (begin != end) {
int carry = *begin;
int i = 0;
// Apply "b58 = b58 * 256 + ch".
for (auto b58it = b58.rbegin(); (carry != 0 || i < length) && (b58it != b58.rend()); b58it++, i++) {
carry += 256 * (*b58it);
*b58it = carry % 58;
carry /= 58;
}

assert(carry == 0);
length = i;
begin += 1;
}

// Skip leading zeroes in base58 result.
auto it = b58.begin() + (base58Size - length);
while (it != b58.end() && *it == 0) {
it++;
}

// Translate the result into a string.
std::string str;
str.reserve(zeroes + (b58.end() - it));
str.assign(zeroes, digits[0]);
while (it != b58.end()) {
str += digits[*it];
it += 1;
}
return str;
}
69 changes: 69 additions & 0 deletions src/Base58.h
@@ -0,0 +1,69 @@
// Copyright © 2017-2019 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#pragma once

#include "Data.h"

#include <array>
#include <string>

namespace TW {

class Base58 {
public:
/// Base58 coder with Bitcoin character map.
static Base58 bitcoin;

/// Base58 coder with Ripple character map.
static Base58 ripple;

public:
/// Ordered list of valid characters.
const std::array<char, 58> digits;

/// Maps characters to base58 values.
const std::array<signed char, 128> characterMap;

/// Initializes a Base58 class with custom digit mapping.
Base58(const std::array<char, 58> digits, std::array<signed char, 128> characterMap) : digits(digits), characterMap(characterMap) {}

/// Decodes a base 58 string into `result` verifying the checksum, returns `false` on failure.
Data decodeCheck(const std::string& string) const {
return decodeCheck(string.data(), string.data() + string.size());
}

/// Decodes a base 58 string into `result` verifying the checksum, returns `false` on failure.
Data decodeCheck(const char* begin, const char* end) const;

/// Decodes a base 58 string into `result`, returns `false` on failure.
Data decode(const std::string& string) const {
return decode(string.data(), string.data() + string.size());
}

/// Decodes a base 58 string into `result`, returns `false` on failure.
Data decode(const char* begin, const char* end) const;

/// Encodes data as a base 58 string with a checksum.
template <typename T>
std::string encodeCheck(const T& data) const {
return encodeCheck(data.data(), data.data() + data.size());
}

/// Encodes data as a base 58 string with a checksum.
std::string encodeCheck(const byte* pbegin, const byte* pend) const;

/// Encodes data as a base 58 string.
template <typename T>
std::string encode(const T& data) const {
return encode(data.data(), data.data() + data.size());
}

/// Encodes data as a base 58 string.
std::string encode(const byte* pbegin, const byte* pend) const;
};

} // namespace
40 changes: 10 additions & 30 deletions src/Bitcoin/Address.cpp
Expand Up @@ -6,51 +6,39 @@

#include "Address.h"

#include <TrezorCrypto/base58.h>
#include "../Base58.h"
#include <TrezorCrypto/ecdsa.h>

#include <cassert>

using namespace TW::Bitcoin;

bool Address::isValid(const std::string& string) {
size_t capacity = 128;
uint8_t buffer[capacity];

int size = base58_decode_check(string.data(), HASHER_SHA2D, buffer, (int)capacity);
if (size != Address::size) {
const auto decoded = Base58::bitcoin.decodeCheck(string);
if (decoded.size() != Address::size) {
return false;
}

return true;
}

bool Address::isValid(const std::string& string, const std::vector<byte>& validPrefixes) {
size_t capacity = 128;
uint8_t buffer[capacity];

int size = base58_decode_check(string.data(), HASHER_SHA2D, buffer, (int)capacity);
if (size != Address::size) {
const auto decoded = Base58::bitcoin.decodeCheck(string);
if (decoded.size() != Address::size) {
return false;
}

if (std::find(validPrefixes.begin(), validPrefixes.end(), buffer[0]) == validPrefixes.end()) {
if (std::find(validPrefixes.begin(), validPrefixes.end(), decoded[0]) == validPrefixes.end()) {
return false;
}

return true;
}

Address::Address(const std::string& string) {
size_t capacity = 128;
uint8_t buffer[capacity];

int size = base58_decode_check(string.data(), HASHER_SHA2D, buffer, (int)capacity);
if (size != Address::size) {
const auto decoded = Base58::bitcoin.decodeCheck(string);
if (decoded.size() != Address::size) {
throw std::invalid_argument("Invalid address string");
}

std::copy(buffer, buffer + Address::size, bytes.begin());
std::copy(decoded.begin(), decoded.end(), bytes.begin());
}

Address::Address(const std::vector<uint8_t>& data) {
Expand All @@ -66,13 +54,5 @@ Address::Address(const PublicKey& publicKey, uint8_t prefix) {
}

std::string Address::string() const {
size_t size = 0;
b58enc(nullptr, &size, bytes.data(), Address::size);
size += 16;

std::string str(size, '\0');
const auto actualSize = base58_encode_check(bytes.data(), Address::size, HASHER_SHA2D, &str[0], size);
str.erase(str.begin() + actualSize - 1, str.end());

return str;
return Base58::bitcoin.encodeCheck(bytes);
}
12 changes: 6 additions & 6 deletions src/HDWallet.cpp
Expand Up @@ -7,12 +7,12 @@
#include "HDWallet.h"

#include "Coin.h"
#include "Base58.h"
#include "Bitcoin/Bech32Address.h"
#include "Bitcoin/CashAddress.h"
#include "Zcash/TAddress.h"
#include "Ripple/Address.h"

#include <TrezorCrypto/base58.h>
#include <TrezorCrypto/bip32.h>
#include <TrezorCrypto/bip39.h>
#include <TrezorCrypto/curves.h>
Expand Down Expand Up @@ -111,12 +111,12 @@ PublicKey HDWallet::getPublicKeyFromExtended(const std::string& extended, TWCurv
}

std::optional<std::string> HDWallet::getAddressFromExtended(const std::string& extended, TWCurve curve, TWCoinType coinType, uint32_t change, uint32_t address) {
uint8_t data[78];
if (base58_decode_check(extended.c_str(), HASHER_SHA2D, data, sizeof(data)) != sizeof(data)) {
return nullptr;
}
const auto decoded = Base58::bitcoin.decodeCheck(extended);
if (decoded.size() != 78) {
return {};
}

TWHDVersion version = (TWHDVersion) read_be(data);
TWHDVersion version = (TWHDVersion) read_be(decoded.data());
if (version != TWHDVersionXPUB && version != TWHDVersionYPUB && version != TWHDVersionLTUB && version != TWHDVersionZPUB && version != TWHDVersionMTUB) {
// Not a public key
return {};
Expand Down