Skip to content

Commit

Permalink
Add Base58 Address Prefix Decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
russeree committed Oct 1, 2023
1 parent 1111111 commit 2222222
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 2 deletions.
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ BITCOIN_CORE_H = \
undo.h \
util/any.h \
util/asmap.h \
util/base58_address.h \
util/batchpriority.h \
util/bip32.h \
util/bitdeque.h \
Expand Down Expand Up @@ -728,6 +729,7 @@ libbitcoin_util_a_SOURCES = \
support/cleanse.cpp \
sync.cpp \
util/asmap.cpp \
util/base58_address.cpp \
util/batchpriority.cpp \
util/bip32.cpp \
util/bytevectorhash.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ BITCOIN_TESTS =\
test/arith_uint256_tests.cpp \
test/banman_tests.cpp \
test/base32_tests.cpp \
test/base58_prefix_decoder_tests.cpp \
test/base58_tests.cpp \
test/base64_tests.cpp \
test/bech32_tests.cpp \
Expand Down
4 changes: 2 additions & 2 deletions src/base58.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
#include <limits>

/** All alphanumeric characters except for "0", "I", "O", and "l" */
static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
static const int8_t mapBase58[256] = {
const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
const int8_t mapBase58[256] = {
-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,
Expand Down
3 changes: 3 additions & 0 deletions src/base58.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
#include <string>
#include <vector>

extern const char* pszBase58;
extern const int8_t mapBase58[256];

/**
* Encode a byte span as a base58-encoded string
*/
Expand Down
89 changes: 89 additions & 0 deletions src/test/base58_prefix_decoder_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include <base58.h>
#include <util/base58_address.h>
#include <boost/test/unit_test.hpp>

#include <string>

BOOST_AUTO_TEST_SUITE(base58_prefix_decode_test)

BOOST_AUTO_TEST_CASE(base58_prefix_decode_testvectors_valid)
{
const std::string base58_address_std = "12higDjoCCNXSA95xZMWUdPvXNmkAduhWv";

static const unsigned char CASES[] = {
0x00, // Mainnet pay to public key hash
0x01, // Largest range of prefixes
0x02, // Second largest range of prefixes
0x03,
0x04,
0x05, // Mainnet script hash
0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D,
0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41,
0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B,
0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55,
0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
0x6F, // Testnet pubkey hash
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83,
0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D,
0x8E, 0x8F, 0x90,
0xC4, // Testnet script hash
0xFF // End of possible range of inputs
};

static const std::vector<char> RANGES[] = {
{'1'},
{'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','m','n','o'},
{'o','p','q','r','s','t','u','v','w','x','y','z','2'},
{'2'},
{'2','3'},
{'3'},
{'3'},{'3','4'},{'4'},{'4','5'},{'5'},{'5'},{'5','6'},{'6'},{'6','7'},{'7'}, // Prefix values 6-15
{'7'},{'7','8'},{'8'},{'8','9'},{'9'},{'9'},{'9','A'},{'A'},{'A','B'},{'B'}, // Prefix values 16-25
{'B'},{'B','C'},{'C'},{'C','D'},{'D'},{'D'},{'D','E'},{'E'},{'E','F'},{'F'}, // Prefix values 26-35
{'F'},{'F','G'},{'G'},{'G','H'},{'H'},{'H'},{'H','J'},{'J'},{'J','K'},{'K'}, // Prefix values 36-45
{'K'},{'K','L'},{'L'},{'L','M'},{'M'},{'M'},{'M','N'},{'N'},{'N','P'},{'P'}, // Prefix values 46-55
{'P'},{'P','Q'},{'Q'},{'Q','R'},{'R'},{'R'},{'R','S'},{'S'},{'S','T'},{'T'}, // Prefix values 56-65
{'T'},{'T','U'},{'U'},{'U','V'},{'V'},{'V'},{'V','W'},{'W'},{'W','X'},{'X'}, // Prefix values 66-75
{'X'},{'X','Y'},{'Y'},{'Y','Z'},{'Z'},{'Z'},{'Z','a'},{'a'},{'a','b'},{'b'}, // Prefix values 76-85
{'b','c'},{'c'},{'c'},{'c','d'},{'d'},{'d','e'},{'e'},{'e'},{'e','f'},{'f'}, // Prefix values 86-95
{'f','g'},{'g'},{'g'},{'g','h'},{'h'},{'h','i'},{'i'},{'i'},{'i','j'},{'j'}, // Prefix values 96-105
{'j','k'},{'k'},{'k'},{'k','m'},{'m'},{'m','n'},{'n'},{'n'},{'n','o'},{'o'}, // Prefix values 106-115
{'o','p'},{'p'},{'p'},{'p','q'},{'q'},{'q','r'},{'r'},{'r'},{'r','s'},{'s'}, // Prefix values 116-125
{'s','t'},{'t'},{'t'},{'t','u'},{'u'},{'u','v'},{'v'},{'v'},{'v','w'},{'w'}, // Prefix values 126-135
{'w','x'},{'x'},{'x'},{'x','y'},{'y'},{'y','z'},{'z'},{'z'},{'z','2'}, // Prefix values 136-144
{'2'},
{'2'}
};


static_assert(std::size(CASES) == std::size(RANGES), "Base58 CASES and RANGES should have the same length");

std::vector<unsigned char> base58_data;
bool valid_base58 = DecodeBase58(base58_address_std, base58_data, 25);
BOOST_CHECK(valid_base58 == true);

/* variable output 25 byte decoded base58 cases */
int i = 0;
for (const unsigned version_byte : CASES) {
std::vector<char> prefixes = Base58PrefixesFromVersionByte(base58_data.size(), version_byte);
std::vector<char> range = RANGES[i];
BOOST_CHECK_MESSAGE(prefixes == range, "Base58 prefix decoder, version byte " << (uint8_t)version_byte << " prefixes " << std::string(prefixes.begin(),prefixes.end()) << " != " << std::string(range.begin(),range.end()) << " with a total len. of 25");
++i;
}

/* static output 25 byte decoded base58 cases */
for (unsigned short version_byte = 0x91; version_byte <= 0xFF; version_byte++) {
std::vector<char> prefixes = Base58PrefixesFromVersionByte(base58_data.size(), version_byte);
std::vector<char> expected(1,'2');
BOOST_CHECK_MESSAGE(prefixes == expected, "Base58 prefix decoder, version byte " << (uint8_t)version_byte << " prefixes " << std::string(prefixes.begin(),prefixes.end()) << " != " << std::string(expected.begin(),expected.end()) << " with a total len. of 25");
}
}

BOOST_AUTO_TEST_SUITE_END()
43 changes: 43 additions & 0 deletions src/util/base58_address.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2023 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 <base58.h>
#include <util/base58_address.h>

#include <algorithm>

std::vector<char> Base58PrefixesFromVersionByte(size_t length, unsigned char version_byte)
{
std::vector<char> base58_prefix_char_range;

if (length) {
static const unsigned char MIN_PAYLOAD_FILL = 0x00;
static const unsigned char MAX_PAYLOAD_FILL = 0xFF;
static const char ENCODED_LEADING_ZEROES = '1';

const std::vector<unsigned char> payload_range = { MIN_PAYLOAD_FILL, MAX_PAYLOAD_FILL };
std::vector<unsigned char> base58_prefix_min_max;

for (const auto& payload : payload_range) {
std::vector<unsigned char> range_test_bound(std::max(1, (int)length), payload);
range_test_bound.at(0) = version_byte;
base58_prefix_min_max.emplace_back(EncodeBase58(range_test_bound).at(0));
}

auto IsBase58Char = [&](char c) { return std::string_view(pszBase58).find_first_of(c) != std::string::npos; };

if (base58_prefix_min_max.front() == ENCODED_LEADING_ZEROES) {
base58_prefix_char_range.emplace_back(base58_prefix_min_max.front());
} else if(IsBase58Char(base58_prefix_min_max.front()) && IsBase58Char(base58_prefix_min_max.back())) {
for (int i = 1; pszBase58[i] != '\0'; i++) {
base58_prefix_char_range.emplace_back(pszBase58[i]);
}
auto start_position = std::find(base58_prefix_char_range.begin(), base58_prefix_char_range.end(), base58_prefix_min_max.front());
std::rotate(base58_prefix_char_range.begin(), start_position, base58_prefix_char_range.end());
base58_prefix_char_range.erase(++std::find(base58_prefix_char_range.begin(), base58_prefix_char_range.end(), base58_prefix_min_max.back()), base58_prefix_char_range.end());
}
}

return base58_prefix_char_range;
};
23 changes: 23 additions & 0 deletions src/util/base58_address.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2023 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_UTIL_BASE58_ADDRESS_H
#define BITCOIN_UTIL_BASE58_ADDRESS_H

#include <cstddef>
#include <vector>

/** Derives the first Base58 character prefixes for a given version byte and a length
*
* @param[in] length length of pre-encoded base58 data
* @param[in] version_byte The address version byte
* @param[out] The possible range of base58 prefixes (eg. ['m','n'])
* @code
* std::vector result = Base58PrefixesFromVersionByte(31,0x05);
* // result will be ['3']
* @endcode
*/
std::vector<char> Base58PrefixesFromVersionByte(size_t length, unsigned char version_byte);

#endif // BITCOIN_UTIL_BASE58_ADDRESS_H

0 comments on commit 2222222

Please sign in to comment.