From e4efabd9d363e81b0aa54c75a524e13e61bbd60d Mon Sep 17 00:00:00 2001 From: janko33bd Date: Sat, 13 Jan 2018 23:29:46 +0100 Subject: [PATCH 1/3] removing nonexisting files --- src/qt/bitcoingui.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 43d3741d2..afd3914f8 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -16,8 +16,6 @@ #include "platformstyle.h" #include "rpcconsole.h" #include "utilitydialog.h" -#include "validation.h" -#include "rpc/server.h" #ifdef ENABLE_WALLET #include "walletframe.h" From bf23edfdced477719523b4b143a0a1d01d1ee886 Mon Sep 17 00:00:00 2001 From: lateminer Date: Sun, 14 Jan 2018 16:44:09 +0300 Subject: [PATCH 2/3] Updated BLK icons about.png bitcoin_testnet.ico --- src/qt/res/icons/about.png | Bin 16377 -> 5652 bytes src/qt/res/icons/bitcoin_testnet.ico | Bin 57251 -> 15086 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/qt/res/icons/about.png b/src/qt/res/icons/about.png index 14e57b2e92c987a42f02e2d54fce6ea6a5ec0f4d..5df7d187f546851fe0fa860dd21293d3718b85fc 100644 GIT binary patch delta 5642 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 diff --git a/src/qt/res/icons/bitcoin_testnet.ico b/src/qt/res/icons/bitcoin_testnet.ico index 909194ecd2e10985bbca35c3cd26fd1adb4730b8..5d7a74b773ca8c41e8ecfb7e554493107ada2871 100644 GIT binary patch literal 15086 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 SS8pJo^oWK^uUgL@z5hRXhu1g& literal 57251 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 N250BL5H*Z+s*3%&l;+rD=4 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 Date: Sun, 14 Jan 2018 22:32:08 +0300 Subject: [PATCH 3/3] Add CashAddr Address Format Ported from Bitcoin Unlimited, Bitcoin ABC --- src/Makefile.am | 9 +- src/Makefile.qttest.include | 9 +- src/Makefile.test.include | 4 + src/base58.cpp | 131 +++----- src/base58.h | 29 +- src/bitcoin-tx.cpp | 70 ++-- src/bitcoind.cpp | 5 +- src/cashaddr.cpp | 332 +++++++++++++++++++ src/cashaddr.h | 26 ++ src/cashaddrenc.cpp | 196 +++++++++++ src/cashaddrenc.h | 33 ++ src/chainparams.cpp | 4 +- src/chainparams.h | 6 +- src/config.cpp | 16 + src/config.h | 46 +++ src/core_write.cpp | 8 +- src/dstencode.cpp | 33 ++ src/dstencode.h | 24 ++ src/fs.h | 24 ++ src/init.cpp | 8 +- src/init.h | 3 +- src/qt/addresstablemodel.cpp | 26 +- src/qt/bitcoin.cpp | 48 +-- src/qt/bitcoinaddressvalidator.cpp | 76 +++-- src/qt/bitcoinaddressvalidator.h | 10 +- src/qt/bitcoingui.cpp | 15 +- src/qt/bitcoingui.h | 7 +- src/qt/coincontroldialog.cpp | 6 +- src/qt/guiconstants.h | 2 +- src/qt/guiutil.cpp | 116 +++++-- src/qt/guiutil.h | 16 +- src/qt/openuridialog.cpp | 10 +- src/qt/openuridialog.h | 5 +- src/qt/paymentserver.cpp | 264 +++++++++------ src/qt/paymentserver.h | 3 +- src/qt/receivecoinsdialog.cpp | 60 +++- src/qt/receivecoinsdialog.h | 6 +- src/qt/receiverequestdialog.cpp | 46 ++- src/qt/receiverequestdialog.h | 8 +- src/qt/recentrequeststablemodel.h | 3 +- src/qt/sendcoinsdialog.cpp | 35 +- src/qt/sendcoinsentry.cpp | 6 + src/qt/signverifymessagedialog.cpp | 21 +- src/qt/test/bitcoinaddressvalidatortests.cpp | 36 ++ src/qt/test/bitcoinaddressvalidatortests.h | 18 + src/qt/test/guiutiltests.cpp | 62 ++++ src/qt/test/guiutiltests.h | 19 ++ src/qt/test/test_main.cpp | 6 + src/qt/test/uritests.cpp | 197 +++++++++-- src/qt/test/uritests.h | 5 +- src/qt/transactiondesc.cpp | 19 +- src/qt/transactionrecord.cpp | 5 +- src/qt/walletframe.cpp | 7 +- src/qt/walletframe.h | 4 +- src/qt/walletmodel.cpp | 35 +- src/qt/walletmodel.h | 2 +- src/qt/walletview.cpp | 6 +- src/qt/walletview.h | 3 +- src/random.cpp | 25 +- src/random.h | 79 ++++- src/rpc/blockchain.cpp | 10 +- src/rpc/mining.cpp | 11 + src/rpc/misc.cpp | 84 +++-- src/rpc/rawtransaction.cpp | 40 ++- src/rpc/server.cpp | 14 +- src/script/standard.cpp | 4 + src/script/standard.h | 5 +- src/test/base58_tests.cpp | 36 +- src/test/cashaddr_tests.cpp | 115 +++++++ src/test/cashaddrenc_tests.cpp | 311 +++++++++++++++++ src/test/dstencode_tests.cpp | 69 ++++ src/test/key_tests.cpp | 29 +- src/test/multisig_tests.cpp | 81 +++++ src/test/util_tests.cpp | 55 +++ src/uint256.h | 13 + src/utilstrencodings.h | 40 +++ src/wallet-utility.cpp | 3 +- src/wallet/rpcdump.cpp | 55 +-- src/wallet/rpcwallet.cpp | 226 +++++++------ src/wallet/test/rpc_wallet_tests.cpp | 50 +-- src/wallet/test/walletdb_tests.cpp | 140 ++++++++ src/wallet/wallet.cpp | 63 ++-- src/wallet/wallet.h | 1 + src/wallet/walletdb.cpp | 71 ++-- src/wallet/walletdb.h | 28 +- 85 files changed, 3107 insertions(+), 780 deletions(-) create mode 100644 src/cashaddr.cpp create mode 100644 src/cashaddr.h create mode 100644 src/cashaddrenc.cpp create mode 100644 src/cashaddrenc.h create mode 100644 src/config.cpp create mode 100644 src/config.h create mode 100644 src/dstencode.cpp create mode 100644 src/dstencode.h create mode 100644 src/fs.h create mode 100644 src/qt/test/bitcoinaddressvalidatortests.cpp create mode 100644 src/qt/test/bitcoinaddressvalidatortests.h create mode 100644 src/qt/test/guiutiltests.cpp create mode 100644 src/qt/test/guiutiltests.h create mode 100644 src/test/cashaddr_tests.cpp create mode 100644 src/test/cashaddrenc_tests.cpp create mode 100644 src/test/dstencode_tests.cpp create mode 100644 src/wallet/test/walletdb_tests.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 5b4ec878d..1fa5b4480 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -82,6 +82,8 @@ BITCOIN_CORE_H = \ arith_uint256.h \ base58.h \ bloom.h \ + cashaddr.h \ + cashaddrenc.h \ chain.h \ chainparams.h \ chainparamsbase.h \ @@ -96,6 +98,7 @@ BITCOIN_CORE_H = \ compat/endian.h \ compat/sanity.h \ compressor.h \ + config.h \ consensus/consensus.h \ consensus/merkle.h \ consensus/params.h \ @@ -103,6 +106,7 @@ BITCOIN_CORE_H = \ core_io.h \ core_memusage.h \ hash.h \ + dstencode.h \ httprpc.h \ httpserver.h \ init.h \ @@ -311,6 +315,8 @@ libbitcoin_common_a_SOURCES = \ amount.cpp \ arith_uint256.cpp \ base58.cpp \ + cashaddr.cpp \ + cashaddrenc.cpp \ chainparams.cpp \ coins.cpp \ compressor.cpp \ @@ -318,6 +324,8 @@ libbitcoin_common_a_SOURCES = \ core_read.cpp \ core_write.cpp \ hash.cpp \ + config.cpp \ + dstencode.cpp \ key.cpp \ keystore.cpp \ netbase.cpp \ @@ -492,7 +500,6 @@ clean-local: -$(MAKE) -C secp256k1 clean -$(MAKE) -C univalue clean -rm -f leveldb/*/*.gcda leveldb/*/*.gcno leveldb/helpers/memenv/*.gcda leveldb/helpers/memenv/*.gcno - -rm -f config.h -rm -rf test/__pycache__ .rc.o: diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index ede3fac4c..8de46c158 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -1,13 +1,18 @@ bin_PROGRAMS += qt/test/test_bitcoin-qt TESTS += qt/test/test_bitcoin-qt -TEST_QT_MOC_CPP = qt/test/moc_uritests.cpp +TEST_QT_MOC_CPP = \ + qt/test/moc_bitcoinaddressvalidatortests.cpp \ + qt/test/moc_guiutiltests.cpp \ + qt/test/moc_uritests.cpp if ENABLE_WALLET TEST_QT_MOC_CPP += qt/test/moc_paymentservertests.cpp endif TEST_QT_H = \ + qt/test/bitcoinaddressvalidatortests.h \ + qt/test/guiutiltests.h \ qt/test/uritests.h \ qt/test/paymentrequestdata.h \ qt/test/paymentservertests.h @@ -16,6 +21,8 @@ qt_test_test_bitcoin_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_ $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(PROTOBUF_CFLAGS) qt_test_test_bitcoin_qt_SOURCES = \ + qt/test/bitcoinaddressvalidatortests.cpp \ + qt/test/guiutiltests.cpp \ qt/test/test_main.cpp \ qt/test/uritests.cpp \ $(TEST_QT_H) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 8a8f05be3..366a03457 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -47,10 +47,13 @@ BITCOIN_TESTS =\ test/bloom_tests.cpp \ test/checkblock_tests.cpp \ test/Checkpoints_tests.cpp \ + test/cashaddr_tests.cpp \ + test/cashaddrenc_tests.cpp \ test/coins_tests.cpp \ test/compress_tests.cpp \ test/crypto_tests.cpp \ test/DoS_tests.cpp \ + test/dstencode_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ test/key_tests.cpp \ @@ -93,6 +96,7 @@ BITCOIN_TESTS += \ wallet/test/accounting_tests.cpp \ wallet/test/wallet_tests.cpp \ wallet/test/rpc_wallet_tests.cpp \ + wallet/test/walletdb_tests.cpp \ wallet/test/crypto_tests.cpp endif diff --git a/src/base58.cpp b/src/base58.cpp index 80fa99c2a..1895cc939 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -5,6 +5,7 @@ #include "base58.h" #include "hash.h" +#include "script/script.h" #include "uint256.h" #include @@ -203,96 +204,56 @@ int CBase58Data::CompareTo(const CBase58Data& b58) const namespace { -class CBitcoinAddressVisitor : public boost::static_visitor +class DestinationEncoder : public boost::static_visitor { private: - CBitcoinAddress* addr; + const CChainParams &m_params; public: - CBitcoinAddressVisitor(CBitcoinAddress* addrIn) : addr(addrIn) {} - - 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; } -}; - -} // anon namespace - -bool CBitcoinAddress::Set(const CKeyID& id) -{ - SetData(Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS), &id, 20); - return true; -} - -bool CBitcoinAddress::Set(const CScriptID& id) -{ - SetData(Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS), &id, 20); - return true; -} - -bool CBitcoinAddress::Set(const CTxDestination& dest) -{ - return boost::apply_visitor(CBitcoinAddressVisitor(this), dest); -} - -bool CBitcoinAddress::IsValid() const -{ - return IsValid(Params()); -} - -bool CBitcoinAddress::IsValid(const CChainParams& params) const -{ - bool fCorrectSize = vchData.size() == 20; - bool fKnownVersion = vchVersion == params.Base58Prefix(CChainParams::PUBKEY_ADDRESS) || - vchVersion == params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); - return fCorrectSize && fKnownVersion; -} - -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(); -} - -bool CBitcoinAddress::GetIndexKey(uint160& hashBytes, int& type) const -{ - if (!IsValid()) { - return false; - } else if (vchVersion == Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS)) { - memcpy(&hashBytes, &vchData[0], 20); - type = 1; - return true; - } else if (vchVersion == Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS)) { - memcpy(&hashBytes, &vchData[0], 20); - type = 2; - return true; + DestinationEncoder(const CChainParams ¶ms) : m_params(params) {} + 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); } - return false; -} + 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); + } -bool CBitcoinAddress::GetKeyID(CKeyID& keyID) const -{ - if (!IsValid() || vchVersion != Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS)) - return false; - uint160 id; - memcpy(&id, &vchData[0], 20); - keyID = CKeyID(id); - return true; -} + std::string operator()(const CNoDestination &no) const { return ""; } +}; -bool CBitcoinAddress::IsScript() const +CTxDestination DecodeDestination(const std::string &str, const CChainParams ¶ms) { - return IsValid() && vchVersion == Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS); + std::vector data; + uint160 hash; + if (!DecodeBase58Check(str, data)) + { + return CNoDestination(); + } + // 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 CNoDestination(); } +} // namespace void CBitcoinSecret::SetKey(const CKey& vchSecret) { @@ -317,12 +278,14 @@ bool CBitcoinSecret::IsValid() const return fExpectedFormat && fCorrectVersion; } -bool CBitcoinSecret::SetString(const char* pszSecret) +bool CBitcoinSecret::SetString(const char *pszSecret) { return CBase58Data::SetString(pszSecret) && IsValid(); } +bool CBitcoinSecret::SetString(const std::string &strSecret) { return SetString(strSecret.c_str()); } +std::string EncodeLegacyAddr(const CTxDestination &dest, const CChainParams ¶ms) { - return CBase58Data::SetString(pszSecret) && IsValid(); + return boost::apply_visitor(DestinationEncoder(params), dest); } -bool CBitcoinSecret::SetString(const std::string& strSecret) +CTxDestination DecodeLegacyAddr(const std::string &str, const CChainParams ¶ms) { - return SetString(strSecret.c_str()); + return DecodeDestination(str, params); } diff --git a/src/base58.h b/src/base58.h index 6c2297e22..0597f5866 100644 --- a/src/base58.h +++ b/src/base58.h @@ -17,7 +17,6 @@ #include "chainparams.h" #include "key.h" #include "pubkey.h" -#include "script/script.h" #include "script/standard.h" #include "support/allocators/zeroafterfree.h" @@ -95,31 +94,6 @@ public: bool operator> (const CBase58Data& b58) const { return CompareTo(b58) > 0; } }; -/** 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 { -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; - bool GetKeyID(CKeyID &keyID) const; - bool GetIndexKey(uint160& hashBytes, int& type) const; - bool IsScript() const; -}; - /** * A base58-encoded secret key */ @@ -168,4 +142,7 @@ public: typedef CBitcoinExtKeyBase CBitcoinExtKey; typedef CBitcoinExtKeyBase CBitcoinExtPubKey; +std::string EncodeLegacyAddr(const CTxDestination &dest, const CChainParams &); +CTxDestination DecodeLegacyAddr(const std::string &str, const CChainParams &); + #endif // BITCOIN_BASE58_H diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 1a71c1d7f..54c6990e0 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -3,10 +3,12 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" +#include "chainparams.h" #include "clientversion.h" #include "coins.h" #include "consensus/consensus.h" #include "core_io.h" +#include "dstencode.h" #include "keystore.h" #include "policy/policy.h" #include "primitives/transaction.h" @@ -158,6 +160,15 @@ static void RegisterLoad(const string& strInput) RegisterSetJson(key, valStr); } +static CAmount ExtractAndValidateValue(const string& strValue) +{ + CAmount value; + if (!ParseMoney(strValue, value)) + throw runtime_error("Invalid TX output value"); + + return value; +} + static void MutateTxVersion(CMutableTransaction& tx, const string& cmdVal) { int64_t newVersion = atoi64(cmdVal); @@ -222,26 +233,23 @@ static void MutateTxAddInput(CMutableTransaction& tx, const string& strInput) static void MutateTxAddOutAddr(CMutableTransaction& tx, const string& strInput) { // separate VALUE:ADDRESS in string - size_t pos = strInput.find(':'); - if ((pos == string::npos) || - (pos == 0) || - (pos == (strInput.size() - 1))) - throw runtime_error("TX output missing separator"); + std::vector vStrInputParts; + boost::split(vStrInputParts, strInput, boost::is_any_of(":")); - // extract and validate VALUE - string strValue = strInput.substr(0, pos); - CAmount value; - if (!ParseMoney(strValue, value)) - throw runtime_error("invalid TX output value"); + if (vStrInputParts.size() != 2) + throw runtime_error("TX output missing or too many separators"); + + // Extract and validate VALUE + CAmount value = ExtractAndValidateValue(vStrInputParts[0]); // extract and validate ADDRESS - string strAddr = strInput.substr(pos + 1, string::npos); - CBitcoinAddress addr(strAddr); - if (!addr.IsValid()) - throw runtime_error("invalid TX output address"); - - // build standard output script via GetScriptForDestination() - CScript scriptPubKey = GetScriptForDestination(addr.Get()); + std::string strAddr = vStrInputParts[1]; + CTxDestination destination = DecodeDestination(strAddr); + if (!IsValidDestination(destination)) + { + throw std::runtime_error("invalid TX output address"); + } + CScript scriptPubKey = GetScriptForDestination(destination); // construct TxOut, append to transaction output list CTxOut txout(value, scriptPubKey); @@ -280,20 +288,30 @@ static void MutateTxAddOutData(CMutableTransaction& tx, const string& strInput) static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput) { // separate VALUE:SCRIPT in string - size_t pos = strInput.find(':'); - if ((pos == string::npos) || - (pos == 0)) + std::vector vStrInputParts; + boost::split(vStrInputParts, strInput, boost::is_any_of(":")); + if (vStrInputParts.size() < 2) throw runtime_error("TX output missing separator"); // extract and validate VALUE - string strValue = strInput.substr(0, pos); - CAmount value; - if (!ParseMoney(strValue, value)) - throw runtime_error("invalid TX output value"); + CAmount value = ExtractAndValidateValue(vStrInputParts[0]); // extract and validate script - string strScript = strInput.substr(pos + 1, string::npos); - CScript scriptPubKey = ParseScript(strScript); // throws on err + std::string strScript = vStrInputParts[1]; + CScript scriptPubKey = ParseScript(strScript); + + // Extract FLAGS + bool bScriptHash = false; + if (vStrInputParts.size() > 2) + { + std::string flags = vStrInputParts.back(); + bScriptHash = (flags.find("S") != std::string::npos); + } + + if (bScriptHash) + { + scriptPubKey = GetScriptForDestination(CScriptID(scriptPubKey)); + } // construct TxOut, append to transaction output list CTxOut txout(value, scriptPubKey); diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 35abdc3fb..80fec86fe 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -6,6 +6,7 @@ #include "chainparams.h" #include "clientversion.h" #include "rpc/server.h" +#include "config.h" #include "init.h" #include "noui.h" #include "scheduler.h" @@ -62,6 +63,8 @@ bool AppInit(int argc, char* argv[]) boost::thread_group threadGroup; CScheduler scheduler; + auto &config = const_cast(GetConfig()); + bool fRet = false; // @@ -153,7 +156,7 @@ bool AppInit(int argc, char* argv[]) // Set this early so that parameter interactions go to console InitLogging(); InitParameterInteraction(); - fRet = AppInit2(threadGroup, scheduler); + fRet = AppInit2(config, threadGroup, scheduler); } catch (const std::exception& e) { PrintExceptionContinue(&e, "AppInit()"); diff --git a/src/cashaddr.cpp b/src/cashaddr.cpp new file mode 100644 index 000000000..a9970ae72 --- /dev/null +++ b/src/cashaddr.cpp @@ -0,0 +1,332 @@ +// Copyright (c) 2017 Pieter Wuille +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "cashaddr.h" + +namespace +{ +typedef std::vector data; + +/** + * The cashaddr character set for encoding. + */ +const char *CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +/** + * The cashaddr character set for decoding. + */ +const int8_t CHARSET_REV[128] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, -1, 10, + 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, + 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1}; + +/** + * Concatenate two byte arrays. + */ +data Cat(data x, const data &y) +{ + x.insert(x.end(), y.begin(), y.end()); + return x; +} + +/** + * This function will compute what 8 5-bit values to XOR into the last 8 input + * values, in order to make the checksum 0. These 8 values are packed together + * in a single 40-bit integer. The higher bits correspond to earlier values. + */ +uint64_t PolyMod(const data &v) +{ + /** + * The input is interpreted as a list of coefficients of a polynomial over F + * = GF(32), with an implicit 1 in front. If the input is [v0,v1,v2,v3,v4], + * that polynomial is v(x) = 1*x^5 + v0*x^4 + v1*x^3 + v2*x^2 + v3*x + v4. + * The implicit 1 guarantees that [v0,v1,v2,...] has a distinct checksum + * from [0,v0,v1,v2,...]. + * + * The output is a 40-bit integer whose 5-bit groups are the coefficients of + * the remainder of v(x) mod g(x), where g(x) is the cashaddr generator, x^8 + * + {19}*x^7 + {3}*x^6 + {25}*x^5 + {11}*x^4 + {25}*x^3 + {3}*x^2 + {19}*x + * + {1}. g(x) is chosen in such a way that the resulting code is a BCH + * code, guaranteeing detection of up to 4 errors within a window of 1025 + * characters. Among the various possible BCH codes, one was selected to in + * fact guarantee detection of up to 5 errors within a window of 160 + * characters and 6 erros within a window of 126 characters. In addition, + * the code guarantee the detection of a burst of up to 8 errors. + * + * Note that the coefficients are elements of GF(32), here represented as + * decimal numbers between {}. In this finite field, addition is just XOR of + * the corresponding numbers. For example, {27} + {13} = {27 ^ 13} = {22}. + * Multiplication is more complicated, and requires treating the bits of + * values themselves as coefficients of a polynomial over a smaller field, + * GF(2), and multiplying those polynomials mod a^5 + a^3 + 1. For example, + * {5} * {26} = (a^2 + 1) * (a^4 + a^3 + a) = (a^4 + a^3 + a) * a^2 + (a^4 + + * a^3 + a) = a^6 + a^5 + a^4 + a = a^3 + 1 (mod a^5 + a^3 + 1) = {9}. + * + * During the course of the loop below, `c` contains the bitpacked + * coefficients of the polynomial constructed from just the values of v that + * were processed so far, mod g(x). In the above example, `c` initially + * corresponds to 1 mod (x), and after processing 2 inputs of v, it + * corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the + * starting value for `c`. + */ + uint64_t c = 1; + for (uint8_t d : v) + { + /** + * We want to update `c` to correspond to a polynomial with one extra + * term. If the initial value of `c` consists of the coefficients of + * c(x) = f(x) mod g(x), we modify it to correspond to + * c'(x) = (f(x) * x + d) mod g(x), where d is the next input to + * process. + * + * Simplifying: + * c'(x) = (f(x) * x + d) mod g(x) + * ((f(x) mod g(x)) * x + d) mod g(x) + * (c(x) * x + d) mod g(x) + * If c(x) = c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5, we want to + * compute + * c'(x) = (c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5) * x + d + * mod g(x) + * = c0*x^6 + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + d + * mod g(x) + * = c0*(x^6 mod g(x)) + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + + * c5*x + d + * If we call (x^6 mod g(x)) = k(x), this can be written as + * c'(x) = (c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + d) + c0*k(x) + */ + + // First, determine the value of c0: + uint8_t c0 = c >> 35; + + // Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + d: + c = ((c & 0x07ffffffff) << 5) ^ d; + + // Finally, for each set bit n in c0, conditionally add {2^n}k(x): + if (c0 & 0x01) + { + // k(x) = {19}*x^7 + {3}*x^6 + {25}*x^5 + {11}*x^4 + {25}*x^3 + + // {3}*x^2 + {19}*x + {1} + c ^= 0x98f2bc8e61; + } + + if (c0 & 0x02) + { + // {2}k(x) = {15}*x^7 + {6}*x^6 + {27}*x^5 + {22}*x^4 + {27}*x^3 + + // {6}*x^2 + {15}*x + {2} + c ^= 0x79b76d99e2; + } + + if (c0 & 0x04) + { + // {4}k(x) = {30}*x^7 + {12}*x^6 + {31}*x^5 + {5}*x^4 + {31}*x^3 + + // {12}*x^2 + {30}*x + {4} + c ^= 0xf33e5fb3c4; + } + + if (c0 & 0x08) + { + // {8}k(x) = {21}*x^7 + {24}*x^6 + {23}*x^5 + {10}*x^4 + {23}*x^3 + + // {24}*x^2 + {21}*x + {8} + c ^= 0xae2eabe2a8; + } + + if (c0 & 0x10) + { + // {16}k(x) = {3}*x^7 + {25}*x^6 + {7}*x^5 + {20}*x^4 + {7}*x^3 + + // {25}*x^2 + {3}*x + {16} + c ^= 0x1e4f43e470; + } + } + + return c; +} + +/** + * Convert to lower case. + * + * Assume the input is a character. + */ +inline uint8_t LowerCase(uint8_t c) +{ + // ASCII black magic. + return c | 0x20; +} + +/** + * Expand the address prefix for the checksum computation. + */ +data ExpandPrefix(const std::string &prefix) +{ + data ret; + ret.resize(prefix.size() + 1); + for (size_t i = 0; i < prefix.size(); ++i) + { + ret[i] = prefix[i] & 0x1f; + } + + ret[prefix.size()] = 0; + return ret; +} + +/** + * Verify a checksum. + */ +bool VerifyChecksum(const std::string &prefix, const data &values) +{ + /** + * PolyMod computes what value to xor into the final values to make the + * checksum 0. However, if we required that the checksum was 0, it would be + * the case that appending a 0 to a valid list of values would result in a + * new valid list. For that reason, cashaddr requires the resulting checksum + * to be 1 instead. + */ + return PolyMod(Cat(ExpandPrefix(prefix), values)) == 1; +} + +/** + * Create a checksum. + */ +data CreateChecksum(const std::string &prefix, const data &values) +{ + data enc = Cat(ExpandPrefix(prefix), values); + // Append 8 zeroes. + enc.resize(enc.size() + 8); + // Determine what to XOR into those 8 zeroes. + uint64_t mod = PolyMod(enc) ^ 1; + data ret(8); + for (size_t i = 0; i < 8; ++i) + { + // Convert the 5-bit groups in mod to checksum values. + ret[i] = (mod >> (5 * (7 - i))) & 0x1f; + } + + return ret; +} + +} // namespace + +namespace cashaddr +{ +/** + * Encode a cashaddr string. + */ +std::string Encode(const std::string &prefix, const data &values) +{ + data checksum = CreateChecksum(prefix, values); + data combined = Cat(values, checksum); + std::string ret = prefix + ':'; + + ret.reserve(ret.size() + combined.size()); + for (uint8_t c : combined) + { + ret += CHARSET[c]; + } + + return ret; +} + +/** + * Decode a cashaddr string. + */ +std::pair Decode(const std::string &str, const std::string &default_prefix) +{ + // Go over the string and do some sanity checks. + bool lower = false, upper = false, hasNumber = false; + size_t prefixSize = 0; + for (size_t i = 0; i < str.size(); ++i) + { + uint8_t c = str[i]; + if (c >= 'a' && c <= 'z') + { + lower = true; + continue; + } + + if (c >= 'A' && c <= 'Z') + { + upper = true; + continue; + } + + if (c >= '0' && c <= '9') + { + // We cannot have numbers in the prefix. + hasNumber = true; + continue; + } + + if (c == ':') + { + // The separator cannot be the first character, cannot have number + // and there must not be 2 separators. + if (hasNumber || i == 0 || prefixSize != 0) + { + return {}; + } + + prefixSize = i; + continue; + } + + // We have an unexpected character. + return {}; + } + + // We can't have both upper case and lowercase. + if (upper && lower) + { + return {}; + } + + // Get the prefix. + std::string prefix; + if (prefixSize == 0) + { + prefix = default_prefix; + } + else + { + prefix.reserve(prefixSize); + for (size_t i = 0; i < prefixSize; ++i) + { + prefix += LowerCase(str[i]); + } + + // Now add the ':' in the size. + prefixSize++; + } + + // Decode values. + const size_t valuesSize = str.size() - prefixSize; + data values(valuesSize); + for (size_t i = 0; i < valuesSize; ++i) + { + uint8_t c = str[i + prefixSize]; + // We have an invalid char in there. + if (c > 127 || CHARSET_REV[c] == -1) + { + return {}; + } + + values[i] = CHARSET_REV[c]; + } + + // Verify the checksum. + if (!VerifyChecksum(prefix, values)) + { + return {}; + } + + return {std::move(prefix), data(values.begin(), values.end() - 8)}; +} + +std::vector EncodingCharset() +{ + const size_t size = 32; + return std::vector(CHARSET, CHARSET + size); +} + +} // namespace cashaddr diff --git a/src/cashaddr.h b/src/cashaddr.h new file mode 100644 index 000000000..c8575da71 --- /dev/null +++ b/src/cashaddr.h @@ -0,0 +1,26 @@ +// Copyright (c) 2017 Pieter Wuille +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// Cashaddr is an address format inspired by bech32. + +#include +#include +#include + +namespace cashaddr +{ +/** + * Encode a cashaddr string. Returns the empty string in case of failure. + */ +std::string Encode(const std::string &prefix, const std::vector &values); + +/** + * Decode a cashaddr string. Returns (prefix, data). Empty prefix means failure. + */ +std::pair > Decode(const std::string &str, const std::string &default_prefix); + +std::vector EncodingCharset(); + +} // namespace cashaddr diff --git a/src/cashaddrenc.cpp b/src/cashaddrenc.cpp new file mode 100644 index 000000000..26c1825f9 --- /dev/null +++ b/src/cashaddrenc.cpp @@ -0,0 +1,196 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "cashaddrenc.h" +#include "cashaddr.h" +#include "chainparams.h" +#include "pubkey.h" +#include "script/script.h" +#include "utilstrencodings.h" + +#include + +#include + +namespace +{ +// Convert the data part to a 5 bit representation. +template +std::vector PackAddrData(const T &id, uint8_t type) +{ + uint8_t version_byte(type << 3); + size_t size = id.size(); + uint8_t encoded_size = 0; + switch (size * 8) + { + case 160: + encoded_size = 0; + break; + case 192: + encoded_size = 1; + break; + case 224: + encoded_size = 2; + break; + case 256: + encoded_size = 3; + break; + case 320: + encoded_size = 4; + break; + case 384: + encoded_size = 5; + break; + case 448: + encoded_size = 6; + break; + case 512: + encoded_size = 7; + break; + default: + throw std::runtime_error("Error packing cashaddr: invalid address length"); + } + version_byte |= encoded_size; + std::vector data = {version_byte}; + data.insert(data.end(), std::begin(id), std::end(id)); + + std::vector converted; + // Reserve the 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. + converted.reserve(((size + 1) * 8 + 4) / 5); + ConvertBits<8, 5, true>(converted, std::begin(data), std::end(data)); + + return converted; +} + +// Implements encoding of CTxDestination using cashaddr. +class CashAddrEncoder : public boost::static_visitor +{ +public: + CashAddrEncoder(const CChainParams &p) : params(p) {} + std::string operator()(const CKeyID &id) const + { + std::vector data = PackAddrData(id, PUBKEY_TYPE); + return cashaddr::Encode(params.CashAddrPrefix(), data); + } + + std::string operator()(const CScriptID &id) const + { + std::vector data = PackAddrData(id, SCRIPT_TYPE); + return cashaddr::Encode(params.CashAddrPrefix(), data); + } + + std::string operator()(const CNoDestination &) const { return ""; } +private: + const CChainParams ¶ms; +}; + +} // anon ns + +std::string EncodeCashAddr(const CTxDestination &dst, const CChainParams ¶ms) +{ + return boost::apply_visitor(CashAddrEncoder(params), dst); +} + +CTxDestination DecodeCashAddr(const std::string &addr, const CChainParams ¶ms) +{ + CashAddrContent content = DecodeCashAddrContent(addr, params); + if (content.hash.size() == 0) + { + return CNoDestination{}; + } + + return DecodeCashAddrDestination(content); +} + +CashAddrContent DecodeCashAddrContent(const std::string &addr, const CChainParams ¶ms) +{ + std::string prefix; + std::vector payload; + std::tie(prefix, payload) = cashaddr::Decode(addr, params.CashAddrPrefix()); + + if (prefix != params.CashAddrPrefix()) + { + return {}; + } + + if (payload.empty()) + { + return {}; + } + + // Check that the padding is zero. + size_t extrabits = payload.size() * 5 % 8; + if (extrabits >= 5) + { + // We have more padding than allowed. + return {}; + } + + uint8_t last = payload.back(); + uint8_t mask = (1 << extrabits) - 1; + if (last & mask) + { + // We have non zero bits as padding. + return {}; + } + + std::vector data; + data.reserve(payload.size() * 5 / 8); + ConvertBits<5, 8, false>(data, begin(payload), end(payload)); + + // Decode type and size from the version. + uint8_t version = data[0]; + if (version & 0x80) + { + // First bit is reserved. + return {}; + } + + auto type = CashAddrType((version >> 3) & 0x1f); + uint32_t hash_size = 20 + 4 * (version & 0x03); + if (version & 0x04) + { + hash_size *= 2; + } + + // Check that we decoded the exact number of bytes we expected. + if (data.size() != hash_size + 1) + { + return {}; + } + + // Pop the version. + data.erase(data.begin()); + return {type, std::move(data)}; +} + +CTxDestination DecodeCashAddrDestination(const CashAddrContent &content) +{ + if (content.hash.size() != 20) + { + // Only 20 bytes hash are supported now. + return CNoDestination{}; + } + + uint160 hash; + std::copy(begin(content.hash), end(content.hash), hash.begin()); + + switch (content.type) + { + case PUBKEY_TYPE: + return CKeyID(hash); + case SCRIPT_TYPE: + return CScriptID(hash); + default: + return CNoDestination{}; + } +} + +// PackCashAddrContent allows for testing PackAddrData in unittests due to +// template definitions. +std::vector PackCashAddrContent(const CashAddrContent &content) +{ + return PackAddrData(content.hash, content.type); +} diff --git a/src/cashaddrenc.h b/src/cashaddrenc.h new file mode 100644 index 000000000..91d81a600 --- /dev/null +++ b/src/cashaddrenc.h @@ -0,0 +1,33 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#ifndef BITCOIN_CASHADDRENC_H +#define BITCOIN_CASHADDRENC_H + +#include "script/standard.h" + +#include +#include + +class CChainParams; + +enum CashAddrType : uint8_t +{ + PUBKEY_TYPE = 0, + SCRIPT_TYPE = 1 +}; + +std::string EncodeCashAddr(const CTxDestination &, const CChainParams &); + +struct CashAddrContent +{ + CashAddrType type; + std::vector hash; +}; + +CTxDestination DecodeCashAddr(const std::string &addr, const CChainParams ¶ms); +CashAddrContent DecodeCashAddrContent(const std::string &addr, const CChainParams ¶ms); +CTxDestination DecodeCashAddrDestination(const CashAddrContent &content); + +std::vector PackCashAddrContent(const CashAddrContent &content); +#endif diff --git a/src/chainparams.cpp b/src/chainparams.cpp index cff842c91..623ce0e79 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -157,6 +157,7 @@ public: base58Prefixes[SECRET_KEY] = std::vector(1,153); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x88)(0xB2)(0x1E).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x88)(0xAD)(0xE4).convert_to_container >(); + cashaddrPrefix = "blackcoin"; vFixedSeeds = std::vector(pnSeed6_main, pnSeed6_main + ARRAYLEN(pnSeed6_main)); @@ -236,6 +237,7 @@ public: base58Prefixes[SECRET_KEY] = std::vector(1,239); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x35)(0x87)(0xCF).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x35)(0x83)(0x94).convert_to_container >(); + cashaddrPrefix = "blktest"; vFixedSeeds = std::vector(pnSeed6_test, pnSeed6_test + ARRAYLEN(pnSeed6_test)); @@ -346,7 +348,7 @@ public: base58Prefixes[SECRET_KEY] = std::vector(1,153); base58Prefixes[EXT_PUBLIC_KEY] = boost::assign::list_of(0x04)(0x88)(0xB2)(0x1E).convert_to_container >(); base58Prefixes[EXT_SECRET_KEY] = boost::assign::list_of(0x04)(0x88)(0xAD)(0xE4).convert_to_container >(); - + cashaddrPrefix = "blkreg"; fMiningRequiresPeers = false; fDefaultConsistencyChecks = true; diff --git a/src/chainparams.h b/src/chainparams.h index f2956ea60..03b51616f 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -73,7 +73,8 @@ public: /** Return the BIP70 network string (main, test or regtest) */ std::string NetworkIDString() const { return strNetworkID; } const std::vector& DNSSeeds() const { return vSeeds; } - const std::vector& Base58Prefix(Base58Type type) const { return base58Prefixes[type]; } + 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; } int LastPOWBlock() const { return consensus.nLastPOWBlock; } @@ -88,7 +89,8 @@ protected: long nMaxTipAge; uint64_t nPruneAfterHeight; std::vector vSeeds; - std::vector base58Prefixes[MAX_BASE58_TYPES]; + std::vector base58Prefixes[MAX_BASE58_TYPES]; + std::string cashaddrPrefix; std::string strNetworkID; CBlock genesis; std::vector vFixedSeeds; diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 000000000..4e17d66ca --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,16 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "config.h" +#include "chainparams.h" +#include "consensus/consensus.h" + +GlobalConfig::GlobalConfig() : useCashAddr(false) {} +const CChainParams &GlobalConfig::GetChainParams() const { return Params(); } +static GlobalConfig gConfig; + +const Config &GetConfig() { return gConfig; } +void GlobalConfig::SetCashAddrEncoding(bool c) { useCashAddr = c; } +bool GlobalConfig::UseCashAddrEncoding() const { return useCashAddr; } +const CChainParams &DummyConfig::GetChainParams() const { return Params(CBaseChainParams::REGTEST); } diff --git a/src/config.h b/src/config.h new file mode 100644 index 000000000..96aeb8e0e --- /dev/null +++ b/src/config.h @@ -0,0 +1,46 @@ +// Copyright (c) 2017 Amaury SÉCHET +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_CONFIG2_H +#define BITCOIN_CONFIG2_H + +#include + +#include + +class CChainParams; + +class Config : public boost::noncopyable +{ +public: + virtual const CChainParams &GetChainParams() const = 0; + virtual void SetCashAddrEncoding(bool) = 0; + virtual bool UseCashAddrEncoding() const = 0; +}; + +class GlobalConfig final : public Config +{ +public: + GlobalConfig(); + const CChainParams &GetChainParams() const; + void SetCashAddrEncoding(bool) override; + bool UseCashAddrEncoding() const override; + +private: + bool useCashAddr; +}; + +// Dummy for subclassing in unittests +class DummyConfig : public Config +{ +public: + const CChainParams &GetChainParams() const override; + void SetCashAddrEncoding(bool) override {} + bool UseCashAddrEncoding() const override { return false; } +}; + +// Temporary woraround. +const Config &GetConfig(); + +#endif diff --git a/src/core_write.cpp b/src/core_write.cpp index b660e86c3..afdf62863 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -4,7 +4,7 @@ #include "core_io.h" -#include "base58.h" +#include "dstencode.h" #include "primitives/transaction.h" #include "script/script.h" #include "script/standard.h" @@ -143,8 +143,10 @@ void ScriptPubKeyToUniv(const CScript& scriptPubKey, out.pushKV("type", GetTxnOutputType(type)); UniValue a(UniValue::VARR); - BOOST_FOREACH(const CTxDestination& addr, addresses) - a.push_back(CBitcoinAddress(addr).ToString()); + for (const CTxDestination& addr : addresses) + { + a.push_back(EncodeDestination(addr)); + } out.pushKV("addresses", a); } diff --git a/src/dstencode.cpp b/src/dstencode.cpp new file mode 100644 index 000000000..841bf74dd --- /dev/null +++ b/src/dstencode.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "dstencode.h" +#include "base58.h" +#include "cashaddrenc.h" +#include "chainparams.h" +#include "config.h" +#include "script/standard.h" + +std::string EncodeDestination(const CTxDestination &dst, const CChainParams ¶ms, const Config &cfg) +{ + return cfg.UseCashAddrEncoding() ? EncodeCashAddr(dst, params) : EncodeLegacyAddr(dst, params); +} + +CTxDestination DecodeDestination(const std::string &addr, const CChainParams ¶ms) +{ + CTxDestination dst = DecodeCashAddr(addr, params); + if (IsValidDestination(dst)) + { + return dst; + } + return DecodeLegacyAddr(addr, params); +} + +bool IsValidDestinationString(const std::string &addr, const CChainParams ¶ms) +{ + return IsValidDestination(DecodeDestination(addr, params)); +} + +std::string EncodeDestination(const CTxDestination &dst) { return EncodeDestination(dst, Params(), GetConfig()); } +CTxDestination DecodeDestination(const std::string &addr) { return DecodeDestination(addr, Params()); } +bool IsValidDestinationString(const std::string &addr) { return IsValidDestinationString(addr, Params()); } diff --git a/src/dstencode.h b/src/dstencode.h new file mode 100644 index 000000000..e2e3e8103 --- /dev/null +++ b/src/dstencode.h @@ -0,0 +1,24 @@ +#ifndef BITCOIN_DSTENCODE_H +#define BITCOIN_DSTENCODE_H + +// key.h and pubkey.h are not used here, but gcc doesn't want to instantiate +// CTxDestination if types are unknown +#include "key.h" +#include "pubkey.h" +#include "script/standard.h" +#include + +class Config; +class CChainParams; + +std::string EncodeDestination(const CTxDestination &, const CChainParams &, const Config &); +CTxDestination DecodeDestination(const std::string &addr, const CChainParams &); +bool IsValidDestinationString(const std::string &addr, const CChainParams ¶ms); + +// Temporary workaround. Don't rely on global state, pass all parameters in new +// code. +std::string EncodeDestination(const CTxDestination &); +CTxDestination DecodeDestination(const std::string &addr); +bool IsValidDestinationString(const std::string &addr); + +#endif // BITCOIN_DSTENCODE_H diff --git a/src/fs.h b/src/fs.h new file mode 100644 index 000000000..585cbf9c3 --- /dev/null +++ b/src/fs.h @@ -0,0 +1,24 @@ +// Copyright (c) 2017 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_FS_H +#define BITCOIN_FS_H + +#include +#include + +#include +#include +#include + +/** Filesystem operations and types */ +namespace fs = boost::filesystem; + +/** Bridge operations to C stdio */ +namespace fsbridge { + FILE *fopen(const fs::path& p, const char *mode); + FILE *freopen(const fs::path& p, const char *mode, FILE *stream); +}; + +#endif diff --git a/src/init.cpp b/src/init.cpp index ca11177fe..62d879bec 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -15,6 +15,7 @@ #include "chainparams.h" #include "checkpoints.h" #include "compat/sanity.h" +#include "config.h" #include "consensus/validation.h" #include "httpserver.h" #include "httprpc.h" @@ -768,7 +769,7 @@ void InitLogging() /** Initialize bitcoin. * @pre Parameters should be parsed and config file should be read. */ -bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) +bool AppInit2(Config& config, boost::thread_group& threadGroup, CScheduler& scheduler) { // ********************************************************* Step 1: setup #ifdef _MSC_VER @@ -1377,6 +1378,11 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) fFeeEstimatesInitialized = true; // ********************************************************* Step 8: load wallet + + // Encoded addresses using cashaddr instead of base58 + // Activates by default on Feb, 15, 2018 + config.SetCashAddrEncoding(GetBoolArg("-usecashaddr", GetAdjustedTime() > 1518652800)); + #ifdef ENABLE_WALLET if (fDisableWallet) { pwalletMain = NULL; diff --git a/src/init.h b/src/init.h index 63e07ccb3..da3028593 100644 --- a/src/init.h +++ b/src/init.h @@ -8,6 +8,7 @@ #include +class Config; class CScheduler; class CWallet; @@ -25,7 +26,7 @@ void Shutdown(); void InitLogging(); //!Parameter interaction: change current parameters depending on various rules void InitParameterInteraction(); -bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler); +bool AppInit2(Config& config, boost::thread_group& threadGroup, CScheduler& scheduler); /** The help message mode determines what help message to show */ enum HelpMessageMode { diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 71ed3618e..688845450 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -7,7 +7,7 @@ #include "guiutil.h" #include "walletmodel.h" -#include "base58.h" +#include "dstencode.h" #include "wallet/wallet.h" #include @@ -81,16 +81,16 @@ public: cachedAddressTable.clear(); { LOCK(wallet->cs_wallet); - BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, wallet->mapAddressBook) + for (const std::pair& item : wallet->mapAddressBook) { - const CBitcoinAddress& address = item.first; - bool fMine = IsMine(*wallet, address.Get()); + const CTxDestination& address = item.first; + bool fMine = IsMine(*wallet, address); AddressTableEntry::Type addressType = translateTransactionType( QString::fromStdString(item.second.purpose), fMine); const std::string& strName = item.second.name; cachedAddressTable.append(AddressTableEntry(addressType, QString::fromStdString(strName), - QString::fromStdString(address.ToString()))); + QString::fromStdString(EncodeDestination(address)))); } } // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order @@ -247,7 +247,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, if(role == Qt::EditRole) { LOCK(wallet->cs_wallet); /* For SetAddressBook / DelAddressBook */ - CTxDestination curAddress = CBitcoinAddress(rec->address.toStdString()).Get(); + CTxDestination curAddress = DecodeDestination(rec->address.toStdString()); if(index.column() == Label) { // Do nothing, if old label == new label @@ -258,7 +258,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, } wallet->SetAddressBook(curAddress, value.toString().toStdString(), strPurpose); } else if(index.column() == Address) { - CTxDestination newAddress = CBitcoinAddress(value.toString().toStdString()).Get(); + CTxDestination newAddress = DecodeDestination(value.toString().toStdString()); // Refuse to set invalid address, set error status and return false if(boost::get(&newAddress)) { @@ -359,7 +359,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con // Check for duplicate addresses { LOCK(wallet->cs_wallet); - if(wallet->mapAddressBook.count(CBitcoinAddress(strAddress).Get())) + if(wallet->mapAddressBook.count(DecodeDestination(strAddress))) { editStatus = DUPLICATE_ADDRESS; return QString(); @@ -385,7 +385,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con return QString(); } } - strAddress = CBitcoinAddress(newKey.GetID()).ToString(); + strAddress = EncodeDestination(newKey.GetID()); } else { @@ -395,7 +395,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con // Add entry { LOCK(wallet->cs_wallet); - wallet->SetAddressBook(CBitcoinAddress(strAddress).Get(), strLabel, + wallet->SetAddressBook(DecodeDestination(strAddress), strLabel, (type == Send ? "send" : "receive")); } return QString::fromStdString(strAddress); @@ -413,7 +413,7 @@ bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent } { LOCK(wallet->cs_wallet); - wallet->DelAddressBook(CBitcoinAddress(rec->address.toStdString()).Get()); + wallet->DelAddressBook(DecodeDestination(rec->address.toStdString())); } return true; } @@ -424,8 +424,8 @@ QString AddressTableModel::labelForAddress(const QString &address) const { { LOCK(wallet->cs_wallet); - CBitcoinAddress address_parsed(address.toStdString()); - std::map::iterator mi = wallet->mapAddressBook.find(address_parsed.Get()); + CTxDestination destination = DecodeDestination(address.toStdString()); + std::map::iterator mi = wallet->mapAddressBook.find(destination); if (mi != wallet->mapAddressBook.end()) { return QString::fromStdString(mi->second.name); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index a1b8a5407..2b26ec4b9 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -10,6 +10,7 @@ #include "chainparams.h" #include "clientmodel.h" +#include "config.h" #include "guiconstants.h" #include "guiutil.h" #include "intro.h" @@ -81,10 +82,10 @@ Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); Q_DECLARE_METATYPE(bool*) Q_DECLARE_METATYPE(CAmount) -static void InitMessage(const std::string &message) -{ - LogPrintf("init message: %s\n", message); -} +// Config is non-copyable so we can only register pointers to it +Q_DECLARE_METATYPE(Config *) + +static void InitMessage(const std::string &message) { LogPrintf("init message: %s\n", message); } /* Translate string to current locale using Qt. @@ -173,7 +174,7 @@ public: explicit BitcoinCore(); public Q_SLOTS: - void initialize(); + void initialize(Config *config); void shutdown(); Q_SIGNALS: @@ -206,12 +207,12 @@ public: /// Create options model void createOptionsModel(bool resetSettings); /// Create main window - void createWindow(const NetworkStyle *networkStyle); + void createWindow(const Config *, const NetworkStyle *networkStyle); /// Create splash screen void createSplashScreen(const NetworkStyle *networkStyle); /// Request core initialization - void requestInitialize(); + void requestInitialize(Config &config); /// Request core shutdown void requestShutdown(); @@ -228,7 +229,7 @@ public Q_SLOTS: void handleRunawayException(const QString &message); Q_SIGNALS: - void requestedInitialize(); + void requestedInitialize(Config *config); void requestedShutdown(); void stopThread(); void splashFinished(QWidget *window); @@ -262,12 +263,13 @@ void BitcoinCore::handleRunawayException(const std::exception *e) Q_EMIT runawayException(QString::fromStdString(strMiscWarning)); } -void BitcoinCore::initialize() +void BitcoinCore::initialize(Config *cfg) { + Config &config(*cfg); try { qDebug() << __func__ << ": Running AppInit2 in thread"; - int rv = AppInit2(threadGroup, scheduler); + int rv = AppInit2(config, threadGroup, scheduler); Q_EMIT initializeResult(rv); } catch (const std::exception& e) { handleRunawayException(&e); @@ -353,9 +355,9 @@ void BitcoinApplication::createOptionsModel(bool resetSettings) optionsModel = new OptionsModel(NULL, resetSettings); } -void BitcoinApplication::createWindow(const NetworkStyle *networkStyle) +void BitcoinApplication::createWindow(const Config *config, const NetworkStyle *networkStyle) { - window = new BitcoinGUI(platformStyle, networkStyle, 0); + window = new BitcoinGUI(config, platformStyle, networkStyle, 0); pollShutdownTimer = new QTimer(window); connect(pollShutdownTimer, SIGNAL(timeout()), window, SLOT(detectShutdown())); @@ -384,7 +386,7 @@ void BitcoinApplication::startThread() connect(executor, SIGNAL(initializeResult(int)), this, SLOT(initializeResult(int))); connect(executor, SIGNAL(shutdownResult(int)), this, SLOT(shutdownResult(int))); connect(executor, SIGNAL(runawayException(QString)), this, SLOT(handleRunawayException(QString))); - connect(this, SIGNAL(requestedInitialize()), executor, SLOT(initialize())); + connect(this, SIGNAL(requestedInitialize(Config *)), executor, SLOT(initialize(Config *))); connect(this, SIGNAL(requestedShutdown()), executor, SLOT(shutdown())); /* make sure executor object is deleted in its own thread */ connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater())); @@ -399,11 +401,11 @@ void BitcoinApplication::parameterSetup() InitParameterInteraction(); } -void BitcoinApplication::requestInitialize() +void BitcoinApplication::requestInitialize(Config &config) { qDebug() << __func__ << ": Requesting initialize"; startThread(); - Q_EMIT requestedInitialize(); + Q_EMIT requestedInitialize(&config); } void BitcoinApplication::requestShutdown() @@ -472,7 +474,7 @@ void BitcoinApplication::initializeResult(int retval) #ifdef ENABLE_WALLET // Now that initialization/startup is done, process any command-line - // bitcoin: URIs or payment requests: + // blackcoin: URIs or payment requests: connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), window, SLOT(handlePaymentRequest(SendCoinsRecipient))); connect(window, SIGNAL(receivedURI(QString)), @@ -547,7 +549,9 @@ int main(int argc, char *argv[]) qRegisterMetaType< bool* >(); // Need to pass name here as CAmount is a typedef (see http://qt-project.org/doc/qt-5/qmetatype.html#qRegisterMetaType) // IMPORTANT if it is no longer a typedef use the normal variant above - qRegisterMetaType< CAmount >("CAmount"); + qRegisterMetaType("CAmount"); + // Config is non-copyable so we can't register as a non pointer type + qRegisterMetaType(); /// 3. Application identification // must be set before OptionsModel is initialized or translations are loaded, @@ -629,7 +633,8 @@ int main(int argc, char *argv[]) exit(0); // Start up the payment server early, too, so impatient users that click on - // bitcoin: links repeatedly have their payment requests routed to this process: + // blackcoin: links repeatedly have their payment requests routed to this + // process: app.createPaymentServer(); #endif @@ -658,10 +663,13 @@ int main(int argc, char *argv[]) if (GetBoolArg("-splash", DEFAULT_SPLASHSCREEN) && !GetBoolArg("-min", false)) app.createSplashScreen(networkStyle.data()); + // Get global config + Config &config = const_cast(GetConfig()); + try { - app.createWindow(networkStyle.data()); - app.requestInitialize(); + app.createWindow(&config, networkStyle.data()); + app.requestInitialize(config); #if defined(Q_OS_WIN) && QT_VERSION >= 0x050000 WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("Bitcoin Core didn't yet exit safely..."), (HWND)app.getMainWinId()); #endif diff --git a/src/qt/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp index d712705c4..7ca897f35 100644 --- a/src/qt/bitcoinaddressvalidator.cpp +++ b/src/qt/bitcoinaddressvalidator.cpp @@ -1,10 +1,13 @@ // Copyright (c) 2011-2014 The Bitcoin Core developers +// Copyright (c) 2015-2017 The Bitcoin Unlimited developers +// Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "bitcoinaddressvalidator.h" -#include "base58.h" +#include "cashaddr.h" +#include "dstencode.h" /* Base58 characters are: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" @@ -14,9 +17,49 @@ - All upper-case letters except for 'I' and 'O' - All lower-case letters except for 'l' */ +static bool ValidLegacyInput(const QString &input) +{ + // Alphanumeric and not a 'forbidden' character + for (QChar ch : input) + { + if (!(((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) && ch != 'l' && + ch != 'I' && ch != '0' && ch != 'O')) + return false; + } + return true; +} -BitcoinAddressEntryValidator::BitcoinAddressEntryValidator(QObject *parent) : - QValidator(parent) +static bool ValidCashaddrInput(const QString &prefix, const QString &input) +{ + std::vector charset = cashaddr::EncodingCharset(); + + // Input may be incomplete. We're checking if it so far looks good. + + for (int i = 0; i < input.size(); ++i) + { + char ch = std::tolower(input[i].toLatin1()); + + // Does the input have the right prefix? + if (i < prefix.size()) + { + if (ch != prefix[i].toLatin1()) + { + return false; + } + continue; + } + + // Payload, must use cashaddr charset. + if (std::find(begin(charset), end(charset), ch) == end(charset)) + { + return false; + } + } + return true; +} + +BitcoinAddressEntryValidator::BitcoinAddressEntryValidator(const std::string &cashaddrprefix, QObject *parent) + : QValidator(parent), cashaddrprefix(cashaddrprefix) { } @@ -59,25 +102,9 @@ QValidator::State BitcoinAddressEntryValidator::validate(QString &input, int &po } // Validation - QValidator::State state = QValidator::Acceptable; - for (int idx = 0; idx < input.size(); ++idx) - { - int ch = input.at(idx).unicode(); - - if (((ch >= '0' && ch<='9') || - (ch >= 'a' && ch<='z') || - (ch >= 'A' && ch<='Z')) && - ch != 'l' && ch != 'I' && ch != '0' && ch != 'O') - { - // Alphanumeric and not a 'forbidden' character - } - else - { - state = QValidator::Invalid; - } - } - - return state; + const QString cashPrefix = QString::fromStdString(cashaddrprefix) + ":"; + return (ValidLegacyInput(input) || ValidCashaddrInput(cashPrefix, input)) ? QValidator::Acceptable : + QValidator::Invalid; } BitcoinAddressCheckValidator::BitcoinAddressCheckValidator(QObject *parent) : @@ -89,9 +116,10 @@ QValidator::State BitcoinAddressCheckValidator::validate(QString &input, int &po { Q_UNUSED(pos); // Validate the passed Bitcoin address - CBitcoinAddress addr(input.toStdString()); - if (addr.IsValid()) + if (IsValidDestinationString(input.toStdString())) + { return QValidator::Acceptable; + } return QValidator::Invalid; } diff --git a/src/qt/bitcoinaddressvalidator.h b/src/qt/bitcoinaddressvalidator.h index 30d4a26d0..98b3e9730 100644 --- a/src/qt/bitcoinaddressvalidator.h +++ b/src/qt/bitcoinaddressvalidator.h @@ -1,4 +1,6 @@ // Copyright (c) 2011-2014 The Bitcoin Core developers +// Copyright (c) 2015-2017 The Bitcoin Unlimited developers +// Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -7,7 +9,8 @@ #include -/** Base58 entry widget validator, checks for valid characters and +/** + * Bitcoin address entry widget validator, checks for valid characters and * removes some whitespace. */ class BitcoinAddressEntryValidator : public QValidator @@ -15,9 +18,12 @@ class BitcoinAddressEntryValidator : public QValidator Q_OBJECT public: - explicit BitcoinAddressEntryValidator(QObject *parent); + explicit BitcoinAddressEntryValidator(const std::string &cashaddrprefix, QObject *parent); State validate(QString &input, int &pos) const; + +private: + std::string cashaddrprefix; }; /** Bitcoin address widget validator, checks for a valid bitcoin address. diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index afd3914f8..c72ded573 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -75,7 +75,7 @@ const QString BitcoinGUI::DEFAULT_WALLET = "~Default"; extern int64_t nLastCoinStakeSearchInterval; double GetPoSKernelPS(); -BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *networkStyle, QWidget *parent) : +BitcoinGUI::BitcoinGUI(const Config *cfg, const PlatformStyle *platformStyle, const NetworkStyle *networkStyle, QWidget *parent) : QMainWindow(parent), clientModel(0), walletFrame(0), @@ -118,7 +118,8 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *n helpMessageDialog(0), prevBlocks(0), spinnerFrame(0), - platformStyle(platformStyle) + platformStyle(platformStyle), + cfg(cfg) { GUIUtil::restoreWindowGeometry("nWindow", QSize(850, 550), this); @@ -156,7 +157,7 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *n if(enableWallet) { /** Create wallet frame and make it the central widget */ - walletFrame = new WalletFrame(platformStyle, this); + walletFrame = new WalletFrame(platformStyle, cfg, this); setCentralWidget(walletFrame); } else #endif // ENABLE_WALLET @@ -297,7 +298,7 @@ void BitcoinGUI::createActions() sendCoinsMenuAction->setToolTip(sendCoinsMenuAction->statusTip()); receiveCoinsAction = new QAction(platformStyle->SingleColorIcon(":/icons/receiving_addresses"), tr("&Receive"), this); - receiveCoinsAction->setStatusTip(tr("Request payments (generates QR codes and bitcoin: URIs)")); + receiveCoinsAction->setStatusTip(tr("Request payments (generates QR codes and %1: URIs)").arg(GUIUtil::bitcoinURIScheme(*cfg))); receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip()); receiveCoinsAction->setCheckable(true); receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3)); @@ -376,7 +377,7 @@ void BitcoinGUI::createActions() usedReceivingAddressesAction->setStatusTip(tr("Show the list of used receiving addresses and labels")); openAction = new QAction(platformStyle->TextColorIcon(":/icons/open"), tr("Open &URI..."), this); - openAction->setStatusTip(tr("Open a bitcoin: URI or payment request")); + openAction->setStatusTip(tr("Open a %1: URI or payment request").arg(GUIUtil::bitcoinURIScheme(*cfg))); showHelpMessageAction = new QAction(platformStyle->TextColorIcon(":/icons/info"), tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); @@ -670,8 +671,8 @@ void BitcoinGUI::showHelpMessageClicked() #ifdef ENABLE_WALLET void BitcoinGUI::openClicked() { - OpenURIDialog dlg(this); - if(dlg.exec()) + OpenURIDialog dlg(cfg, this); + if (dlg.exec()) { Q_EMIT receivedURI(dlg.getURI()); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 4b8cf6a04..602e54238 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -30,6 +30,7 @@ class WalletFrame; class WalletModel; class HelpMessageDialog; +class Config; class CWallet; QT_BEGIN_NAMESPACE @@ -50,7 +51,10 @@ public: static const QString DEFAULT_WALLET; static const std::string DEFAULT_UIPLATFORM; - explicit BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *networkStyle, QWidget *parent = 0); + explicit BitcoinGUI(const Config *, + const PlatformStyle *platformStyle, + const NetworkStyle *networkStyle, + QWidget *parent = 0); ~BitcoinGUI(); /** Set the client model. @@ -129,6 +133,7 @@ private: uint64_t nWeight; const PlatformStyle *platformStyle; + const Config *cfg; /** Create the main UI actions. */ void createActions(); diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index f24322a92..eb711108c 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -14,6 +14,7 @@ #include "walletmodel.h" #include "coincontrol.h" +#include "dstencode.h" #include "init.h" #include "main.h" // For minRelayTxFee #include "wallet/wallet.h" @@ -734,9 +735,10 @@ void CoinControlDialog::updateView() QString sAddress = ""; if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, outputAddress)) { - sAddress = QString::fromStdString(CBitcoinAddress(outputAddress).ToString()); + sAddress = QString::fromStdString(EncodeDestination(outputAddress)); - // if listMode or change => show bitcoin address. In tree mode, address is not shown again for direct wallet address outputs + // if listMode or change => show bitcoin address. In tree mode, + // address is not shown again for direct wallet address outputs if (!treeMode || (!(sAddress == sWalletAddress))) itemOutput->setText(COLUMN_ADDRESS, sAddress); diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 268d39666..483df1d99 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -47,7 +47,7 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80; static const int MAX_URI_LENGTH = 255; /* QRCodeDialog -- size of exported QR Code image */ -#define QR_IMAGE_SIZE 300 +#define QR_IMAGE_SIZE 350 /* Number of frames in spinner animation */ #define SPINNER_FRAMES 36 diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 24c2eecb3..4dd7cb30b 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -10,12 +10,16 @@ #include "walletmodel.h" #include "primitives/transaction.h" +#include "cashaddr.h" +#include "config.h" +#include "dstencode.h" #include "init.h" #include "main.h" // For minRelayTxFee #include "protocol.h" #include "script/script.h" #include "script/standard.h" #include "util.h" +#include "utilstrencodings.h" #ifdef WIN32 #ifdef _WIN32_WINNT @@ -107,35 +111,46 @@ QFont fixedPitchFont() #endif } -// Just some dummy data to generate an convincing random-looking (but consistent) address -static const uint8_t dummydata[] = {0xeb,0x15,0x23,0x1d,0xfc,0xeb,0x60,0x92,0x58,0x86,0xb6,0x7d,0x06,0x52,0x99,0x92,0x59,0x15,0xae,0xb1,0x72,0xc0,0x66,0x47}; - -// Generate a dummy address with invalid CRC, starting with the network prefix. -static std::string DummyAddress(const CChainParams ¶ms) +static std::string MakeAddrInvalid(std::string addr) { - std::vector sourcedata = params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); - sourcedata.insert(sourcedata.end(), dummydata, dummydata + sizeof(dummydata)); - for(int i=0; i<256; ++i) { // Try every trailing byte - std::string s = EncodeBase58(begin_ptr(sourcedata), end_ptr(sourcedata)); - if (!CBitcoinAddress(s).IsValid()) - return s; - sourcedata[sourcedata.size()-1] += 1; + if (addr.size() < 2) + { + return ""; + } + + // Checksum is at the end of the address. Swapping chars to make it invalid. + std::swap(addr[addr.size() - 1], addr[addr.size() - 2]); + if (!IsValidDestinationString(addr)) + { + return addr; } return ""; } +std::string DummyAddress(const CChainParams ¶ms, const Config &cfg) +{ + // Just some dummy data to generate an convincing random-looking (but + // consistent) address + static const std::vector dummydata = {0xeb, 0x15, 0x23, 0x1d, 0xfc, 0xeb, 0x60, 0x92, 0x58, 0x86, 0xb6, + 0x7d, 0x06, 0x52, 0x99, 0x92, 0x59, 0x15, 0xae, 0xb1}; + + const CTxDestination dstKey = CKeyID(uint160(dummydata)); + return MakeAddrInvalid(EncodeDestination(dstKey, params, cfg)); +} + void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) { parent->setFocusProxy(widget); widget->setFont(fixedPitchFont()); + const CChainParams ¶ms = Params(); #if QT_VERSION >= 0x040700 // We don't want translators to use own addresses in translations // and this is the only place, where this address is supplied. - widget->setPlaceholderText(QObject::tr("Enter a Bitcoin address (e.g. %1)").arg( - QString::fromStdString(DummyAddress(Params())))); + widget->setPlaceholderText(QObject::tr("Enter a Bitcoin address (e.g. %1)") + .arg(QString::fromStdString(DummyAddress(params, GetConfig())))); #endif - widget->setValidator(new BitcoinAddressEntryValidator(parent)); + widget->setValidator(new BitcoinAddressEntryValidator(params.CashAddrPrefix(), parent)); widget->setCheckValidator(new BitcoinAddressCheckValidator(parent)); } @@ -148,16 +163,48 @@ void setupAmountWidget(QLineEdit *widget, QWidget *parent) widget->setAlignment(Qt::AlignRight|Qt::AlignVCenter); } -bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) +QString bitcoinURIScheme(const CChainParams ¶ms, bool useCashAddr) { - // return if URI is not valid or is no bitcoin: URI - if(!uri.isValid() || uri.scheme() != QString("bitcoin")) + if (!useCashAddr) + { + return "blackcoin"; + } + return QString::fromStdString(params.CashAddrPrefix()); +} + +QString bitcoinURIScheme(const Config &cfg) +{ + return bitcoinURIScheme(cfg.GetChainParams(), cfg.UseCashAddrEncoding()); +} + +static bool IsCashAddrEncoded(const QUrl &uri) +{ + const std::string addr = (uri.scheme() + ":" + uri.path()).toStdString(); + auto decoded = cashaddr::Decode(addr, ""); + return !decoded.first.empty(); +} + +bool parseBitcoinURI(const QString &scheme, const QUrl &uri, SendCoinsRecipient *out) +{ + // return if URI has wrong scheme. + if (!uri.isValid() || uri.scheme() != scheme) + { return false; + } SendCoinsRecipient rv; - rv.address = uri.path(); + if (IsCashAddrEncoded(uri)) + { + rv.address = uri.scheme() + ":" + uri.path(); + } + else + { + // strip out uri scheme for base58 encoded addresses + rv.address = uri.path(); + } // Trim any following forward slash which may have been added by the OS - if (rv.address.endsWith("/")) { + if (rv.address.endsWith("/")) + { rv.address.truncate(rv.address.length() - 1); } rv.amount = 0; @@ -189,9 +236,9 @@ bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) } else if (i->first == "amount") { - if(!i->second.isEmpty()) + if (!i->second.isEmpty()) { - if(!BitcoinUnits::parse(BitcoinUnits::BTC, i->second, &rv.amount)) + if (!BitcoinUnits::parse(BitcoinUnits::BTC, i->second, &rv.amount)) { return false; } @@ -202,30 +249,35 @@ bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) if (fShouldReturnFalse) return false; } - if(out) + if (out) { *out = rv; } return true; } -bool parseBitcoinURI(QString uri, SendCoinsRecipient *out) +bool parseBitcoinURI(const QString &scheme, QString uri, SendCoinsRecipient *out) { - // Convert bitcoin:// to bitcoin: // - // Cannot handle this later, because bitcoin:// will cause Qt to see the part after // as host, + // Cannot handle this later, because blackcoin:// + // will cause Qt to see the part after // as host, // which will lower-case it (and thus invalidate the address). - if(uri.startsWith("bitcoin://", Qt::CaseInsensitive)) + if (uri.startsWith(scheme + "://", Qt::CaseInsensitive)) { - uri.replace(0, 10, "bitcoin:"); + uri.replace(0, scheme.length() + 3, scheme + ":"); } QUrl uriInstance(uri); - return parseBitcoinURI(uriInstance, out); + return parseBitcoinURI(scheme, uriInstance, out); } -QString formatBitcoinURI(const SendCoinsRecipient &info) +QString formatBitcoinURI(const Config &cfg, const SendCoinsRecipient &info) { - QString ret = QString("blackcoin:%1").arg(info.address); + QString ret = info.address; + if (!cfg.UseCashAddrEncoding()) + { + // prefix address with uri scheme for base58 encoded addresses. + ret = (bitcoinURIScheme(cfg) + ":%1").arg(ret); + } int paramCount = 0; if (info.amount) @@ -253,7 +305,7 @@ QString formatBitcoinURI(const SendCoinsRecipient &info) bool isDust(const QString& address, const CAmount& amount) { - CTxDestination dest = CBitcoinAddress(address.toStdString()).Get(); + CTxDestination dest = DecodeDestination(address.toStdString()); CScript script = GetScriptForDestination(dest); CTxOut txOut(amount, script); return txOut.IsDust(::minRelayTxFee); diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 83cd6b5d4..90f85343d 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -19,6 +19,8 @@ class QValidatedLineEdit; class SendCoinsRecipient; +class CChainParams; +class Config; QT_BEGIN_NAMESPACE class QAbstractItemView; @@ -40,14 +42,20 @@ namespace GUIUtil // Return a monospace font QFont fixedPitchFont(); + // Generate an invalid, but convincing address. + std::string DummyAddress(const CChainParams ¶ms, const Config &cfg); + // Set up widgets for address and amounts void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent); void setupAmountWidget(QLineEdit *widget, QWidget *parent); - // Parse "bitcoin:" URI into recipient object, return true on successful parsing - bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out); - bool parseBitcoinURI(QString uri, SendCoinsRecipient *out); - QString formatBitcoinURI(const SendCoinsRecipient &info); + QString bitcoinURIScheme(const CChainParams &, bool useCashAddr); + QString bitcoinURIScheme(const Config &); + // Parse "blackcoin:" URI into recipient object, return true on successful + // parsing + bool parseBitcoinURI(const QString &scheme, const QUrl &uri, SendCoinsRecipient *out); + bool parseBitcoinURI(const QString &scheme, QString uri, SendCoinsRecipient *out); + QString formatBitcoinURI(const Config &cfg, const SendCoinsRecipient &info); // Returns true if given address+amount meets "dust" definition bool isDust(const QString& address, const CAmount& amount); diff --git a/src/qt/openuridialog.cpp b/src/qt/openuridialog.cpp index 80d0b8228..be070e4f8 100644 --- a/src/qt/openuridialog.cpp +++ b/src/qt/openuridialog.cpp @@ -10,9 +10,10 @@ #include -OpenURIDialog::OpenURIDialog(QWidget *parent) : +OpenURIDialog::OpenURIDialog(const Config *cfg, QWidget *parent) : QDialog(parent), - ui(new Ui::OpenURIDialog) + ui(new Ui::OpenURIDialog), + cfg(cfg) { ui->setupUi(this); #if QT_VERSION >= 0x040700 @@ -33,7 +34,8 @@ QString OpenURIDialog::getURI() void OpenURIDialog::accept() { SendCoinsRecipient rcp; - if(GUIUtil::parseBitcoinURI(getURI(), &rcp)) + QString uriScheme = GUIUtil::bitcoinURIScheme(*cfg); + if (GUIUtil::parseBitcoinURI(uriScheme, getURI(), &rcp)) { /* Only accept value URIs */ QDialog::accept(); @@ -48,5 +50,5 @@ void OpenURIDialog::on_selectFileButton_clicked() if(filename.isEmpty()) return; QUrl fileUri = QUrl::fromLocalFile(filename); - ui->uriEdit->setText("blackcoin:?r=" + QUrl::toPercentEncoding(fileUri.toString())); + ui->uriEdit->setText(GUIUtil::bitcoinURIScheme(*cfg) + ":?r=" + QUrl::toPercentEncoding(fileUri.toString())); } diff --git a/src/qt/openuridialog.h b/src/qt/openuridialog.h index e94593d5b..2bcb0a578 100644 --- a/src/qt/openuridialog.h +++ b/src/qt/openuridialog.h @@ -7,6 +7,8 @@ #include +class Config; + namespace Ui { class OpenURIDialog; } @@ -16,7 +18,7 @@ class OpenURIDialog : public QDialog Q_OBJECT public: - explicit OpenURIDialog(QWidget *parent); + explicit OpenURIDialog(const Config *cfg, QWidget *parent); ~OpenURIDialog(); QString getURI(); @@ -29,6 +31,7 @@ private Q_SLOTS: private: Ui::OpenURIDialog *ui; + const Config *cfg; }; #endif // BITCOIN_QT_OPENURIDIALOG_H diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 8355263ac..fe21b5a6d 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -8,8 +8,9 @@ #include "guiutil.h" #include "optionsmodel.h" -#include "base58.h" #include "chainparams.h" +#include "config.h" +#include "dstencode.h" #include "main.h" // For minRelayTxFee #include "ui_interface.h" #include "util.h" @@ -52,9 +53,9 @@ const QString BITCOIN_IPC_PREFIX("blackcoin:"); const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK"; const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; // BIP71 payment protocol media types -const char* BIP71_MIMETYPE_PAYMENT = "application/bitcoin-payment"; -const char* BIP71_MIMETYPE_PAYMENTACK = "application/bitcoin-paymentack"; -const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/bitcoin-paymentrequest"; +const char* BIP71_MIMETYPE_PAYMENT = "application/blackcoin-payment"; +const char* BIP71_MIMETYPE_PAYMENTACK = "application/blackcoin-paymentack"; +const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/blackcoin-paymentrequest"; // BIP70 max payment request size in bytes (DoS protection) const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE = 50000; @@ -192,6 +193,37 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store) // "certificate stapling" with server-side caching is more efficient } +static std::string ipcParseURI(const QString &arg, const CChainParams ¶ms, bool useCashAddr) +{ + const QString scheme = GUIUtil::bitcoinURIScheme(params, useCashAddr); + if (!arg.startsWith(scheme + ":", Qt::CaseInsensitive)) + { + return {}; + } + + SendCoinsRecipient r; + if (!GUIUtil::parseBitcoinURI(scheme, arg, &r)) + { + return {}; + } + + return r.address.toStdString(); +} + +static bool ipcCanParseCashAddrURI(const QString &arg, const std::string &network) +{ + const CChainParams ¶ms(Params(network)); + std::string addr = ipcParseURI(arg, params, true); + return IsValidDestinationString(addr, params); +} + +static bool ipcCanParseLegacyURI(const QString &arg, const std::string &network) +{ + const CChainParams ¶ms(Params(network)); + std::string addr = ipcParseURI(arg, params, false); + return IsValidDestinationString(addr, params); +} + // // Sending to the server is done synchronously, at startup. // If the server isn't already running, startup continues, @@ -203,58 +235,77 @@ void PaymentServer::LoadRootCAs(X509_STORE* _store) // void PaymentServer::ipcParseCommandLine(int argc, char* argv[]) { + std::array networks = { + &CBaseChainParams::MAIN, &CBaseChainParams::TESTNET, &CBaseChainParams::REGTEST}; + + const std::string *chosenNetwork = nullptr; + for (int i = 1; i < argc; i++) { QString arg(argv[i]); if (arg.startsWith("-")) continue; - // If the bitcoin: URI contains a payment request, we are not able to detect the - // network as that would require fetching and parsing the payment request. - // That means clicking such an URI which contains a testnet payment request - // will start a mainnet instance and throw a "wrong network" error. - if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI + const std::string *itemNetwork = nullptr; + + // Try to parse as a URI + for (auto net : networks) { - savedPaymentRequests.append(arg); - - SendCoinsRecipient r; - if (GUIUtil::parseBitcoinURI(arg, &r) && !r.address.isEmpty()) + if (ipcCanParseCashAddrURI(arg, *net)) { - CBitcoinAddress address(r.address.toStdString()); + itemNetwork = net; + break; + } - if (address.IsValid(Params(CBaseChainParams::MAIN))) - { - SelectParams(CBaseChainParams::MAIN); - } - else if (address.IsValid(Params(CBaseChainParams::TESTNET))) - { - SelectParams(CBaseChainParams::TESTNET); - } + if (ipcCanParseLegacyURI(arg, *net)) + { + itemNetwork = net; + break; } } - else if (QFile::exists(arg)) // Filename - { - savedPaymentRequests.append(arg); + if (!itemNetwork && QFile::exists(arg)) + { + // Filename PaymentRequestPlus request; if (readPaymentRequestFromFile(arg, request)) { - if (request.getDetails().network() == "main") + for (auto net : networks) { - SelectParams(CBaseChainParams::MAIN); - } - else if (request.getDetails().network() == "test") - { - SelectParams(CBaseChainParams::TESTNET); + if (*net == request.getDetails().network()) + { + itemNetwork = net; + } } } } - else + + if (itemNetwork == nullptr) { - // Printing to debug.log is about the best we can do here, the - // GUI hasn't started yet so we can't pop up a message box. - qWarning() << "PaymentServer::ipcSendCommandLine: Payment request file does not exist: " << arg; + // Printing to debug.log is about the best we can do here, the GUI + // hasn't started yet so we can't pop up a message box. + qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " + "file or URI does not exist or is invalid: " + << arg; + continue; } + + if (chosenNetwork && chosenNetwork != itemNetwork) + { + qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " + "from network " + << QString(itemNetwork->c_str()) << " does not match already chosen network " + << QString(chosenNetwork->c_str()); + continue; + } + + savedPaymentRequests.append(arg); + chosenNetwork = itemNetwork; + } + + if (chosenNetwork) + { + SelectParams(*chosenNetwork); } } @@ -309,7 +360,7 @@ PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : GOOGLE_PROTOBUF_VERIFY_VERSION; // Install global event filter to catch QFileOpenEvents - // on Mac: sent when you click bitcoin: links + // on Mac: sent when you click blackcoin: links // other OSes: helpful when dealing with payment request files if (parent) parent->installEventFilter(this); @@ -323,10 +374,11 @@ PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : { uriServer = new QLocalServer(this); - if (!uriServer->listen(name)) { - // constructor is called early in init, so don't use "Q_EMIT message()" here - QMessageBox::critical(0, tr("Payment request error"), - tr("Cannot start blackcoin: click-to-pay handler")); + if (!uriServer->listen(name)) + { + // constructor is called early in init, so don't use "Q_EMIT + // message()" here + QMessageBox::critical(0, tr("Payment request error"), tr("Cannot start click-to-pay handler")); } else { connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection())); @@ -341,7 +393,7 @@ PaymentServer::~PaymentServer() } // -// OSX-specific way of handling bitcoin: URIs and PaymentRequest mime types. +// OSX-specific way of handling blackcoin: URIs and PaymentRequest mime types. // Also used by paymentservertests.cpp and when opening a payment request file // via "Open URI..." menu entry. // @@ -367,7 +419,7 @@ void PaymentServer::initNetManager() if (netManager != NULL) delete netManager; - // netManager is used to fetch paymentrequests given in bitcoin: URIs + // netManager is used to fetch paymentrequests given in blackcoin: URIs netManager = new QNetworkAccessManager(this); QNetworkProxy proxy; @@ -399,7 +451,65 @@ void PaymentServer::uiReady() savedPaymentRequests.clear(); } -void PaymentServer::handleURIOrFile(const QString& s) +bool PaymentServer::handleURI(const QString &scheme, const QString &s) +{ + if (!s.startsWith(scheme + ":", Qt::CaseInsensitive)) + { + return false; + } +#if QT_VERSION < 0x050000 + QUrl uri(s); +#else + QUrlQuery uri((QUrl(s))); +#endif + if (uri.hasQueryItem("r")) + { + // payment request URI + QByteArray temp; + temp.append(uri.queryItemValue("r")); + QString decoded = QUrl::fromPercentEncoding(temp); + QUrl fetchUrl(decoded, QUrl::StrictMode); + + if (fetchUrl.isValid()) + { + qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" << fetchUrl << ")"; + fetchRequest(fetchUrl); + } + else + { + qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " << fetchUrl; + Q_EMIT message(tr("URI handling"), tr("Payment request fetch URL is invalid: %1").arg(fetchUrl.toString()), + CClientUIInterface::ICON_WARNING); + } + + return true; + } + + // normal URI + SendCoinsRecipient recipient; + if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) + { + if (!IsValidDestinationString(recipient.address.toStdString())) + { + Q_EMIT message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address), + CClientUIInterface::MSG_ERROR); + } + else + { + Q_EMIT receivedPaymentRequest(recipient); + } + } + else + { + Q_EMIT message(tr("URI handling"), tr("URI cannot be parsed! This can be caused by an invalid " + "Bitcoin address or malformed URI parameters."), + CClientUIInterface::ICON_WARNING); + } + + return true; +} + +void PaymentServer::handleURIOrFile(const QString &s) { if (saveURIs) { @@ -407,55 +517,18 @@ void PaymentServer::handleURIOrFile(const QString& s) return; } - if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI + // blackcoin: CashAddr URI + QString schemeCash = GUIUtil::bitcoinURIScheme(Params(), true); + if (handleURI(schemeCash, s)) { -#if QT_VERSION < 0x050000 - QUrl uri(s); -#else - QUrlQuery uri((QUrl(s))); -#endif - if (uri.hasQueryItem("r")) // payment request URI - { - QByteArray temp; - temp.append(uri.queryItemValue("r")); - QString decoded = QUrl::fromPercentEncoding(temp); - QUrl fetchUrl(decoded, QUrl::StrictMode); + return; + } - if (fetchUrl.isValid()) - { - qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" << fetchUrl << ")"; - fetchRequest(fetchUrl); - } - else - { - qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " << fetchUrl; - Q_EMIT message(tr("URI handling"), - tr("Payment request fetch URL is invalid: %1").arg(fetchUrl.toString()), - CClientUIInterface::ICON_WARNING); - } - - return; - } - else // normal URI - { - SendCoinsRecipient recipient; - if (GUIUtil::parseBitcoinURI(s, &recipient)) - { - CBitcoinAddress address(recipient.address.toStdString()); - if (!address.IsValid()) { - Q_EMIT message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address), - CClientUIInterface::MSG_ERROR); - } - else - Q_EMIT receivedPaymentRequest(recipient); - } - else - Q_EMIT message(tr("URI handling"), - tr("URI cannot be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."), - CClientUIInterface::ICON_WARNING); - - return; - } + // blackcoin: Legacy URI + QString schemeLegacy = GUIUtil::bitcoinURIScheme(Params(), false); + if (handleURI(schemeLegacy, s)) + { + return; } if (QFile::exists(s)) // payment request file @@ -560,12 +633,13 @@ bool PaymentServer::processPaymentRequest(const PaymentRequestPlus& request, Sen CTxDestination dest; if (ExtractDestination(sendingTo.first, dest)) { // Append destination address - addresses.append(QString::fromStdString(CBitcoinAddress(dest).ToString())); + addresses.append(QString::fromStdString(EncodeDestination(dest))); } - else if (!recipient.authenticatedMerchant.isEmpty()) { - // Unauthenticated payment requests to custom bitcoin addresses are not supported - // (there is no good way to tell the user where they are paying in a way they'd - // have a chance of understanding). + else if (!recipient.authenticatedMerchant.isEmpty()) + { + // Unauthenticated payment requests to custom bitcoin addresses are + // not supported (there is no good way to tell the user where they + // are paying in a way they'd have a chance of understanding). Q_EMIT message(tr("Payment request rejected"), tr("Unverified payment requests to custom payment scripts are unsupported."), CClientUIInterface::MSG_ERROR); diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h index 7202e7dad..e629ffc65 100644 --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -6,7 +6,7 @@ #define BITCOIN_QT_PAYMENTSERVER_H // This class handles payment requests from clicking on -// bitcoin: URIs +// blackcoin: URIs // // This is somewhat tricky, because we have to deal with // the situation where the user clicks on a link during @@ -131,6 +131,7 @@ protected: private: static bool readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request); + bool handleURI(const QString &scheme, const QString &s); bool processPaymentRequest(const PaymentRequestPlus& request, SendCoinsRecipient& recipient); void fetchRequest(const QUrl& url); diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 752a05a40..52c6286c1 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -8,6 +8,7 @@ #include "addressbookpage.h" #include "addresstablemodel.h" #include "bitcoinunits.h" +#include "config.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" @@ -22,12 +23,13 @@ #include #include -ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent) : +ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *platformStyle, const Config *cfg, QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveCoinsDialog), columnResizingFixer(0), model(0), - platformStyle(platformStyle) + platformStyle(platformStyle), + cfg(cfg) { ui->setupUi(this); @@ -44,18 +46,21 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(const PlatformStyle *platformStyle, QWidg } // context menu actions + QAction *copyURIAction = new QAction(tr("Copy URI"), this); QAction *copyLabelAction = new QAction(tr("Copy label"), this); QAction *copyMessageAction = new QAction(tr("Copy message"), this); QAction *copyAmountAction = new QAction(tr("Copy amount"), this); // context menu contextMenu = new QMenu(this); + contextMenu->addAction(copyURIAction); contextMenu->addAction(copyLabelAction); contextMenu->addAction(copyMessageAction); contextMenu->addAction(copyAmountAction); // context menu signals connect(ui->recentRequestsView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); + connect(copyURIAction, SIGNAL(triggered()), this, SLOT(copyURI())); connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); connect(copyMessageAction, SIGNAL(triggered()), this, SLOT(copyMessage())); connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); @@ -153,7 +158,7 @@ void ReceiveCoinsDialog::on_receiveButton_clicked() } SendCoinsRecipient info(address, label, ui->reqAmount->value(), ui->reqMessage->text()); - ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this); + ReceiveRequestDialog *dialog = new ReceiveRequestDialog(cfg, this); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setModel(model->getOptionsModel()); dialog->setInfo(info); @@ -167,7 +172,7 @@ void ReceiveCoinsDialog::on_receiveButton_clicked() void ReceiveCoinsDialog::on_recentRequestsView_doubleClicked(const QModelIndex &index) { const RecentRequestsTableModel *submodel = model->getRecentRequestsTableModel(); - ReceiveRequestDialog *dialog = new ReceiveRequestDialog(this); + ReceiveRequestDialog *dialog = new ReceiveRequestDialog(cfg, this); dialog->setModel(model->getOptionsModel()); dialog->setInfo(submodel->entry(index.row()).recipient); dialog->setAttribute(Qt::WA_DeleteOnClose); @@ -229,30 +234,55 @@ void ReceiveCoinsDialog::keyPressEvent(QKeyEvent *event) this->QDialog::keyPressEvent(event); } +QModelIndex ReceiveCoinsDialog::selectedRow() +{ + if (!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel()) + return QModelIndex(); + QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows(); + if (selection.empty()) + return QModelIndex(); + // correct for selection mode ContiguousSelection + QModelIndex firstIndex = selection.at(0); + return firstIndex; +} + // copy column of selected row to clipboard void ReceiveCoinsDialog::copyColumnToClipboard(int column) { - if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel()) + QModelIndex firstIndex = selectedRow(); + if (!firstIndex.isValid()) + { return; - QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows(); - if(selection.empty()) - return; - // correct for selection mode ContiguousSelection - QModelIndex firstIndex = selection.at(0); - GUIUtil::setClipboard(model->getRecentRequestsTableModel()->data(firstIndex.child(firstIndex.row(), column), Qt::EditRole).toString()); + } + GUIUtil::setClipboard(model->getRecentRequestsTableModel() + ->data(firstIndex.child(firstIndex.row(), column), Qt::EditRole) + .toString()); } // context menu void ReceiveCoinsDialog::showMenu(const QPoint &point) { - if(!model || !model->getRecentRequestsTableModel() || !ui->recentRequestsView->selectionModel()) - return; - QModelIndexList selection = ui->recentRequestsView->selectionModel()->selectedRows(); - if(selection.empty()) + if (!selectedRow().isValid()) + { return; + } contextMenu->exec(QCursor::pos()); } +// context menu action: copy URI +void ReceiveCoinsDialog::copyURI() +{ + QModelIndex sel = selectedRow(); + if (!sel.isValid()) + { + return; + } + + const RecentRequestsTableModel *const submodel = model->getRecentRequestsTableModel(); + const QString uri = GUIUtil::formatBitcoinURI(*cfg, submodel->entry(sel.row()).recipient); + GUIUtil::setClipboard(uri); +} + // context menu action: copy label void ReceiveCoinsDialog::copyLabel() { diff --git a/src/qt/receivecoinsdialog.h b/src/qt/receivecoinsdialog.h index 226fd65cf..931fea32e 100644 --- a/src/qt/receivecoinsdialog.h +++ b/src/qt/receivecoinsdialog.h @@ -15,6 +15,7 @@ #include #include +class Config; class OptionsModel; class PlatformStyle; class WalletModel; @@ -40,7 +41,7 @@ public: MINIMUM_COLUMN_WIDTH = 130 }; - explicit ReceiveCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent = 0); + explicit ReceiveCoinsDialog(const PlatformStyle *platformStyle, const Config *cfg, QWidget *parent = 0); ~ReceiveCoinsDialog(); void setModel(WalletModel *model); @@ -59,7 +60,9 @@ private: WalletModel *model; QMenu *contextMenu; const PlatformStyle *platformStyle; + const Config *cfg; + QModelIndex selectedRow(); void copyColumnToClipboard(int column); virtual void resizeEvent(QResizeEvent *event); @@ -71,6 +74,7 @@ private Q_SLOTS: void recentRequestsView_selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); void updateDisplayUnit(); void showMenu(const QPoint &point); + void copyURI(); void copyLabel(); void copyMessage(); void copyAmount(); diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index 233aefe82..9760678ac 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -1,4 +1,6 @@ // Copyright (c) 2011-2013 The Bitcoin Core developers +// Copyright (c) 2015-2017 The Bitcoin Unlimited developers +// Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -6,6 +8,8 @@ #include "ui_receiverequestdialog.h" #include "bitcoinunits.h" +#include "config.h" +#include "dstencode.h" #include "guiconstants.h" #include "guiutil.h" #include "optionsmodel.h" @@ -45,7 +49,7 @@ QImage QRImageWidget::exportImage() { if(!pixmap()) return QImage(); - return pixmap()->toImage(); + return pixmap()->toImage().scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE); } void QRImageWidget::mousePressEvent(QMouseEvent *event) @@ -89,10 +93,11 @@ void QRImageWidget::contextMenuEvent(QContextMenuEvent *event) contextMenu->exec(event->globalPos()); } -ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) : +ReceiveRequestDialog::ReceiveRequestDialog(const Config *cfg, QWidget *parent) : QDialog(parent), ui(new Ui::ReceiveRequestDialog), - model(0) + model(0), + cfg(cfg) { ui->setupUi(this); @@ -120,9 +125,26 @@ void ReceiveRequestDialog::setModel(OptionsModel *model) update(); } -void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &info) +// Addresses are stored in the database with the encoding that the client was +// configured with at the time of creation. +// +// This converts to clients current configuration. +QString ToCurrentEncoding(const QString &addr, const Config &cfg) { - this->info = info; + if (!IsValidDestinationString(addr.toStdString(), cfg.GetChainParams())) + { + // We have something sketchy as input. Do not try to convert. + return addr; + } + CTxDestination dst = DecodeDestination(addr.toStdString(), cfg.GetChainParams()); + return QString::fromStdString(EncodeDestination(dst, cfg.GetChainParams(), cfg)); +} + +void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &_info) +{ + this->info = _info; + // Display addresses with currently configured encoding. + this->info.address = ToCurrentEncoding(this->info.address, *cfg); update(); } @@ -135,7 +157,7 @@ void ReceiveRequestDialog::update() target = info.address; setWindowTitle(tr("Request payment to %1").arg(target)); - QString uri = GUIUtil::formatBitcoinURI(info); + QString uri = GUIUtil::formatBitcoinURI(*cfg, info); ui->btnSaveAs->setEnabled(false); QString html; html += ""; @@ -152,6 +174,8 @@ void ReceiveRequestDialog::update() ui->outUri->setText(html); #ifdef USE_QRCODE + int fontSize = cfg->UseCashAddrEncoding() ? 10 : 12; + ui->lblQRCode->setText(""); if(!uri.isEmpty()) { @@ -179,16 +203,16 @@ void ReceiveRequestDialog::update() } QRcode_free(code); - QImage qrAddrImage = QImage(QR_IMAGE_SIZE, QR_IMAGE_SIZE+20, QImage::Format_RGB32); + QImage qrAddrImage = QImage(QR_IMAGE_SIZE, QR_IMAGE_SIZE + 20, QImage::Format_RGB32); qrAddrImage.fill(0xffffff); QPainter painter(&qrAddrImage); painter.drawImage(0, 0, qrImage.scaled(QR_IMAGE_SIZE, QR_IMAGE_SIZE)); QFont font = GUIUtil::fixedPitchFont(); - font.setPixelSize(12); + font.setPixelSize(fontSize); painter.setFont(font); QRect paddedRect = qrAddrImage.rect(); - paddedRect.setHeight(QR_IMAGE_SIZE+12); - painter.drawText(paddedRect, Qt::AlignBottom|Qt::AlignCenter, info.address); + paddedRect.setHeight(QR_IMAGE_SIZE + 12); + painter.drawText(paddedRect, Qt::AlignBottom | Qt::AlignCenter, info.address); painter.end(); ui->lblQRCode->setPixmap(QPixmap::fromImage(qrAddrImage)); @@ -200,7 +224,7 @@ void ReceiveRequestDialog::update() void ReceiveRequestDialog::on_btnCopyURI_clicked() { - GUIUtil::setClipboard(GUIUtil::formatBitcoinURI(info)); + GUIUtil::setClipboard(GUIUtil::formatBitcoinURI(*cfg, info)); } void ReceiveRequestDialog::on_btnCopyAddress_clicked() diff --git a/src/qt/receiverequestdialog.h b/src/qt/receiverequestdialog.h index 676745a85..1cc451629 100644 --- a/src/qt/receiverequestdialog.h +++ b/src/qt/receiverequestdialog.h @@ -11,8 +11,10 @@ #include #include #include +#include class OptionsModel; +class Config; namespace Ui { class ReceiveRequestDialog; @@ -50,7 +52,7 @@ class ReceiveRequestDialog : public QDialog Q_OBJECT public: - explicit ReceiveRequestDialog(QWidget *parent = 0); + explicit ReceiveRequestDialog(const Config *cfg, QWidget *parent = 0); ~ReceiveRequestDialog(); void setModel(OptionsModel *model); @@ -66,6 +68,10 @@ private: Ui::ReceiveRequestDialog *ui; OptionsModel *model; SendCoinsRecipient info; + const Config *cfg; }; +// exported for unittesting +QString ToCurrentEncoding(const QString &addr, const Config &); + #endif // BITCOIN_QT_RECEIVEREQUESTDIALOG_H diff --git a/src/qt/recentrequeststablemodel.h b/src/qt/recentrequeststablemodel.h index f3cf03f4e..51cf24b9b 100644 --- a/src/qt/recentrequeststablemodel.h +++ b/src/qt/recentrequeststablemodel.h @@ -53,7 +53,8 @@ private: Qt::SortOrder order; }; -/** Model for list of recently generated payment requests / bitcoin: URIs. +/** + * Model for list of recently generated payment requests / blackcoin: URIs. * Part of wallet model. */ class RecentRequestsTableModel: public QAbstractTableModel diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 4ec02393c..8687f29b0 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -15,8 +15,8 @@ #include "sendcoinsentry.h" #include "walletmodel.h" -#include "base58.h" #include "coincontrol.h" +#include "dstencode.h" #include "main.h" // mempool and minRelayTxFee #include "ui_interface.h" #include "txmempool.h" @@ -746,26 +746,43 @@ void SendCoinsDialog::coinControlChangeEdited(const QString& text) CoinControlDialog::coinControl->destChange = CNoDestination(); ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); - CBitcoinAddress addr = CBitcoinAddress(text.toStdString()); + const CTxDestination dest = DecodeDestination(text.toStdString()); if (text.isEmpty()) // Nothing entered { ui->labelCoinControlChangeLabel->setText(""); } - else if (!addr.IsValid()) // Invalid address + else if (!IsValidDestination(dest)) { + // Invalid address ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address")); } - else // Valid address + else { - CKeyID keyid; - addr.GetKeyID(keyid); - if (!model->havePrivKey(keyid)) // Unknown change address + // Valid address + if (!model->IsSpendable(dest)) { ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address")); + + // confirmation dialog + QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm custom change address"), + tr("The address you selected for change is not part of " + "this wallet. Any or all funds in your wallet may be " + "sent to this address. Are you sure?"), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel); + + if (btnRetVal == QMessageBox::Yes) + CoinControlDialog::coinControl->destChange = dest; + else + { + ui->lineEditCoinControlChange->setText(""); + ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); + ui->labelCoinControlChangeLabel->setText(""); + } } - else // Known change address + else { + // Known change address ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); // Query label @@ -775,7 +792,7 @@ void SendCoinsDialog::coinControlChangeEdited(const QString& text) else ui->labelCoinControlChangeLabel->setText(tr("(no label)")); - CoinControlDialog::coinControl->destChange = addr.Get(); + CoinControlDialog::coinControl->destChange = dest; } } } diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index d063f2c89..36d8b0123 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -7,6 +7,7 @@ #include "addressbookpage.h" #include "addresstablemodel.h" +#include "config.h" #include "guiutil.h" #include "optionsmodel.h" #include "platformstyle.h" @@ -29,6 +30,11 @@ SendCoinsEntry::SendCoinsEntry(const PlatformStyle *platformStyle, QWidget *pare ui->deleteButton_is->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); ui->deleteButton_s->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); + ui->messageTextLabel->setToolTip(tr("A message that was attached to the %1 URI which will be" + " stored with the transaction for your reference. Note: " + "This message will not be sent over the Bitcoin network.") + .arg(GUIUtil::bitcoinURIScheme(GetConfig()))); + setCurrentWidget(ui->SendCoins); if (platformStyle->getUseExtraSpacing()) diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 8e2e8a509..b9a151ae2 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -10,7 +10,7 @@ #include "platformstyle.h" #include "walletmodel.h" -#include "base58.h" +#include "dstencode.h" #include "init.h" #include "main.h" // For strMessageMagic #include "wallet/wallet.h" @@ -117,15 +117,15 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() /* Clear old signature to ensure users don't get confused on error with an old signature displayed */ ui->signatureOut_SM->clear(); - CBitcoinAddress addr(ui->addressIn_SM->text().toStdString()); - if (!addr.IsValid()) + CTxDestination destination = DecodeDestination(ui->addressIn_SM->text().toStdString()); + if (!IsValidDestination(destination)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again.")); return; } - CKeyID keyID; - if (!addr.GetKeyID(keyID)) + const CKeyID *keyID = boost::get(&destination); + if (!keyID) { ui->addressIn_SM->setValid(false); ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); @@ -142,7 +142,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() } CKey key; - if (!pwalletMain->GetKey(keyID, key)) + if (!pwalletMain->GetKey(*keyID, key)) { ui->statusLabel_SM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_SM->setText(tr("Private key for the entered address is not available.")); @@ -197,15 +197,14 @@ void SignVerifyMessageDialog::on_addressBookButton_VM_clicked() void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked() { - CBitcoinAddress addr(ui->addressIn_VM->text().toStdString()); - if (!addr.IsValid()) + CTxDestination destination = DecodeDestination(ui->addressIn_VM->text().toStdString()); + if (!IsValidDestination(destination)) { ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again.")); return; } - CKeyID keyID; - if (!addr.GetKeyID(keyID)) + if (!boost::get(&destination)) { ui->addressIn_VM->setValid(false); ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); @@ -237,7 +236,7 @@ void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked() return; } - if (!(CBitcoinAddress(pubkey.GetID()) == addr)) + if (!(CTxDestination(pubkey.GetID()) == destination)) { ui->statusLabel_VM->setStyleSheet("QLabel { color: red; }"); ui->statusLabel_VM->setText(QString("") + tr("Message verification failed.") + QString("")); diff --git a/src/qt/test/bitcoinaddressvalidatortests.cpp b/src/qt/test/bitcoinaddressvalidatortests.cpp new file mode 100644 index 000000000..78a9d077a --- /dev/null +++ b/src/qt/test/bitcoinaddressvalidatortests.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2017 The Bitcoin Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "qt/test/bitcoinaddressvalidatortests.h" +#include "chainparams.h" +#include "qt/bitcoinaddressvalidator.h" +#include + +void BitcoinAddressValidatorTests::inputTests() { + const std::string prefix = Params(CBaseChainParams::MAIN).CashAddrPrefix(); + BitcoinAddressEntryValidator v(prefix, nullptr); + + int unused = 0; + QString in; + + // invalid base58 because of I, invalid cashaddr + in = "BIIC"; + QVERIFY(QValidator::Invalid == v.validate(in, unused)); + + // invalid base58, invalid cashaddr + in = "BITCOINCASHH"; + QVERIFY(QValidator::Invalid == v.validate(in, unused)); + + // invalid base58 because of I, but could be a cashaddr prefix + in = "BITC"; + QVERIFY(QValidator::Acceptable == v.validate(in, unused)); + + // invalid base58, valid cashaddr + in = "BITCOINCASH:QP"; + QVERIFY(QValidator::Acceptable == v.validate(in, unused)); + + // valid base58, invalid cash + in = "BBBBBBBBBBBBBB"; + QVERIFY(QValidator::Acceptable == v.validate(in, unused)); +} diff --git a/src/qt/test/bitcoinaddressvalidatortests.h b/src/qt/test/bitcoinaddressvalidatortests.h new file mode 100644 index 000000000..d0e76eadf --- /dev/null +++ b/src/qt/test/bitcoinaddressvalidatortests.h @@ -0,0 +1,18 @@ +// Copyright (c) 2017 The Bitcoin Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_TEST_BITCOINADDRESSVALIDATORTESTS_H +#define BITCOIN_QT_TEST_BITCOINADDRESSVALIDATORTESTS_H + +#include +#include + +class BitcoinAddressValidatorTests : public QObject { + Q_OBJECT + +private Q_SLOTS: + void inputTests(); +}; + +#endif diff --git a/src/qt/test/guiutiltests.cpp b/src/qt/test/guiutiltests.cpp new file mode 100644 index 000000000..a3e1c997e --- /dev/null +++ b/src/qt/test/guiutiltests.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "guiutiltests.h" +#include "chainparams.h" +#include "config.h" +#include "dstencode.h" +#include "guiutil.h" +#include "receiverequestdialog.h" + +namespace { + +class UtilCfgDummy : public DummyConfig { +public: + UtilCfgDummy() : useCashAddr(false) {} + void SetCashAddrEncoding(bool b) override { useCashAddr = b; } + bool UseCashAddrEncoding() const override { return useCashAddr; } + const CChainParams &GetChainParams() const override { + return Params(CBaseChainParams::MAIN); + } + +private: + bool useCashAddr; +}; + +} // anon ns + +void GUIUtilTests::dummyAddressTest() { + CChainParams ¶ms = Params(CBaseChainParams::MAIN); + UtilCfgDummy cfg; + std::string dummyaddr; + + cfg.SetCashAddrEncoding(false); + dummyaddr = GUIUtil::DummyAddress(params, cfg); + QVERIFY(!IsValidDestinationString(dummyaddr, params)); + QVERIFY(!dummyaddr.empty()); + + cfg.SetCashAddrEncoding(true); + dummyaddr = GUIUtil::DummyAddress(params, cfg); + QVERIFY(!IsValidDestinationString(dummyaddr, params)); + QVERIFY(!dummyaddr.empty()); +} + +void GUIUtilTests::toCurrentEncodingTest() { + UtilCfgDummy config; + + // garbage in, garbage out + QVERIFY(ToCurrentEncoding("garbage", config) == "garbage"); + + QString cashaddr_pubkey = + "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"; + QString base58_pubkey = "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"; + + config.SetCashAddrEncoding(true); + QVERIFY(ToCurrentEncoding(cashaddr_pubkey, config) == cashaddr_pubkey); + QVERIFY(ToCurrentEncoding(base58_pubkey, config) == cashaddr_pubkey); + + config.SetCashAddrEncoding(false); + QVERIFY(ToCurrentEncoding(cashaddr_pubkey, config) == base58_pubkey); + QVERIFY(ToCurrentEncoding(base58_pubkey, config) == base58_pubkey); +} diff --git a/src/qt/test/guiutiltests.h b/src/qt/test/guiutiltests.h new file mode 100644 index 000000000..6bf559a64 --- /dev/null +++ b/src/qt/test/guiutiltests.h @@ -0,0 +1,19 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_TEST_GUIUTILTESTS_H +#define BITCOIN_QT_TEST_GUIUTILTESTS_H + +#include +#include + +class GUIUtilTests : public QObject { + Q_OBJECT + +private Q_SLOTS: + void dummyAddressTest(); + void toCurrentEncodingTest(); +}; + +#endif // BITCOIN_QT_TEST_GUIUTILTESTS_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index db193420b..fa5f5ddbf 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -6,6 +6,8 @@ #include "config/bitcoin-config.h" #endif +#include "bitcoinaddressvalidatortests.h" +#include "guiutiltests.h" #include "util.h" #include "uritests.h" @@ -48,6 +50,10 @@ int main(int argc, char *argv[]) if (QTest::qExec(&test2) != 0) fInvalid = true; #endif + GUIUtilTests test5; + if (QTest::qExec(&test5) != 0) fInvalid = true; + BitcoinAddressValidatorTests test6; + if (QTest::qExec(&test6) != 0) fInvalid = true; return fInvalid; } diff --git a/src/qt/test/uritests.cpp b/src/qt/test/uritests.cpp index 8b53c0d5c..0e900293e 100644 --- a/src/qt/test/uritests.cpp +++ b/src/qt/test/uritests.cpp @@ -1,66 +1,219 @@ // Copyright (c) 2009-2014 The Bitcoin Core developers +// Copyright (c) 2015-2017 The Bitcoin Unlimited developers +// Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "uritests.h" +#include "chainparams.h" +#include "config.h" #include "guiutil.h" #include "walletmodel.h" #include -void URITests::uriTests() +void URITests::uriTestsBase58() { SendCoinsRecipient rv; + QString scheme = + QString::fromStdString(Params(CBaseChainParams::MAIN).CashAddrPrefix()); QUrl uri; - uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?req-dontexist=")); - QVERIFY(!GUIUtil::parseBitcoinURI(uri, &rv)); + uri.setUrl(QString("blackcoin175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?req-dontexist=")); + QVERIFY(!GUIUtil::parseBitcoinURI(scheme, uri, &rv)); - uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?dontexist=")); - QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + uri.setUrl(QString("blackcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?dontexist=")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); QVERIFY(rv.address == QString("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W")); QVERIFY(rv.label == QString()); QVERIFY(rv.amount == 0); - uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?label=Wikipedia Example Address")); - QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + uri.setUrl(QString("blackcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?label=Wikipedia Example Address")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); QVERIFY(rv.address == QString("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W")); QVERIFY(rv.label == QString("Wikipedia Example Address")); QVERIFY(rv.amount == 0); - uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=0.001")); - QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + uri.setUrl(QString("blackcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=0.001")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); QVERIFY(rv.address == QString("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W")); QVERIFY(rv.label == QString()); QVERIFY(rv.amount == 100000); - uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=1.001")); - QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + uri.setUrl(QString("blackcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=1.001")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); QVERIFY(rv.address == QString("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W")); QVERIFY(rv.label == QString()); QVERIFY(rv.amount == 100100000); - uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=100&label=Wikipedia Example")); - QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + uri.setUrl(QString("blackcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=100&label=Wikipedia Example")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); QVERIFY(rv.address == QString("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W")); QVERIFY(rv.amount == 10000000000LL); QVERIFY(rv.label == QString("Wikipedia Example")); - uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?message=Wikipedia Example Address")); - QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + uri.setUrl(QString("blackcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?message=Wikipedia Example Address")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); QVERIFY(rv.address == QString("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W")); QVERIFY(rv.label == QString()); - QVERIFY(GUIUtil::parseBitcoinURI("bitcoin://175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?message=Wikipedia Example Address", &rv)); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, "blackcoin://175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?" + "message=Wikipedia Example Address", + &rv)); QVERIFY(rv.address == QString("175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W")); QVERIFY(rv.label == QString()); - uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?req-message=Wikipedia Example Address")); - QVERIFY(GUIUtil::parseBitcoinURI(uri, &rv)); + uri.setUrl(QString("blackcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?req-message=Wikipedia Example Address")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); - uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=1,000&label=Wikipedia Example")); - QVERIFY(!GUIUtil::parseBitcoinURI(uri, &rv)); + uri.setUrl(QString("blackcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=1,000&label=Wikipedia Example")); + QVERIFY(!GUIUtil::parseBitcoinURI(scheme, uri, &rv)); - uri.setUrl(QString("bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=1,000.0&label=Wikipedia Example")); - QVERIFY(!GUIUtil::parseBitcoinURI(uri, &rv)); + uri.setUrl(QString("blackcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=1,000.0&label=Wikipedia Example")); + QVERIFY(!GUIUtil::parseBitcoinURI(scheme, uri, &rv)); +} + +void URITests::uriTestsCashAddr() { + SendCoinsRecipient rv; + QUrl uri; + QString scheme = + QString::fromStdString(Params(CBaseChainParams::MAIN).CashAddrPrefix()); + uri.setUrl(QString("blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?" + "req-dontexist=")); + QVERIFY(!GUIUtil::parseBitcoinURI(scheme, uri, &rv)); + + uri.setUrl(QString( + "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?dontexist=")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); + QVERIFY(rv.address == + QString("blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString()); + QVERIFY(rv.amount == 0); + + uri.setUrl( + QString("blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?label=" + "Wikipedia Example Address")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); + QVERIFY(rv.address == + QString("blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString("Wikipedia Example Address")); + QVERIFY(rv.amount == 0); + + uri.setUrl(QString( + "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?amount=0.001")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); + QVERIFY(rv.address == + QString("blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString()); + QVERIFY(rv.amount == 100000); + + uri.setUrl(QString( + "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?amount=1.001")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); + QVERIFY(rv.address == + QString("blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString()); + QVERIFY(rv.amount == 100100000); + + uri.setUrl(QString( + "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?amount=100&" + "label=Wikipedia Example")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); + QVERIFY(rv.address == + QString("blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.amount == 10000000000LL); + QVERIFY(rv.label == QString("Wikipedia Example")); + + uri.setUrl(QString( + "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?message=" + "Wikipedia Example Address")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); + QVERIFY(rv.address == + QString("blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString()); + + QVERIFY(GUIUtil::parseBitcoinURI( + scheme, "blackcoin://" + "qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?" + "message=Wikipedia Example Address", + &rv)); + QVERIFY(rv.address == + QString("blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a")); + QVERIFY(rv.label == QString()); + + uri.setUrl(QString( + "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?req-message=" + "Wikipedia Example Address")); + QVERIFY(GUIUtil::parseBitcoinURI(scheme, uri, &rv)); + + uri.setUrl(QString( + "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?amount=1," + "000&label=Wikipedia Example")); + QVERIFY(!GUIUtil::parseBitcoinURI(scheme, uri, &rv)); + + uri.setUrl(QString( + "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?amount=1," + "000.0&label=Wikipedia Example")); + QVERIFY(!GUIUtil::parseBitcoinURI(scheme, uri, &rv)); +} + +namespace { +class UriTestConfig : public DummyConfig { +public: + UriTestConfig(bool useCashAddr) + : useCashAddr(useCashAddr), net(CBaseChainParams::MAIN) {} + bool UseCashAddrEncoding() const override { return useCashAddr; } + const CChainParams &GetChainParams() const override { return Params(net); } + void SetChainParams(const std::string &n) { net = n; } + +private: + bool useCashAddr; + std::string net; +}; + +} // anon ns + +void URITests::uriTestFormatURI() { + { + UriTestConfig cfg(true); + SendCoinsRecipient r; + r.address = "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"; + r.message = "test"; + QString uri = GUIUtil::formatBitcoinURI(cfg, r); + QVERIFY(uri == "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a?" + "message=test"); + } + + { + UriTestConfig cfg(false); + SendCoinsRecipient r; + r.address = "175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W"; + r.message = "test"; + QString uri = GUIUtil::formatBitcoinURI(cfg, r); + QVERIFY(uri == + "blackcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?message=test"); + } +} + +void URITests::uriTestScheme() { + { + // cashaddr - scheme depends on selected chain params + UriTestConfig config(true); + config.SetChainParams(CBaseChainParams::MAIN); + QVERIFY("blackcoin" == GUIUtil::bitcoinURIScheme(config)); + config.SetChainParams(CBaseChainParams::TESTNET); + QVERIFY("blktest" == GUIUtil::bitcoinURIScheme(config)); + config.SetChainParams(CBaseChainParams::REGTEST); + QVERIFY("blkreg" == GUIUtil::bitcoinURIScheme(config)); + } + { + // legacy - scheme is "blackcoin" regardless of chain params + UriTestConfig config(false); + config.SetChainParams(CBaseChainParams::MAIN); + QVERIFY("blackcoin" == GUIUtil::bitcoinURIScheme(config)); + config.SetChainParams(CBaseChainParams::TESTNET); + QVERIFY("blackcoin" == GUIUtil::bitcoinURIScheme(config)); + config.SetChainParams(CBaseChainParams::REGTEST); + QVERIFY("blackcoin" == GUIUtil::bitcoinURIScheme(config)); + } } diff --git a/src/qt/test/uritests.h b/src/qt/test/uritests.h index 499484279..6ead31318 100644 --- a/src/qt/test/uritests.h +++ b/src/qt/test/uritests.h @@ -13,7 +13,10 @@ class URITests : public QObject Q_OBJECT private Q_SLOTS: - void uriTests(); + void uriTestsBase58(); + void uriTestsCashAddr(); + void uriTestFormatURI(); + void uriTestScheme(); }; #endif // BITCOIN_QT_TEST_URITESTS_H diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 7b22cdbfb..3205c78ff 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -8,9 +8,8 @@ #include "guiutil.h" #include "paymentserver.h" #include "transactionrecord.h" - -#include "base58.h" #include "consensus/consensus.h" +#include "dstencode.h" #include "main.h" #include "script/script.h" #include "timedata.h" @@ -91,9 +90,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco if (nNet > 0) { // Credit - if (CBitcoinAddress(rec->address).IsValid()) + CTxDestination address = DecodeDestination(rec->address); + if (IsValidDestination(address)) { - CTxDestination address = CBitcoinAddress(rec->address).Get(); if (wallet->mapAddressBook.count(address)) { strHTML += "" + tr("From") + ": " + tr("unknown") + "
"; @@ -118,7 +117,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco // Online transaction std::string strAddress = wtx.mapValue["to"]; strHTML += "" + tr("To") + ": "; - CTxDestination dest = CBitcoinAddress(strAddress).Get(); + CTxDestination dest = DecodeDestination(strAddress); if (wallet->mapAddressBook.count(dest) && !wallet->mapAddressBook[dest].name.empty()) strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[dest].name) + " "; strHTML += GUIUtil::HtmlEscape(strAddress) + "
"; @@ -189,7 +188,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco strHTML += "" + tr("To") + ": "; if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty()) strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " "; - strHTML += GUIUtil::HtmlEscape(CBitcoinAddress(address).ToString()); + strHTML += GUIUtil::HtmlEscape(EncodeDestination(address)); if(toSelf == ISMINE_SPENDABLE) strHTML += " (own address)"; else if(toSelf & ISMINE_WATCH_ONLY) @@ -243,10 +242,12 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco strHTML += "" + tr("Transaction ID") + ": " + rec->getTxID() + "
"; strHTML += "" + tr("Output index") + ": " + QString::number(rec->getOutputIndex()) + "
"; - // Message from normal bitcoin:URI (bitcoin:123...?message=example) - Q_FOREACH (const PAIRTYPE(std::string, std::string)& r, wtx.vOrderForm) + // Message from normal blackcoin:URI (blackcoin:123...?message=example) + for (const std::pair &r : wtx.vOrderForm) + { if (r.first == "Message") strHTML += "
" + tr("Message") + ":
" + GUIUtil::HtmlEscape(r.second, true) + "
"; + } // // PaymentRequest info: @@ -304,7 +305,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco { if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].name.empty()) strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " "; - strHTML += QString::fromStdString(CBitcoinAddress(address).ToString()); + strHTML += QString::fromStdString(EncodeDestination(address)); } strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue); strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + ""; diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 041e93982..071154dda 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -5,6 +5,7 @@ #include "transactionrecord.h" #include "base58.h" +#include "dstencode.h" #include "consensus/consensus.h" #include "main.h" #include "timedata.h" @@ -61,7 +62,7 @@ QList TransactionRecord::decomposeTransaction(const CWallet * { // Received by Bitcoin Address sub.type = TransactionRecord::RecvWithAddress; - sub.address = CBitcoinAddress(address).ToString(); + sub.address = EncodeDestination(address); } else { @@ -140,7 +141,7 @@ QList TransactionRecord::decomposeTransaction(const CWallet * { // Sent to Bitcoin Address sub.type = TransactionRecord::SendToAddress; - sub.address = CBitcoinAddress(address).ToString(); + sub.address = EncodeDestination(address); } else { diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 1b9426a4f..70eb9a02e 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -12,10 +12,11 @@ #include #include -WalletFrame::WalletFrame(const PlatformStyle *platformStyle, BitcoinGUI *_gui) : +WalletFrame::WalletFrame(const PlatformStyle *platformStyle, const Config *cfg, BitcoinGUI *_gui) : QFrame(_gui), gui(_gui), - platformStyle(platformStyle) + platformStyle(platformStyle), + cfg(cfg) { // Leave HBox hook for adding a list view later QHBoxLayout *walletFrameLayout = new QHBoxLayout(this); @@ -43,7 +44,7 @@ bool WalletFrame::addWallet(const QString& name, WalletModel *walletModel) if (!gui || !clientModel || !walletModel || mapWalletViews.count(name) > 0) return false; - WalletView *walletView = new WalletView(platformStyle, this); + WalletView *walletView = new WalletView(platformStyle, cfg, this); walletView->setBitcoinGUI(gui); walletView->setClientModel(clientModel); walletView->setWalletModel(walletModel); diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index b7fe7292e..76977479e 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -14,6 +14,7 @@ class PlatformStyle; class SendCoinsRecipient; class WalletModel; class WalletView; +class Config; QT_BEGIN_NAMESPACE class QStackedWidget; @@ -24,7 +25,7 @@ class WalletFrame : public QFrame Q_OBJECT public: - explicit WalletFrame(const PlatformStyle *platformStyle, BitcoinGUI *_gui = 0); + explicit WalletFrame(const PlatformStyle *platformStyle, const Config *cfg, BitcoinGUI *_gui = 0); ~WalletFrame(); void setClientModel(ClientModel *clientModel); @@ -47,6 +48,7 @@ private: bool bOutOfSync; const PlatformStyle *platformStyle; + const Config *cfg; WalletView *currentWalletView(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 8c52c4033..544c319bb 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -10,8 +10,7 @@ #include "paymentserver.h" #include "recentrequeststablemodel.h" #include "transactiontablemodel.h" - -#include "base58.h" +#include "dstencode.h" #include "keystore.h" #include "main.h" #include "sync.h" @@ -183,11 +182,7 @@ void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) Q_EMIT notifyWatchonlyChanged(fHaveWatchonly); } -bool WalletModel::validateAddress(const QString &address) -{ - CBitcoinAddress addressParsed(address.toStdString()); - return addressParsed.IsValid(); -} +bool WalletModel::validateAddress(const QString &address) { return IsValidDestinationString(address.toStdString()); } WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl) { @@ -244,14 +239,14 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact setAddress.insert(rcp.address); ++nAddresses; - CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get()); + CScript scriptPubKey = GetScriptForDestination(DecodeDestination(rcp.address.toStdString())); CRecipient recipient = {scriptPubKey, rcp.amount, rcp.fSubtractFeeFromAmount}; vecSend.push_back(recipient); total += rcp.amount; } } - if(setAddress.size() != nAddresses) + if (setAddress.size() != nAddresses) { return DuplicateAddress; } @@ -323,7 +318,13 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran rcp.paymentRequest.SerializeToString(&value); newTx->vOrderForm.push_back(make_pair(key, value)); } - else if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example) + else if (!rcp.message.isEmpty()) + { + // Message from normal blackcoin:URI + // (blackcoin:123...?message=example) + newTx->vOrderForm.push_back(make_pair("Message", rcp.message.toStdString())); + } + else if (!rcp.message.isEmpty()) // Message from normal blackcoin:URI (blackcoin:123...?message=example) newTx->vOrderForm.push_back(make_pair("Message", rcp.message.toStdString())); } @@ -345,7 +346,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran if (!rcp.paymentRequest.IsInitialized()) { std::string strAddress = rcp.address.toStdString(); - CTxDestination dest = CBitcoinAddress(strAddress).Get(); + CTxDestination dest = DecodeDestination(strAddress); std::string strLabel = rcp.label.toStdString(); { LOCK(wallet->cs_wallet); @@ -461,7 +462,7 @@ static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, const std::string &purpose, ChangeType status) { - QString strAddress = QString::fromStdString(CBitcoinAddress(address).ToString()); + QString strAddress = QString::fromStdString(EncodeDestination(address)); QString strLabel = QString::fromStdString(label); QString strPurpose = QString::fromStdString(purpose); @@ -566,11 +567,7 @@ bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const return wallet->GetPubKey(address, vchPubKeyOut); } -bool WalletModel::havePrivKey(const CKeyID &address) const -{ - return wallet->HaveKey(address); -} - +bool WalletModel::IsSpendable(const CTxDestination &dest) const { return wallet->IsMine(dest) & ISMINE_SPENDABLE; } // returns a list of COutputs from COutPoints void WalletModel::getOutputs(const std::vector& vOutpoints, std::vector& vOutputs) { @@ -625,7 +622,7 @@ void WalletModel::listCoins(std::map >& mapCoins) CTxDestination address; if(!out.fSpendable || !ExtractDestination(cout.tx->vout[cout.i].scriptPubKey, address)) continue; - mapCoins[QString::fromStdString(CBitcoinAddress(address).ToString())].push_back(out); + mapCoins[QString::fromStdString(EncodeDestination(address))].push_back(out); } } @@ -664,7 +661,7 @@ void WalletModel::loadReceiveRequests(std::vector& vReceiveRequests bool WalletModel::saveReceiveRequest(const std::string &sAddress, const int64_t nId, const std::string &sRequest) { - CTxDestination dest = CBitcoinAddress(sAddress).Get(); + CTxDestination dest = DecodeDestination(sAddress); std::stringstream ss; ss << nId; diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index c93798b76..978fedec1 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -187,7 +187,7 @@ public: UnlockContext requestUnlock(); bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; - bool havePrivKey(const CKeyID &address) const; + bool IsSpendable(const CTxDestination &dest) const; void getOutputs(const std::vector& vOutpoints, std::vector& vOutputs); bool isSpent(const COutPoint& outpoint) const; void listCoins(std::map >& mapCoins) const; diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 5d4da8253..41823f507 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -29,11 +29,11 @@ #include #include -WalletView::WalletView(const PlatformStyle *platformStyle, QWidget *parent): +WalletView::WalletView(const PlatformStyle *_platformStyle, const Config *cfg, QWidget *parent): QStackedWidget(parent), clientModel(0), walletModel(0), - platformStyle(platformStyle) + platformStyle(_platformStyle) { // Create tabs overviewPage = new OverviewPage(platformStyle); @@ -53,7 +53,7 @@ WalletView::WalletView(const PlatformStyle *platformStyle, QWidget *parent): vbox->addLayout(hbox_buttons); transactionsPage->setLayout(vbox); - receiveCoinsPage = new ReceiveCoinsDialog(platformStyle); + receiveCoinsPage = new ReceiveCoinsDialog(platformStyle, cfg); sendCoinsPage = new SendCoinsDialog(platformStyle); usedSendingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::SendingTab, this); diff --git a/src/qt/walletview.h b/src/qt/walletview.h index c0c9cf291..47295b5a4 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -19,6 +19,7 @@ class SendCoinsRecipient; class TransactionView; class WalletModel; class AddressBookPage; +class Config; QT_BEGIN_NAMESPACE class QModelIndex; @@ -36,7 +37,7 @@ class WalletView : public QStackedWidget Q_OBJECT public: - explicit WalletView(const PlatformStyle *platformStyle, QWidget *parent); + explicit WalletView(const PlatformStyle *platformStyle, const Config *cfg, QWidget *parent); ~WalletView(); void setBitcoinGUI(BitcoinGUI *gui); diff --git a/src/random.cpp b/src/random.cpp index d9a8cc145..88b2015f2 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -93,7 +93,7 @@ static void RandAddSeedPerfmon() } /** Get 32 bytes of system entropy. */ -static void GetOSRand(unsigned char *ent32) +void GetOSRand(unsigned char *ent32) { #ifdef WIN32 HCRYPTPROV hProvider; @@ -197,3 +197,26 @@ void seed_insecure_rand(bool fDeterministic) insecure_rand_Rw = tmp; } } + +FastRandomContext::FastRandomContext(bool fDeterministic) +{ + // The seed values have some unlikely fixed points which we avoid. + if (fDeterministic) + { + Rz = Rw = 11; + } + else + { + uint32_t tmp; + do + { + GetRandBytes((unsigned char *)&tmp, 4); + } while (tmp == 0 || tmp == 0x9068ffffU); + Rz = tmp; + do + { + GetRandBytes((unsigned char *)&tmp, 4); + } while (tmp == 0 || tmp == 0x464fffffU); + Rw = tmp; + } +} \ No newline at end of file diff --git a/src/random.h b/src/random.h index 31b80bd56..8bba4cfbd 100644 --- a/src/random.h +++ b/src/random.h @@ -34,11 +34,80 @@ void GetStrongRandBytes(unsigned char* buf, int num); void seed_insecure_rand(bool fDeterministic = false); /** - * MWC RNG of George Marsaglia - * This is intended to be fast. It has a period of 2^59.3, though the - * least significant 16 bits only have a period of about 2^30.1. - * - * @return random value + * Fast randomness source. This is seeded once with secure random data, but + * is completely deterministic and insecure after that. + * This class is not thread-safe. + */ +class FastRandomContext +{ +private: + uint64_t bitbuf; + int bitbuf_size; + + void FillBitBuffer() + { + bitbuf = rand64(); + bitbuf_size = 64; + } + +public: + explicit FastRandomContext(bool fDeterministic = false); + + uint32_t Rz; + uint32_t Rw; + + uint32_t rand32() + { + Rz = 36969 * (Rz & 65535) + (Rz >> 16); + Rw = 18000 * (Rw & 65535) + (Rw >> 16); + return (Rw << 16) + Rz; + } + + uint64_t rand64() + { + uint64_t a = rand32(); + uint64_t b = rand32(); + return (b << 32) + a; + } + + bool randbool() { return rand32() & 1; } + uint64_t randbits(int bits) + { + if (bits == 0) + { + return 0; + } + else if (bits > 32) + { + return rand64() >> (64 - bits); + } + else + { + if (bitbuf_size < bits) + FillBitBuffer(); + + uint64_t ret = bitbuf & (~uint64_t(0) >> (64 - bits)); + bitbuf >>= bits; + bitbuf_size -= bits; + return ret; + } + } +}; + +/* Number of random bytes returned by GetOSRand. + * When changing this constant make sure to change all call sites, and make + * sure that the underlying OS APIs for all platforms support the number. + * (many cap out at 256 bytes). + */ +static const ssize_t NUM_OS_RANDOM_BYTES = 32; + +/** Get 32 bytes of system entropy. Do not use this in application code: use + * GetStrongRandBytes instead. + */ +void GetOSRand(unsigned char *ent32); + +/** Check that OS randomness is available and returning the requested number + * of bytes. */ extern uint32_t insecure_rand_Rz; extern uint32_t insecure_rand_Rw; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index b9627ba6f..b1be57724 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -4,12 +4,12 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "amount.h" -#include "base58.h" #include "chain.h" #include "chainparams.h" #include "checkpoints.h" #include "coins.h" #include "consensus/validation.h" +#include "dstencode.h" #include "main.h" #include "policy/policy.h" #include "primitives/transaction.h" @@ -139,9 +139,9 @@ UniValue blockToDeltasJSON(const CBlock& block, const CBlockIndex* blockindex) if (GetSpentIndex(spentKey, spentInfo)) { if (spentInfo.addressType == 1) { - delta.push_back(Pair("address", CBitcoinAddress(CKeyID(spentInfo.addressHash)).ToString())); + delta.push_back(Pair("address", EncodeDestination(CKeyID(spentInfo.addressHash)))); } else if (spentInfo.addressType == 2) { - delta.push_back(Pair("address", CBitcoinAddress(CScriptID(spentInfo.addressHash)).ToString())); + delta.push_back(Pair("address", EncodeDestination(CScriptID(spentInfo.addressHash)))); } else { continue; } @@ -169,11 +169,11 @@ UniValue blockToDeltasJSON(const CBlock& block, const CBlockIndex* blockindex) if (out.scriptPubKey.IsPayToScriptHash()) { vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); - delta.push_back(Pair("address", CBitcoinAddress(CScriptID(uint160(hashBytes))).ToString())); + delta.push_back(Pair("address", EncodeDestination(CScriptID(uint160(hashBytes))))); } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); - delta.push_back(Pair("address", CBitcoinAddress(CKeyID(uint160(hashBytes))).ToString())); + delta.push_back(Pair("address", EncodeDestination(CKeyID(uint160(hashBytes))))); } else { continue; } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 6ca239323..b9f13b5b8 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -9,6 +9,7 @@ #include "consensus/consensus.h" #include "consensus/validation.h" #include "core_io.h" +#include "dstencode.h" #include "init.h" #include "main.h" #include "miner.h" @@ -226,6 +227,16 @@ UniValue setgenerate(const UniValue& params, bool fHelp) mapArgs["-gen"] = (fGenerate ? "1" : "0"); mapArgs ["-genproclimit"] = itostr(nGenProcLimit); + + CTxDestination destination = DecodeDestination(params[1].get_str()); + if (!IsValidDestination(destination)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Error: Invalid address"); + } + + boost::shared_ptr coinbaseScript(new CReserveScript()); + coinbaseScript->reserveScript = GetScriptForDestination(destination); + GenerateBitcoins(fGenerate, nGenProcLimit, Params()); return NullUniValue; diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 56f5c2ea0..a364ee32a 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -5,6 +5,7 @@ #include "base58.h" #include "clientversion.h" +#include "dstencode.h" #include "init.h" #include "main.h" #include "net.h" @@ -144,8 +145,9 @@ public: obj.push_back(Pair("script", GetTxnOutputType(whichType))); obj.push_back(Pair("hex", HexStr(subscript.begin(), subscript.end()))); UniValue a(UniValue::VARR); - BOOST_FOREACH(const CTxDestination& addr, addresses) - a.push_back(CBitcoinAddress(addr).ToString()); + for (const CTxDestination &addr : addresses) { + a.push_back(EncodeDestination(addr)); + } obj.push_back(Pair("addresses", a)); if (whichType == TX_MULTISIG) obj.push_back(Pair("sigsrequired", nRequired)); @@ -188,15 +190,13 @@ UniValue validateaddress(const UniValue& params, bool fHelp) LOCK(cs_main); #endif - CBitcoinAddress address(params[0].get_str()); - bool isValid = address.IsValid(); + CTxDestination dest = DecodeDestination(params[0].get_str()); + bool isValid = IsValidDestination(dest); UniValue ret(UniValue::VOBJ); ret.push_back(Pair("isvalid", isValid)); - if (isValid) - { - CTxDestination dest = address.Get(); - string currentAddress = address.ToString(); + if (isValid) { + std::string currentAddress = EncodeDestination(dest); ret.push_back(Pair("address", currentAddress)); CScript scriptPubKey = GetScriptForDestination(dest); @@ -210,11 +210,21 @@ UniValue validateaddress(const UniValue& params, bool fHelp) ret.pushKVs(detail); if (pwalletMain && pwalletMain->mapAddressBook.count(dest)) ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest].name)); - CKeyID keyID; - if (pwalletMain && address.GetKeyID(keyID) && pwalletMain->mapKeyMetadata.count(keyID) && !pwalletMain->mapKeyMetadata[keyID].hdKeypath.empty()) - { - ret.push_back(Pair("hdkeypath", pwalletMain->mapKeyMetadata[keyID].hdKeypath)); - ret.push_back(Pair("hdmasterkeyid", pwalletMain->mapKeyMetadata[keyID].hdMasterKeyID.GetHex())); + if (pwalletMain) { + const auto &meta = pwalletMain->mapKeyMetadata; + const CKeyID *keyID = boost::get(&dest); + auto it = keyID ? meta.find(*keyID) : meta.end(); + if (it == meta.end()) { + it = meta.find(CScriptID(scriptPubKey)); + } + if (it != meta.end()) { + ret.push_back(Pair("timestamp", it->second.nCreateTime)); + if (!it->second.hdKeypath.empty()) { + ret.push_back(Pair("hdkeypath", it->second.hdKeypath)); + ret.push_back(Pair("hdmasterkeyid", + it->second.hdMasterKeyID.GetHex())); + } + } } #endif } @@ -245,17 +255,18 @@ CScript _createmultisig_redeemScript(const UniValue& params) const std::string& ks = keys[i].get_str(); #ifdef ENABLE_WALLET // Case 1: Bitcoin address and we have full public key: - CBitcoinAddress address(ks); - if (pwalletMain && address.IsValid()) - { - CKeyID keyID; - if (!address.GetKeyID(keyID)) - throw runtime_error( - strprintf("%s does not refer to a key",ks)); + CTxDestination dest = DecodeDestination(ks); + if (pwalletMain && IsValidDestination(dest)) { + const CKeyID *keyID = boost::get(&dest); + if (!keyID) { + throw std::runtime_error( + strprintf("%s does not refer to a key", ks)); + } CPubKey vchPubKey; - if (!pwalletMain->GetPubKey(keyID, vchPubKey)) - throw runtime_error( - strprintf("no full public key for address %s",ks)); + if (!pwalletMain->GetPubKey(*keyID, vchPubKey)) { + throw std::runtime_error( + strprintf("no full public key for address %s", ks)); + } if (!vchPubKey.IsFullyValid()) throw runtime_error(" Invalid public key: "+ks); pubkeys[i] = vchPubKey; @@ -319,10 +330,9 @@ UniValue createmultisig(const UniValue& params, bool fHelp) // Construct using pay-to-script-hash: CScript inner = _createmultisig_redeemScript(params); CScriptID innerID(inner); - CBitcoinAddress address(innerID); UniValue result(UniValue::VOBJ); - result.push_back(Pair("address", address.ToString())); + result.push_back(Pair("address", EncodeDestination(innerID))); result.push_back(Pair("redeemScript", HexStr(inner.begin(), inner.end()))); return result; @@ -357,13 +367,15 @@ UniValue verifymessage(const UniValue& params, bool fHelp) string strSign = params[1].get_str(); string strMessage = params[2].get_str(); - CBitcoinAddress addr(strAddress); - if (!addr.IsValid()) + CTxDestination destination = DecodeDestination(strAddress); + if (!IsValidDestination(destination)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); + } - CKeyID keyID; - if (!addr.GetKeyID(keyID)) + const CKeyID *keyID = boost::get(&destination); + if (!keyID) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); + } bool fInvalid = false; vector vchSig = DecodeBase64(strSign.c_str(), &fInvalid); @@ -379,7 +391,7 @@ UniValue verifymessage(const UniValue& params, bool fHelp) if (!pubkey.RecoverCompact(ss.GetHash(), vchSig)) return false; - return (pubkey.GetID() == keyID); + return (pubkey.GetID() == *keyID); } UniValue signmessagewithprivkey(const UniValue& params, bool fHelp) @@ -458,9 +470,9 @@ UniValue setmocktime(const UniValue& params, bool fHelp) bool getAddressFromIndex(const int &type, const uint160 &hash, std::string &address) { if (type == 2) { - address = CBitcoinAddress(CScriptID(hash)).ToString(); + address = EncodeDestination(CScriptID(hash)); } else if (type == 1) { - address = CBitcoinAddress(CKeyID(hash)).ToString(); + address = EncodeDestination(CKeyID(hash)); } else { return false; } @@ -470,10 +482,10 @@ bool getAddressFromIndex(const int &type, const uint160 &hash, std::string &addr bool getAddressesFromParams(const UniValue& params, std::vector > &addresses) { if (params[0].isStr()) { - CBitcoinAddress address(params[0].get_str()); + CTxDestination dest = DecodeDestination(params[0].get_str()); uint160 hashBytes; int type = 0; - if (!address.GetIndexKey(hashBytes, type)) { + if (IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); } addresses.push_back(std::make_pair(hashBytes, type)); @@ -488,10 +500,10 @@ bool getAddressesFromParams(const UniValue& params, std::vector::iterator it = values.begin(); it != values.end(); ++it) { - CBitcoinAddress address(it->get_str()); + CTxDestination dest = DecodeDestination(it->get_str()); uint160 hashBytes; int type = 0; - if (!address.GetIndexKey(hashBytes, type)) { + if (IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); } addresses.push_back(std::make_pair(hashBytes, type)); diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index febef24b7..063beca3c 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -8,6 +8,7 @@ #include "coins.h" #include "consensus/validation.h" #include "core_io.h" +#include "dstencode.h" #include "init.h" #include "keystore.h" #include "main.h" @@ -55,8 +56,10 @@ void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fInclud out.push_back(Pair("type", GetTxnOutputType(type))); UniValue a(UniValue::VARR); - BOOST_FOREACH(const CTxDestination& addr, addresses) - a.push_back(CBitcoinAddress(addr).ToString()); + for (const CTxDestination &addr : addresses) { + a.push_back(EncodeDestination(addr)); + } + out.push_back(Pair("addresses", a)); } @@ -89,9 +92,9 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue& in.push_back(Pair("value", ValueFromAmount(spentInfo.satoshis))); in.push_back(Pair("valueSat", spentInfo.satoshis)); if (spentInfo.addressType == 1) { - in.push_back(Pair("address", CBitcoinAddress(CKeyID(spentInfo.addressHash)).ToString())); + in.push_back(Pair("address", EncodeDestination(CKeyID(spentInfo.addressHash)))); } else if (spentInfo.addressType == 2) { - in.push_back(Pair("address", CBitcoinAddress(CScriptID(spentInfo.addressHash)).ToString())); + in.push_back(Pair("address", EncodeDestination(CScriptID(spentInfo.addressHash)))); } } @@ -512,25 +515,30 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp) rawTx.vin.push_back(in); } - set setAddress; - vector addrList = sendTo.getKeys(); - BOOST_FOREACH(const string& name_, addrList) { - + std::set destinations; + std::vector addrList = sendTo.getKeys(); + for (const std::string &name_ : addrList) { if (name_ == "data") { std::vector data = ParseHexV(sendTo[name_].getValStr(),"Data"); CTxOut out(0, CScript() << OP_RETURN << data); rawTx.vout.push_back(out); } else { - CBitcoinAddress address(name_); - if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_); + CTxDestination destination = DecodeDestination(name_); + if (!IsValidDestination(destination)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + std::string("Invalid Bitcoin address: ") + + name_); + } - if (setAddress.count(address)) - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_); - setAddress.insert(address); + if (!destinations.insert(destination).second) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + std::string("Invalid parameter, duplicated address: ") + + name_); + } - CScript scriptPubKey = GetScriptForDestination(address.Get()); + CScript scriptPubKey = GetScriptForDestination(destination); CAmount nAmount = AmountFromValue(sendTo[name_]); CTxOut out(nAmount, scriptPubKey); @@ -650,7 +658,7 @@ UniValue decodescript(const UniValue& params, bool fHelp) if (type.isStr() && type.get_str() != "scripthash") { // P2SH cannot be wrapped in a P2SH. If this script is already a P2SH, // don't return the address for a P2SH of the P2SH. - r.push_back(Pair("p2sh", CBitcoinAddress(CScriptID(script)).ToString())); + r.push_back(Pair("p2sh", EncodeDestination(CScriptID(script)))); } return r; diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index dc0d8074d..a9d718d7d 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -96,11 +96,23 @@ void RPCTypeCheckObj(const UniValue& o, if (!fAllowNull && v.isNull()) throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first)); - if (!(t.second.typeAny || v.type() == t.second.type || (fAllowNull && v.isNull()))) { + if (!(t.second.typeAny || (v.type() == t.second.type) || (fAllowNull && (v.isNull())))) { string err = strprintf("Expected type %s for %s, got %s", uvTypeName(t.second.type), t.first, uvTypeName(v.type())); throw JSONRPCError(RPC_TYPE_ERROR, err); } + + if (fStrict) + { + for (const std::string &k : o.getKeys()) + { + if (typesExpected.count(k) == 0) + { + string err = strprintf("Unexpected keys %s", k); + throw JSONRPCError(RPC_TYPE_ERROR, err); + } + } + } } if (fStrict) diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 67b6af327..d282cebde 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -282,3 +282,7 @@ CScript GetScriptForMultisig(int nRequired, const std::vector& keys) script << CScript::EncodeOP_N(keys.size()) << OP_CHECKMULTISIG; return script; } + +bool IsValidDestination(const CTxDestination &dest) { + return dest.which() != 0; +} diff --git a/src/script/standard.h b/src/script/standard.h index a85aec5ca..5ac259738 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -69,7 +69,7 @@ public: * * CNoDestination: no destination set * * CKeyID: TX_PUBKEYHASH destination * * CScriptID: TX_SCRIPTHASH destination - * A CTxDestination is the internal data type encoded in a CBitcoinAddress + * A CTxDestination is the internal data type encoded in a bitcoin address */ typedef boost::variant CTxDestination; @@ -79,6 +79,9 @@ bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector& addressRet, int& nRequiredRet); +const char *GetTxnOutputType(txnouttype t); +bool IsValidDestination(const CTxDestination &dest); + CScript GetScriptForDestination(const CTxDestination& dest); CScript GetScriptForRawPubKey(const CPubKey& pubkey); CScript GetScriptForMultisig(int nRequired, const std::vector& keys); diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index e5a2e28b2..c9cf65f96 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -123,7 +123,7 @@ BOOST_AUTO_TEST_CASE(base58_keys_valid_parse) UniValue tests = read_json(std::string(json_tests::base58_keys_valid, json_tests::base58_keys_valid + sizeof(json_tests::base58_keys_valid))); std::vector result; CBitcoinSecret secret; - CBitcoinAddress addr; + CTxDestination destination; SelectParams(CBaseChainParams::MAIN); for (unsigned int idx = 0; idx < tests.size(); idx++) { @@ -155,18 +155,20 @@ BOOST_AUTO_TEST_CASE(base58_keys_valid_parse) BOOST_CHECK_MESSAGE(privkey.size() == exp_payload.size() && std::equal(privkey.begin(), privkey.end(), exp_payload.begin()), "key mismatch:" + strTest); // Private key must be invalid public key - addr.SetString(exp_base58string); - BOOST_CHECK_MESSAGE(!addr.IsValid(), "IsValid privkey as pubkey:" + strTest); + destination = DecodeLegacyAddr(exp_base58string, Params()); + BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid privkey as pubkey:" + strTest); } else { - std::string exp_addrType = find_value(metadata, "addrType").get_str(); // "script" or "pubkey" + // "script" or "pubkey" + std::string exp_addrType = find_value(metadata, "addrType").get_str(); // Must be valid public key - BOOST_CHECK_MESSAGE(addr.SetString(exp_base58string), "SetString:" + strTest); - BOOST_CHECK_MESSAGE(addr.IsValid(), "!IsValid:" + strTest); - BOOST_CHECK_MESSAGE(addr.IsScript() == (exp_addrType == "script"), "isScript mismatch" + strTest); - CTxDestination dest = addr.Get(); - BOOST_CHECK_MESSAGE(boost::apply_visitor(TestAddrTypeVisitor(exp_addrType), dest), "addrType mismatch" + strTest); + destination = DecodeLegacyAddr(exp_base58string, Params()); + BOOST_CHECK_MESSAGE(IsValidDestination(destination), "!IsValid:" + strTest); + BOOST_CHECK_MESSAGE((boost::get(&destination) != nullptr) == (exp_addrType == "script"), + "isScript mismatch" + strTest); + BOOST_CHECK_MESSAGE( + boost::apply_visitor(TestAddrTypeVisitor(exp_addrType), destination), "addrType mismatch" + strTest); // Public key must be invalid private key secret.SetString(exp_base58string); @@ -229,17 +231,11 @@ BOOST_AUTO_TEST_CASE(base58_keys_valid_gen) BOOST_ERROR("Bad addrtype: " << strTest); continue; } - CBitcoinAddress addrOut; - BOOST_CHECK_MESSAGE(addrOut.Set(dest), "encode dest: " + strTest); - BOOST_CHECK_MESSAGE(addrOut.ToString() == exp_base58string, "mismatch: " + strTest); + std::string address = EncodeLegacyAddr(dest, Params()); + BOOST_CHECK_MESSAGE(address == exp_base58string, "mismatch: " + strTest); } } - // Visiting a CNoDestination must fail - CBitcoinAddress dummyAddr; - CTxDestination nodest = CNoDestination(); - BOOST_CHECK(!dummyAddr.Set(nodest)); - SelectParams(CBaseChainParams::MAIN); } @@ -249,7 +245,7 @@ BOOST_AUTO_TEST_CASE(base58_keys_invalid) UniValue tests = read_json(std::string(json_tests::base58_keys_invalid, json_tests::base58_keys_invalid + sizeof(json_tests::base58_keys_invalid))); // Negative testcases std::vector result; CBitcoinSecret secret; - CBitcoinAddress addr; + CTxDestination destination; for (unsigned int idx = 0; idx < tests.size(); idx++) { UniValue test = tests[idx]; @@ -262,8 +258,8 @@ BOOST_AUTO_TEST_CASE(base58_keys_invalid) std::string exp_base58string = test[0].get_str(); // must be invalid as public and as private key - addr.SetString(exp_base58string); - BOOST_CHECK_MESSAGE(!addr.IsValid(), "IsValid pubkey:" + strTest); + destination = DecodeLegacyAddr(exp_base58string, Params()); + BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid pubkey:" + strTest); secret.SetString(exp_base58string); BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid privkey:" + strTest); } diff --git a/src/test/cashaddr_tests.cpp b/src/test/cashaddr_tests.cpp new file mode 100644 index 000000000..71986b77e --- /dev/null +++ b/src/test/cashaddr_tests.cpp @@ -0,0 +1,115 @@ +// Copyright (c) 2017 Pieter Wuille +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "cashaddr.h" +#include "test/test_bitcoin.h" + +#include + +static std::pair > CashAddrDecode(const std::string &str) +{ + return cashaddr::Decode(str, ""); +} + +BOOST_FIXTURE_TEST_SUITE(cashaddr_tests, BasicTestingSetup) + +bool CaseInsensitiveEqual(const std::string &s1, const std::string &s2) +{ + if (s1.size() != s2.size()) + { + return false; + } + + for (size_t i = 0; i < s1.size(); ++i) + { + char c1 = s1[i]; + if (c1 >= 'A' && c1 <= 'Z') + { + c1 -= ('A' - 'a'); + } + char c2 = s2[i]; + if (c2 >= 'A' && c2 <= 'Z') + { + c2 -= ('A' - 'a'); + } + if (c1 != c2) + { + return false; + } + } + + return true; +} + +BOOST_AUTO_TEST_CASE(cashaddr_testvectors_valid) +{ + static const std::string CASES[] = { + "prefix:x64nx6hz", "PREFIX:X64NX6HZ", "p:gpf8m4h7", "blackcoin:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn", + "bchtest:testnetaddress4d6njnut", "bchreg:555555555555555555555555555555555555555555555udxmlmrz", + }; + + for (const std::string &str : CASES) + { + auto ret = CashAddrDecode(str); + BOOST_CHECK_MESSAGE(!ret.first.empty(), str); + std::string recode = cashaddr::Encode(ret.first, ret.second); + BOOST_CHECK_MESSAGE(!recode.empty(), str); + BOOST_CHECK_MESSAGE(CaseInsensitiveEqual(str, recode), str); + } +} + +BOOST_AUTO_TEST_CASE(cashaddr_testvectors_invalid) +{ + static const std::string CASES[] = { + "prefix:x32nx6hz", "prEfix:x64nx6hz", "prefix:x64nx6Hz", "pref1x:6m8cxv73", "prefix:", ":u9wsx07j", + "bchreg:555555555555555555x55555555555555555555555555udxmlmrz", + "bchreg:555555555555555555555555555555551555555555555udxmlmrz", "pre:fix:x32nx6hz", "prefixx64nx6hz", + }; + + for (const std::string &str : CASES) + { + auto ret = CashAddrDecode(str); + BOOST_CHECK_MESSAGE(ret.first.empty(), str); + } +} + +BOOST_AUTO_TEST_CASE(cashaddr_rawencode) +{ + typedef std::pair > raw; + + raw toEncode; + toEncode.first = "helloworld"; + toEncode.second = {0x1f, 0x0d}; + + std::string encoded = cashaddr::Encode(toEncode.first, toEncode.second); + raw decoded = CashAddrDecode(encoded); + + BOOST_CHECK_EQUAL(toEncode.first, decoded.first); + BOOST_CHECK_EQUAL_COLLECTIONS( + begin(toEncode.second), end(toEncode.second), begin(decoded.second), end(decoded.second)); +} + +BOOST_AUTO_TEST_CASE(cashaddr_testvectors_noprefix) +{ + static const std::pair CASES[] = { + {"blackcoin", "qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn"}, {"prefix", "x64nx6hz"}, {"PREFIX", "X64NX6HZ"}, + {"p", "gpf8m4h7"}, {"blackcoin", "qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn"}, + {"blktest", "testnetaddress4d6njnut"}, {"blkreg", "555555555555555555555555555555555555555555555udxmlmrz"}, + }; + + for (const std::pair &c : CASES) + { + std::string prefix = c.first; + std::string payload = c.second; + std::string addr = prefix + ":" + payload; + auto ret = cashaddr::Decode(payload, prefix); + BOOST_CHECK_MESSAGE(CaseInsensitiveEqual(ret.first, prefix), addr); + std::string recode = cashaddr::Encode(ret.first, ret.second); + BOOST_CHECK_MESSAGE(!recode.empty(), addr); + BOOST_CHECK_MESSAGE(CaseInsensitiveEqual(addr, recode), addr); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/cashaddrenc_tests.cpp b/src/test/cashaddrenc_tests.cpp new file mode 100644 index 000000000..10c0898bc --- /dev/null +++ b/src/test/cashaddrenc_tests.cpp @@ -0,0 +1,311 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "cashaddr.h" +#include "cashaddrenc.h" +#include "chainparams.h" +#include "random.h" +#include "test/test_bitcoin.h" +#include "uint256.h" + +#include + +namespace +{ +std::vector GetNetworks() +{ + return {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::REGTEST}; +} + +uint160 insecure_GetRandUInt160(FastRandomContext &rand) +{ + uint160 n; + for (uint8_t *c = n.begin(); c != n.end(); ++c) + { + *c = static_cast(rand.rand32()); + } + return n; +} + +std::vector insecure_GetRandomByteArray(FastRandomContext &rand, size_t n) +{ + std::vector out; + out.reserve(n); + + for (size_t i = 0; i < n; i++) + { + out.push_back(uint8_t(rand.randbits(8))); + } + return out; +} + +class DstTypeChecker : public boost::static_visitor +{ +public: + void operator()(const CKeyID &id) { isKey = true; } + void operator()(const CScriptID &id) { isScript = true; } + void operator()(const CNoDestination &) {} + static bool IsScriptDst(const CTxDestination &d) + { + DstTypeChecker checker; + boost::apply_visitor(checker, d); + return checker.isScript; + } + + static bool IsKeyDst(const CTxDestination &d) + { + DstTypeChecker checker; + boost::apply_visitor(checker, d); + return checker.isKey; + } + +private: + DstTypeChecker() : isKey(false), isScript(false) {} + bool isKey; + bool isScript; +}; + +// Map all possible size bits in the version to the expected size of the +// hash in bytes. +const std::array, 8> valid_sizes = { + {{0, 20}, {1, 24}, {2, 28}, {3, 32}, {4, 40}, {5, 48}, {6, 56}, {7, 64}}}; + +} // anon ns + +BOOST_FIXTURE_TEST_SUITE(cashaddrenc_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(encode_decode_all_sizes) +{ + FastRandomContext rand(true); + const CChainParams ¶ms = Params(CBaseChainParams::MAIN); + + for (auto ps : valid_sizes) + { + std::vector data = insecure_GetRandomByteArray(rand, ps.second); + CashAddrContent content = {PUBKEY_TYPE, data}; + std::vector packed_data = PackCashAddrContent(content); + + // Check that the packed size is correct + BOOST_CHECK_EQUAL(packed_data[1] >> 2, ps.first); + std::string address = cashaddr::Encode(params.CashAddrPrefix(), packed_data); + + // Check that the address decodes properly + CashAddrContent decoded = DecodeCashAddrContent(address, params); + BOOST_CHECK_EQUAL_COLLECTIONS( + std::begin(content.hash), std::end(content.hash), std::begin(decoded.hash), std::end(decoded.hash)); + } +} + +BOOST_AUTO_TEST_CASE(check_packaddr_throws) +{ + FastRandomContext rand(true); + + for (auto ps : valid_sizes) + { + std::vector data = insecure_GetRandomByteArray(rand, ps.second - 1); + CashAddrContent content = {PUBKEY_TYPE, data}; + BOOST_CHECK_THROW(PackCashAddrContent(content), std::runtime_error); + } +} + +BOOST_AUTO_TEST_CASE(encode_decode) +{ + std::vector toTest = { + CNoDestination{}, CKeyID(uint160S("badf00d")), CScriptID(uint160S("f00dbad"))}; + + for (auto dst : toTest) + { + for (auto net : GetNetworks()) + { + std::string encoded = EncodeCashAddr(dst, Params(net)); + CTxDestination decoded = DecodeCashAddr(encoded, Params(net)); + BOOST_CHECK(dst == decoded); + } + } +} + +// Check that an encoded cash address is not valid on another network. +BOOST_AUTO_TEST_CASE(invalid_on_wrong_network) +{ + const CTxDestination dst = CKeyID(uint160S("c0ffee")); + const CTxDestination invalidDst = CNoDestination{}; + + for (auto net : GetNetworks()) + { + for (auto otherNet : GetNetworks()) + { + if (net == otherNet) + continue; + + std::string encoded = EncodeCashAddr(dst, Params(net)); + CTxDestination decoded = DecodeCashAddr(encoded, Params(otherNet)); + BOOST_CHECK(decoded != dst); + BOOST_CHECK(decoded == invalidDst); + } + } +} + +BOOST_AUTO_TEST_CASE(random_dst) +{ + FastRandomContext rand(true); + + const size_t NUM_TESTS = 5000; + const CChainParams ¶ms = Params(CBaseChainParams::MAIN); + + for (size_t i = 0; i < NUM_TESTS; ++i) + { + uint160 hash = insecure_GetRandUInt160(rand); + const CTxDestination dst_key = CKeyID(hash); + const CTxDestination dst_scr = CScriptID(hash); + + const std::string encoded_key = EncodeCashAddr(dst_key, params); + const CTxDestination decoded_key = DecodeCashAddr(encoded_key, params); + + const std::string encoded_scr = EncodeCashAddr(dst_scr, params); + const CTxDestination decoded_scr = DecodeCashAddr(encoded_scr, params); + + std::string err("cashaddr failed for hash: "); + err += hash.ToString(); + + BOOST_CHECK_MESSAGE(dst_key == decoded_key, err); + BOOST_CHECK_MESSAGE(dst_scr == decoded_scr, err); + + BOOST_CHECK_MESSAGE(DstTypeChecker::IsKeyDst(decoded_key), err); + BOOST_CHECK_MESSAGE(DstTypeChecker::IsScriptDst(decoded_scr), err); + } +} + +/** + * Cashaddr payload made of 5-bit nibbles. The last one is padded. When + * converting back to bytes, this extra padding is truncated. In order to ensure + * cashaddr are cannonicals, we check that the data we truncate is zeroed. + */ +BOOST_AUTO_TEST_CASE(check_padding) +{ + uint8_t version = 0; + std::vector data = {version}; + for (size_t i = 0; i < 33; ++i) + { + data.push_back(1); + } + + BOOST_CHECK_EQUAL(data.size(), 34UL); + + const CTxDestination nodst = CNoDestination{}; + const CChainParams params = Params(CBaseChainParams::MAIN); + + for (uint8_t i = 0; i < 32; i++) + { + data[data.size() - 1] = i; + std::string fake = cashaddr::Encode(params.CashAddrPrefix(), data); + CTxDestination dst = DecodeCashAddr(fake, params); + + // We have 168 bits of payload encoded as 170 bits in 5 bits nimbles. As + // a result, we must have 2 zeros. + if (i & 0x03) + { + BOOST_CHECK(dst == nodst); + } + else + { + BOOST_CHECK(dst != nodst); + } + } +} + +/** + * We ensure type is extracted properly from the version. + */ +BOOST_AUTO_TEST_CASE(check_type) +{ + std::vector data; + data.resize(34); + + const CChainParams params = Params(CBaseChainParams::MAIN); + + for (uint8_t v = 0; v < 16; v++) + { + std::fill(begin(data), end(data), 0); + data[0] = v; + auto content = DecodeCashAddrContent(cashaddr::Encode(params.CashAddrPrefix(), data), params); + BOOST_CHECK_EQUAL(content.type, v); + BOOST_CHECK_EQUAL(content.hash.size(), 20UL); + + // Check that using the reserved bit result in a failure. + data[0] |= 0x10; + content = DecodeCashAddrContent(cashaddr::Encode(params.CashAddrPrefix(), data), params); + BOOST_CHECK_EQUAL(content.type, 0); + BOOST_CHECK_EQUAL(content.hash.size(), 0UL); + } +} + +/** + * We ensure size is extracted and checked properly. + */ +BOOST_AUTO_TEST_CASE(check_size) +{ + const CTxDestination nodst = CNoDestination{}; + const CChainParams params = Params(CBaseChainParams::MAIN); + + 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(params.CashAddrPrefix(), data), params); + + BOOST_CHECK_EQUAL(content.type, 0); + BOOST_CHECK_EQUAL(content.hash.size(), ps.second); + + data.push_back(0); + content = DecodeCashAddrContent(cashaddr::Encode(params.CashAddrPrefix(), data), params); + + BOOST_CHECK_EQUAL(content.type, 0); + BOOST_CHECK_EQUAL(content.hash.size(), 0UL); + + data.pop_back(); + data.pop_back(); + content = DecodeCashAddrContent(cashaddr::Encode(params.CashAddrPrefix(), data), params); + + BOOST_CHECK_EQUAL(content.type, 0); + BOOST_CHECK_EQUAL(content.hash.size(), 0UL); + } +} + +BOOST_AUTO_TEST_CASE(test_addresses) +{ + const CChainParams params = Params(CBaseChainParams::MAIN); + + std::vector > hash{ + {118, 160, 64, 83, 189, 160, 168, 139, 218, 81, 119, 184, 106, 21, 195, 178, 159, 85, 152, 115}, + {203, 72, 18, 50, 41, 156, 213, 116, 49, 81, 172, 75, 45, 99, 174, 25, 142, 123, 176, 169}, + {1, 31, 40, 228, 115, 201, 95, 64, 19, 215, 213, 62, 197, 251, 195, 180, 45, 248, 237, 16}}; + + std::vector pubkey = {"blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a", + "blackcoin:qr95sy3j9xwd2ap32xkykttr4cvcu7as4y0qverfuy", + "blackcoin:qqq3728yw0y47sqn6l2na30mcw6zm78dzqre909m2r"}; + std::vector script = {"blackcoin:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq", + "blackcoin:pr95sy3j9xwd2ap32xkykttr4cvcu7as4yc93ky28e", + "blackcoin:pqq3728yw0y47sqn6l2na30mcw6zm78dzq5ucqzc37"}; + + for (size_t i = 0; i < hash.size(); ++i) + { + const CTxDestination dstKey = CKeyID(uint160(hash[i])); + BOOST_CHECK_EQUAL(pubkey[i], EncodeCashAddr(dstKey, params)); + + const CTxDestination dstScript = CScriptID(uint160(hash[i])); + BOOST_CHECK_EQUAL(script[i], EncodeCashAddr(dstScript, params)); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/dstencode_tests.cpp b/src/test/dstencode_tests.cpp new file mode 100644 index 000000000..0a0236635 --- /dev/null +++ b/src/test/dstencode_tests.cpp @@ -0,0 +1,69 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "chainparams.h" +#include "config.h" +#include "dstencode.h" +#include "test/test_bitcoin.h" + +#include + +namespace { + +class DstCfgDummy : public DummyConfig { +public: + DstCfgDummy() : useCashAddr(false) {} + void SetCashAddrEncoding(bool b) override { useCashAddr = b; } + bool UseCashAddrEncoding() const override { return useCashAddr; } + +private: + bool useCashAddr; +}; + +} // anon ns + +BOOST_FIXTURE_TEST_SUITE(dstencode_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(test_addresses) { + std::vector hash = {118, 160, 64, 83, 189, 160, 168, + 139, 218, 81, 119, 184, 106, 21, + 195, 178, 159, 85, 152, 115}; + + const CTxDestination dstKey = CKeyID(uint160(hash)); + const CTxDestination dstScript = CScriptID(uint160(hash)); + + std::string cashaddr_pubkey = + "blackcoin:qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a"; + std::string cashaddr_script = + "blackcoin:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq"; + std::string base58_pubkey = "1BpEi6DfDAUFd7GtittLSdBeYJvcoaVggu"; + std::string base58_script = "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"; + + const CChainParams ¶ms = Params(CBaseChainParams::MAIN); + DstCfgDummy cfg; + + // Check encoding + cfg.SetCashAddrEncoding(true); + BOOST_CHECK_EQUAL(cashaddr_pubkey, EncodeDestination(dstKey, params, cfg)); + BOOST_CHECK_EQUAL(cashaddr_script, + EncodeDestination(dstScript, params, cfg)); + cfg.SetCashAddrEncoding(false); + BOOST_CHECK_EQUAL(base58_pubkey, EncodeDestination(dstKey, params, cfg)); + BOOST_CHECK_EQUAL(base58_script, EncodeDestination(dstScript, params, cfg)); + + // Check decoding + BOOST_CHECK(dstKey == DecodeDestination(cashaddr_pubkey, params)); + BOOST_CHECK(dstScript == DecodeDestination(cashaddr_script, params)); + BOOST_CHECK(dstKey == DecodeDestination(base58_pubkey, params)); + BOOST_CHECK(dstScript == DecodeDestination(base58_script, params)); + + // Validation + BOOST_CHECK(IsValidDestinationString(cashaddr_pubkey, params)); + BOOST_CHECK(IsValidDestinationString(cashaddr_script, params)); + BOOST_CHECK(IsValidDestinationString(base58_pubkey, params)); + BOOST_CHECK(IsValidDestinationString(base58_script, params)); + BOOST_CHECK(!IsValidDestinationString("notvalid", params)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 4978c9513..a383f2e35 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -5,6 +5,7 @@ #include "key.h" #include "base58.h" +#include "dstencode.h" #include "script/script.h" #include "uint256.h" #include "util.h" @@ -18,18 +19,16 @@ using namespace std; -static const string strSecret1 ("5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"); -static const string strSecret2 ("5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"); -static const string strSecret1C ("Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"); -static const string strSecret2C ("L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"); -static const CBitcoinAddress addr1 ("1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ"); -static const CBitcoinAddress addr2 ("1F5y5E5FMc5YzdJtB9hLaUe43GDxEKXENJ"); -static const CBitcoinAddress addr1C("1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs"); -static const CBitcoinAddress addr2C("1CRj2HyM1CXWzHAXLQtiGLyggNT9WQqsDs"); - - -static const string strAddressBad("1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF"); +static const std::string strSecret1 = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj"; +static const std::string strSecret2 = "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3"; +static const std::string strSecret1C = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw"; +static const std::string strSecret2C = "L3Hq7a8FEQwJkW1M2GNKDW28546Vp5miewcCzSqUD9kCAXrJdS3g"; +static const std::string addr1 = "1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ"; +static const std::string addr2 = "1F5y5E5FMc5YzdJtB9hLaUe43GDxEKXENJ"; +static const std::string addr1C = "1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs"; +static const std::string addr2C = "1CRj2HyM1CXWzHAXLQtiGLyggNT9WQqsDs"; +static const std::string strAddressBad = "1HV9Lc3sNHZxwj4Zk6fB38tEmBryq2cBiF"; #ifdef KEY_TESTS_DUMPINFO void dumpKeyInfo(uint256 privkey) @@ -104,10 +103,10 @@ BOOST_AUTO_TEST_CASE(key_test1) BOOST_CHECK(!key2C.VerifyPubKey(pubkey2)); BOOST_CHECK(key2C.VerifyPubKey(pubkey2C)); - BOOST_CHECK(addr1.Get() == CTxDestination(pubkey1.GetID())); - BOOST_CHECK(addr2.Get() == CTxDestination(pubkey2.GetID())); - BOOST_CHECK(addr1C.Get() == CTxDestination(pubkey1C.GetID())); - BOOST_CHECK(addr2C.Get() == CTxDestination(pubkey2C.GetID())); + BOOST_CHECK(DecodeDestination(addr1) == CTxDestination(pubkey1.GetID())); + BOOST_CHECK(DecodeDestination(addr2) == CTxDestination(pubkey2.GetID())); + BOOST_CHECK(DecodeDestination(addr1C) == CTxDestination(pubkey1C.GetID())); + BOOST_CHECK(DecodeDestination(addr2C) == CTxDestination(pubkey2C.GetID())); for (int n=0; n<16; n++) { diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 3b0a460c9..d5b32acbb 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -2,6 +2,9 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "base58.h" // Freeze CBitcoinAddress +#include "chain.h" // Freeze CBlockIndex +#include "dstencode.h" #include "key.h" #include "keystore.h" #include "policy/policy.h" @@ -306,5 +309,83 @@ BOOST_AUTO_TEST_CASE(multisig_Sign) } } +/* +#ifdef ENABLE_WALLET +BOOST_AUTO_TEST_CASE(cltv_freeze) +{ + CKey key[4]; + for (int i = 0; i < 2; i++) + key[i].MakeNewKey(true); + + // Create and unpack a CLTV script + vector solutions; + txnouttype whichType; + vector addresses; + int nRequiredReturn; + txnouttype type = TX_CLTV; + + // check cltv solve for block + CPubKey newKey1 = ToByteVector(key[0].GetPubKey()); + CTxDestination newAddr1 = CTxDestination(newKey1.GetID()); + CScriptNum nFreezeLockTime(50000); + CScript s1 = GetScriptForFreeze(nFreezeLockTime, newKey1); + + BOOST_CHECK(Solver(s1, whichType, solutions)); + BOOST_CHECK(whichType == TX_CLTV); + BOOST_CHECK(solutions.size() == 2); + BOOST_CHECK(CScriptNum(solutions[0], false) == nFreezeLockTime); + + nRequiredReturn = 0; + ExtractDestinations(s1, type, addresses, nRequiredReturn); + + for (const CTxDestination &addr : addresses) + BOOST_CHECK(EncodeDestination(newAddr1) == EncodeDestination(addr)); + BOOST_CHECK(nRequiredReturn == 1); + + + // check cltv solve for datetime + CPubKey newKey2 = ToByteVector(key[0].GetPubKey()); + CTxDestination newAddr2 = CTxDestination(newKey2.GetID()); + nFreezeLockTime = CScriptNum(1482255731); + CScript s2 = GetScriptForFreeze(nFreezeLockTime, newKey2); + + BOOST_CHECK(Solver(s2, whichType, solutions)); + BOOST_CHECK(whichType == TX_CLTV); + BOOST_CHECK(solutions.size() == 2); + BOOST_CHECK(CScriptNum(solutions[0], false) == nFreezeLockTime); + + nRequiredReturn = 0; + ExtractDestinations(s2, type, addresses, nRequiredReturn); + + for (const CTxDestination &addr : addresses) + BOOST_CHECK(newAddr2 == addr); + + BOOST_CHECK(nRequiredReturn == 1); +} + +BOOST_AUTO_TEST_CASE(opreturn_send) +{ + CKey key[4]; + for (int i = 0; i < 2; i++) + key[i].MakeNewKey(true); + + CBasicKeyStore keystore; + + // Create and unpack a CLTV script + vector solutions; + txnouttype whichType; + vector addresses; + + string inMsg = "hello world", outMsg = ""; + CScript s = GetScriptLabelPublic(inMsg); + + outMsg = getLabelPublic(s); + BOOST_CHECK(inMsg == outMsg); + BOOST_CHECK(Solver(s, whichType, solutions)); + BOOST_CHECK(whichType == TX_LABELPUBLIC); +} +#endif +*/ + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 28cecfffa..e00956c72 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -484,4 +484,59 @@ BOOST_AUTO_TEST_CASE(test_ParseFixedPoint) BOOST_CHECK(!ParseFixedPoint("1.", 8, &amount)); } +template +static void CheckConvertBits(const std::vector &in, const std::vector &expected) +{ + std::vector outpad; + bool ret = ConvertBits(outpad, in.begin(), in.end()); + BOOST_CHECK(ret); + BOOST_CHECK(outpad == expected); + + const bool dopad = (in.size() * F) % T; + std::vector outnopad; + ret = ConvertBits(outnopad, in.begin(), in.end()); + BOOST_CHECK(ret != dopad); + + if (dopad) + { + // We should have skipped the last digit. + outnopad.push_back(expected.back()); + } + + BOOST_CHECK(outnopad == expected); + + // Check the other way around. + std::vector orignopad; + ret = ConvertBits(orignopad, expected.begin(), expected.end()); + BOOST_CHECK(ret == !((expected.size() * T) % F)); + BOOST_CHECK(orignopad == in); + + // Check with padding. We may get an extra 0 in that case. + std::vector origpad; + ret = ConvertBits(origpad, expected.begin(), expected.end()); + BOOST_CHECK(ret); + + if (dopad) + { + BOOST_CHECK_EQUAL(origpad.back(), 0); + origpad.pop_back(); + } + + BOOST_CHECK(origpad == in); +} + +BOOST_AUTO_TEST_CASE(test_ConvertBits) +{ + CheckConvertBits<8, 5>({}, {}); + CheckConvertBits<8, 5>({0xff}, {0x1f, 0x1c}); + CheckConvertBits<8, 5>({0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x10}); + CheckConvertBits<8, 5>({0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1e}); + CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x18}); + CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); + CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); + CheckConvertBits<8, 5>({0xff, 0xff, 0xff, 0xff, 0xff}, {0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f}); + CheckConvertBits<8, 5>({0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, + {0x00, 0x04, 0x11, 0x14, 0x0a, 0x19, 0x1c, 0x09, 0x15, 0x0f, 0x06, 0x1e, 0x1e}); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/uint256.h b/src/uint256.h index 4ce062e7e..5ea109ad9 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -163,4 +163,17 @@ inline uint256 uint256S(const std::string& str) return rv; } +inline uint160 uint160S(const char *str) +{ + uint160 rv; + rv.SetHex(str); + return rv; +} + +inline uint160 uint160S(const std::string &str) +{ + uint160 rv; + rv.SetHex(str); + return rv; +} #endif // BITCOIN_UINT256_H diff --git a/src/utilstrencodings.h b/src/utilstrencodings.h index d40613cfc..ef8f76b17 100644 --- a/src/utilstrencodings.h +++ b/src/utilstrencodings.h @@ -130,4 +130,44 @@ bool TimingResistantEqual(const T& a, const T& b) */ bool ParseFixedPoint(const std::string &val, int decimals, int64_t *amount_out); +/** + * Convert from one power-of-2 number base to another. + * + * If padding is enabled, this always return true. If not, then it returns true + * of all the bits of the input are encoded in the output. + */ +template +bool ConvertBits(O &out, I it, I end) +{ + size_t acc = 0; + size_t bits = 0; + constexpr size_t maxv = (1 << tobits) - 1; + constexpr size_t max_acc = (1 << (frombits + tobits - 1)) - 1; + while (it != end) + { + acc = ((acc << frombits) | *it) & max_acc; + bits += frombits; + while (bits >= tobits) + { + bits -= tobits; + out.push_back((acc >> bits) & maxv); + } + ++it; + } + + // We have remaining bits to encode but do not pad. + if (!pad && bits) + { + return false; + } + + // We have remaining bits to encode so we do pad. + if (pad && bits) + { + out.push_back((acc << (tobits - bits)) & maxv); + } + + return true; +} + #endif // BITCOIN_UTILSTRENCODINGS_H diff --git a/src/wallet-utility.cpp b/src/wallet-utility.cpp index b52e6634d..11a56a65c 100644 --- a/src/wallet-utility.cpp +++ b/src/wallet-utility.cpp @@ -5,6 +5,7 @@ #include "wallet/walletdb.h" #include "util.h" #include "base58.h" +#include "dstencode.h" #include "wallet/crypter.h" #include @@ -65,7 +66,7 @@ std::string WalletUtilityDB::getAddress(CDataStream ssKey) CPubKey vchPubKey; ssKey >> vchPubKey; CKeyID id = vchPubKey.GetID(); - std::string strAddr = CBitcoinAddress(id).ToString(); + std::string strAddr = EncodeDestination(id); return strAddr; } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 67aded7c3..b12986098 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -4,6 +4,7 @@ #include "base58.h" #include "chain.h" +#include "dstencode.h" #include "rpc/server.h" #include "init.h" #include "main.h" @@ -152,7 +153,7 @@ UniValue importprivkey(const UniValue& params, bool fHelp) return NullUniValue; } -void ImportAddress(const CBitcoinAddress& address, const string& strLabel); +void ImportAddress(const CTxDestination& dest, const string& strLabel); void ImportScript(const CScript& script, const string& strLabel, bool isRedeemScript) { if (!isRedeemScript && ::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE) @@ -166,7 +167,7 @@ void ImportScript(const CScript& script, const string& strLabel, bool isRedeemSc if (isRedeemScript) { if (!pwalletMain->HaveCScript(script) && !pwalletMain->AddCScript(script)) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); - ImportAddress(CBitcoinAddress(CScriptID(script)), strLabel); + ImportAddress(CScriptID(script), strLabel); } else { CTxDestination destination; if (ExtractDestination(script, destination)) { @@ -175,13 +176,12 @@ void ImportScript(const CScript& script, const string& strLabel, bool isRedeemSc } } -void ImportAddress(const CBitcoinAddress& address, const string& strLabel) -{ - CScript script = GetScriptForDestination(address.Get()); +void ImportAddress(const CTxDestination &dest, const std::string &strLabel) { + CScript script = GetScriptForDestination(dest); ImportScript(script, strLabel, false); // add to address book or update label - if (address.IsValid()) - pwalletMain->SetAddressBook(address.Get(), strLabel, "receive"); + if (IsValidDestination(dest)) + pwalletMain->SetAddressBook(dest, strLabel, "receive"); } UniValue importaddress(const UniValue& params, bool fHelp) @@ -231,13 +231,16 @@ UniValue importaddress(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); - CBitcoinAddress address(params[0].get_str()); - if (address.IsValid()) { - if (fP2SH) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); - ImportAddress(address, strLabel); + CTxDestination dest = DecodeDestination(params[0].get_str()); + if (IsValidDestination(dest)) { + if (fP2SH) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Cannot use the p2sh flag with an address - use " + "a script instead"); + } + ImportAddress(dest, strLabel); } else if (IsHex(params[0].get_str())) { - std::vector data(ParseHex(params[0].get_str())); + std::vector data(ParseHex(params[0].get_str())); ImportScript(CScript(data.begin(), data.end()), strLabel, fP2SH); } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); @@ -393,7 +396,7 @@ UniValue importpubkey(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); - ImportAddress(CBitcoinAddress(pubKey.GetID()), strLabel); + ImportAddress(pubKey.GetID(), strLabel); ImportScript(GetScriptForRawPubKey(pubKey), strLabel, false); if (fRescan) @@ -465,7 +468,8 @@ UniValue importwallet(const UniValue& params, bool fHelp) assert(key.VerifyPubKey(pubkey)); CKeyID keyid = pubkey.GetID(); if (pwalletMain->HaveKey(keyid)) { - LogPrintf("Skipping import of %s (key already present)\n", CBitcoinAddress(keyid).ToString()); + LogPrintf("Skipping import of %s (key already present)\n", + EncodeDestination(keyid)); continue; } int64_t nTime = DecodeDumpTime(vstr[1]); @@ -483,7 +487,7 @@ UniValue importwallet(const UniValue& params, bool fHelp) fLabel = true; } } - LogPrintf("Importing %s...\n", CBitcoinAddress(keyid).ToString()); + LogPrintf("Importing %s...\n", EncodeDestination(keyid)); if (!pwalletMain->AddKeyPubKey(key, pubkey)) { fGood = false; continue; @@ -537,15 +541,18 @@ UniValue dumpprivkey(const UniValue& params, bool fHelp) EnsureWalletIsUnlocked(); - string strAddress = params[0].get_str(); - CBitcoinAddress address; - if (!address.SetString(strAddress)) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); - CKeyID keyID; - if (!address.GetKeyID(keyID)) + std::string strAddress = params[0].get_str(); + CTxDestination dest = DecodeDestination(strAddress); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Invalid Bitcoin address"); + } + const CKeyID *keyID = boost::get(&dest); + if (!keyID) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); + } CKey vchSecret; - if (!pwalletMain->GetKey(keyID, vchSecret)) + if (!pwalletMain->GetKey(*keyID, vchSecret)) throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); return CBitcoinSecret(vchSecret).ToString(); } @@ -616,7 +623,7 @@ UniValue dumpwallet(const UniValue& params, bool fHelp) for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { const CKeyID &keyid = it->second; std::string strTime = EncodeDumpTime(it->first); - std::string strAddr = CBitcoinAddress(keyid).ToString(); + std::string strAddr = EncodeDestination(keyid); CKey key; if (pwalletMain->GetKey(keyid, key)) { file << strprintf("%s %s ", CBitcoinSecret(key).ToString(), strTime); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 284744017..9ee907f20 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4,9 +4,9 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "amount.h" -#include "base58.h" #include "chain.h" #include "core_io.h" +#include "dstencode.h" #include "init.h" #include "main.h" #include "net.h" @@ -167,18 +167,17 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) pwalletMain->SetAddressBook(keyID, strAccount, "receive"); - return CBitcoinAddress(keyID).ToString(); + return EncodeDestination(keyID); } - -CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) +CTxDestination GetAccountAddress(string strAccount, bool bForceNew=false) { CPubKey pubKey; if (!pwalletMain->GetAccountPubkey(pubKey, strAccount, bForceNew)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } - return CBitcoinAddress(pubKey.GetID()); + return pubKey.GetID(); } UniValue getaccountaddress(const UniValue& params, bool fHelp) @@ -208,7 +207,7 @@ UniValue getaccountaddress(const UniValue& params, bool fHelp) UniValue ret(UniValue::VSTR); - ret = GetAccountAddress(strAccount).ToString(); + ret = EncodeDestination(GetAccountAddress(strAccount)); return ret; } @@ -244,10 +243,9 @@ UniValue getrawchangeaddress(const UniValue& params, bool fHelp) CKeyID keyID = vchPubKey.GetID(); - return CBitcoinAddress(keyID).ToString(); + return EncodeDestination(keyID); } - UniValue setaccount(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) @@ -267,25 +265,28 @@ UniValue setaccount(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); - CBitcoinAddress address(params[0].get_str()); - if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + CTxDestination dest = DecodeDestination(params[0].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Invalid Bitcoin address"); + } string strAccount; if (params.size() > 1) strAccount = AccountFromValue(params[1]); // Only add the account if the address is yours. - if (IsMine(*pwalletMain, address.Get())) + if (IsMine(*pwalletMain, dest)) { - // Detect when changing the account of an address that is the 'unused current key' of another account: - if (pwalletMain->mapAddressBook.count(address.Get())) + // Detect when changing the account of an address that is the 'unused + // current key' of another account: + if (pwalletMain->mapAddressBook.count(dest)) { - string strOldAccount = pwalletMain->mapAddressBook[address.Get()].name; - if (address == GetAccountAddress(strOldAccount)) + std::string strOldAccount = pwalletMain->mapAddressBook[dest].name; + if (dest == GetAccountAddress(strOldAccount)) GetAccountAddress(strOldAccount, true); } - pwalletMain->SetAddressBook(address.Get(), strAccount, "receive"); + pwalletMain->SetAddressBook(dest, strAccount, "receive"); } else throw JSONRPCError(RPC_MISC_ERROR, "setaccount can only be used with own address"); @@ -293,7 +294,6 @@ UniValue setaccount(const UniValue& params, bool fHelp) return NullUniValue; } - UniValue getaccount(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) @@ -314,18 +314,21 @@ UniValue getaccount(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); - CBitcoinAddress address(params[0].get_str()); - if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + CTxDestination dest = DecodeDestination(params[0].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Invalid Bitcoin address"); + } - string strAccount; - map::iterator mi = pwalletMain->mapAddressBook.find(address.Get()); - if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.name.empty()) + std::string strAccount; + std::map::iterator mi = + pwalletMain->mapAddressBook.find(dest); + if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.name.empty()) { strAccount = (*mi).second.name; + } return strAccount; } - UniValue getaddressesbyaccount(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) @@ -353,12 +356,13 @@ UniValue getaddressesbyaccount(const UniValue& params, bool fHelp) // Find all addresses that have the given account UniValue ret(UniValue::VARR); - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook) - { - const CBitcoinAddress& address = item.first; - const string& strName = item.second.name; - if (strName == strAccount) - ret.push_back(address.ToString()); + for (const std::pair &item : + pwalletMain->mapAddressBook) { + const CTxDestination &dest = item.first; + const std::string &strName = item.second.name; + if (strName == strAccount) { + ret.push_back(EncodeDestination(dest)); + } } return ret; } @@ -427,9 +431,10 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); - CBitcoinAddress address(params[0].get_str()); - if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + CTxDestination dest = DecodeDestination(params[0].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + } // Amount CAmount nAmount = AmountFromValue(params[1]); @@ -449,7 +454,7 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp) EnsureWalletIsUnlocked(); - SendMoney(address.Get(), nAmount, fSubtractFeeFromAmount, wtx); + SendMoney(dest, nAmount, fSubtractFeeFromAmount, wtx); return wtx.GetHash().GetHex(); } @@ -485,18 +490,20 @@ UniValue listaddressgroupings(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); UniValue jsonGroupings(UniValue::VARR); - map balances = pwalletMain->GetAddressBalances(); - BOOST_FOREACH(set grouping, pwalletMain->GetAddressGroupings()) - { + std::map balances = + pwalletMain->GetAddressBalances(); + for (const std::set &grouping : + pwalletMain->GetAddressGroupings()) { UniValue jsonGrouping(UniValue::VARR); - BOOST_FOREACH(CTxDestination address, grouping) - { + for (const CTxDestination &address : grouping) { UniValue addressInfo(UniValue::VARR); - addressInfo.push_back(CBitcoinAddress(address).ToString()); + addressInfo.push_back(EncodeDestination(address)); addressInfo.push_back(ValueFromAmount(balances[address])); - { - if (pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get()) != pwalletMain->mapAddressBook.end()) - addressInfo.push_back(pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get())->second.name); + + if (pwalletMain->mapAddressBook.find(address) != + pwalletMain->mapAddressBook.end()) { + addressInfo.push_back( + pwalletMain->mapAddressBook.find(address)->second.name); } jsonGrouping.push_back(addressInfo); } @@ -538,16 +545,16 @@ UniValue signmessage(const UniValue& params, bool fHelp) string strAddress = params[0].get_str(); string strMessage = params[1].get_str(); - CBitcoinAddress addr(strAddress); - if (!addr.IsValid()) + CTxDestination dest = DecodeDestination(strAddress); + if (!IsValidDestination(dest)) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); - CKeyID keyID; - if (!addr.GetKeyID(keyID)) + const CKeyID *keyID = boost::get(&dest); + if (!keyID) throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); CKey key; - if (!pwalletMain->GetKey(keyID, key)) + if (!pwalletMain->GetKey(*keyID, key)) throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); CHashWriter ss(SER_GETHASH, 0); @@ -589,10 +596,11 @@ UniValue getreceivedbyaddress(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); // Bitcoin address - CBitcoinAddress address = CBitcoinAddress(params[0].get_str()); - if (!address.IsValid()) + CTxDestination dest = DecodeDestination(params[0].get_str()); + if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); - CScript scriptPubKey = GetScriptForDestination(address.Get()); + } + CScript scriptPubKey = GetScriptForDestination(dest); if (!IsMine(*pwalletMain, scriptPubKey)) return ValueFromAmount(0); @@ -609,7 +617,7 @@ UniValue getreceivedbyaddress(const UniValue& params, bool fHelp) if (wtx.IsCoinBase() || !CheckFinalTx(wtx)) continue; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + for (const CTxOut &txout : wtx.vout) if (txout.scriptPubKey == scriptPubKey) if (wtx.GetDepthInMainChain() >= nMinDepth) nAmount += txout.nValue; @@ -618,7 +626,6 @@ UniValue getreceivedbyaddress(const UniValue& params, bool fHelp) return ValueFromAmount(nAmount); } - UniValue getreceivedbyaccount(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp)) @@ -853,10 +860,12 @@ UniValue sendfrom(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); - string strAccount = AccountFromValue(params[0]); - CBitcoinAddress address(params[1].get_str()); - if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + std::string strAccount = AccountFromValue(params[0]); + CTxDestination dest = DecodeDestination(params[1].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Invalid Bitcoin address"); + } CAmount nAmount = AmountFromValue(params[2]); if (nAmount <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); @@ -878,7 +887,7 @@ UniValue sendfrom(const UniValue& params, bool fHelp) if (nAmount > nBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); - SendMoney(address.Get(), nAmount, false, wtx); + SendMoney(dest, nAmount, false, wtx); return wtx.GetHash().GetHex(); } @@ -942,22 +951,27 @@ UniValue sendmany(const UniValue& params, bool fHelp) if (params.size() > 4) subtractFeeFromAmount = params[4].get_array(); - set setAddress; - vector vecSend; + std::set destinations; + std::vector vecSend; CAmount totalAmount = 0; - vector keys = sendTo.getKeys(); - BOOST_FOREACH(const string& name_, keys) - { - CBitcoinAddress address(name_); - if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_); + std::vector keys = sendTo.getKeys(); + for (const std::string &name_ : keys) { + CTxDestination dest = DecodeDestination(name_); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + std::string("Invalid Bitcoin address: ") + + name_); + } - if (setAddress.count(address)) - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_); - setAddress.insert(address); + if (destinations.count(dest)) { + throw JSONRPCError( + RPC_INVALID_PARAMETER, + std::string("Invalid parameter, duplicated address: ") + name_); + } + destinations.insert(dest); - CScript scriptPubKey = GetScriptForDestination(address.Get()); + CScript scriptPubKey = GetScriptForDestination(dest); CAmount nAmount = AmountFromValue(sendTo[name_]); if (nAmount <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); @@ -1043,7 +1057,7 @@ UniValue addmultisigaddress(const UniValue& params, bool fHelp) pwalletMain->AddCScript(inner); pwalletMain->SetAddressBook(innerID, strAccount, "send"); - return CBitcoinAddress(innerID).ToString(); + return EncodeDestination(innerID); } @@ -1079,11 +1093,13 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) filter = filter | ISMINE_WATCH_ONLY; // Tally - map mapTally; - for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) + std::map mapTally; + for (std::map::iterator it = pwalletMain->mapWallet.begin(); + it != pwalletMain->mapWallet.end(); ++it) { - const CWalletTx& wtx = (*it).second; + const CWalletTx &wtx = (*it).second; + // CValidationState state; if (wtx.IsCoinBase() || !CheckFinalTx(wtx)) continue; @@ -1091,7 +1107,7 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) if (nDepth < nMinDepth) continue; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + for (const CTxOut &txout : wtx.vout) { CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address)) @@ -1112,12 +1128,12 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) // Reply UniValue ret(UniValue::VARR); - map mapAccountTally; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook) + std::map mapAccountTally; + for (const std::pair &item : pwalletMain->mapAddressBook) { - const CBitcoinAddress& address = item.first; - const string& strAccount = item.second.name; - map::iterator it = mapTally.find(address); + const CTxDestination &dest = item.first; + const std::string &strAccount = item.second.name; + std::map::iterator it = mapTally.find(dest); if (it == mapTally.end() && !fIncludeEmpty) continue; @@ -1142,17 +1158,23 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) { UniValue obj(UniValue::VOBJ); if(fIsWatchonly) + { obj.push_back(Pair("involvesWatchonly", true)); - obj.push_back(Pair("address", address.ToString())); - obj.push_back(Pair("account", strAccount)); - obj.push_back(Pair("amount", ValueFromAmount(nAmount))); - obj.push_back(Pair("confirmations", (nConf == std::numeric_limits::max() ? 0 : nConf))); + } + obj.push_back(Pair("address", EncodeDestination(dest))); + obj.push_back(Pair("account", strAccount)); + obj.push_back(Pair("amount", ValueFromAmount(nAmount))); + obj.push_back( + Pair("confirmations", + (nConf == std::numeric_limits::max() ? 0 : nConf))); if (!fByAccounts) + { obj.push_back(Pair("label", strAccount)); + } UniValue transactions(UniValue::VARR); if (it != mapTally.end()) { - BOOST_FOREACH(const uint256& item, (*it).second.txids) + for (const uint256 &item : (*it).second.txids) { transactions.push_back(item.GetHex()); } @@ -1258,11 +1280,10 @@ UniValue listreceivedbyaccount(const UniValue& params, bool fHelp) return ListReceived(params, true); } -static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) -{ - CBitcoinAddress addr; - if (addr.Set(dest)) - entry.push_back(Pair("address", addr.ToString())); +static void MaybePushAddress(UniValue &entry, const CTxDestination &dest) { + if (IsValidDestination(dest)) { + entry.push_back(Pair("address", EncodeDestination(dest))); + } } void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) @@ -2357,17 +2378,17 @@ UniValue listunspent(const UniValue& params, bool fHelp) if (params.size() > 1) nMaxDepth = params[1].get_int(); - set setAddress; + set destinations; if (params.size() > 2) { UniValue inputs = params[2].get_array(); for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; - CBitcoinAddress address(input.get_str()); - if (!address.IsValid()) + CTxDestination address = DecodeDestination(input.get_str()); + if (!IsValidDestination(address)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+input.get_str()); - if (setAddress.count(address)) + if (destinations.count(address)) throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+input.get_str()); - setAddress.insert(address); + destinations.insert(address); } } @@ -2384,7 +2405,7 @@ UniValue listunspent(const UniValue& params, bool fHelp) const CScript& scriptPubKey = out.tx->vout[out.i].scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, address); - if (setAddress.size() && (!fValidAddress || !setAddress.count(address))) + if (destinations.size() && (!fValidAddress || !destinations.count(address))) continue; UniValue entry(UniValue::VOBJ); @@ -2392,7 +2413,7 @@ UniValue listunspent(const UniValue& params, bool fHelp) entry.push_back(Pair("vout", out.i)); if (fValidAddress) { - entry.push_back(Pair("address", CBitcoinAddress(address).ToString())); + entry.push_back(Pair("address", EncodeDestination(address))); if (pwalletMain->mapAddressBook.count(address)) entry.push_back(Pair("account", pwalletMain->mapAddressBook[address].name)); @@ -2476,7 +2497,7 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp) // backward compatibility bool only fallback includeWatching = params[1].get_bool(); } - else { + } else { RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VOBJ)); UniValue options = params[1]; @@ -2492,12 +2513,13 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp) true, true); if (options.exists("changeAddress")) { - CBitcoinAddress address(options["changeAddress"].get_str()); + CTxDestination dest = + DecodeDestination(options["changeAddress"].get_str()); - if (!address.IsValid()) + if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "changeAddress must be a valid bitcoin address"); - changeAddress = address.Get(); + changeAddress = dest; } if (options.exists("changePosition")) diff --git a/src/wallet/test/rpc_wallet_tests.cpp b/src/wallet/test/rpc_wallet_tests.cpp index 4e7d177f5..8af8fe7a2 100644 --- a/src/wallet/test/rpc_wallet_tests.cpp +++ b/src/wallet/test/rpc_wallet_tests.cpp @@ -6,6 +6,7 @@ #include "rpc/client.h" #include "base58.h" +#include "dstencode.h" #include "main.h" #include "wallet/wallet.h" @@ -35,18 +36,17 @@ BOOST_AUTO_TEST_CASE(rpc_addmultisig) const char address2Hex[] = "0388c2037017c62240b6b72ac1a2a5f94da790596ebd06177c8572752922165cb4"; UniValue v; - CBitcoinAddress address; BOOST_CHECK_NO_THROW(v = addmultisig(createArgs(1, address1Hex), false)); - address.SetString(v.get_str()); - BOOST_CHECK(address.IsValid() && address.IsScript()); + CTxDestination address = DecodeDestination(v.get_str()); + BOOST_CHECK(IsValidDestination(address)); BOOST_CHECK_NO_THROW(v = addmultisig(createArgs(1, address1Hex, address2Hex), false)); - address.SetString(v.get_str()); - BOOST_CHECK(address.IsValid() && address.IsScript()); + address = DecodeDestination(v.get_str()); + BOOST_CHECK(IsValidDestination(address)); BOOST_CHECK_NO_THROW(v = addmultisig(createArgs(2, address1Hex, address2Hex), false)); - address.SetString(v.get_str()); - BOOST_CHECK(address.IsValid() && address.IsScript()); + address = DecodeDestination(v.get_str()); + BOOST_CHECK(IsValidDestination(address)); BOOST_CHECK_THROW(addmultisig(createArgs(0), false), runtime_error); BOOST_CHECK_THROW(addmultisig(createArgs(1), false), runtime_error); @@ -67,15 +67,15 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) // Test RPC calls for various wallet statistics UniValue r; CPubKey demoPubkey; - CBitcoinAddress demoAddress; + CTxDestination demoAddress; UniValue retValue; string strAccount = "walletDemoAccount"; - CBitcoinAddress setaccountDemoAddress; + CTxDestination setaccountDemoAddress; { LOCK(pwalletMain->cs_wallet); demoPubkey = pwalletMain->GenerateNewKey(); - demoAddress = CBitcoinAddress(CTxDestination(demoPubkey.GetID())); + demoAddress = CTxDestination(demoPubkey.GetID()); string strPurpose = "receive"; BOOST_CHECK_NO_THROW({ /*Initialize Wallet with an account */ CWalletDB walletdb(pwalletMain->strWalletFile); @@ -86,12 +86,12 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) }); CPubKey setaccountDemoPubkey = pwalletMain->GenerateNewKey(); - setaccountDemoAddress = CBitcoinAddress(CTxDestination(setaccountDemoPubkey.GetID())); + setaccountDemoAddress = CTxDestination(setaccountDemoPubkey.GetID()); } /********************************* * setaccount *********************************/ - BOOST_CHECK_NO_THROW(CallRPC("setaccount " + setaccountDemoAddress.ToString() + " nullaccount")); + BOOST_CHECK_NO_THROW(CallRPC("setaccount " + EncodeDestination(setaccountDemoAddress) + " nullaccount")); /* 1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ is not owned by the test wallet. */ BOOST_CHECK_THROW(CallRPC("setaccount 1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ nullaccount"), runtime_error); BOOST_CHECK_THROW(CallRPC("setaccount"), runtime_error); @@ -103,7 +103,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) * getbalance *********************************/ BOOST_CHECK_NO_THROW(CallRPC("getbalance")); - BOOST_CHECK_NO_THROW(CallRPC("getbalance " + demoAddress.ToString())); + BOOST_CHECK_NO_THROW(CallRPC("getbalance " + EncodeDestination(demoAddress))); /********************************* * listunspent @@ -145,10 +145,10 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) * listtransactions *********************************/ BOOST_CHECK_NO_THROW(CallRPC("listtransactions")); - BOOST_CHECK_NO_THROW(CallRPC("listtransactions " + demoAddress.ToString())); - BOOST_CHECK_NO_THROW(CallRPC("listtransactions " + demoAddress.ToString() + " 20")); - BOOST_CHECK_NO_THROW(CallRPC("listtransactions " + demoAddress.ToString() + " 20 0")); - BOOST_CHECK_THROW(CallRPC("listtransactions " + demoAddress.ToString() + " not_int"), runtime_error); + BOOST_CHECK_NO_THROW(CallRPC("listtransactions " + EncodeDestination(demoAddress))); + BOOST_CHECK_NO_THROW(CallRPC("listtransactions " + EncodeDestination(demoAddress) + " 20")); + BOOST_CHECK_NO_THROW(CallRPC("listtransactions " + EncodeDestination(demoAddress) + " 20 0")); + BOOST_CHECK_THROW(CallRPC("listtransactions " + EncodeDestination(demoAddress) + " not_int"), runtime_error); /********************************* * listlockunspent @@ -182,33 +182,33 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) BOOST_CHECK_NO_THROW(CallRPC("getaccountaddress \"\"")); BOOST_CHECK_NO_THROW(CallRPC("getaccountaddress accountThatDoesntExists")); // Should generate a new account BOOST_CHECK_NO_THROW(retValue = CallRPC("getaccountaddress " + strAccount)); - BOOST_CHECK(CBitcoinAddress(retValue.get_str()).Get() == demoAddress.Get()); + BOOST_CHECK(retValue.get_str() == EncodeDestination(demoAddress)); /********************************* * getaccount *********************************/ BOOST_CHECK_THROW(CallRPC("getaccount"), runtime_error); - BOOST_CHECK_NO_THROW(CallRPC("getaccount " + demoAddress.ToString())); + BOOST_CHECK_NO_THROW(CallRPC("getaccount " + EncodeDestination(demoAddress))); /********************************* * signmessage + verifymessage *********************************/ - BOOST_CHECK_NO_THROW(retValue = CallRPC("signmessage " + demoAddress.ToString() + " mymessage")); + BOOST_CHECK_NO_THROW(retValue = CallRPC("signmessage " + EncodeDestination(demoAddress) + " mymessage")); BOOST_CHECK_THROW(CallRPC("signmessage"), runtime_error); /* Should throw error because this address is not loaded in the wallet */ BOOST_CHECK_THROW(CallRPC("signmessage 1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ mymessage"), runtime_error); /* missing arguments */ - BOOST_CHECK_THROW(CallRPC("verifymessage " + demoAddress.ToString()), runtime_error); - BOOST_CHECK_THROW(CallRPC("verifymessage " + demoAddress.ToString() + " " + retValue.get_str()), runtime_error); + BOOST_CHECK_THROW(CallRPC("verifymessage " + EncodeDestination(demoAddress)), runtime_error); + BOOST_CHECK_THROW(CallRPC("verifymessage " + EncodeDestination(demoAddress) + " " + retValue.get_str()), runtime_error); /* Illegal address */ BOOST_CHECK_THROW(CallRPC("verifymessage 1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4X " + retValue.get_str() + " mymessage"), runtime_error); /* wrong address */ BOOST_CHECK(CallRPC("verifymessage 1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ " + retValue.get_str() + " mymessage").get_bool() == false); /* Correct address and signature but wrong message */ - BOOST_CHECK(CallRPC("verifymessage " + demoAddress.ToString() + " " + retValue.get_str() + " wrongmessage").get_bool() == false); + BOOST_CHECK(CallRPC("verifymessage " + EncodeDestination(demoAddress) + " " + retValue.get_str() + " wrongmessage").get_bool() == false); /* Correct address, message and signature*/ - BOOST_CHECK(CallRPC("verifymessage " + demoAddress.ToString() + " " + retValue.get_str() + " mymessage").get_bool() == true); + BOOST_CHECK(CallRPC("verifymessage " + EncodeDestination(demoAddress) + " " + retValue.get_str() + " mymessage").get_bool() == true); /********************************* * getaddressesbyaccount @@ -217,7 +217,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet) BOOST_CHECK_NO_THROW(retValue = CallRPC("getaddressesbyaccount " + strAccount)); UniValue arr = retValue.get_array(); BOOST_CHECK(arr.size() > 0); - BOOST_CHECK(CBitcoinAddress(arr[0].get_str()).Get() == demoAddress.Get()); + BOOST_CHECK(arr[0].get_str() == EncodeDestination(demoAddress)); /********************************* * fundrawtransaction diff --git a/src/wallet/test/walletdb_tests.cpp b/src/wallet/test/walletdb_tests.cpp new file mode 100644 index 000000000..c9da69bb4 --- /dev/null +++ b/src/wallet/test/walletdb_tests.cpp @@ -0,0 +1,140 @@ +// Copyright (c) 2017 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "test/test_bitcoin.h" +#include "random.h" +#include "fs.h" + +#include "wallet/wallet.h" +#include "wallet/walletdb.h" + +#include + +namespace { +struct WalletDBTestingSetup : public TestingSetup +{ + WalletDBTestingSetup(const std::string &chainName = CBaseChainParams::MAIN) + { + bitdb.MakeMock(); + } + + ~WalletDBTestingSetup() + { + bitdb.Flush(true); + bitdb.Reset(); + } +}; + +static std::unique_ptr TmpDB(const fs::path &pathTemp, const std::string &testname) +{ + fs::path dir = pathTemp / testname; + BOOST_CHECK_MESSAGE(fs::create_directory(dir), + "Unable to create a directory for test " + testname); + fs::path path = dir / strprintf("testwallet%i", static_cast(insecure_rand() % 1000000)); + return std::unique_ptr(new CWalletDB(path.string(), "cr+")); +} + +static std::unique_ptr LoadWallet(CWalletDB *db) { + std::unique_ptr wallet(new CWallet); + DBErrors res = db->LoadWallet(wallet.get()); + BOOST_CHECK(res == DB_LOAD_OK); + return wallet; +} +} + +BOOST_FIXTURE_TEST_SUITE(walletdb_tests, WalletDBTestingSetup); + +BOOST_AUTO_TEST_CASE(write_erase_name) { + auto walletdb = TmpDB(pathTemp, "write_erase_name"); + + CTxDestination dst1 = CKeyID(uint160S("c0ffee")); + CTxDestination dst2 = CKeyID(uint160S("f00d")); + + BOOST_CHECK(walletdb->WriteName(dst1, "name1")); + BOOST_CHECK(walletdb->WriteName(dst2, "name2")); + { + auto w = LoadWallet(walletdb.get()); + BOOST_CHECK_EQUAL(1, w->mapAddressBook.count(dst1)); + BOOST_CHECK_EQUAL("name1", w->mapAddressBook[dst1].name); + BOOST_CHECK_EQUAL("name2", w->mapAddressBook[dst2].name); + } + + walletdb->EraseName(dst1); + + { + auto w = LoadWallet(walletdb.get()); + BOOST_CHECK_EQUAL(0, w->mapAddressBook.count(dst1)); + BOOST_CHECK_EQUAL(1, w->mapAddressBook.count(dst2)); + } +} + +BOOST_AUTO_TEST_CASE(write_erase_purpose) { + auto walletdb = TmpDB(pathTemp, "write_erase_purpose"); + + CTxDestination dst1 = CKeyID(uint160S("c0ffee")); + CTxDestination dst2 = CKeyID(uint160S("f00d")); + + BOOST_CHECK(walletdb->WritePurpose(dst1, "purpose1")); + BOOST_CHECK(walletdb->WritePurpose(dst2, "purpose2")); + { + auto w = LoadWallet(walletdb.get()); + BOOST_CHECK_EQUAL(1, w->mapAddressBook.count(dst1)); + BOOST_CHECK_EQUAL("purpose1", w->mapAddressBook[dst1].purpose); + BOOST_CHECK_EQUAL("purpose2", w->mapAddressBook[dst2].purpose); + } + + walletdb->ErasePurpose(dst1); + + { + auto w = LoadWallet(walletdb.get()); + BOOST_CHECK_EQUAL(0, w->mapAddressBook.count(dst1)); + BOOST_CHECK_EQUAL(1, w->mapAddressBook.count(dst2)); + } +} + +BOOST_AUTO_TEST_CASE(write_erase_destdata) { + auto walletdb = TmpDB(pathTemp, "write_erase_destdata"); + + CTxDestination dst1 = CKeyID(uint160S("c0ffee")); + CTxDestination dst2 = CKeyID(uint160S("f00d")); + + BOOST_CHECK(walletdb->WriteDestData(dst1, "key1", "value1")); + BOOST_CHECK(walletdb->WriteDestData(dst1, "key2", "value2")); + BOOST_CHECK(walletdb->WriteDestData(dst2, "key1", "value3")); + BOOST_CHECK(walletdb->WriteDestData(dst2, "key2", "value4")); + { + auto w = LoadWallet(walletdb.get()); + std::string val; + BOOST_CHECK(w->GetDestData(dst1, "key1", &val)); + BOOST_CHECK_EQUAL("value1", val); + BOOST_CHECK(w->GetDestData(dst1, "key2", &val)); + BOOST_CHECK_EQUAL("value2", val); + BOOST_CHECK(w->GetDestData(dst2, "key1", &val)); + BOOST_CHECK_EQUAL("value3", val); + BOOST_CHECK(w->GetDestData(dst2, "key2", &val)); + BOOST_CHECK_EQUAL("value4", val); + } + + walletdb->EraseDestData(dst1, "key2"); + + { + auto w = LoadWallet(walletdb.get()); + std::string dummy; + BOOST_CHECK(w->GetDestData(dst1, "key1", &dummy)); + BOOST_CHECK(!w->GetDestData(dst1, "key2", &dummy)); + BOOST_CHECK(w->GetDestData(dst2, "key1", &dummy)); + BOOST_CHECK(w->GetDestData(dst2, "key2", &dummy)); + } +} + +BOOST_AUTO_TEST_CASE(no_dest_fails) { + auto walletdb = TmpDB(pathTemp, "no_dest_fails"); + + CTxDestination dst = CNoDestination{}; + BOOST_CHECK(!walletdb->WriteName(dst, "name")); + BOOST_CHECK(!walletdb->WritePurpose(dst, "purpose")); + BOOST_CHECK(!walletdb->WriteDestData(dst, "key", "value")); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 727562976..adce98601 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5,12 +5,12 @@ #include "wallet/wallet.h" -#include "base58.h" -#include "checkpoints.h" #include "chain.h" +#include "checkpoints.h" #include "coincontrol.h" #include "consensus/consensus.h" #include "consensus/validation.h" +#include "dstencode.h" #include "key.h" #include "keystore.h" #include "main.h" @@ -233,16 +233,19 @@ bool CWallet::AddCScript(const CScript& redeemScript) return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript); } -bool CWallet::LoadCScript(const CScript& redeemScript) -{ - /* A sanity check was added in pull #3843 to avoid adding redeemScripts - * that never can be redeemed. However, old wallets may still contain - * these. Do not add them to the wallet and warn. */ - if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) - { - std::string strAddr = CBitcoinAddress(CScriptID(redeemScript)).ToString(); - LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n", - __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); +bool CWallet::LoadCScript(const CScript &redeemScript) { + /** + * A sanity check was added in pull #3843 to avoid adding redeemScripts that + * never can be redeemed. However, old wallets may still contain these. Do + * not add them to the wallet and warn. + */ + if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) { + std::string strAddr = EncodeDestination(CScriptID(redeemScript)); + LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i " + "which exceeds maximum size %i thus can never be redeemed. " + "Do not use address %s.\n", + __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, + strAddr); return true; } @@ -1467,6 +1470,11 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const return 0; } +isminetype CWallet::IsMine(const CTxDestination &dest) const +{ + return ::IsMine(*this, dest); +} + isminetype CWallet::IsMine(const CTxOut& txout) const { return ::IsMine(*this, txout.scriptPubKey); @@ -3028,9 +3036,13 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const string& strNam strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) ); if (!fFileBacked) return false; - if (!strPurpose.empty() && !CWalletDB(strWalletFile).WritePurpose(CBitcoinAddress(address).ToString(), strPurpose)) + + if (!strPurpose.empty() && !CWalletDB(strWalletFile).WritePurpose(address, strPurpose)) + { return false; - return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName); + } + + return CWalletDB(strWalletFile).WriteName(address, strName); } bool CWallet::DelAddressBook(const CTxDestination& address) @@ -3038,13 +3050,11 @@ bool CWallet::DelAddressBook(const CTxDestination& address) { LOCK(cs_wallet); // mapAddressBook - if(fFileBacked) - { - // Delete destdata tuples associated with address - std::string strAddress = CBitcoinAddress(address).ToString(); - BOOST_FOREACH(const PAIRTYPE(string, string) &item, mapAddressBook[address].destdata) - { - CWalletDB(strWalletFile).EraseDestData(strAddress, item.first); + if (fFileBacked) { + // Delete destdata tuples associated with address. + for (const std::pair &item : + mapAddressBook[address].destdata) { + CWalletDB(strWalletFile).EraseDestData(address, item.first); } } mapAddressBook.erase(address); @@ -3054,8 +3064,9 @@ bool CWallet::DelAddressBook(const CTxDestination& address) if (!fFileBacked) return false; - CWalletDB(strWalletFile).ErasePurpose(CBitcoinAddress(address).ToString()); - return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString()); + + CWalletDB(strWalletFile).ErasePurpose(address); + return CWalletDB(strWalletFile).EraseName(address); } bool CWallet::SetDefaultKey(const CPubKey &vchPubKey) @@ -3645,7 +3656,8 @@ bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, co mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); if (!fFileBacked) return true; - return CWalletDB(strWalletFile).WriteDestData(CBitcoinAddress(dest).ToString(), key, value); + + return CWalletDB(strWalletFile).WriteDestData(dest, key, value); } bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key) @@ -3654,7 +3666,8 @@ bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key) return false; if (!fFileBacked) return true; - return CWalletDB(strWalletFile).EraseDestData(CBitcoinAddress(dest).ToString(), key); + + return CWalletDB(strWalletFile).EraseDestData(dest, key); } bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 9da3f39a7..31420d6d3 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -803,6 +803,7 @@ public: CAmount GetAccountBalance(CWalletDB& walletdb, const std::string& strAccount, int nMinDepth, const isminefilter& filter); std::set GetAccountAddresses(const std::string& strAccount) const; + isminetype IsMine(const CTxDestination &dest) const; isminetype IsMine(const CTxIn& txin) const; CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const; isminetype IsMine(const CTxOut& txout) const; diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index b2e963263..b3d20cafd 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -1,5 +1,7 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2015 The Bitcoin Core developers +// Copyright (c) 2015-2017 The Bitcoin Unlimited developers +// Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -8,6 +10,7 @@ #include "base58.h" #include "consensus/validation.h" #include "main.h" // For CheckTransaction +#include "dstencode.h" #include "protocol.h" #include "serialize.h" #include "sync.h" @@ -29,30 +32,42 @@ static uint64_t nAccountingEntryNumber = 0; // CWalletDB // -bool CWalletDB::WriteName(const string& strAddress, const string& strName) +bool CWalletDB::WriteName(const CTxDestination &address, const std::string &strName) { + if (!IsValidDestination(address)) + return false; + nWalletDBUpdated++; - return Write(make_pair(string("name"), strAddress), strName); + return Write(std::make_pair(std::string("name"), EncodeLegacyAddr(address, Params())), strName); } -bool CWalletDB::EraseName(const string& strAddress) +bool CWalletDB::EraseName(const CTxDestination &address) { - // This should only be used for sending addresses, never for receiving addresses, - // receiving addresses must always have an address book entry if they're not change return. + // This should only be used for sending addresses, never for receiving + // addresses, receiving addresses must always have an address book entry if + // they're not change return. + if (!IsValidDestination(address)) + return false; + nWalletDBUpdated++; - return Erase(make_pair(string("name"), strAddress)); + return Erase(std::make_pair(std::string("name"), EncodeLegacyAddr(address, Params()))); } -bool CWalletDB::WritePurpose(const string& strAddress, const string& strPurpose) -{ +bool CWalletDB::WritePurpose(const CTxDestination &address, const std::string &strPurpose) { + if (!IsValidDestination(address)) + return false; + nWalletDBUpdated++; - return Write(make_pair(string("purpose"), strAddress), strPurpose); + return Write(std::make_pair(std::string("purpose"), EncodeLegacyAddr(address, Params())), strPurpose); } -bool CWalletDB::ErasePurpose(const string& strPurpose) +bool CWalletDB::ErasePurpose(const CTxDestination &address) { + if (!IsValidDestination(address)) + return false; + nWalletDBUpdated++; - return Erase(make_pair(string("purpose"), strPurpose)); + return Erase(std::make_pair(std::string("purpose"), EncodeLegacyAddr(address, Params()))); } bool CWalletDB::WriteTx(const CWalletTx& wtx) @@ -359,16 +374,14 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { string strAddress; ssKey >> strAddress; - ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].name; - } - else if (strType == "purpose") - { - string strAddress; + ssValue >> + pwallet->mapAddressBook[DecodeDestination(strAddress)].name; + } else if (strType == "purpose") { + std::string strAddress; ssKey >> strAddress; - ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].purpose; - } - else if (strType == "tx") - { + ssValue >> + pwallet->mapAddressBook[DecodeDestination(strAddress)].purpose; + } else if (strType == "tx") { uint256 hash; ssKey >> hash; CWalletTx wtx; @@ -593,8 +606,8 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssKey >> strAddress; ssKey >> strKey; ssValue >> strValue; - if (!pwallet->LoadDestData(CBitcoinAddress(strAddress).Get(), strKey, strValue)) - { + if (!pwallet->LoadDestData(DecodeDestination(strAddress), strKey, + strValue)) { strErr = "Error reading wallet database: LoadDestData failed"; return false; } @@ -1002,10 +1015,13 @@ bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename) return CWalletDB::Recover(dbenv, filename, false); } -bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value) +bool CWalletDB::WriteDestData(const CTxDestination &address, const std::string &key, const std::string &value) { + if (!IsValidDestination(address)) + return false; + nWalletDBUpdated++; - return Write(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value); + return Write(std::make_pair(std::string("destdata"), std::make_pair(EncodeLegacyAddr(address, Params()), key)), value); } bool CWalletDB::WriteHDChain(const CHDChain& chain) @@ -1014,8 +1030,11 @@ bool CWalletDB::WriteHDChain(const CHDChain& chain) return Write(std::string("hdchain"), chain); } -bool CWalletDB::EraseDestData(const std::string &address, const std::string &key) +bool CWalletDB::EraseDestData(const CTxDestination &address, const std::string &key) { + if (!IsValidDestination(address)) + return false; + nWalletDBUpdated++; - return Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key))); + return Erase(std::make_pair(std::string("destdata"), std::make_pair(EncodeLegacyAddr(address, Params()), key))); } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index d608ddc78..5ebcb05a9 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -1,5 +1,7 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2015 The Bitcoin Core developers +// Copyright (c) 2015-2017 The Bitcoin Unlimited developers +// Copyright (c) 2017 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -7,6 +9,9 @@ #define BITCOIN_WALLET_WALLETDB_H #include "amount.h" +#include "key.h" +#include "primitives/transaction.h" +#include "script/standard.h" // for CTxDestination #include "wallet/db.h" #include "key.h" @@ -121,11 +126,11 @@ public: { } - bool WriteName(const std::string& strAddress, const std::string& strName); - bool EraseName(const std::string& strAddress); + bool WriteName(const CTxDestination &address, const std::string &strName); + bool EraseName(const CTxDestination &address); - bool WritePurpose(const std::string& strAddress, const std::string& purpose); - bool ErasePurpose(const std::string& strAddress); + bool WritePurpose(const CTxDestination &address, const std::string &purpose); + bool ErasePurpose(const CTxDestination &address); bool WriteTx(const CWalletTx& wtx); bool EraseTx(uint256 hash); @@ -154,15 +159,16 @@ public: /// This writes directly to the database, and will not update the CWallet's cached accounting entries! /// Use wallet.AddAccountingEntry instead, to write *and* update its caches. - bool WriteAccountingEntry_Backend(const CAccountingEntry& acentry); - bool ReadAccount(const std::string& strAccount, CAccount& account); - bool WriteAccount(const std::string& strAccount, const CAccount& account); + bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry &acentry); + bool WriteAccountingEntry_Backend(const CAccountingEntry &acentry); + bool ReadAccount(const std::string &strAccount, CAccount &account); + bool WriteAccount(const std::string &strAccount, const CAccount &account); /// Write destination data key,value tuple to database - bool WriteDestData(const std::string &address, const std::string &key, const std::string &value); - bool WriteHDChain(const CHDChain& chain); + bool WriteDestData(const CTxDestination &address, const std::string &key, const std::string &value); /// Erase destination data tuple from wallet database - bool EraseDestData(const std::string &address, const std::string &key); + bool EraseDestData(const CTxDestination &address, const std::string &key); + bool WriteHDChain(const CHDChain& chain); CAmount GetAccountCreditDebit(const std::string& strAccount); void ListAccountCreditDebit(const std::string& strAccount, std::list& acentries); @@ -177,8 +183,6 @@ public: private: CWalletDB(const CWalletDB&); void operator=(const CWalletDB&); - - bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry); }; void ThreadFlushWalletDB(const std::string& strFile);