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()
|