diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -6,6 +6,7 @@ TESTS += qt/test/test_bitcoin-qt TEST_QT_MOC_CPP = \ + qt/test/moc_bitcoinaddressvalidatortests.cpp \ qt/test/moc_compattests.cpp \ qt/test/moc_guiutiltests.cpp \ qt/test/moc_rpcnestedtests.cpp \ @@ -16,6 +17,7 @@ endif TEST_QT_H = \ + qt/test/bitcoinaddressvalidatortests.h \ qt/test/compattests.h \ qt/test/guiutiltests.h \ qt/test/rpcnestedtests.h \ @@ -27,6 +29,7 @@ $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(PROTOBUF_CFLAGS) qt_test_test_bitcoin_qt_SOURCES = \ + qt/test/bitcoinaddressvalidatortests.cpp \ qt/test/compattests.cpp \ qt/test/guiutiltests.cpp \ qt/test/rpcnestedtests.cpp \ diff --git a/src/cashaddr.h b/src/cashaddr.h --- a/src/cashaddr.h +++ b/src/cashaddr.h @@ -22,4 +22,6 @@ */ std::pair> Decode(const std::string &str); +std::vector EncodingCharset(); + } // namespace cashaddr diff --git a/src/cashaddr.cpp b/src/cashaddr.cpp --- a/src/cashaddr.cpp +++ b/src/cashaddr.cpp @@ -292,4 +292,9 @@ return {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/qt/bitcoinaddressvalidator.h b/src/qt/bitcoinaddressvalidator.h --- a/src/qt/bitcoinaddressvalidator.h +++ b/src/qt/bitcoinaddressvalidator.h @@ -1,4 +1,5 @@ // Copyright (c) 2011-2014 The Bitcoin Core 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,16 +8,21 @@ #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 { 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/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp --- a/src/qt/bitcoinaddressvalidator.cpp +++ b/src/qt/bitcoinaddressvalidator.cpp @@ -1,9 +1,11 @@ // Copyright (c) 2011-2014 The Bitcoin Core 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 "cashaddr.h" #include "dstencode.h" /* Base58 characters are: @@ -14,9 +16,44 @@ - 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) {} QValidator::State BitcoinAddressEntryValidator::validate(QString &input, int &pos) const { @@ -53,20 +90,10 @@ } // 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) diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -144,14 +144,16 @@ 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(), GlobalConfig())))); + 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)); } diff --git a/src/qt/test/bitcoinaddressvalidatortests.h b/src/qt/test/bitcoinaddressvalidatortests.h new file mode 100644 --- /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/bitcoinaddressvalidatortests.cpp b/src/qt/test/bitcoinaddressvalidatortests.cpp new file mode 100644 --- /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/test_main.cpp b/src/qt/test/test_main.cpp --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -6,6 +6,7 @@ #include "config/bitcoin-config.h" #endif +#include "bitcoinaddressvalidatortests.h" #include "chainparams.h" #include "compattests.h" #include "guiutiltests.h" @@ -63,6 +64,8 @@ if (QTest::qExec(&test4) != 0) fInvalid = true; GUIUtilTests test5; if (QTest::qExec(&test5) != 0) fInvalid = true; + BitcoinAddressValidatorTests test6; + if (QTest::qExec(&test6) != 0) fInvalid = true; ECC_Stop(); return fInvalid;