20 #include <openssl/x509_vfy.h>
22 #include <QApplication>
24 #include <QDataStream>
28 #include <QFileOpenEvent>
31 #include <QLocalServer>
32 #include <QLocalSocket>
33 #include <QNetworkAccessManager>
34 #include <QNetworkProxy>
35 #include <QNetworkReply>
36 #include <QNetworkRequest>
37 #include <QSslCertificate>
38 #include <QSslConfiguration>
40 #include <QStringList>
41 #include <QTextDocument>
50 const char *BIP70_MESSAGE_PAYMENTACK =
"PaymentACK";
51 const char *BIP70_MESSAGE_PAYMENTREQUEST =
"PaymentRequest";
53 const char *BIP71_MIMETYPE_PAYMENT =
"application/ecash-payment";
54 const char *BIP71_MIMETYPE_PAYMENTACK =
"application/ecash-paymentack";
55 const char *BIP71_MIMETYPE_PAYMENTREQUEST =
"application/ecash-paymentrequest";
64 QString
name(
"BitcoinQt");
70 name.append(QString::number(qHash(ddir)));
83 const QString scheme = QString::fromStdString(params.
CashAddrPrefix());
84 if (!arg.startsWith(scheme +
":", Qt::CaseInsensitive)) {
97 const std::string &network) {
99 std::string addr =
ipcParseURI(arg, *tempChainParams,
true);
104 const std::string &network) {
106 std::string addr =
ipcParseURI(arg, *tempChainParams,
false);
119 std::array<const std::string *, 3> networks = {
123 const std::string *chosenNetwork =
nullptr;
125 for (
int i = 1; i < argc; i++) {
126 QString arg(argv[i]);
127 if (arg.startsWith(
"-")) {
131 const std::string *itemNetwork =
nullptr;
134 for (
auto net : networks) {
150 if (readPaymentRequestFromFile(arg, request)) {
151 for (
auto net : networks) {
160 if (itemNetwork ==
nullptr) {
163 qWarning() <<
"PaymentServer::ipcSendCommandLine: Payment request "
164 "file or URI does not exist or is invalid: "
170 if (chosenNetwork && chosenNetwork != itemNetwork) {
171 qWarning() <<
"PaymentServer::ipcSendCommandLine: Payment request "
173 << QString(itemNetwork->c_str())
174 <<
" does not match already chosen network "
175 << QString(chosenNetwork->c_str());
184 chosenNetwork = itemNetwork;
198 bool fResult =
false;
200 QLocalSocket *socket =
new QLocalSocket();
201 socket->connectToServer(
ipcServerName(), QIODevice::WriteOnly);
209 QDataStream out(&block, QIODevice::WriteOnly);
210 out.setVersion(QDataStream::Qt_4_0);
212 out.device()->seek(0);
214 socket->write(block);
217 socket->disconnectFromServer();
228 : QObject(parent), saveURIs(true), uriServer(nullptr), optionsModel(nullptr)
238 GOOGLE_PROTOBUF_VERIFY_VERSION;
245 parent->installEventFilter(
this);
251 QLocalServer::removeServer(
name);
253 if (startLocalServer) {
259 QMessageBox::critical(
nullptr, tr(
"Payment request error"),
260 tr(
"Cannot start click-to-pay handler"));
262 connect(
uriServer, &QLocalServer::newConnection,
this,
265 connect(
this, &PaymentServer::receivedPaymentACK,
this,
266 &PaymentServer::handlePaymentACK);
274 google::protobuf::ShutdownProtobufLibrary();
284 if (event->type() == QEvent::FileOpen) {
285 QFileOpenEvent *fileEvent =
static_cast<QFileOpenEvent *
>(event);
286 if (!fileEvent->file().isEmpty()) {
288 }
else if (!fileEvent->url().isEmpty()) {
295 return QObject::eventFilter(
object, event);
311 const QString scheme = QString::fromStdString(params.
CashAddrPrefix());
312 if (!s.startsWith(scheme +
":", Qt::CaseInsensitive)) {
316 QUrlQuery uri((QUrl(s)));
319 if (uri.hasQueryItem(
"r")) {
321 temp.append(uri.queryItemValue(
"r").toUtf8());
322 QString decoded = QUrl::fromPercentEncoding(temp);
323 QUrl fetchUrl(decoded, QUrl::StrictMode);
325 if (fetchUrl.isValid()) {
326 qDebug() <<
"PaymentServer::handleURIOrFile: fetchRequest("
328 fetchRequest(fetchUrl);
330 qWarning() <<
"PaymentServer::handleURIOrFile: Invalid URL: "
332 Q_EMIT
message(tr(
"URI handling"),
333 tr(
"Payment request fetch URL is invalid: %1")
334 .arg(fetchUrl.toString()),
348 if (uri.hasQueryItem(
"r")) {
349 Q_EMIT
message(tr(
"URI handling"),
350 tr(
"Cannot process payment request because "
351 "BIP70 support was not compiled in."),
357 tr(
"Invalid payment address %1").arg(recipient.
address),
365 tr(
"URI cannot be parsed! This can be caused by an invalid "
366 "Bitcoin address or malformed URI parameters."),
389 if (!readPaymentRequestFromFile(s, request)) {
390 Q_EMIT
message(tr(
"Payment request file handling"),
391 tr(
"Payment request file cannot be read! This can "
392 "be caused by an invalid payment request file."),
394 }
else if (processPaymentRequest(request, recipient)) {
400 Q_EMIT
message(tr(
"Payment request file handling"),
401 tr(
"Cannot process payment request because BIP70 "
402 "support was not compiled in."),
409 QLocalSocket *clientConnection =
uriServer->nextPendingConnection();
411 while (clientConnection->bytesAvailable() < (
int)
sizeof(quint32)) {
412 clientConnection->waitForReadyRead();
415 connect(clientConnection, &QLocalSocket::disconnected, clientConnection,
416 &QLocalSocket::deleteLater);
418 QDataStream in(clientConnection);
419 in.setVersion(QDataStream::Qt_4_0);
420 if (clientConnection->bytesAvailable() < (
int)
sizeof(quint16)) {
434 struct X509StoreDeleter {
435 void operator()(X509_STORE *b) { X509_STORE_free(b); }
439 void operator()(X509 *b) { X509_free(b); }
444 std::unique_ptr<X509_STORE, X509StoreDeleter> certStore;
447 static void ReportInvalidCertificate(
const QSslCertificate &cert) {
448 qDebug() << QString(
"%1: Payment server found an invalid certificate: ")
450 << cert.serialNumber()
451 << cert.subjectInfo(QSslCertificate::CommonName)
452 << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier)
453 << cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
459 void PaymentServer::LoadRootCAs(X509_STORE *_store) {
462 certStore.reset(_store);
467 certStore.reset(X509_STORE_new());
473 QString::fromStdString(
gArgs.
GetArg(
"-rootcertificates",
"-system-"));
476 if (certFile.isEmpty()) {
477 qDebug() << QString(
"PaymentServer::%1: Payment request authentication "
478 "via X.509 certificates disabled.")
483 QList<QSslCertificate> certList;
485 if (certFile !=
"-system-") {
486 qDebug() << QString(
"PaymentServer::%1: Using \"%2\" as trusted root "
491 certList = QSslCertificate::fromPath(certFile);
493 QSslConfiguration::defaultConfiguration().setCaCertificates(certList);
495 certList = QSslConfiguration::systemCaCertificates();
499 const QDateTime currentTime = QDateTime::currentDateTime();
501 for (
const QSslCertificate &cert : certList) {
508 if (currentTime < cert.effectiveDate() ||
509 currentTime > cert.expiryDate()) {
510 ReportInvalidCertificate(cert);
515 if (cert.isBlacklisted()) {
516 ReportInvalidCertificate(cert);
520 QByteArray certData = cert.toDer();
521 const uint8_t *data = (
const uint8_t *)certData.data();
523 std::unique_ptr<X509, X509Deleter> x509(
524 d2i_X509(0, &data, certData.size()));
525 if (x509 && X509_STORE_add_cert(certStore.get(), x509.get())) {
530 ReportInvalidCertificate(cert);
534 qInfo() <<
"PaymentServer::LoadRootCAs: Loaded " << nRootCerts
535 <<
" root certificates";
547 void PaymentServer::initNetManager() {
551 if (netManager !=
nullptr) {
556 netManager =
new QNetworkAccessManager(
this);
562 netManager->setProxy(proxy);
564 qDebug() <<
"PaymentServer::initNetManager: Using SOCKS5 proxy"
565 << proxy.hostName() <<
":" << proxy.port();
568 <<
"PaymentServer::initNetManager: No active proxy server found.";
571 connect(netManager, &QNetworkAccessManager::finished,
this,
572 &PaymentServer::netRequestFinished);
573 connect(netManager, &QNetworkAccessManager::sslErrors,
this,
574 &PaymentServer::reportSslErrors);
581 bool PaymentServer::readPaymentRequestFromFile(
const QString &filename,
584 if (!f.open(QIODevice::ReadOnly)) {
585 qWarning() << QString(
"PaymentServer::%1: Failed to open %2")
592 if (!verifySize(f.size())) {
596 QByteArray data = f.readAll();
598 return request.
parse(data);
611 tr(
"Payment request rejected"),
612 tr(
"Payment request network doesn't match client network."),
622 Q_EMIT
message(tr(
"Payment request rejected"),
623 tr(
"Payment request expired."),
629 Q_EMIT
message(tr(
"Payment request error"),
630 tr(
"Payment request is not initialized."),
636 recipient.paymentRequest = request;
641 QList<std::pair<CScript, Amount>> sendingTos = request.
getPayTo();
642 QStringList addresses;
644 for (
const std::pair<CScript, Amount> &sendingTo : sendingTos) {
655 Q_EMIT
message(tr(
"Payment request rejected"),
656 tr(
"Unverified payment requests to custom payment "
657 "scripts are unsupported."),
666 if (!verifyAmount(sendingTo.second)) {
667 Q_EMIT
message(tr(
"Payment request rejected"),
668 tr(
"Invalid payment request."),
674 CTxOut txOut(
Amount(sendingTo.second), sendingTo.first);
677 tr(
"Payment request error"),
678 tr(
"Requested payment amount of %1 is too small (considered "
687 recipient.
amount += sendingTo.second;
690 if (!verifyAmount(recipient.
amount)) {
691 Q_EMIT
message(tr(
"Payment request rejected"),
692 tr(
"Invalid payment request."),
698 recipient.
address = addresses.join(
"<br />");
701 qDebug() <<
"PaymentServer::processPaymentRequest: Secure payment "
705 qDebug() <<
"PaymentServer::processPaymentRequest: Insecure payment "
707 << addresses.join(
", ");
713 void PaymentServer::fetchRequest(
const QUrl &
url) {
714 QNetworkRequest netRequest;
715 netRequest.setAttribute(QNetworkRequest::User,
716 BIP70_MESSAGE_PAYMENTREQUEST);
717 netRequest.setUrl(
url);
718 netRequest.setRawHeader(
"User-Agent",
CLIENT_NAME.c_str());
719 netRequest.setRawHeader(
"Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
720 netManager->get(netRequest);
725 QByteArray transaction) {
726 const payments::PaymentDetails &details =
727 recipient.paymentRequest.getDetails();
728 if (!details.has_payment_url()) {
732 QNetworkRequest netRequest;
733 netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
734 netRequest.setUrl(QString::fromStdString(details.payment_url()));
735 netRequest.setHeader(QNetworkRequest::ContentTypeHeader,
736 BIP71_MIMETYPE_PAYMENT);
737 netRequest.setRawHeader(
"User-Agent",
CLIENT_NAME.c_str());
738 netRequest.setRawHeader(
"Accept", BIP71_MIMETYPE_PAYMENTACK);
740 payments::Payment payment;
741 payment.set_merchant_data(details.merchant_data());
742 payment.add_transactions(transaction.data(), transaction.size());
753 std::string label = tr(
"Refund from %1")
756 wallet.setAddressBook(dest, label,
"refund");
759 payments::Output *refund_to = payment.add_refund_to();
760 refund_to->set_script(&s[0], s.
size());
764 qWarning() <<
"PaymentServer::fetchPaymentACK: Error getting refund "
765 "key, refund_to not set";
769 #ifdef USE_PROTOBUF_MESSAGE_BYTESIZELONG
770 length.setValue(payment.ByteSizeLong());
772 length.setValue(payment.ByteSize());
775 netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
776 QByteArray serData(length.toInt(),
'\0');
777 if (payment.SerializeToArray(serData.data(), length.toInt())) {
778 netManager->post(netRequest, serData);
781 qWarning() <<
"PaymentServer::fetchPaymentACK: Error serializing "
786 void PaymentServer::netRequestFinished(QNetworkReply *reply) {
787 reply->deleteLater();
790 if (!verifySize(reply->size())) {
792 tr(
"Payment request rejected"),
793 tr(
"Payment request %1 is too large (%2 bytes, allowed %3 bytes).")
794 .arg(reply->request().url().toString())
801 if (reply->error() != QNetworkReply::NoError) {
802 QString msg = tr(
"Error communicating with %1: %2")
803 .arg(reply->request().url().toString())
804 .arg(reply->errorString());
806 qWarning() <<
"PaymentServer::netRequestFinished: " << msg;
807 Q_EMIT
message(tr(
"Payment request error"), msg,
812 QByteArray data = reply->readAll();
814 QString requestType =
815 reply->request().attribute(QNetworkRequest::User).toString();
816 if (requestType == BIP70_MESSAGE_PAYMENTREQUEST) {
819 if (!request.
parse(data)) {
820 qWarning() <<
"PaymentServer::netRequestFinished: Error parsing "
822 Q_EMIT
message(tr(
"Payment request error"),
823 tr(
"Payment request cannot be parsed!"),
825 }
else if (processPaymentRequest(request, recipient)) {
830 }
else if (requestType == BIP70_MESSAGE_PAYMENTACK) {
831 payments::PaymentACK paymentACK;
832 if (!paymentACK.ParseFromArray(data.data(), data.size())) {
833 QString msg = tr(
"Bad response from server %1")
834 .arg(reply->request().url().toString());
836 qWarning() <<
"PaymentServer::netRequestFinished: " << msg;
837 Q_EMIT
message(tr(
"Payment request error"), msg,
845 void PaymentServer::reportSslErrors(QNetworkReply *reply,
846 const QList<QSslError> &errs) {
850 for (
const QSslError &err : errs) {
851 qWarning() <<
"PaymentServer::reportSslErrors: " << err;
852 errString += err.errorString() +
"\n";
854 Q_EMIT
message(tr(
"Network request error"), errString,
858 void PaymentServer::handlePaymentACK(
const QString &paymentACKMsg) {
860 Q_EMIT
message(tr(
"Payment acknowledged"), paymentACKMsg,
865 bool PaymentServer::verifyNetwork(
867 const std::string clientNetwork =
869 bool fVerified = requestDetails.network() == clientNetwork;
871 qWarning() << QString(
"PaymentServer::%1: Payment request network "
872 "\"%2\" doesn't match client network \"%3\".")
874 .arg(QString::fromStdString(requestDetails.network()))
875 .arg(QString::fromStdString(clientNetwork));
880 bool PaymentServer::verifyExpired(
881 const payments::PaymentDetails &requestDetails) {
882 bool fVerified = (requestDetails.has_expires() &&
883 (int64_t)requestDetails.expires() <
GetTime());
885 const QString requestExpires = QString::fromStdString(
887 qWarning() << QString(
888 "PaymentServer::%1: Payment request expired \"%2\".")
890 .arg(requestExpires);
895 bool PaymentServer::verifySize(qint64 requestSize) {
898 qWarning() << QString(
"PaymentServer::%1: Payment request too large "
899 "(%2 bytes, allowed %3 bytes).")
907 bool PaymentServer::verifyAmount(
const Amount requestAmount) {
910 qWarning() << QString(
"PaymentServer::%1: Payment request amount out "
911 "of allowed range (%2, allowed 0 - %3).")
919 X509_STORE *PaymentServer::getCertStore() {
920 return certStore.get();
bool MoneyRange(const Amount nValue)
static constexpr Amount SATOSHI
static constexpr Amount MAX_MONEY
No amount larger than this (in satoshi) is valid.
std::string EncodeCashAddr(const CTxDestination &dst, const CChainParams ¶ms)
void SelectParams(const std::string &network)
Sets the params returned by Params() to those for the given BIP70 chain name.
const CChainParams & Params()
Return the currently selected parameters.
std::unique_ptr< CChainParams > CreateChainParams(const std::string &chain)
Creates and returns a std::unique_ptr<CChainParams> of the chosen chain.
const fs::path & GetDataDirNet() const
Get data directory path with appended network identifier.
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
static QString formatWithUnit(int unit, const Amount amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
static const std::string REGTEST
static const std::string TESTNET
static const std::string MAIN
BIP70 chain name strings (main, test or regtest)
CChainParams defines various tweakable parameters of a given instance of the Bitcoin system.
std::string NetworkIDString() const
Return the BIP70 network string (main, test or regtest)
const std::string & CashAddrPrefix() const
@ MODAL
Force blocking, modal message box dialog (not just OS notification)
Serialized script, used inside transaction inputs and outputs.
An output of a transaction.
virtual const CChainParams & GetChainParams() const =0
Interface from Qt to configuration data structure for Bitcoin client.
int getDisplayUnit() const
bool getProxySettings(QNetworkProxy &proxy) const
interfaces::Node & node() const
QList< std::pair< CScript, Amount > > getPayTo() const
bool getMerchant(X509_STORE *certStore, QString &merchant) const
bool IsInitialized() const
bool parse(const QByteArray &data)
const payments::PaymentDetails & getDetails() const
static bool ipcSendCommandLine()
void setOptionsModel(OptionsModel *optionsModel)
PaymentServer(QObject *parent, bool startLocalServer=true)
void message(const QString &title, const QString &message, unsigned int style)
void handleURIConnection()
static void ipcParseCommandLine(int argc, char *argv[])
void receivedPaymentRequest(SendCoinsRecipient)
bool eventFilter(QObject *object, QEvent *event) override
void handleURIOrFile(const QString &s)
bool handleURI(const CChainParams ¶ms, const QString &s)
OptionsModel * optionsModel
QString authenticatedMerchant
Top-level interface for a bitcoin node (bitcoind process).
virtual CFeeRate getDustRelayFee()=0
Get dust relay fee.
Interface for accessing a wallet.
const std::string CLIENT_NAME
const Config & GetConfig()
bool IsValidDestinationString(const std::string &str, const CChainParams ¶ms)
bool parseBitcoinURI(const QString &scheme, const QUrl &uri, SendCoinsRecipient *out)
QString HtmlEscape(const QString &str, bool fMultiLine)
QString boostPathToQString(const fs::path &path)
Convert OS specific boost path to QString through UTF-8.
static bool exists(const path &p)
static QString ipcServerName()
static bool ipcCanParseLegacyURI(const QString &arg, const std::string &network)
const int BITCOIN_IPC_CONNECT_TIMEOUT
static std::string ipcParseURI(const QString &arg, const CChainParams ¶ms, bool useCashAddr)
static bool ipcCanParseCashAddrURI(const QString &arg, const std::string &network)
static QSet< QString > savedPaymentRequests
static QT_END_NAMESPACE const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE
bool IsDust(const CTxOut &txout, const CFeeRate &dustRelayFeeIn)
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet)
Parse a standard scriptPubKey for the destination address.
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
boost::variant< CNoDestination, PKHash, ScriptHash > CTxDestination
A txout script template with a specific destination.
T GetTime()
Return system time (or mocked time, if set)
std::string FormatISO8601DateTime(int64_t nTime)
ISO 8601 formatting is preferred.