|
5 | 5 | #include <test/data/key_io_invalid.json.h> |
6 | 6 | #include <test/data/key_io_valid.json.h> |
7 | 7 |
|
| 8 | +#include <bech32.h> |
8 | 9 | #include <chainparams.h> |
9 | 10 | #include <key.h> |
10 | 11 | #include <key_io.h> |
@@ -146,4 +147,102 @@ BOOST_AUTO_TEST_CASE(key_io_invalid) |
146 | 147 | } |
147 | 148 | } |
148 | 149 |
|
| 150 | +// DIP-18: Dash Platform bech32m address encoding. |
| 151 | +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 | + const Sample samples[] = { |
| 161 | + {"f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525", "dash1krma5z3ttj75la4m93xcndna9ullamq9y5e9n5rs", CBaseChainParams::MAIN, false}, |
| 162 | + {"a5ff0046217fd1c7d238e3e146cc5bfd90832a7e", "dash1kzjl7qzxy9lar37j8r37z3kvt07epqe20ckxfezw", CBaseChainParams::MAIN, false}, |
| 163 | + {"6d92674fd64472a3dfcfc3ebcfed7382bf699d7b", "dash1kpkeye606ez89g7lelp7hnldwwpt76va0v3j6x28", CBaseChainParams::MAIN, false}, |
| 164 | + {"f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525", "tdash1krma5z3ttj75la4m93xcndna9ullamq9y5fzq2j7", CBaseChainParams::TESTNET, false}, |
| 165 | + {"a5ff0046217fd1c7d238e3e146cc5bfd90832a7e", "tdash1kzjl7qzxy9lar37j8r37z3kvt07epqe20cxp68nq", CBaseChainParams::TESTNET, false}, |
| 166 | + {"6d92674fd64472a3dfcfc3ebcfed7382bf699d7b", "tdash1kpkeye606ez89g7lelp7hnldwwpt76va0vp4fcmf", CBaseChainParams::TESTNET, false}, |
| 167 | + {"43fa183cf3fb6e9e7dc62b692aeb4fc8d8045636", "dash1sppl5xpu70aka8nacc4kj2htflydspzkxch4cad6", CBaseChainParams::MAIN, true}, |
| 168 | + {"43fa183cf3fb6e9e7dc62b692aeb4fc8d8045636", "tdash1sppl5xpu70aka8nacc4kj2htflydspzkxc8jtru5", CBaseChainParams::TESTNET, true}, |
| 169 | + }; |
| 170 | + for (const auto& s : samples) { |
| 171 | + SelectParams(s.chain); |
| 172 | + std::string err; |
| 173 | + PlatformDestination dest = DecodePlatformDestination(s.address, err); |
| 174 | + BOOST_REQUIRE_MESSAGE(IsValidPlatformDestination(dest), |
| 175 | + std::string{"decode failed: "} + s.address + " err=" + err); |
| 176 | + std::vector<unsigned char> got_hash; |
| 177 | + if (s.is_p2sh) { |
| 178 | + BOOST_REQUIRE(std::holds_alternative<PlatformP2SHDestination>(dest)); |
| 179 | + const auto& h = std::get<PlatformP2SHDestination>(dest); |
| 180 | + got_hash.assign(h.begin(), h.end()); |
| 181 | + } else { |
| 182 | + BOOST_REQUIRE(std::holds_alternative<PlatformP2PKHDestination>(dest)); |
| 183 | + const auto& h = std::get<PlatformP2PKHDestination>(dest); |
| 184 | + got_hash.assign(h.begin(), h.end()); |
| 185 | + } |
| 186 | + BOOST_CHECK_EQUAL(HexStr(got_hash), std::string(s.hash_hex)); |
| 187 | + BOOST_CHECK_EQUAL(EncodePlatformDestination(dest), std::string(s.address)); |
| 188 | + } |
| 189 | + SelectParams(CBaseChainParams::MAIN); |
| 190 | +} |
| 191 | + |
| 192 | +BOOST_AUTO_TEST_CASE(dip18_platform_invalid) |
| 193 | +{ |
| 194 | + SelectParams(CBaseChainParams::MAIN); |
| 195 | + std::string err; |
| 196 | + |
| 197 | + // Wrong HRP for the selected network (testnet string on mainnet). |
| 198 | + BOOST_CHECK(!IsValidPlatformDestination( |
| 199 | + DecodePlatformDestination("tdash1krma5z3ttj75la4m93xcndna9ullamq9y5fzq2j7", err))); |
| 200 | + |
| 201 | + // Mixed case is forbidden by BIP-173. |
| 202 | + 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 | + std::vector<uint8_t> payload = ParseHex("b0f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525"); |
| 209 | + std::vector<uint8_t> values; |
| 210 | + ConvertBits<8, 5, true>([&](uint8_t b) { values.push_back(b); }, payload.begin(), payload.end()); |
| 211 | + const std::string bech32_str = bech32::Encode(bech32::Encoding::BECH32, "dash", values); |
| 212 | + BOOST_REQUIRE(!bech32_str.empty()); |
| 213 | + BOOST_CHECK(!IsValidPlatformDestination(DecodePlatformDestination(bech32_str, err))); |
| 214 | + } |
| 215 | + |
| 216 | + // Unknown DIP-18 type byte (0x00) must be rejected. |
| 217 | + { |
| 218 | + std::vector<uint8_t> payload = ParseHex("00f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec0525"); |
| 219 | + std::vector<uint8_t> values; |
| 220 | + ConvertBits<8, 5, true>([&](uint8_t b) { values.push_back(b); }, payload.begin(), payload.end()); |
| 221 | + const std::string bad = bech32::Encode(bech32::Encoding::BECH32M, "dash", values); |
| 222 | + BOOST_REQUIRE(!bad.empty()); |
| 223 | + BOOST_CHECK(!IsValidPlatformDestination(DecodePlatformDestination(bad, err))); |
| 224 | + } |
| 225 | + |
| 226 | + // Wrong payload length (19-byte hash) must be rejected. |
| 227 | + { |
| 228 | + std::vector<uint8_t> payload = ParseHex("b0f7da0a2b5cbd4ff6bb2c4d89b67d2f3ffeec05"); |
| 229 | + std::vector<uint8_t> values; |
| 230 | + ConvertBits<8, 5, true>([&](uint8_t b) { values.push_back(b); }, payload.begin(), payload.end()); |
| 231 | + const std::string bad = bech32::Encode(bech32::Encoding::BECH32M, "dash", values); |
| 232 | + BOOST_REQUIRE(!bad.empty()); |
| 233 | + BOOST_CHECK(!IsValidPlatformDestination(DecodePlatformDestination(bad, err))); |
| 234 | + } |
| 235 | + |
| 236 | + // Empty / garbage inputs. |
| 237 | + BOOST_CHECK(!IsValidPlatformDestination(DecodePlatformDestination("", err))); |
| 238 | + BOOST_CHECK(!IsValidPlatformDestination(DecodePlatformDestination("not-an-address", err))); |
| 239 | + |
| 240 | + // Mainnet address on testnet must fail. |
| 241 | + SelectParams(CBaseChainParams::TESTNET); |
| 242 | + BOOST_CHECK(!IsValidPlatformDestination( |
| 243 | + DecodePlatformDestination("dash1krma5z3ttj75la4m93xcndna9ullamq9y5e9n5rs", err))); |
| 244 | + |
| 245 | + SelectParams(CBaseChainParams::MAIN); |
| 246 | +} |
| 247 | + |
149 | 248 | BOOST_AUTO_TEST_SUITE_END() |
0 commit comments