From 323a6750c2bfb36cbbd2f7db5e1bd4e849b8523c Mon Sep 17 00:00:00 2001 From: lateminer Date: Sun, 14 Jan 2018 22:32:08 +0300 Subject: [PATCH] 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);