data-driven base58 CBitcoinAddress/CBitcoinSecret tests
Arbitrary numbers of test vectors can be generated using the script `gen_base58_test_vectors.py`.
This commit is contained in:
@@ -1,87 +1,259 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include "json/json_spirit_reader_template.h"
|
||||
#include "json/json_spirit_writer_template.h"
|
||||
#include "json/json_spirit_utils.h"
|
||||
|
||||
#include "base58.h"
|
||||
#include "util.h"
|
||||
|
||||
using namespace json_spirit;
|
||||
extern Array read_json(const std::string& filename);
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(base58_tests)
|
||||
|
||||
// TODO:
|
||||
// EncodeBase58Check
|
||||
// DecodeBase58Check
|
||||
// CBase58Data
|
||||
// bool SetString(const char* psz)
|
||||
// bool SetString(const std::string& str)
|
||||
// std::string ToString() const
|
||||
// int CompareTo(const CBase58Data& b58) const
|
||||
// bool operator==(const CBase58Data& b58) const
|
||||
// bool operator<=(const CBase58Data& b58) const
|
||||
// bool operator>=(const CBase58Data& b58) const
|
||||
// bool operator< (const CBase58Data& b58) const
|
||||
// bool operator> (const CBase58Data& b58) const
|
||||
|
||||
// CBitcoinAddress
|
||||
// bool SetHash160(const uint160& hash160)
|
||||
// bool SetPubKey(const std::vector<unsigned char>& vchPubKey)
|
||||
// bool IsValid() const
|
||||
// CBitcoinAddress()
|
||||
// CBitcoinAddress(uint160 hash160In)
|
||||
// CBitcoinAddress(const std::vector<unsigned char>& vchPubKey)
|
||||
// CBitcoinAddress(const std::string& strAddress)
|
||||
// CBitcoinAddress(const char* pszAddress)
|
||||
// uint160 GetHash160() const
|
||||
|
||||
#define U(x) (reinterpret_cast<const unsigned char*>(x))
|
||||
static struct {
|
||||
const unsigned char *data;
|
||||
int size;
|
||||
} vstrIn[] = {
|
||||
{U(""), 0},
|
||||
{U("\x61"), 1},
|
||||
{U("\x62\x62\x62"), 3},
|
||||
{U("\x63\x63\x63"), 3},
|
||||
{U("\x73\x69\x6d\x70\x6c\x79\x20\x61\x20\x6c\x6f\x6e\x67\x20\x73\x74\x72\x69\x6e\x67"), 20},
|
||||
{U("\x00\xeb\x15\x23\x1d\xfc\xeb\x60\x92\x58\x86\xb6\x7d\x06\x52\x99\x92\x59\x15\xae\xb1\x72\xc0\x66\x47"), 25},
|
||||
{U("\x51\x6b\x6f\xcd\x0f"), 5},
|
||||
{U("\xbf\x4f\x89\x00\x1e\x67\x02\x74\xdd"), 9},
|
||||
{U("\x57\x2e\x47\x94"), 4},
|
||||
{U("\xec\xac\x89\xca\xd9\x39\x23\xc0\x23\x21"), 10},
|
||||
{U("\x10\xc8\x51\x1e"), 4},
|
||||
{U("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), 10},
|
||||
};
|
||||
|
||||
const char *vstrOut[] = {
|
||||
"",
|
||||
"2g",
|
||||
"a3gV",
|
||||
"aPEr",
|
||||
"2cFupjhnEsSn59qHXstmK2ffpLv2",
|
||||
"1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L",
|
||||
"ABnLTmg",
|
||||
"3SEo3LWLoPntC",
|
||||
"3EFU7m",
|
||||
"EJDM8drfXA6uyA",
|
||||
"Rt5zm",
|
||||
"1111111111"
|
||||
};
|
||||
|
||||
// Goal: test low-level base58 encoding functionality
|
||||
BOOST_AUTO_TEST_CASE(base58_EncodeBase58)
|
||||
{
|
||||
for (unsigned int i=0; i<sizeof(vstrIn)/sizeof(vstrIn[0]); i++)
|
||||
Array tests = read_json("base58_encode_decode.json");
|
||||
|
||||
BOOST_FOREACH(Value& tv, tests)
|
||||
{
|
||||
BOOST_CHECK_EQUAL(EncodeBase58(vstrIn[i].data, vstrIn[i].data + vstrIn[i].size), vstrOut[i]);
|
||||
Array test = tv.get_array();
|
||||
std::string strTest = write_string(tv, false);
|
||||
if (test.size() < 2) // Allow for extra stuff (useful for comments)
|
||||
{
|
||||
BOOST_ERROR("Bad test: " << strTest);
|
||||
continue;
|
||||
}
|
||||
std::vector<unsigned char> sourcedata = ParseHex(test[0].get_str());
|
||||
std::string base58string = test[1].get_str();
|
||||
BOOST_CHECK_MESSAGE(
|
||||
EncodeBase58(&sourcedata[0], &sourcedata[sourcedata.size()]) == base58string,
|
||||
strTest);
|
||||
}
|
||||
}
|
||||
|
||||
// Goal: test low-level base58 decoding functionality
|
||||
BOOST_AUTO_TEST_CASE(base58_DecodeBase58)
|
||||
{
|
||||
Array tests = read_json("base58_encode_decode.json");
|
||||
std::vector<unsigned char> result;
|
||||
for (unsigned int i=0; i<sizeof(vstrIn)/sizeof(vstrIn[0]); i++)
|
||||
|
||||
BOOST_FOREACH(Value& tv, tests)
|
||||
{
|
||||
std::vector<unsigned char> expected(vstrIn[i].data, vstrIn[i].data + vstrIn[i].size);
|
||||
BOOST_CHECK(DecodeBase58(vstrOut[i], result));
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end());
|
||||
Array test = tv.get_array();
|
||||
std::string strTest = write_string(tv, false);
|
||||
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), strTest);
|
||||
BOOST_CHECK_MESSAGE(result.size() == expected.size() && std::equal(result.begin(), result.end(), expected.begin()), strTest);
|
||||
}
|
||||
|
||||
BOOST_CHECK(!DecodeBase58("invalid", result));
|
||||
}
|
||||
|
||||
// Visitor to check address type
|
||||
class TestAddrTypeVisitor : public boost::static_visitor<bool>
|
||||
{
|
||||
private:
|
||||
std::string exp_addrType;
|
||||
public:
|
||||
TestAddrTypeVisitor(const std::string &exp_addrType) : exp_addrType(exp_addrType) { }
|
||||
bool operator()(const CKeyID &id) const
|
||||
{
|
||||
return (exp_addrType == "pubkey");
|
||||
}
|
||||
bool operator()(const CScriptID &id) const
|
||||
{
|
||||
return (exp_addrType == "script");
|
||||
}
|
||||
bool operator()(const CNoDestination &no) const
|
||||
{
|
||||
return (exp_addrType == "none");
|
||||
}
|
||||
};
|
||||
|
||||
// Visitor to check address payload
|
||||
class TestPayloadVisitor : public boost::static_visitor<bool>
|
||||
{
|
||||
private:
|
||||
std::vector<unsigned char> exp_payload;
|
||||
public:
|
||||
TestPayloadVisitor(std::vector<unsigned char> &exp_payload) : exp_payload(exp_payload) { }
|
||||
bool operator()(const CKeyID &id) const
|
||||
{
|
||||
uint160 exp_key(exp_payload);
|
||||
return exp_key == id;
|
||||
}
|
||||
bool operator()(const CScriptID &id) const
|
||||
{
|
||||
uint160 exp_key(exp_payload);
|
||||
return exp_key == id;
|
||||
}
|
||||
bool operator()(const CNoDestination &no) const
|
||||
{
|
||||
return exp_payload.size() == 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Goal: check that parsed keys match test payload
|
||||
BOOST_AUTO_TEST_CASE(base58_keys_valid_parse)
|
||||
{
|
||||
Array tests = read_json("base58_keys_valid.json");
|
||||
std::vector<unsigned char> result;
|
||||
CBitcoinSecret secret;
|
||||
CBitcoinAddress addr;
|
||||
// Save global state
|
||||
bool fTestNet_stored = fTestNet;
|
||||
|
||||
BOOST_FOREACH(Value& tv, tests)
|
||||
{
|
||||
Array test = tv.get_array();
|
||||
std::string strTest = write_string(tv, false);
|
||||
if (test.size() < 3) // Allow for extra stuff (useful for comments)
|
||||
{
|
||||
BOOST_ERROR("Bad test: " << strTest);
|
||||
continue;
|
||||
}
|
||||
std::string exp_base58string = test[0].get_str();
|
||||
std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str());
|
||||
const Object &metadata = test[2].get_obj();
|
||||
bool isPrivkey = find_value(metadata, "isPrivkey").get_bool();
|
||||
bool isTestnet = find_value(metadata, "isTestnet").get_bool();
|
||||
fTestNet = isTestnet; // Override testnet flag
|
||||
if(isPrivkey)
|
||||
{
|
||||
bool isCompressed = find_value(metadata, "isCompressed").get_bool();
|
||||
// Must be valid private key
|
||||
// Note: CBitcoinSecret::SetString tests isValid, whereas CBitcoinAddress does not!
|
||||
BOOST_CHECK_MESSAGE(secret.SetString(exp_base58string), "!SetString:"+ strTest);
|
||||
BOOST_CHECK_MESSAGE(secret.IsValid(), "!IsValid:" + strTest);
|
||||
bool fCompressedOut = false;
|
||||
CSecret privkey = secret.GetSecret(fCompressedOut);
|
||||
BOOST_CHECK_MESSAGE(fCompressedOut == isCompressed, "compressed mismatch:" + strTest);
|
||||
BOOST_CHECK_MESSAGE(privkey.size() == exp_payload.size() && std::equal(privkey.begin(), privkey.end(), exp_payload.begin()), "key mismatch:" + strTest);
|
||||
|
||||
// Private key must be invalid public key
|
||||
addr.SetString(exp_base58string);
|
||||
BOOST_CHECK_MESSAGE(!addr.IsValid(), "IsValid privkey as pubkey:" + strTest);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string exp_addrType = find_value(metadata, "addrType").get_str(); // "script" or "pubkey"
|
||||
// Must be valid public key
|
||||
BOOST_CHECK_MESSAGE(addr.SetString(exp_base58string), "SetString:" + strTest);
|
||||
BOOST_CHECK_MESSAGE(addr.IsValid(), "!IsValid:" + strTest);
|
||||
BOOST_CHECK_MESSAGE(addr.IsScript() == (exp_addrType == "script"), "isScript mismatch" + strTest);
|
||||
CTxDestination dest = addr.Get();
|
||||
BOOST_CHECK_MESSAGE(boost::apply_visitor(TestAddrTypeVisitor(exp_addrType), dest), "addrType mismatch" + strTest);
|
||||
|
||||
// Public key must be invalid private key
|
||||
secret.SetString(exp_base58string);
|
||||
BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid pubkey as privkey:" + strTest);
|
||||
}
|
||||
}
|
||||
// Restore global state
|
||||
fTestNet = fTestNet_stored;
|
||||
}
|
||||
|
||||
// Goal: check that generated keys match test vectors
|
||||
BOOST_AUTO_TEST_CASE(base58_keys_valid_gen)
|
||||
{
|
||||
Array tests = read_json("base58_keys_valid.json");
|
||||
std::vector<unsigned char> result;
|
||||
// Save global state
|
||||
bool fTestNet_stored = fTestNet;
|
||||
|
||||
BOOST_FOREACH(Value& tv, tests)
|
||||
{
|
||||
Array test = tv.get_array();
|
||||
std::string strTest = write_string(tv, false);
|
||||
if (test.size() < 3) // Allow for extra stuff (useful for comments)
|
||||
{
|
||||
BOOST_ERROR("Bad test: " << strTest);
|
||||
continue;
|
||||
}
|
||||
std::string exp_base58string = test[0].get_str();
|
||||
std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str());
|
||||
const Object &metadata = test[2].get_obj();
|
||||
bool isPrivkey = find_value(metadata, "isPrivkey").get_bool();
|
||||
bool isTestnet = find_value(metadata, "isTestnet").get_bool();
|
||||
fTestNet = isTestnet; // Override testnet flag
|
||||
if(isPrivkey)
|
||||
{
|
||||
bool isCompressed = find_value(metadata, "isCompressed").get_bool();
|
||||
CBitcoinSecret secret;
|
||||
secret.SetSecret(CSecret(exp_payload.begin(), exp_payload.end()), isCompressed);
|
||||
BOOST_CHECK_MESSAGE(secret.ToString() == exp_base58string, "result mismatch: " + strTest);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string exp_addrType = find_value(metadata, "addrType").get_str();
|
||||
CTxDestination dest;
|
||||
if(exp_addrType == "pubkey")
|
||||
{
|
||||
dest = CKeyID(uint160(exp_payload));
|
||||
}
|
||||
else if(exp_addrType == "script")
|
||||
{
|
||||
dest = CScriptID(uint160(exp_payload));
|
||||
}
|
||||
else if(exp_addrType == "none")
|
||||
{
|
||||
dest = CNoDestination();
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOST_ERROR("Bad addrtype: " << strTest);
|
||||
continue;
|
||||
}
|
||||
CBitcoinAddress addrOut;
|
||||
BOOST_CHECK_MESSAGE(boost::apply_visitor(CBitcoinAddressVisitor(&addrOut), dest), "encode dest: " + strTest);
|
||||
BOOST_CHECK_MESSAGE(addrOut.ToString() == exp_base58string, "mismatch: " + strTest);
|
||||
}
|
||||
}
|
||||
|
||||
// Visiting a CNoDestination must fail
|
||||
CBitcoinAddress dummyAddr;
|
||||
CTxDestination nodest = CNoDestination();
|
||||
BOOST_CHECK(!boost::apply_visitor(CBitcoinAddressVisitor(&dummyAddr), nodest));
|
||||
|
||||
// Restore global state
|
||||
fTestNet = fTestNet_stored;
|
||||
}
|
||||
|
||||
// Goal: check that base58 parsing code is robust against a variety of corrupted data
|
||||
BOOST_AUTO_TEST_CASE(base58_keys_invalid)
|
||||
{
|
||||
Array tests = read_json("base58_keys_invalid.json"); // Negative testcases
|
||||
std::vector<unsigned char> result;
|
||||
CBitcoinSecret secret;
|
||||
CBitcoinAddress addr;
|
||||
|
||||
BOOST_FOREACH(Value& tv, tests)
|
||||
{
|
||||
Array test = tv.get_array();
|
||||
std::string strTest = write_string(tv, false);
|
||||
if (test.size() < 1) // Allow for extra stuff (useful for comments)
|
||||
{
|
||||
BOOST_ERROR("Bad test: " << strTest);
|
||||
continue;
|
||||
}
|
||||
std::string exp_base58string = test[0].get_str();
|
||||
|
||||
// must be invalid as public and as private key
|
||||
addr.SetString(exp_base58string);
|
||||
BOOST_CHECK_MESSAGE(!addr.IsValid(), "IsValid pubkey:" + strTest);
|
||||
secret.SetString(exp_base58string);
|
||||
BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid privkey:" + strTest);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user