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