key_io_tests.cpp
1 // Copyright (c) 2011-2022 The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <test/data/key_io_invalid.json.h> 6 #include <test/data/key_io_valid.json.h> 7 8 #include <key.h> 9 #include <key_io.h> 10 #include <script/script.h> 11 #include <test/util/json.h> 12 #include <test/util/setup_common.h> 13 #include <util/chaintype.h> 14 #include <util/strencodings.h> 15 16 #include <boost/test/unit_test.hpp> 17 18 #include <univalue.h> 19 20 BOOST_FIXTURE_TEST_SUITE(key_io_tests, BasicTestingSetup) 21 22 // Goal: check that parsed keys match test payload 23 BOOST_AUTO_TEST_CASE(key_io_valid_parse) 24 { 25 UniValue tests = read_json(json_tests::key_io_valid); 26 CKey privkey; 27 CTxDestination destination; 28 SelectParams(ChainType::MAIN); 29 30 for (unsigned int idx = 0; idx < tests.size(); idx++) { 31 const UniValue& test = tests[idx]; 32 std::string strTest = test.write(); 33 if (test.size() < 3) { // Allow for extra stuff (useful for comments) 34 BOOST_ERROR("Bad test: " << strTest); 35 continue; 36 } 37 std::string exp_base58string = test[0].get_str(); 38 const std::vector<std::byte> exp_payload{ParseHex<std::byte>(test[1].get_str())}; 39 const UniValue &metadata = test[2].get_obj(); 40 bool isPrivkey = metadata.find_value("isPrivkey").get_bool(); 41 SelectParams(ChainTypeFromString(metadata.find_value("chain").get_str()).value()); 42 bool try_case_flip = metadata.find_value("tryCaseFlip").isNull() ? false : metadata.find_value("tryCaseFlip").get_bool(); 43 if (isPrivkey) { 44 bool isCompressed = metadata.find_value("isCompressed").get_bool(); 45 // Must be valid private key 46 privkey = DecodeSecret(exp_base58string); 47 BOOST_CHECK_MESSAGE(privkey.IsValid(), "!IsValid:" + strTest); 48 BOOST_CHECK_MESSAGE(privkey.IsCompressed() == isCompressed, "compressed mismatch:" + strTest); 49 BOOST_CHECK_MESSAGE(Span{privkey} == Span{exp_payload}, "key mismatch:" + strTest); 50 51 // Private key must be invalid public key 52 destination = DecodeDestination(exp_base58string); 53 BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid privkey as pubkey:" + strTest); 54 } else { 55 // Must be valid public key 56 destination = DecodeDestination(exp_base58string); 57 CScript script = GetScriptForDestination(destination); 58 BOOST_CHECK_MESSAGE(IsValidDestination(destination), "!IsValid:" + strTest); 59 BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload)); 60 61 // Try flipped case version 62 for (char& c : exp_base58string) { 63 if (c >= 'a' && c <= 'z') { 64 c = (c - 'a') + 'A'; 65 } else if (c >= 'A' && c <= 'Z') { 66 c = (c - 'A') + 'a'; 67 } 68 } 69 destination = DecodeDestination(exp_base58string); 70 BOOST_CHECK_MESSAGE(IsValidDestination(destination) == try_case_flip, "!IsValid case flipped:" + strTest); 71 if (IsValidDestination(destination)) { 72 script = GetScriptForDestination(destination); 73 BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload)); 74 } 75 76 // Public key must be invalid private key 77 privkey = DecodeSecret(exp_base58string); 78 BOOST_CHECK_MESSAGE(!privkey.IsValid(), "IsValid pubkey as privkey:" + strTest); 79 } 80 } 81 } 82 83 // Goal: check that generated keys match test vectors 84 BOOST_AUTO_TEST_CASE(key_io_valid_gen) 85 { 86 UniValue tests = read_json(json_tests::key_io_valid); 87 88 for (unsigned int idx = 0; idx < tests.size(); idx++) { 89 const UniValue& test = tests[idx]; 90 std::string strTest = test.write(); 91 if (test.size() < 3) // Allow for extra stuff (useful for comments) 92 { 93 BOOST_ERROR("Bad test: " << strTest); 94 continue; 95 } 96 std::string exp_base58string = test[0].get_str(); 97 std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str()); 98 const UniValue &metadata = test[2].get_obj(); 99 bool isPrivkey = metadata.find_value("isPrivkey").get_bool(); 100 SelectParams(ChainTypeFromString(metadata.find_value("chain").get_str()).value()); 101 if (isPrivkey) { 102 bool isCompressed = metadata.find_value("isCompressed").get_bool(); 103 CKey key; 104 key.Set(exp_payload.begin(), exp_payload.end(), isCompressed); 105 assert(key.IsValid()); 106 BOOST_CHECK_MESSAGE(EncodeSecret(key) == exp_base58string, "result mismatch: " + strTest); 107 } else { 108 CTxDestination dest; 109 CScript exp_script(exp_payload.begin(), exp_payload.end()); 110 BOOST_CHECK(ExtractDestination(exp_script, dest)); 111 std::string address = EncodeDestination(dest); 112 113 BOOST_CHECK_EQUAL(address, exp_base58string); 114 } 115 } 116 117 SelectParams(ChainType::MAIN); 118 } 119 120 121 // Goal: check that base58 parsing code is robust against a variety of corrupted data 122 BOOST_AUTO_TEST_CASE(key_io_invalid) 123 { 124 UniValue tests = read_json(json_tests::key_io_invalid); // Negative testcases 125 CKey privkey; 126 CTxDestination destination; 127 128 for (unsigned int idx = 0; idx < tests.size(); idx++) { 129 const UniValue& test = tests[idx]; 130 std::string strTest = test.write(); 131 if (test.size() < 1) // Allow for extra stuff (useful for comments) 132 { 133 BOOST_ERROR("Bad test: " << strTest); 134 continue; 135 } 136 std::string exp_base58string = test[0].get_str(); 137 138 // must be invalid as public and as private key 139 for (const auto& chain : {ChainType::MAIN, ChainType::TESTNET, ChainType::SIGNET, ChainType::REGTEST}) { 140 SelectParams(chain); 141 destination = DecodeDestination(exp_base58string); 142 BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid pubkey in mainnet:" + strTest); 143 privkey = DecodeSecret(exp_base58string); 144 BOOST_CHECK_MESSAGE(!privkey.IsValid(), "IsValid privkey in mainnet:" + strTest); 145 } 146 } 147 } 148 149 BOOST_AUTO_TEST_SUITE_END()