diff options
Diffstat (limited to 'src/test/base58_tests.cpp')
| -rw-r--r-- | src/test/base58_tests.cpp | 325 |
1 files changed, 255 insertions, 70 deletions
diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index de4096cd3..9845df697 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -1,89 +1,274 @@ -#include <boost/test/unit_test.hpp> +// Copyright (c) 2011-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. + +#include "base58.h" -#include "main.h" -#include "wallet.h" +#include "data/base58_encode_decode.json.h" +#include "data/base58_keys_invalid.json.h" +#include "data/base58_keys_valid.json.h" + +#include "key.h" +#include "script/script.h" +#include "uint256.h" #include "util.h" +#include "utilstrencodings.h" +#include "test/test_bitcoin.h" -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}, -}; +#include <boost/foreach.hpp> +#include <boost/test/unit_test.hpp> -const char *vstrOut[] = { -"", -"2g", -"a3gV", -"aPEr", -"2cFupjhnEsSn59qHXstmK2ffpLv2", -"1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L", -"ABnLTmg", -"3SEo3LWLoPntC", -"3EFU7m", -"EJDM8drfXA6uyA", -"Rt5zm", -"1111111111" -}; +#include <univalue.h> + +extern UniValue read_json(const std::string& jsondata); +BOOST_FIXTURE_TEST_SUITE(base58_tests, BasicTestingSetup) + +// 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++) - { - BOOST_CHECK_EQUAL(EncodeBase58(vstrIn[i].data, vstrIn[i].data + vstrIn[i].size), vstrOut[i]); + UniValue tests = read_json(std::string(json_tests::base58_encode_decode, json_tests::base58_encode_decode + sizeof(json_tests::base58_encode_decode))); + for (unsigned int idx = 0; idx < tests.size(); idx++) { + 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> sourcedata = ParseHex(test[0].get_str()); + std::string base58string = test[1].get_str(); + BOOST_CHECK_MESSAGE( + EncodeBase58(begin_ptr(sourcedata), end_ptr(sourcedata)) == base58string, + strTest); } } +// Goal: test low-level base58 decoding functionality BOOST_AUTO_TEST_CASE(base58_DecodeBase58) { + UniValue tests = read_json(std::string(json_tests::base58_encode_decode, json_tests::base58_encode_decode + sizeof(json_tests::base58_encode_decode))); std::vector<unsigned char> result; - for (unsigned int i=0; i<sizeof(vstrIn)/sizeof(vstrIn[0]); i++) - { - 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()); + + for (unsigned int idx = 0; idx < tests.size(); idx++) { + 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), strTest); + BOOST_CHECK_MESSAGE(result.size() == expected.size() && std::equal(result.begin(), result.end(), expected.begin()), strTest); } + BOOST_CHECK(!DecodeBase58("invalid", result)); + + // 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)); + BOOST_CHECK( DecodeBase58(" \t\n\v\f\r skip \r\f\v\n\t ", result)); + std::vector<unsigned char> expected = ParseHex("971a55"); + BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); } +// 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) +{ + UniValue tests = read_json(std::string(json_tests::base58_keys_valid, json_tests::base58_keys_valid + sizeof(json_tests::base58_keys_valid))); + std::vector<unsigned char> result; + CBitcoinSecret secret; + CBitcoinAddress addr; + SelectParams(CBaseChainParams::MAIN); + + for (unsigned int idx = 0; idx < tests.size(); idx++) { + UniValue test = tests[idx]; + std::string strTest = test.write(); + 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 UniValue &metadata = test[2].get_obj(); + bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); + bool isTestnet = find_value(metadata, "isTestnet").get_bool(); + if (isTestnet) + SelectParams(CBaseChainParams::TESTNET); + else + SelectParams(CBaseChainParams::MAIN); + 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); + CKey privkey = secret.GetKey(); + BOOST_CHECK_MESSAGE(privkey.IsCompressed() == 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); + } + } +} + +// Goal: check that generated keys match test vectors +BOOST_AUTO_TEST_CASE(base58_keys_valid_gen) +{ + UniValue tests = read_json(std::string(json_tests::base58_keys_valid, json_tests::base58_keys_valid + sizeof(json_tests::base58_keys_valid))); + std::vector<unsigned char> result; + + for (unsigned int idx = 0; idx < tests.size(); idx++) { + UniValue test = tests[idx]; + std::string strTest = test.write(); + 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 UniValue &metadata = test[2].get_obj(); + bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); + bool isTestnet = find_value(metadata, "isTestnet").get_bool(); + if (isTestnet) + SelectParams(CBaseChainParams::TESTNET); + else + SelectParams(CBaseChainParams::MAIN); + if(isPrivkey) + { + bool isCompressed = find_value(metadata, "isCompressed").get_bool(); + CKey key; + key.Set(exp_payload.begin(), exp_payload.end(), isCompressed); + assert(key.IsValid()); + CBitcoinSecret secret; + secret.SetKey(key); + 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(addrOut.Set(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(!dummyAddr.Set(nodest)); + + SelectParams(CBaseChainParams::MAIN); +} + +// Goal: check that base58 parsing code is robust against a variety of corrupted data +BOOST_AUTO_TEST_CASE(base58_keys_invalid) +{ + UniValue tests = read_json(std::string(json_tests::base58_keys_invalid, json_tests::base58_keys_invalid + sizeof(json_tests::base58_keys_invalid))); // Negative testcases + std::vector<unsigned char> result; + CBitcoinSecret secret; + CBitcoinAddress addr; + + for (unsigned int idx = 0; idx < tests.size(); idx++) { + UniValue test = tests[idx]; + std::string strTest = test.write(); + 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() |