diff --git a/src/base58.h b/src/base58.h --- a/src/base58.h +++ b/src/base58.h @@ -19,7 +19,6 @@ #include "chainparams.h" #include "key.h" #include "pubkey.h" -#include "script/script.h" #include "script/standard.h" #include "support/allocators/zeroafterfree.h" @@ -156,7 +155,10 @@ CChainParams::EXT_PUBLIC_KEY> CBitcoinExtPubKey; +// Encodes destination with the cash address format. std::string EncodeDestination(const CTxDestination &dest); +// Encodes destination with the deprecated base58 format. +std::string EncodeDestinationBase58(const CTxDestination &dest); CTxDestination DecodeDestination(const std::string &str); bool IsValidDestinationString(const std::string &str); bool IsValidDestinationString(const std::string &str, diff --git a/src/base58.cpp b/src/base58.cpp --- a/src/base58.cpp +++ b/src/base58.cpp @@ -4,8 +4,11 @@ #include "base58.h" +#include "cashaddr.h" #include "hash.h" +#include "script/script.h" #include "uint256.h" +#include "utilstrencodings.h" #include #include @@ -208,84 +211,120 @@ } namespace { -/** - * base58-encoded Bitcoin addresses. - * Public-key-hash-addresses have version 0 (or 111 testnet). - * The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the - * serialized public key. - * Script-hash-addresses have version 5 (or 196 testnet). - * The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the - * serialized redemption script. - */ -class CBitcoinAddress : public CBase58Data { + +const uint8_t CASHADDR_VERSION_PUBKEY = 0; +const uint8_t CASHADDR_VERISON_SCRIPT = 8; + +class DestinationEncoder : public boost::static_visitor { +private: + const CChainParams &m_params; + public: - bool Set(const CKeyID &id); - bool Set(const CScriptID &id); - bool Set(const CTxDestination &dest); - bool IsValid() const; - bool IsValid(const CChainParams ¶ms) const; - - CBitcoinAddress() {} - CBitcoinAddress(const CTxDestination &dest) { Set(dest); } - CBitcoinAddress(const std::string &strAddress) { SetString(strAddress); } - CBitcoinAddress(const char *pszAddress) { SetString(pszAddress); } - - CTxDestination Get() const; + DestinationEncoder(const CChainParams ¶ms) : m_params(params) {} + + std::string operator()(const CKeyID &id) const { + std::vector data = {CASHADDR_VERSION_PUBKEY}; + ConvertBits<8, 5, true>(data, id.begin(), id.end()); + return cashaddr::Encode(m_params.CashAddrPrefix(), data); + } + + std::string operator()(const CScriptID &id) const { + std::vector data = {CASHADDR_VERISON_SCRIPT}; + ConvertBits<8, 5, true>(data, id.begin(), id.end()); + return cashaddr::Encode(m_params.CashAddrPrefix(), data); + } + + std::string operator()(const CNoDestination &no) const { return ""; } }; -class CBitcoinAddressVisitor : public boost::static_visitor { +/// Deprecated: This is the legacy BTC encoding. +class Base58DestinationEncoder : public boost::static_visitor { private: - CBitcoinAddress *addr; + const CChainParams &m_params; public: - CBitcoinAddressVisitor(CBitcoinAddress *addrIn) : addr(addrIn) {} + Base58DestinationEncoder(const CChainParams ¶ms) : m_params(params) {} - bool operator()(const CKeyID &id) const { return addr->Set(id); } - bool operator()(const CScriptID &id) const { return addr->Set(id); } - bool operator()(const CNoDestination &no) const { return false; } + std::string operator()(const CKeyID &id) const { + std::vector data = + m_params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); + data.insert(data.end(), id.begin(), id.end()); + return EncodeBase58Check(data); + } + + std::string operator()(const CScriptID &id) const { + std::vector data = + m_params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); + data.insert(data.end(), id.begin(), id.end()); + return EncodeBase58Check(data); + } + + std::string operator()(const CNoDestination &no) const { return ""; } }; -} // anon namespace +CTxDestination +DecodeCashAddr(const std::pair> &cashaddr, + const CChainParams ¶ms) { -bool CBitcoinAddress::Set(const CKeyID &id) { - SetData(Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS), &id, 20); - return true; -} + if (cashaddr.first != params.CashAddrPrefix()) { + return CNoDestination{}; + } -bool CBitcoinAddress::Set(const CScriptID &id) { - SetData(Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS), &id, 20); - return true; -} + if (cashaddr.second.empty()) { + return CNoDestination{}; + } -bool CBitcoinAddress::Set(const CTxDestination &dest) { - return boost::apply_visitor(CBitcoinAddressVisitor(this), dest); -} + std::vector data; + if (!ConvertBits<5, 8, false>(data, begin(cashaddr.second) + 1, + end(cashaddr.second))) { + return CNoDestination(); + } -bool CBitcoinAddress::IsValid() const { - return IsValid(Params()); -} + if (data.size() != 20) { + return CNoDestination{}; + } + + uint160 hash; + memcpy(hash.begin(), &data[0], 20); -bool CBitcoinAddress::IsValid(const CChainParams ¶ms) const { - bool fCorrectSize = vchData.size() == 20; - bool fKnownVersion = - vchVersion == params.Base58Prefix(CChainParams::PUBKEY_ADDRESS) || - vchVersion == params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); - return fCorrectSize && fKnownVersion; + int version = cashaddr.second.at(0); + if (version == CASHADDR_VERSION_PUBKEY) { + return CKeyID(hash); + } + if (version == CASHADDR_VERISON_SCRIPT) { + return CScriptID(hash); + } + + // unknown version + return CNoDestination{}; } -CTxDestination CBitcoinAddress::Get() const { - if (!IsValid()) return CNoDestination(); - uint160 id; - memcpy(&id, &vchData[0], 20); - if (vchVersion == Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS)) { - return CKeyID(id); - } else if (vchVersion == - Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS)) { - return CScriptID(id); - } else { - return CNoDestination(); +CTxDestination DecodeDestination(const std::string &str, + const CChainParams ¶ms) { + std::vector data; + uint160 hash; + if (DecodeBase58Check(str, data)) { + // Base58Check decoding + const std::vector &pubkey_prefix = + params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); + if (data.size() == 20 + pubkey_prefix.size() && + std::equal(pubkey_prefix.begin(), pubkey_prefix.end(), + data.begin())) { + memcpy(hash.begin(), &data[pubkey_prefix.size()], 20); + return CKeyID(hash); + } + const std::vector &script_prefix = + params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); + if (data.size() == 20 + script_prefix.size() && + std::equal(script_prefix.begin(), script_prefix.end(), + data.begin())) { + memcpy(hash.begin(), &data[script_prefix.size()], 20); + return CScriptID(hash); + } } + return DecodeCashAddr(cashaddr::Decode(str), params); } +} // namespace void CBitcoinSecret::SetKey(const CKey &vchSecret) { assert(vchSecret.IsValid()); @@ -319,19 +358,23 @@ } std::string EncodeDestination(const CTxDestination &dest) { - CBitcoinAddress addr(dest); - return addr.IsValid() ? addr.ToString() : ""; + return boost::apply_visitor(DestinationEncoder(Params()), dest); +} + +// deprecated +std::string EncodeDestinationBase58(const CTxDestination &dest) { + return boost::apply_visitor(Base58DestinationEncoder(Params()), dest); } CTxDestination DecodeDestination(const std::string &str) { - return CBitcoinAddress(str).Get(); + return DecodeDestination(str, Params()); } bool IsValidDestinationString(const std::string &str, const CChainParams ¶ms) { - return CBitcoinAddress(str).IsValid(params); + return IsValidDestination(DecodeDestination(str, params)); } bool IsValidDestinationString(const std::string &str) { - return CBitcoinAddress(str).IsValid(); + return IsValidDestination(DecodeDestination(str, Params())); } diff --git a/src/chainparams.h b/src/chainparams.h --- a/src/chainparams.h +++ b/src/chainparams.h @@ -86,6 +86,7 @@ const std::vector &Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } + const std::string &CashAddrPrefix() const { return cashaddrPrefix; } const std::vector &FixedSeeds() const { return vFixedSeeds; } const CCheckpointData &Checkpoints() const { return checkpointData; } const ChainTxData &TxData() const { return chainTxData; } @@ -100,6 +101,7 @@ uint64_t nPruneAfterHeight; std::vector vSeeds; std::vector base58Prefixes[MAX_BASE58_TYPES]; + std::string cashaddrPrefix; 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 @@ -195,6 +195,7 @@ base58Prefixes[SECRET_KEY] = std::vector(1, 128); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x88, 0xB2, 0x1E}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x88, 0xAD, 0xE4}; + cashaddrPrefix = "bitcoincash"; vFixedSeeds = std::vector( pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); @@ -354,6 +355,7 @@ base58Prefixes[SECRET_KEY] = std::vector(1, 239); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; + cashaddrPrefix = "bcctest"; vFixedSeeds = std::vector( pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); @@ -471,6 +473,7 @@ base58Prefixes[SECRET_KEY] = std::vector(1, 239); base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; + cashaddrPrefix = "bccreg"; } void UpdateBIP9Parameters(Consensus::DeploymentPos d, int64_t nStartTime, diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -190,8 +190,8 @@ } } -// Goal: check that generated keys match test vectors -BOOST_AUTO_TEST_CASE(base58_keys_valid_gen) { +static void testGeneratedKeys(bool cashaddr) { + UniValue tests = read_json(std::string( json_tests::base58_keys_valid, json_tests::base58_keys_valid + sizeof(json_tests::base58_keys_valid))); @@ -236,15 +236,31 @@ BOOST_ERROR("Bad addrtype: " << strTest); continue; } - std::string address = EncodeDestination(dest); - BOOST_CHECK_MESSAGE(address == exp_base58string, + std::string address = cashaddr ? EncodeDestination(dest) + : EncodeDestinationBase58(dest); + + BOOST_CHECK_MESSAGE(dest == DecodeDestination(address), "mismatch: " + strTest); + + if (!cashaddr) { + // TODO: We only have test files for base58 encoding. + // We need to create a json_tests::cashaddr_keys_valid + BOOST_CHECK_MESSAGE(address == exp_base58string, + "mismatch: " + strTest); + } } } SelectParams(CBaseChainParams::MAIN); } +// Goal: check that generated keys match test vectors +BOOST_AUTO_TEST_CASE(base58_keys_valid_gen) { + bool cashaddr = true; + testGeneratedKeys(cashaddr); + testGeneratedKeys(!cashaddr); +} + // Goal: check that base58 parsing code is robust against a variety of corrupted // data BOOST_AUTO_TEST_CASE(base58_keys_invalid) {