/ src / test / key_io_tests.cpp
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()