LCOV - code coverage report
Current view: top level - src/test - key_io_tests.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 149 158 94.3 %
Date: 2026-06-25 07:23:51 Functions: 43 43 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2011-2021 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 <bech32.h>
       9             : #include <chainparams.h>
      10             : #include <key.h>
      11             : #include <key_io.h>
      12             : #include <script/script.h>
      13             : #include <test/util/json.h>
      14             : #include <test/util/setup_common.h>
      15             : #include <util/strencodings.h>
      16             : 
      17             : #include <boost/test/unit_test.hpp>
      18             : 
      19             : #include <univalue.h>
      20             : 
      21         146 : BOOST_FIXTURE_TEST_SUITE(key_io_tests, BasicTestingSetup)
      22             : 
      23             : // Goal: check that parsed keys match test payload
      24         149 : BOOST_AUTO_TEST_CASE(key_io_valid_parse)
      25             : {
      26           1 :     UniValue tests = read_json(std::string(json_tests::key_io_valid, json_tests::key_io_valid + sizeof(json_tests::key_io_valid)));
      27           1 :     CKey privkey;
      28           1 :     CTxDestination destination;
      29           1 :     SelectParams(CBaseChainParams::MAIN);
      30             : 
      31          51 :     for (unsigned int idx = 0; idx < tests.size(); idx++) {
      32          50 :         const UniValue& test = tests[idx];
      33          50 :         std::string strTest = test.write();
      34          50 :         if (test.size() < 3) { // Allow for extra stuff (useful for comments)
      35           0 :             BOOST_ERROR("Bad test: " << strTest);
      36           0 :             continue;
      37             :         }
      38          50 :         std::string exp_base58string = test[0].get_str();
      39          50 :         const std::vector<std::byte> exp_payload{ParseHex<std::byte>(test[1].get_str())};
      40          50 :         const UniValue &metadata = test[2].get_obj();
      41          50 :         bool isPrivkey = metadata.find_value("isPrivkey").get_bool();
      42          50 :         SelectParams(metadata.find_value("chain").get_str());
      43          50 :         bool try_case_flip = metadata.find_value("tryCaseFlip").isNull() ? false : metadata.find_value("tryCaseFlip").get_bool();
      44          50 :         if (isPrivkey) {
      45          24 :             bool isCompressed = metadata.find_value("isCompressed").get_bool();
      46             :             // Must be valid private key
      47          24 :             privkey = DecodeSecret(exp_base58string);
      48          24 :             BOOST_CHECK_MESSAGE(privkey.IsValid(), "!IsValid:" + strTest);
      49          24 :             BOOST_CHECK_MESSAGE(privkey.IsCompressed() == isCompressed, "compressed mismatch:" + strTest);
      50          24 :             BOOST_CHECK_MESSAGE(Span{privkey} == Span{exp_payload}, "key mismatch:" + strTest);
      51             : 
      52             :             // Private key must be invalid public key
      53          24 :             destination = DecodeDestination(exp_base58string);
      54          24 :             BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid privkey as pubkey:" + strTest);
      55          24 :         } else {
      56             :             // Must be valid public key
      57          26 :             destination = DecodeDestination(exp_base58string);
      58          26 :             CScript script = GetScriptForDestination(destination);
      59          26 :             BOOST_CHECK_MESSAGE(IsValidDestination(destination), "!IsValid:" + strTest);
      60          26 :             BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload));
      61             : 
      62             :             // Try flipped case version
      63         910 :             for (char& c : exp_base58string) {
      64         884 :                 if (c >= 'a' && c <= 'z') {
      65         378 :                     c = (c - 'a') + 'A';
      66         884 :                 } else if (c >= 'A' && c <= 'Z') {
      67         364 :                     c = (c - 'A') + 'a';
      68         364 :                 }
      69             :             }
      70          26 :             destination = DecodeDestination(exp_base58string);
      71          26 :             BOOST_CHECK_MESSAGE(IsValidDestination(destination) == try_case_flip, "!IsValid case flipped:" + strTest);
      72          26 :             if (IsValidDestination(destination)) {
      73           0 :                 script = GetScriptForDestination(destination);
      74           0 :                 BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload));
      75           0 :             }
      76             : 
      77             :             // Public key must be invalid private key
      78          26 :             privkey = DecodeSecret(exp_base58string);
      79          26 :             BOOST_CHECK_MESSAGE(!privkey.IsValid(), "IsValid pubkey as privkey:" + strTest);
      80          26 :         }
      81          50 :     }
      82           1 : }
      83             : 
      84             : // Goal: check that generated keys match test vectors
      85         149 : BOOST_AUTO_TEST_CASE(key_io_valid_gen)
      86             : {
      87           1 :     UniValue tests = read_json(std::string(json_tests::key_io_valid, json_tests::key_io_valid + sizeof(json_tests::key_io_valid)));
      88             : 
      89          51 :     for (unsigned int idx = 0; idx < tests.size(); idx++) {
      90          50 :         const UniValue& test = tests[idx];
      91          50 :         std::string strTest = test.write();
      92          50 :         if (test.size() < 3) // Allow for extra stuff (useful for comments)
      93             :         {
      94           0 :             BOOST_ERROR("Bad test: " << strTest);
      95           0 :             continue;
      96             :         }
      97          50 :         std::string exp_base58string = test[0].get_str();
      98          50 :         std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str());
      99          50 :         const UniValue &metadata = test[2].get_obj();
     100          50 :         bool isPrivkey = metadata.find_value("isPrivkey").get_bool();
     101          50 :         SelectParams(metadata.find_value("chain").get_str());
     102          50 :         if (isPrivkey) {
     103          24 :             bool isCompressed = metadata.find_value("isCompressed").get_bool();
     104          24 :             CKey key;
     105          24 :             key.Set(exp_payload.begin(), exp_payload.end(), isCompressed);
     106          24 :             assert(key.IsValid());
     107          24 :             BOOST_CHECK_MESSAGE(EncodeSecret(key) == exp_base58string, "result mismatch: " + strTest);
     108          24 :         } else {
     109          26 :             CTxDestination dest;
     110          26 :             CScript exp_script(exp_payload.begin(), exp_payload.end());
     111          26 :             BOOST_CHECK(ExtractDestination(exp_script, dest));
     112          26 :             std::string address = EncodeDestination(dest);
     113             : 
     114          26 :             BOOST_CHECK_EQUAL(address, exp_base58string);
     115          26 :         }
     116          50 :     }
     117             : 
     118           1 :     SelectParams(CBaseChainParams::MAIN);
     119           1 : }
     120             : 
     121             : 
     122             : // Goal: check that base58 parsing code is robust against a variety of corrupted data
     123         149 : BOOST_AUTO_TEST_CASE(key_io_invalid)
     124             : {
     125           1 :     UniValue tests = read_json(std::string(json_tests::key_io_invalid, json_tests::key_io_invalid + sizeof(json_tests::key_io_invalid))); // Negative testcases
     126           1 :     CKey privkey;
     127           1 :     CTxDestination destination;
     128             : 
     129          51 :     for (unsigned int idx = 0; idx < tests.size(); idx++) {
     130          50 :         const UniValue& test = tests[idx];
     131          50 :         std::string strTest = test.write();
     132          50 :         if (test.size() < 1) // Allow for extra stuff (useful for comments)
     133             :         {
     134           0 :             BOOST_ERROR("Bad test: " << strTest);
     135           0 :             continue;
     136             :         }
     137          50 :         std::string exp_base58string = test[0].get_str();
     138             : 
     139             :         // must be invalid as public and as private key
     140         200 :         for (const auto& chain : { CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::REGTEST }) {
     141         150 :             SelectParams(chain);
     142         150 :             destination = DecodeDestination(exp_base58string);
     143         150 :             BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid pubkey in mainnet:" + strTest);
     144         150 :             privkey = DecodeSecret(exp_base58string);
     145         150 :             BOOST_CHECK_MESSAGE(!privkey.IsValid(), "IsValid privkey in mainnet:" + strTest);
     146             :         }
     147          50 :     }
     148           1 : }
     149             : 
     150             : // DIP-18: Dash Platform bech32m address encoding.
     151         149 : BOOST_AUTO_TEST_CASE(dip18_platform_roundtrip)
     152             : {
     153             :     struct Sample {
     154             :         std::string hash_hex;
     155             :         std::string address;
     156             :         std::string chain;
     157             :         bool is_p2sh;
     158             :     };
     159             :     // Samples from DIP-0018 (Test Vectors section).
     160           8 :     const Sample samples[] = {
     161           1 :         {"f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525", "dash1krma5z3ttj75la4m93xcndna9ullamq9y5e9n5rs",  CBaseChainParams::MAIN, false},
     162           1 :         {"a5ff0046217fd1c7d238e3e146cc5bfd90832a7e", "dash1kzjl7qzxy9lar37j8r37z3kvt07epqe20ckxfezw",  CBaseChainParams::MAIN, false},
     163           1 :         {"6d92674fd64472a3dfcfc3ebcfed7382bf699d7b", "dash1kpkeye606ez89g7lelp7hnldwwpt76va0v3j6x28",  CBaseChainParams::MAIN, false},
     164           1 :         {"f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525", "tdash1krma5z3ttj75la4m93xcndna9ullamq9y5fzq2j7", CBaseChainParams::TESTNET, false},
     165           1 :         {"a5ff0046217fd1c7d238e3e146cc5bfd90832a7e", "tdash1kzjl7qzxy9lar37j8r37z3kvt07epqe20cxp68nq", CBaseChainParams::TESTNET, false},
     166           1 :         {"6d92674fd64472a3dfcfc3ebcfed7382bf699d7b", "tdash1kpkeye606ez89g7lelp7hnldwwpt76va0vp4fcmf", CBaseChainParams::TESTNET, false},
     167           1 :         {"43fa183cf3fb6e9e7dc62b692aeb4fc8d8045636", "dash1sppl5xpu70aka8nacc4kj2htflydspzkxch4cad6",  CBaseChainParams::MAIN, true},
     168           1 :         {"43fa183cf3fb6e9e7dc62b692aeb4fc8d8045636", "tdash1sppl5xpu70aka8nacc4kj2htflydspzkxc8jtru5", CBaseChainParams::TESTNET, true},
     169             :     };
     170           9 :     for (const auto& s : samples) {
     171           8 :         SelectParams(s.chain);
     172           8 :         std::string err;
     173           8 :         PlatformDestination dest = DecodePlatformDestination(s.address, err);
     174           8 :         BOOST_REQUIRE_MESSAGE(IsValidPlatformDestination(dest),
     175             :                               std::string{"decode failed: "} + s.address + " err=" + err);
     176           8 :         std::vector<unsigned char> got_hash;
     177           8 :         if (s.is_p2sh) {
     178           2 :             BOOST_REQUIRE(std::holds_alternative<PlatformP2SHDestination>(dest));
     179           2 :             const auto& h = std::get<PlatformP2SHDestination>(dest);
     180           2 :             got_hash.assign(h.begin(), h.end());
     181           2 :         } else {
     182           6 :             BOOST_REQUIRE(std::holds_alternative<PlatformP2PKHDestination>(dest));
     183           6 :             const auto& h = std::get<PlatformP2PKHDestination>(dest);
     184           6 :             got_hash.assign(h.begin(), h.end());
     185             :         }
     186           8 :         BOOST_CHECK_EQUAL(HexStr(got_hash), std::string(s.hash_hex));
     187           8 :         BOOST_CHECK_EQUAL(EncodePlatformDestination(dest), std::string(s.address));
     188           8 :     }
     189           1 :     SelectParams(CBaseChainParams::MAIN);
     190           1 : }
     191             : 
     192         149 : BOOST_AUTO_TEST_CASE(dip18_platform_invalid)
     193             : {
     194           1 :     SelectParams(CBaseChainParams::MAIN);
     195           1 :     std::string err;
     196             : 
     197             :     // Wrong HRP for the selected network (testnet string on mainnet).
     198           1 :     BOOST_CHECK(!IsValidPlatformDestination(
     199             :         DecodePlatformDestination("tdash1krma5z3ttj75la4m93xcndna9ullamq9y5fzq2j7", err)));
     200             : 
     201             :     // Mixed case is forbidden by BIP-173.
     202           1 :     BOOST_CHECK(!IsValidPlatformDestination(
     203             :         DecodePlatformDestination("Dash1krma5z3ttj75la4m93xcndna9ullamq9y5e9n5rs", err)));
     204             : 
     205             :     // Bech32 (BIP-173) checksum MUST be rejected; only bech32m is valid for DIP-18.
     206             :     // Re-encode the same 21-byte payload with the BIP-173 generator and verify rejection.
     207             :     {
     208           1 :         std::vector<uint8_t> payload = ParseHex("b0f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525");
     209           1 :         std::vector<uint8_t> values;
     210          35 :         ConvertBits<8, 5, true>([&](uint8_t b) { values.push_back(b); }, payload.begin(), payload.end());
     211           1 :         const std::string bech32_str = bech32::Encode(bech32::Encoding::BECH32, "dash", values);
     212           1 :         BOOST_REQUIRE(!bech32_str.empty());
     213           1 :         BOOST_CHECK(!IsValidPlatformDestination(DecodePlatformDestination(bech32_str, err)));
     214           1 :     }
     215             : 
     216             :     // Unknown DIP-18 type byte (0x00) must be rejected.
     217             :     {
     218           1 :         std::vector<uint8_t> payload = ParseHex("00f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525");
     219           1 :         std::vector<uint8_t> values;
     220          35 :         ConvertBits<8, 5, true>([&](uint8_t b) { values.push_back(b); }, payload.begin(), payload.end());
     221           1 :         const std::string bad = bech32::Encode(bech32::Encoding::BECH32M, "dash", values);
     222           1 :         BOOST_REQUIRE(!bad.empty());
     223           1 :         BOOST_CHECK(!IsValidPlatformDestination(DecodePlatformDestination(bad, err)));
     224           1 :     }
     225             : 
     226             :     // Wrong payload length (19-byte hash) must be rejected.
     227             :     {
     228           1 :         std::vector<uint8_t> payload = ParseHex("b0f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec05");
     229           1 :         std::vector<uint8_t> values;
     230          33 :         ConvertBits<8, 5, true>([&](uint8_t b) { values.push_back(b); }, payload.begin(), payload.end());
     231           1 :         const std::string bad = bech32::Encode(bech32::Encoding::BECH32M, "dash", values);
     232           1 :         BOOST_REQUIRE(!bad.empty());
     233           1 :         BOOST_CHECK(!IsValidPlatformDestination(DecodePlatformDestination(bad, err)));
     234           1 :     }
     235             : 
     236             :     // Empty / garbage inputs.
     237           1 :     BOOST_CHECK(!IsValidPlatformDestination(DecodePlatformDestination("", err)));
     238           1 :     BOOST_CHECK(!IsValidPlatformDestination(DecodePlatformDestination("not-an-address", err)));
     239             : 
     240             :     // Mainnet address on testnet must fail.
     241           1 :     SelectParams(CBaseChainParams::TESTNET);
     242           1 :     BOOST_CHECK(!IsValidPlatformDestination(
     243             :         DecodePlatformDestination("dash1krma5z3ttj75la4m93xcndna9ullamq9y5e9n5rs", err)));
     244             : 
     245           1 :     SelectParams(CBaseChainParams::MAIN);
     246           1 : }
     247             : 
     248         146 : BOOST_AUTO_TEST_SUITE_END()

Generated by: LCOV version 1.16