Created
July 2, 2024 07:13
-
-
Save l0rinc/96d8e355f6fef10ac79f62d89a6d9f19 to your computer and use it in GitHub Desktop.
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
// Copyright (c) 2011-2022 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 <test/data/base58_encode_decode.json.h> | |
#include <base58.h> | |
#include <test/util/json.h> | |
#include <test/util/random.h> | |
#include <test/util/setup_common.h> | |
#include <util/strencodings.h> | |
#include <util/vector.h> | |
#include <base58.h> | |
#include <hash.h> | |
#include <uint256.h> | |
#include <util/strencodings.h> | |
#include <util/string.h> | |
#include <assert.h> | |
#include <string.h> | |
#include <limits> | |
#include <univalue.h> | |
#include <boost/test/unit_test.hpp> | |
#include <string> | |
using util::ContainsNoNUL; | |
using namespace std::literals; | |
BOOST_FIXTURE_TEST_SUITE(base58_tests, BasicTestingSetup) | |
// Goal: test low-level base58 encoding functionality | |
BOOST_AUTO_TEST_CASE(base58_EncodeBase58) | |
{ | |
UniValue tests = read_json(json_tests::base58_encode_decode); | |
for (unsigned int idx = 0; idx < tests.size(); idx++) { | |
const UniValue& test = tests[idx]; | |
auto strTest = test.write(); | |
if (test.size() < 2) // Allow for extra stuff (useful for comments) | |
{ | |
BOOST_ERROR("Bad test: " << strTest); | |
continue; | |
} | |
auto encodedSource = EncodeBase58(ParseHex(test[0].get_str())); | |
auto base58string = test[1].get_str(); | |
BOOST_CHECK_MESSAGE( | |
encodedSource == base58string, | |
strTest << ": got \"" << encodedSource << "\"" | |
); | |
} | |
} | |
// Goal: test low-level base58 decoding functionality | |
BOOST_AUTO_TEST_CASE(base58_DecodeBase58) | |
{ | |
UniValue tests = read_json(json_tests::base58_encode_decode); | |
std::vector<unsigned char> result; | |
for (unsigned int idx = 0; idx < tests.size(); idx++) { | |
const UniValue& test = tests[idx]; | |
std::string strTest = test.write(); | |
if (test.size() < 2) // Allow for extra stuff (useful for comments) | |
{ | |
BOOST_ERROR("Bad test: " << strTest); | |
continue; | |
} | |
std::vector<unsigned char> expected = ParseHex(test[0].get_str()); | |
std::string base58string = test[1].get_str(); | |
BOOST_CHECK_MESSAGE(DecodeBase58(base58string, result, 256), strTest); | |
BOOST_CHECK_MESSAGE( | |
result == expected, | |
strTest << ": got \"" << HexStr(result) << "\"" | |
); | |
} | |
BOOST_CHECK(!DecodeBase58("invalid"s, result, 100)); | |
BOOST_CHECK(!DecodeBase58("invalid\0"s, result, 100)); | |
BOOST_CHECK(!DecodeBase58("\0invalid"s, result, 100)); | |
BOOST_CHECK(DecodeBase58("good"s, result, 100)); | |
BOOST_CHECK(!DecodeBase58("bad0IOl"s, result, 100)); | |
BOOST_CHECK(!DecodeBase58("goodbad0IOl"s, result, 100)); | |
BOOST_CHECK(!DecodeBase58("good\0bad0IOl"s, result, 100)); | |
// check that DecodeBase58 skips whitespace, but still fails with unexpected non-whitespace at the end. | |
BOOST_CHECK(!DecodeBase58(" \t\n\v\f\r skip \r\f\v\n\t a", result, 3)); | |
BOOST_CHECK(DecodeBase58(" \t\n\v\f\r skip \r\f\v\n\t ", result, 3)); | |
std::vector<unsigned char> expected = ParseHex("971a55"); | |
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); | |
BOOST_CHECK(DecodeBase58Check("3vQB7B6MrGQZaxCuFg4oh"s, result, 100)); | |
BOOST_CHECK(!DecodeBase58Check("3vQB7B6MrGQZaxCuFg4oi"s, result, 100)); | |
BOOST_CHECK(!DecodeBase58Check("3vQB7B6MrGQZaxCuFg4oh0IOl"s, result, 100)); | |
BOOST_CHECK(!DecodeBase58Check("3vQB7B6MrGQZaxCuFg4oh\0" "0IOl"s, result, 100)); | |
} | |
/** All alphanumeric characters except for "0", "I", "O", and "l" */ | |
static const char* pszBase58_old = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; | |
static const int8_t mapBase58_old[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, | |
-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, | |
-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,-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,-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, | |
}; | |
[[nodiscard]] static bool DecodeBase58_old(const char* psz, std::vector<unsigned char>& vch, int max_ret_len) | |
{ | |
// Skip leading spaces. | |
while (*psz && IsSpace(*psz)) | |
psz++; | |
// Skip and count leading '1's. | |
int zeroes = 0; | |
int length = 0; | |
while (*psz == '1') { | |
zeroes++; | |
if (zeroes > max_ret_len) return false; | |
psz++; | |
} | |
// Allocate enough space in big-endian base256 representation. | |
int size = strlen(psz) * 733 /1000 + 1; // log(58) / log(256), rounded up. | |
std::vector<unsigned char> b256(size); | |
// Process the characters. | |
static_assert(std::size(mapBase58_old) == 256, "mapBase58_old.size() should be 256"); // guarantee not out of range | |
while (*psz && !IsSpace(*psz)) { | |
// Decode base58 character | |
int carry = mapBase58_old[(uint8_t)*psz]; | |
if (carry == -1) // Invalid b58 character | |
return false; | |
int i = 0; | |
for (std::vector<unsigned char>::reverse_iterator it = b256.rbegin(); (carry != 0 || i < length) && (it != b256.rend()); ++it, ++i) { | |
carry += 58 * (*it); | |
*it = carry % 256; | |
carry /= 256; | |
} | |
assert(carry == 0); | |
length = i; | |
if (length + zeroes > max_ret_len) return false; | |
psz++; | |
} | |
// Skip trailing spaces. | |
while (IsSpace(*psz)) | |
psz++; | |
if (*psz != 0) | |
return false; | |
// Skip leading zeroes in b256. | |
std::vector<unsigned char>::iterator it = b256.begin() + (size - length); | |
// Copy result into output vector. | |
vch.reserve(zeroes + (b256.end() - it)); | |
vch.assign(zeroes, 0x00); | |
while (it != b256.end()) | |
vch.push_back(*(it++)); | |
return true; | |
} | |
std::string EncodeBase58_old(Span<const unsigned char> input) | |
{ | |
// Skip & count leading zeroes. | |
int zeroes = 0; | |
int length = 0; | |
while (input.size() > 0 && input[0] == 0) { | |
input = input.subspan(1); | |
zeroes++; | |
} | |
// Allocate enough space in big-endian base58 representation. | |
int size = input.size() * 138 / 100 + 1; // log(256) / log(58), rounded up. | |
std::vector<unsigned char> b58(size); | |
// Process the bytes. | |
while (input.size() > 0) { | |
int carry = input[0]; | |
int i = 0; | |
// Apply "b58 = b58 * 256 + ch". | |
for (std::vector<unsigned char>::reverse_iterator it = b58.rbegin(); (carry != 0 || i < length) && (it != b58.rend()); it++, i++) { | |
carry += 256 * (*it); | |
*it = carry % 58; | |
carry /= 58; | |
} | |
assert(carry == 0); | |
length = i; | |
input = input.subspan(1); | |
} | |
// Skip leading zeroes in base58 result. | |
std::vector<unsigned char>::iterator it = b58.begin() + (size - 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, '1'); | |
while (it != b58.end()) | |
str += pszBase58_old[*(it++)]; | |
return str; | |
} | |
BOOST_AUTO_TEST_CASE(base58_random_encode_decode_with_optional_spaces) | |
{ | |
for (int n = 0; n < 10'000'000; ++n) { | |
auto len = 1 + InsecureRandBits(8); | |
auto zeroes = InsecureRandBool() ? InsecureRandRange(len + 1) : 0; | |
auto data = Cat(std::vector<unsigned char>(zeroes, '\000'), g_insecure_rand_ctx.randbytes(len - zeroes)); | |
auto leadingSpaces = InsecureRandBool() ? std::string(InsecureRandRange(10), ' ') : ""; | |
auto trailingSpaces = InsecureRandBool() ? std::string(InsecureRandRange(10), ' ') : ""; | |
// Test encoding | |
auto encoded = leadingSpaces + EncodeBase58(data) + trailingSpaces; | |
auto encoded_old = leadingSpaces + EncodeBase58_old(data) + trailingSpaces; | |
BOOST_CHECK_MESSAGE(encoded == encoded_old, "New and old encoding should match for input: " << HexStr(data)); | |
// Test decoding | |
std::vector<unsigned char> decoded, decoded_old; | |
auto invalidSmallResultLength = InsecureRandRange(len); | |
bool new_decode_result = DecodeBase58(encoded, decoded, invalidSmallResultLength); | |
bool old_decode_result = DecodeBase58_old(encoded_old.c_str(), decoded_old, invalidSmallResultLength); | |
BOOST_CHECK_MESSAGE(new_decode_result == old_decode_result, | |
"New and old decoding should have the same result for invalid length: " << invalidSmallResultLength); | |
auto maxResultLength = len + InsecureRandRange(257 - len); | |
new_decode_result = DecodeBase58(encoded, decoded, maxResultLength); | |
old_decode_result = DecodeBase58_old(encoded_old.c_str(), decoded_old, maxResultLength); | |
BOOST_CHECK_MESSAGE(new_decode_result == old_decode_result, | |
"New and old decoding should have the same result for max length: " << maxResultLength); | |
if (new_decode_result && old_decode_result) { | |
BOOST_CHECK_MESSAGE(decoded == decoded_old, | |
"New and old decoding should produce the same output for input: " << encoded); | |
BOOST_CHECK_MESSAGE(data == decoded, | |
"Decoding `" << encoded << "` as `" << HexStr(decoded) << "` should match `" << HexStr(data) << "`"); | |
} | |
} | |
} | |
BOOST_AUTO_TEST_SUITE_END() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment