diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -25,3 +25,6 @@ `verbose=true` now return an additional `unbroadcast` field. This indicates whether initial broadcast of the transaction has been acknowledged by a peer. `getmempoolancestors` and `getmempooldescendants` are also updated. + - `bitcoincash:`, `bchtest:` and `bchreg:` CashAddr prefixes have been + deprecated in favor of `abc:`, `abctest:` and `abcreg:` and will be + removed in a future version. \ No newline at end of file diff --git a/src/cashaddrenc.cpp b/src/cashaddrenc.cpp --- a/src/cashaddrenc.cpp +++ b/src/cashaddrenc.cpp @@ -101,13 +101,16 @@ CTxDestination DecodeCashAddr(const std::string &addr, const CChainParams ¶ms) { - CashAddrContent content = - DecodeCashAddrContent(addr, params.CashAddrPrefix()); - if (content.hash.size() == 0) { - return CNoDestination{}; + const std::array prefixes{ + {params.CashAddrPrefix(), params.CashAddrLegacyPrefix()}}; + + for (const auto &prefix : prefixes) { + CashAddrContent content = DecodeCashAddrContent(addr, prefix); + if (content.hash.size() != 0) { + return DecodeCashAddrDestination(content); + } } - - return DecodeCashAddrDestination(content); + return CNoDestination{}; } CashAddrContent DecodeCashAddrContent(const std::string &addr, diff --git a/src/chainparams.h b/src/chainparams.h --- a/src/chainparams.h +++ b/src/chainparams.h @@ -90,6 +90,9 @@ return base58Prefixes[type]; } const std::string &CashAddrPrefix() const { return cashaddrPrefix; } + const std::string &CashAddrLegacyPrefix() const { + return cashaddrLegacyPrefix; + } const std::vector &FixedSeeds() const { return vFixedSeeds; } const CCheckpointData &Checkpoints() const { return checkpointData; } const ChainTxData &TxData() const { return chainTxData; } @@ -107,6 +110,7 @@ std::vector vSeeds; std::vector base58Prefixes[MAX_BASE58_TYPES]; std::string cashaddrPrefix; + std::string cashaddrLegacyPrefix; std::string strNetworkID; CBlock genesis; std::vector vFixedSeeds; diff --git a/src/chainparams.cpp b/src/chainparams.cpp --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -201,6 +201,7 @@ base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4}; cashaddrPrefix = "abc"; + cashaddrLegacyPrefix = "bitcoincash"; vFixedSeeds = std::vector( pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); @@ -345,6 +346,7 @@ base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; cashaddrPrefix = "abctest"; + cashaddrLegacyPrefix = "bchtest"; vFixedSeeds = std::vector( pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); @@ -474,6 +476,7 @@ base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; cashaddrPrefix = "abcreg"; + cashaddrLegacyPrefix = "bchreg"; } }; diff --git a/src/qt/test/bitcoinaddressvalidatortests.cpp b/src/qt/test/bitcoinaddressvalidatortests.cpp --- a/src/qt/test/bitcoinaddressvalidatortests.cpp +++ b/src/qt/test/bitcoinaddressvalidatortests.cpp @@ -24,26 +24,39 @@ // invalid cashaddr, currently considered valid anyway. in = "ABBC"; QVERIFY(v.validate(in, unused) == QValidator::Acceptable); + // invalid base58 because of I + in = "BIIC"; + QVERIFY(v.validate(in, unused) == QValidator::Acceptable); // invalid base58, invalid cashaddr, currently considered valid anyway. in = "ABCC"; QVERIFY(v.validate(in, unused) == QValidator::Acceptable); + in = "BITCOINCASHH"; + QVERIFY(v.validate(in, unused) == QValidator::Acceptable); // invalid base58 because of I, but could be a cashaddr prefix in = "ABIC"; QVERIFY(v.validate(in, unused) == QValidator::Acceptable); + in = "BITC"; + QVERIFY(v.validate(in, unused) == QValidator::Acceptable); // invalid base58, valid cashaddr in = "ABC:QP"; QVERIFY(v.validate(in, unused) == QValidator::Acceptable); + in = "BITCOINCASH:QP"; + QVERIFY(v.validate(in, unused) == QValidator::Acceptable); // invalid base58, valid cashaddr, lower case in = "abc:qp"; QVERIFY(v.validate(in, unused) == QValidator::Acceptable); + in = "bitcoincash:qp"; + QVERIFY(v.validate(in, unused) == QValidator::Acceptable); // invalid base58, valid cashaddr, mixed case in = "aBc:Qp"; QVERIFY(v.validate(in, unused) == QValidator::Acceptable); + in = "bItCoInCaSh:Qp"; + QVERIFY(v.validate(in, unused) == QValidator::Acceptable); // valid base58, invalid cash in = "BBBBBBBBBBBBBB"; diff --git a/src/qt/test/guiutiltests.cpp b/src/qt/test/guiutiltests.cpp --- a/src/qt/test/guiutiltests.cpp +++ b/src/qt/test/guiutiltests.cpp @@ -43,6 +43,8 @@ QVERIFY(GUIUtil::convertToCashAddr(params, "garbage") == "garbage"); QString cashaddr_pubkey = "abc:qpm2qsznhks23z7629mms6s4cwef74vcwvjr9zzhs3"; + QString backwards_compatible_cashaddr_pubkey = + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"; QString base58_pubkey = "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"; @@ -50,4 +52,10 @@ cashaddr_pubkey); QVERIFY(GUIUtil::convertToCashAddr(params, base58_pubkey) == cashaddr_pubkey); + QVERIFY(GUIUtil::convertToCashAddr(params, + backwards_compatible_cashaddr_pubkey) == + cashaddr_pubkey); + QVERIFY(GUIUtil::convertToCashAddr(params, + backwards_compatible_cashaddr_pubkey) == + GUIUtil::convertToCashAddr(params, base58_pubkey)); } diff --git a/src/test/cashaddrenc_tests.cpp b/src/test/cashaddrenc_tests.cpp --- a/src/test/cashaddrenc_tests.cpp +++ b/src/test/cashaddrenc_tests.cpp @@ -226,39 +226,43 @@ */ BOOST_AUTO_TEST_CASE(check_size) { const CTxDestination nodst = CNoDestination{}; - const std::string prefix = "abc"; + const auto prefixes = {"bitcoincash", "abc"}; std::vector data; - for (auto ps : valid_sizes) { - // Number of bytes required for a 5-bit packed version of a hash, - // with version byte. Add half a byte(4) so integer math provides - // the next multiple-of-5 that would fit all the data. - size_t expectedSize = (8 * (1 + ps.second) + 4) / 5; - data.resize(expectedSize); - std::fill(begin(data), end(data), 0); - // After conversion from 8 bit packing to 5 bit packing, the size - // will be in the second 5-bit group, shifted left twice. - data[1] = ps.first << 2; - - auto content = - DecodeCashAddrContent(cashaddr::Encode(prefix, data), prefix); - - BOOST_CHECK_EQUAL(content.type, 0); - BOOST_CHECK_EQUAL(content.hash.size(), ps.second); - - data.push_back(0); - content = DecodeCashAddrContent(cashaddr::Encode(prefix, data), prefix); - - BOOST_CHECK_EQUAL(content.type, 0); - BOOST_CHECK_EQUAL(content.hash.size(), 0UL); - - data.pop_back(); - data.pop_back(); - content = DecodeCashAddrContent(cashaddr::Encode(prefix, data), prefix); - - BOOST_CHECK_EQUAL(content.type, 0); - BOOST_CHECK_EQUAL(content.hash.size(), 0UL); + for (const auto &prefix : prefixes) { + for (auto ps : valid_sizes) { + // Number of bytes required for a 5-bit packed version of a hash, + // with version byte. Add half a byte(4) so integer math provides + // the next multiple-of-5 that would fit all the data. + size_t expectedSize = (8 * (1 + ps.second) + 4) / 5; + data.resize(expectedSize); + std::fill(begin(data), end(data), 0); + // After conversion from 8 bit packing to 5 bit packing, the size + // will be in the second 5-bit group, shifted left twice. + data[1] = ps.first << 2; + + auto content = + DecodeCashAddrContent(cashaddr::Encode(prefix, data), prefix); + + BOOST_CHECK_EQUAL(content.type, 0); + BOOST_CHECK_EQUAL(content.hash.size(), ps.second); + + data.push_back(0); + content = + DecodeCashAddrContent(cashaddr::Encode(prefix, data), prefix); + + BOOST_CHECK_EQUAL(content.type, 0); + BOOST_CHECK_EQUAL(content.hash.size(), 0UL); + + data.pop_back(); + data.pop_back(); + content = + DecodeCashAddrContent(cashaddr::Encode(prefix, data), prefix); + + BOOST_CHECK_EQUAL(content.type, 0); + BOOST_CHECK_EQUAL(content.hash.size(), 0UL); + } } } @@ -295,6 +299,24 @@ CashAddrContent scriptContent{SCRIPT_TYPE, hash[i]}; BOOST_CHECK_EQUAL(script[i], EncodeCashAddr("abc", scriptContent)); } + + std::vector backward_compat_pubkey = { + "bitcoincash:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a", + "bitcoincash:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy", + "bitcoincash:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r"}; + std::vector backward_compat_script = { + "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq", + "bitcoincash:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e", + "bitcoincash:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37"}; + for (size_t i = 0; i < hash.size(); ++i) { + CashAddrContent keyContent{PUBKEY_TYPE, hash[i]}; + BOOST_CHECK_EQUAL(backward_compat_pubkey[i], + EncodeCashAddr("bitcoincash", keyContent)); + + CashAddrContent scriptContent{SCRIPT_TYPE, hash[i]}; + BOOST_CHECK_EQUAL(backward_compat_script[i], + EncodeCashAddr("bitcoincash", scriptContent)); + } } struct CashAddrTestVector { diff --git a/test/functional/abc_feature_minerfund.py b/test/functional/abc_feature_minerfund.py --- a/test/functional/abc_feature_minerfund.py +++ b/test/functional/abc_feature_minerfund.py @@ -20,6 +20,7 @@ MINER_FUND_RATIO = 8 +LEGACY_MINER_FUND_ADDR = 'bchreg:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdgd35g0pkl' MINER_FUND_ADDR = 'abcreg:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdgyrm5ege8' @@ -32,6 +33,24 @@ '-axionactivationtime={}'.format(AXION_ACTIVATION_TIME), ]] + def test_validate_address_backwards_compatibility(self): + self.log.info( + "Testing CashAddr old prefix backward compatibility using the miner fund destination") + + assert(self.nodes[0].validateaddress(MINER_FUND_ADDR)['isvalid']) + self.log.info("Address {} is valid".format(MINER_FUND_ADDR)) + + assert(self.nodes[0].validateaddress( + LEGACY_MINER_FUND_ADDR)['isvalid']) + self.log.info("Address {} is valid".format(LEGACY_MINER_FUND_ADDR)) + + minerFundScriptPubKey = self.nodes[0].validateaddress(MINER_FUND_ADDR)[ + 'scriptPubKey'] + assert_equal(minerFundScriptPubKey, self.nodes[0].validateaddress( + LEGACY_MINER_FUND_ADDR)['scriptPubKey']) + self.log.info("Both addresses {} and {} share the {} scriptPubKey".format( + MINER_FUND_ADDR, LEGACY_MINER_FUND_ADDR, minerFundScriptPubKey)) + def run_test(self): node = self.nodes[0] address = node.get_deterministic_priv_key().address @@ -91,6 +110,8 @@ assert_equal(node.submitblock(ToHex(block)), 'bad-cb-minerfund') + self.test_validate_address_backwards_compatibility() + if __name__ == '__main__': MinerFundTest().main()