Bitcoin ABC 0.32.4
P2P Digital Currency
paymentserver.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2016 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <qt/paymentserver.h>
6
7#include <cashaddrenc.h>
8#include <chainparams.h>
9#include <common/args.h>
10#include <config.h>
11#include <interfaces/node.h>
12#include <key_io.h>
13#include <node/ui_interface.h>
14#include <policy/policy.h>
15#include <qt/bitcoinunits.h>
16#include <qt/guiutil.h>
17#include <qt/optionsmodel.h>
18#include <util/chaintype.h>
19#include <wallet/wallet.h>
20
21#ifdef ENABLE_BIP70
22#include <openssl/x509_vfy.h>
23#endif
24
25#include <QApplication>
26#include <QByteArray>
27#include <QDataStream>
28#include <QDateTime>
29#include <QDebug>
30#include <QFile>
31#include <QFileOpenEvent>
32#include <QHash>
33#include <QList>
34#include <QLocalServer>
35#include <QLocalSocket>
36#ifdef ENABLE_BIP70
37#include <QNetworkAccessManager>
38#include <QNetworkProxy>
39#include <QNetworkReply>
40#include <QNetworkRequest>
41#include <QSslCertificate>
42#include <QSslConfiguration>
43#include <QSslError>
44#endif
45#include <QStringList>
46#include <QTextDocument>
47#include <QUrlQuery>
48
49#include <cstdlib>
50#include <memory>
51
52const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
53#ifdef ENABLE_BIP70
54// BIP70 payment protocol messages
55const char *BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
56const char *BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest";
57// BIP71 payment protocol media types
58const char *BIP71_MIMETYPE_PAYMENT = "application/ecash-payment";
59const char *BIP71_MIMETYPE_PAYMENTACK = "application/ecash-paymentack";
60const char *BIP71_MIMETYPE_PAYMENTREQUEST = "application/ecash-paymentrequest";
61#endif
62
63//
64// Create a name that is unique for:
65// testnet / non-testnet
66// data directory
67//
68static QString ipcServerName() {
69 QString name("BitcoinQt");
70
71 // Append a simple hash of the datadir
72 // Note that gArgs.GetDataDirNet() returns a different path for -testnet
73 // versus main net
75 name.append(QString::number(qHash(ddir)));
76
77 return name;
78}
79
80//
81// We store payment URIs and requests received before the main GUI window is up
82// and ready to ask the user to send payment.
83//
84static QSet<QString> savedPaymentRequests;
85
86static std::string ipcParseURI(const QString &arg, const CChainParams &params,
87 bool useCashAddr) {
88 const QString scheme = QString::fromStdString(params.CashAddrPrefix());
89 if (!arg.startsWith(scheme + ":", Qt::CaseInsensitive)) {
90 return {};
91 }
92
94 if (!GUIUtil::parseBitcoinURI(scheme, arg, &r)) {
95 return {};
96 }
97
98 return r.address.toStdString();
99}
100
101static bool ipcCanParseCashAddrURI(const QString &arg,
102 const ChainType chain_type) {
103 auto tempChainParams = CreateChainParams(ArgsManager{}, chain_type);
104 std::string addr = ipcParseURI(arg, *tempChainParams, true);
105 return IsValidDestinationString(addr, *tempChainParams);
106}
107
108static bool ipcCanParseLegacyURI(const QString &arg,
109 const ChainType chain_type) {
110 auto tempChainParams = CreateChainParams(ArgsManager{}, chain_type);
111 std::string addr = ipcParseURI(arg, *tempChainParams, false);
112 return IsValidDestinationString(addr, *tempChainParams);
113}
114
115//
116// Sending to the server is done synchronously, at startup.
117// If the server isn't already running, startup continues, and the items in
118// savedPaymentRequest will be handled when uiReady() is called.
119//
120// Warning: ipcSendCommandLine() is called early in init, so don't use "Q_EMIT
121// message()", but "QMessageBox::"!
122//
123void PaymentServer::ipcParseCommandLine(int argc, char *argv[]) {
124 std::array<const ChainType, 3> networks = {
126
127 const ChainType *chosenNetwork = nullptr;
128
129 for (int i = 1; i < argc; i++) {
130 QString arg(argv[i]);
131 if (arg.startsWith("-")) {
132 continue;
133 }
134
135 const ChainType *itemNetwork = nullptr;
136
137 // Try to parse as a URI
138 for (auto net : networks) {
139 if (ipcCanParseCashAddrURI(arg, net)) {
140 itemNetwork = &net;
141 break;
142 }
143
144 if (ipcCanParseLegacyURI(arg, net)) {
145 itemNetwork = &net;
146 break;
147 }
148 }
149
150#ifdef ENABLE_BIP70
151 if (!itemNetwork && QFile::exists(arg)) {
152 // Filename
153 PaymentRequestPlus request;
154 if (readPaymentRequestFromFile(arg, request)) {
155 for (auto net : networks) {
156 if (ChainTypeToString(net) ==
157 request.getDetails().network()) {
158 itemNetwork = &net;
159 }
160 }
161 }
162 }
163#endif
164
165 if (itemNetwork == nullptr) {
166 // Printing to debug.log is about the best we can do here, the GUI
167 // hasn't started yet so we can't pop up a message box.
168 qWarning() << "PaymentServer::ipcSendCommandLine: Payment request "
169 "file or URI does not exist or is invalid: "
170 << arg;
171 continue;
172 }
173
174#ifdef ENABLE_BIP70
175 if (chosenNetwork && chosenNetwork != itemNetwork) {
176 qWarning() << "PaymentServer::ipcSendCommandLine: Payment request "
177 "from network "
178 << QString(ChainTypeToString(*itemNetwork).c_str())
179 << " does not match already chosen network "
180 << QString(ChainTypeToString(*chosenNetwork).c_str());
181 continue;
182 }
183
184 if (savedPaymentRequests.contains(arg)) {
185 continue;
186 }
187 savedPaymentRequests.insert(arg);
188#endif
189 chosenNetwork = itemNetwork;
190 }
191
192 if (chosenNetwork) {
193 SelectParams(*chosenNetwork);
194 }
195}
196
197//
198// Sending to the server is done synchronously, at startup.
199// If the server isn't already running, startup continues, and the items in
200// savedPaymentRequest will be handled when uiReady() is called.
201//
203 bool fResult = false;
204 for (const QString &r : savedPaymentRequests) {
205 QLocalSocket *socket = new QLocalSocket();
206 socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
207 if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT)) {
208 delete socket;
209 socket = nullptr;
210 return false;
211 }
212
213 QByteArray block;
214 QDataStream out(&block, QIODevice::WriteOnly);
215 out.setVersion(QDataStream::Qt_4_0);
216 out << r;
217 out.device()->seek(0);
218
219 socket->write(block);
220 socket->flush();
221 socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
222 socket->disconnectFromServer();
223
224 delete socket;
225 socket = nullptr;
226 fResult = true;
227 }
228
229 return fResult;
230}
231
232PaymentServer::PaymentServer(QObject *parent, bool startLocalServer)
233 : QObject(parent), saveURIs(true), uriServer(nullptr), optionsModel(nullptr)
234// clang-format off
235#ifdef ENABLE_BIP70
236 ,netManager(nullptr)
237#endif
238// clang-format on
239{
240#ifdef ENABLE_BIP70
241 // Verify that the version of the library that we linked against is
242 // compatible with the version of the headers we compiled against.
243 GOOGLE_PROTOBUF_VERIFY_VERSION;
244#endif
245
246 // Install global event filter to catch QFileOpenEvents
247 // on Mac: sent when you click bitcoincash: links
248 // other OSes: helpful when dealing with payment request files
249 if (parent) {
250 parent->installEventFilter(this);
251 }
252
253 QString name = ipcServerName();
254
255 // Clean up old socket leftover from a crash:
256 QLocalServer::removeServer(name);
257
258 if (startLocalServer) {
259 uriServer = new QLocalServer(this);
260
261 if (!uriServer->listen(name)) {
262 // constructor is called early in init, so don't use "Q_EMIT
263 // message()" here
264 QMessageBox::critical(nullptr, tr("Payment request error"),
265 tr("Cannot start click-to-pay handler"));
266 } else {
267 connect(uriServer, &QLocalServer::newConnection, this,
269#ifdef ENABLE_BIP70
270 connect(this, &PaymentServer::receivedPaymentACK, this,
271 &PaymentServer::handlePaymentACK);
272#endif
273 }
274 }
275}
276
278#ifdef ENABLE_BIP70
279 google::protobuf::ShutdownProtobufLibrary();
280#endif
281}
282
283//
284// OSX-specific way of handling bitcoincash: URIs and PaymentRequest mime types.
285// Also used by paymentservertests.cpp and when opening a payment request file
286// via "Open URI..." menu entry.
287//
288bool PaymentServer::eventFilter(QObject *object, QEvent *event) {
289 if (event->type() == QEvent::FileOpen) {
290 QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent *>(event);
291 if (!fileEvent->file().isEmpty()) {
292 handleURIOrFile(fileEvent->file());
293 } else if (!fileEvent->url().isEmpty()) {
294 handleURIOrFile(fileEvent->url().toString());
295 }
296
297 return true;
298 }
299
300 return QObject::eventFilter(object, event);
301}
302
304#ifdef ENABLE_BIP70
305 initNetManager();
306#endif
307
308 saveURIs = false;
309 for (const QString &s : savedPaymentRequests) {
311 }
312 savedPaymentRequests.clear();
313}
314
315bool PaymentServer::handleURI(const CChainParams &params, const QString &s) {
316 const QString scheme = QString::fromStdString(params.CashAddrPrefix());
317 if (!s.startsWith(scheme + ":", Qt::CaseInsensitive)) {
318 return false;
319 }
320
321 QUrlQuery uri((QUrl(s)));
322#ifdef ENABLE_BIP70
323 // payment request URI
324 if (uri.hasQueryItem("r")) {
325 QByteArray temp;
326 temp.append(uri.queryItemValue("r").toUtf8());
327 QString decoded = QUrl::fromPercentEncoding(temp);
328 QUrl fetchUrl(decoded, QUrl::StrictMode);
329
330 if (fetchUrl.isValid()) {
331 qDebug() << "PaymentServer::handleURIOrFile: fetchRequest("
332 << fetchUrl << ")";
333 fetchRequest(fetchUrl);
334 } else {
335 qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: "
336 << fetchUrl;
337 Q_EMIT message(tr("URI handling"),
338 tr("Payment request fetch URL is invalid: %1")
339 .arg(fetchUrl.toString()),
341 }
342 return true;
343 }
344#endif
345
346 // normal URI
347 SendCoinsRecipient recipient;
348 if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) {
349 if (!IsValidDestinationString(recipient.address.toStdString(),
350 params)) {
351#ifndef ENABLE_BIP70
352 // payment request
353 if (uri.hasQueryItem("r")) {
354 Q_EMIT message(tr("URI handling"),
355 tr("Cannot process payment request because "
356 "BIP70 support was not compiled in."),
358 }
359#endif
360 Q_EMIT message(
361 tr("URI handling"),
362 tr("Invalid payment address %1").arg(recipient.address),
364 } else {
365 Q_EMIT receivedPaymentRequest(recipient);
366 }
367 } else {
368 Q_EMIT message(
369 tr("URI handling"),
370 tr("URI cannot be parsed! This can be caused by an invalid "
371 "Bitcoin address or malformed URI parameters."),
373 }
374
375 return true;
376}
377
378void PaymentServer::handleURIOrFile(const QString &s) {
379 if (saveURIs) {
380 savedPaymentRequests.insert(s);
381 return;
382 }
383
384 // bitcoincash: CashAddr URI
385 if (handleURI(Params(), s)) {
386 return;
387 }
388
389 // payment request file
390 if (QFile::exists(s)) {
391#ifdef ENABLE_BIP70
392 PaymentRequestPlus request;
393 SendCoinsRecipient recipient;
394 if (!readPaymentRequestFromFile(s, request)) {
395 Q_EMIT message(tr("Payment request file handling"),
396 tr("Payment request file cannot be read! This can "
397 "be caused by an invalid payment request file."),
399 } else if (processPaymentRequest(request, recipient)) {
400 Q_EMIT receivedPaymentRequest(recipient);
401 }
402
403 return;
404#else
405 Q_EMIT message(tr("Payment request file handling"),
406 tr("Cannot process payment request because BIP70 "
407 "support was not compiled in."),
409#endif
410 }
411}
412
414 QLocalSocket *clientConnection = uriServer->nextPendingConnection();
415
416 while (clientConnection->bytesAvailable() < (int)sizeof(quint32)) {
417 clientConnection->waitForReadyRead();
418 }
419
420 connect(clientConnection, &QLocalSocket::disconnected, clientConnection,
421 &QLocalSocket::deleteLater);
422
423 QDataStream in(clientConnection);
424 in.setVersion(QDataStream::Qt_4_0);
425 if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
426 return;
427 }
428 QString msg;
429 in >> msg;
430
431 handleURIOrFile(msg);
432}
433
435 this->optionsModel = _optionsModel;
436}
437
438#ifdef ENABLE_BIP70
439struct X509StoreDeleter {
440 void operator()(X509_STORE *b) { X509_STORE_free(b); }
441};
442
443struct X509Deleter {
444 void operator()(X509 *b) { X509_free(b); }
445};
446
447// Anon namespace
448namespace {
449std::unique_ptr<X509_STORE, X509StoreDeleter> certStore;
450}
451
452static void ReportInvalidCertificate(const QSslCertificate &cert) {
453 qDebug() << QString("%1: Payment server found an invalid certificate: ")
454 .arg(__func__)
455 << cert.serialNumber()
456 << cert.subjectInfo(QSslCertificate::CommonName)
457 << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier)
458 << cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
459}
460
461//
462// Load OpenSSL's list of root certificate authorities
463//
464void PaymentServer::LoadRootCAs(X509_STORE *_store) {
465 // Unit tests mostly use this, to pass in fake root CAs:
466 if (_store) {
467 certStore.reset(_store);
468 return;
469 }
470
471 // Normal execution, use either -rootcertificates or system certs:
472 certStore.reset(X509_STORE_new());
473
474 // Note: use "-system-" default here so that users can pass
475 // -rootcertificates="" and get 'I don't like X.509 certificates, don't
476 // trust anybody' behavior:
477 QString certFile =
478 QString::fromStdString(gArgs.GetArg("-rootcertificates", "-system-"));
479
480 // Empty store
481 if (certFile.isEmpty()) {
482 qDebug() << QString("PaymentServer::%1: Payment request authentication "
483 "via X.509 certificates disabled.")
484 .arg(__func__);
485 return;
486 }
487
488 QList<QSslCertificate> certList;
489
490 if (certFile != "-system-") {
491 qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root "
492 "certificate.")
493 .arg(__func__)
494 .arg(certFile);
495
496 certList = QSslCertificate::fromPath(certFile);
497 // Use those certificates when fetching payment requests, too:
498 QSslConfiguration::defaultConfiguration().setCaCertificates(certList);
499 } else {
500 certList = QSslConfiguration::systemCaCertificates();
501 }
502
503 int nRootCerts = 0;
504 const QDateTime currentTime = QDateTime::currentDateTime();
505
506 for (const QSslCertificate &cert : certList) {
507 // Don't log NULL certificates
508 if (cert.isNull()) {
509 continue;
510 }
511
512 // Not yet active/valid, or expired certificate
513 if (currentTime < cert.effectiveDate() ||
514 currentTime > cert.expiryDate()) {
515 ReportInvalidCertificate(cert);
516 continue;
517 }
518
519 // Blacklisted certificate
520 if (cert.isBlacklisted()) {
521 ReportInvalidCertificate(cert);
522 continue;
523 }
524
525 QByteArray certData = cert.toDer();
526 const uint8_t *data = (const uint8_t *)certData.data();
527
528 std::unique_ptr<X509, X509Deleter> x509(
529 d2i_X509(0, &data, certData.size()));
530 if (x509 && X509_STORE_add_cert(certStore.get(), x509.get())) {
531 // Note: X509_STORE increases the reference count to the X509
532 // object, we still have to release our reference to it.
533 ++nRootCerts;
534 } else {
535 ReportInvalidCertificate(cert);
536 continue;
537 }
538 }
539 qInfo() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts
540 << " root certificates";
541
542 // Project for another day:
543 // Fetch certificate revocation lists, and add them to certStore.
544 // Issues to consider:
545 // performance (start a thread to fetch in background?)
546 // privacy (fetch through tor/proxy so IP address isn't revealed)
547 // would it be easier to just use a compiled-in blacklist?
548 // or use Qt's blacklist?
549 // "certificate stapling" with server-side caching is more efficient
550}
551
552void PaymentServer::initNetManager() {
553 if (!optionsModel) {
554 return;
555 }
556 if (netManager != nullptr) {
557 delete netManager;
558 }
559
560 // netManager is used to fetch paymentrequests given in bitcoincash: URIs
561 netManager = new QNetworkAccessManager(this);
562
563 QNetworkProxy proxy;
564
565 // Query active SOCKS5 proxy
566 if (optionsModel->getProxySettings(proxy)) {
567 netManager->setProxy(proxy);
568
569 qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy"
570 << proxy.hostName() << ":" << proxy.port();
571 } else {
572 qDebug()
573 << "PaymentServer::initNetManager: No active proxy server found.";
574 }
575
576 connect(netManager, &QNetworkAccessManager::finished, this,
577 &PaymentServer::netRequestFinished);
578 connect(netManager, &QNetworkAccessManager::sslErrors, this,
579 &PaymentServer::reportSslErrors);
580}
581
582//
583// Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine()
584// so don't use "Q_EMIT message()", but "QMessageBox::"!
585//
586bool PaymentServer::readPaymentRequestFromFile(const QString &filename,
587 PaymentRequestPlus &request) {
588 QFile f(filename);
589 if (!f.open(QIODevice::ReadOnly)) {
590 qWarning() << QString("PaymentServer::%1: Failed to open %2")
591 .arg(__func__)
592 .arg(filename);
593 return false;
594 }
595
596 // BIP70 DoS protection
597 if (!verifySize(f.size())) {
598 return false;
599 }
600
601 QByteArray data = f.readAll();
602
603 return request.parse(data);
604}
605
606bool PaymentServer::processPaymentRequest(const PaymentRequestPlus &request,
607 SendCoinsRecipient &recipient) {
608 if (!optionsModel) {
609 return false;
610 }
611
612 if (request.IsInitialized()) {
613 // Payment request network matches client network?
614 if (!verifyNetwork(optionsModel->node(), request.getDetails())) {
615 Q_EMIT message(
616 tr("Payment request rejected"),
617 tr("Payment request network doesn't match client network."),
619
620 return false;
621 }
622
623 // Make sure any payment requests involved are still valid.
624 // This is re-checked just before sending coins in
625 // WalletModel::sendCoins().
626 if (verifyExpired(request.getDetails())) {
627 Q_EMIT message(tr("Payment request rejected"),
628 tr("Payment request expired."),
630
631 return false;
632 }
633 } else {
634 Q_EMIT message(tr("Payment request error"),
635 tr("Payment request is not initialized."),
637
638 return false;
639 }
640
641 recipient.paymentRequest = request;
642 recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo());
643
644 request.getMerchant(certStore.get(), recipient.authenticatedMerchant);
645
646 QList<std::pair<CScript, Amount>> sendingTos = request.getPayTo();
647 QStringList addresses;
648
649 for (const std::pair<CScript, Amount> &sendingTo : sendingTos) {
650 // Extract and check destination addresses
651 CTxDestination dest;
652 if (ExtractDestination(sendingTo.first, dest)) {
653 // Append destination address
654 addresses.append(
655 QString::fromStdString(EncodeCashAddr(dest, Params())));
656 } else if (!recipient.authenticatedMerchant.isEmpty()) {
657 // Unauthenticated payment requests to custom bitcoin addresses are
658 // not supported (there is no good way to tell the user where they
659 // are paying in a way they'd have a chance of understanding).
660 Q_EMIT message(tr("Payment request rejected"),
661 tr("Unverified payment requests to custom payment "
662 "scripts are unsupported."),
664 return false;
665 }
666
667 // Bitcoin amounts are stored as (optional) uint64 in the protobuf
668 // messages (see paymentrequest.proto), but Amount is defined as
669 // int64_t. Because of that we need to verify that amounts are in a
670 // valid range and no overflow has happened.
671 if (!verifyAmount(sendingTo.second)) {
672 Q_EMIT message(tr("Payment request rejected"),
673 tr("Invalid payment request."),
675 return false;
676 }
677
678 // Extract and check amounts
679 CTxOut txOut(Amount(sendingTo.second), sendingTo.first);
680 if (IsDust(txOut, optionsModel->node().getDustRelayFee())) {
681 Q_EMIT message(
682 tr("Payment request error"),
683 tr("Requested payment amount of %1 is too small (considered "
684 "dust).")
686 optionsModel->getDisplayUnit(), sendingTo.second)),
688
689 return false;
690 }
691
692 recipient.amount += sendingTo.second;
693 // Also verify that the final amount is still in a valid range after
694 // adding additional amounts.
695 if (!verifyAmount(recipient.amount)) {
696 Q_EMIT message(tr("Payment request rejected"),
697 tr("Invalid payment request."),
699 return false;
700 }
701 }
702 // Store addresses and format them to fit nicely into the GUI
703 recipient.address = addresses.join("<br />");
704
705 if (!recipient.authenticatedMerchant.isEmpty()) {
706 qDebug() << "PaymentServer::processPaymentRequest: Secure payment "
707 "request from "
708 << recipient.authenticatedMerchant;
709 } else {
710 qDebug() << "PaymentServer::processPaymentRequest: Insecure payment "
711 "request to "
712 << addresses.join(", ");
713 }
714
715 return true;
716}
717
718void PaymentServer::dataDownloaded() {
719 if (paymentRequestReply->bytesAvailable() > BIP70_MAX_PAYMENTREQUEST_SIZE) {
720 qWarning() << "PaymentServer::dataDownloaded: Payment request size "
721 "bigger than expected - aborting";
722 // Note that we use finished() instead of abort() so the data remains
723 // readable and the actual oversize error can be catched during the
724 // netRequestFinished processing
725 paymentRequestReply->finished();
726 }
727}
728
729void PaymentServer::fetchRequest(const QUrl &url) {
730 QNetworkRequest netRequest;
731 netRequest.setAttribute(QNetworkRequest::User,
732 BIP70_MESSAGE_PAYMENTREQUEST);
733 netRequest.setUrl(url);
734 netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
735 netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
736
737 // Start the download
738 paymentRequestReply = netManager->get(netRequest);
739 assert(paymentRequestReply);
740
741 // Set the read buffer size to max size + 1000 bytes which is enough to
742 // detect if the downloaded content is too big while not overshooting too
743 // much.
744 paymentRequestReply->setReadBufferSize(BIP70_MAX_PAYMENTREQUEST_SIZE +
745 1000);
746 connect(paymentRequestReply, &QIODevice::readyRead, this,
747 &PaymentServer::dataDownloaded);
748}
749
750void PaymentServer::fetchPaymentACK(interfaces::Wallet &wallet,
751 const SendCoinsRecipient &recipient,
752 QByteArray transaction) {
753 const payments::PaymentDetails &details =
754 recipient.paymentRequest.getDetails();
755 if (!details.has_payment_url()) {
756 return;
757 }
758
759 QNetworkRequest netRequest;
760 netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
761 netRequest.setUrl(QString::fromStdString(details.payment_url()));
762 netRequest.setHeader(QNetworkRequest::ContentTypeHeader,
763 BIP71_MIMETYPE_PAYMENT);
764 netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
765 netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK);
766
767 payments::Payment payment;
768 payment.set_merchant_data(details.merchant_data());
769 payment.add_transactions(transaction.data(), transaction.size());
770
771 // Create a new refund address, or re-use:
772 auto op_dest = wallet.getNewDestination(OutputType::LEGACY, "");
773 if (op_dest) {
774 // BIP70 requests encode the scriptPubKey directly, so we are not
775 // restricted to address types supported by the receiver. As a result,
776 // we choose the address format we also use for change. Despite an
777 // actual payment and not change, this is a close match: it's the output
778 // type we use subject to privacy issues, but not restricted by what
779 // other software supports.
780 std::string label = tr("Refund from %1")
781 .arg(recipient.authenticatedMerchant)
782 .toStdString();
783 wallet.setAddressBook(*op_dest, label, "refund");
784
785 CScript s = GetScriptForDestination(*op_dest);
786 payments::Output *refund_to = payment.add_refund_to();
787 refund_to->set_script(&s[0], s.size());
788 } else {
789 // This should never happen, because sending coins should have
790 // just unlocked the wallet and refilled the keypool.
791 qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund "
792 "key, refund_to not set";
793 }
794
795 QVariant length;
796#ifdef USE_PROTOBUF_MESSAGE_BYTESIZELONG
797 length.setValue(payment.ByteSizeLong());
798#else
799 length.setValue(payment.ByteSize());
800#endif
801
802 netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
803 QByteArray serData(length.toInt(), '\0');
804 if (payment.SerializeToArray(serData.data(), length.toInt())) {
805 netManager->post(netRequest, serData);
806 } else {
807 // This should never happen, either.
808 qWarning() << "PaymentServer::fetchPaymentACK: Error serializing "
809 "payment message";
810 }
811}
812
813void PaymentServer::netRequestFinished(QNetworkReply *reply) {
814 reply->deleteLater();
815
816 // BIP70 DoS protection
817 // Since the download is aborted as soon as the data size is over the limit,
818 // we don't know the actual size of the payment request so there is no point
819 // printing it. This is still printed to the debug.log so we can check the
820 // proper behavior.
821 if (!verifySize(reply->size())) {
822 Q_EMIT message(
823 tr("Payment request rejected"),
824 tr("Payment request %1 is larger than the max allowed %2 bytes).")
825 .arg(reply->request().url().toString())
828 return;
829 }
830
831 if (reply->error() != QNetworkReply::NoError) {
832 QString msg = tr("Error communicating with %1: %2")
833 .arg(reply->request().url().toString())
834 .arg(reply->errorString());
835
836 qWarning() << "PaymentServer::netRequestFinished: " << msg;
837 Q_EMIT message(tr("Payment request error"), msg,
839 return;
840 }
841
842 QByteArray data = reply->readAll();
843
844 QString requestType =
845 reply->request().attribute(QNetworkRequest::User).toString();
846 if (requestType == BIP70_MESSAGE_PAYMENTREQUEST) {
847 PaymentRequestPlus request;
848 SendCoinsRecipient recipient;
849 if (!request.parse(data)) {
850 qWarning() << "PaymentServer::netRequestFinished: Error parsing "
851 "payment request";
852 Q_EMIT message(tr("Payment request error"),
853 tr("Payment request cannot be parsed!"),
855 } else if (processPaymentRequest(request, recipient)) {
856 Q_EMIT receivedPaymentRequest(recipient);
857 }
858
859 return;
860 } else if (requestType == BIP70_MESSAGE_PAYMENTACK) {
861 payments::PaymentACK paymentACK;
862 if (!paymentACK.ParseFromArray(data.data(), data.size())) {
863 QString msg = tr("Bad response from server %1")
864 .arg(reply->request().url().toString());
865
866 qWarning() << "PaymentServer::netRequestFinished: " << msg;
867 Q_EMIT message(tr("Payment request error"), msg,
869 } else {
870 Q_EMIT receivedPaymentACK(GUIUtil::HtmlEscape(paymentACK.memo()));
871 }
872 }
873}
874
875void PaymentServer::reportSslErrors(QNetworkReply *reply,
876 const QList<QSslError> &errs) {
877 Q_UNUSED(reply);
878
879 QString errString;
880 for (const QSslError &err : errs) {
881 qWarning() << "PaymentServer::reportSslErrors: " << err;
882 errString += err.errorString() + "\n";
883 }
884 Q_EMIT message(tr("Network request error"), errString,
886}
887
888void PaymentServer::handlePaymentACK(const QString &paymentACKMsg) {
889 // currently we don't further process or store the paymentACK message
890 Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg,
893}
894
895bool PaymentServer::verifyNetwork(
896 interfaces::Node &node, const payments::PaymentDetails &requestDetails) {
897 const std::string clientNetwork =
899 bool fVerified = requestDetails.network() == clientNetwork;
900 if (!fVerified) {
901 qWarning() << QString("PaymentServer::%1: Payment request network "
902 "\"%2\" doesn't match client network \"%3\".")
903 .arg(__func__)
904 .arg(QString::fromStdString(requestDetails.network()))
905 .arg(QString::fromStdString(clientNetwork));
906 }
907 return fVerified;
908}
909
910bool PaymentServer::verifyExpired(
911 const payments::PaymentDetails &requestDetails) {
912 bool fVerified = (requestDetails.has_expires() &&
913 (int64_t)requestDetails.expires() < GetTime());
914 if (fVerified) {
915 const QString requestExpires = QString::fromStdString(
916 FormatISO8601DateTime((int64_t)requestDetails.expires()));
917 qWarning() << QString(
918 "PaymentServer::%1: Payment request expired \"%2\".")
919 .arg(__func__)
920 .arg(requestExpires);
921 }
922 return fVerified;
923}
924
925bool PaymentServer::verifySize(qint64 requestSize) {
926 bool fVerified = (requestSize <= BIP70_MAX_PAYMENTREQUEST_SIZE);
927 if (!fVerified) {
928 qWarning() << QString("PaymentServer::%1: Payment request too large "
929 "(downloaded %2 bytes, max allowed %3 bytes).")
930 .arg(__func__)
931 .arg(requestSize)
933 }
934 return fVerified;
935}
936
937bool PaymentServer::verifyAmount(const Amount requestAmount) {
938 bool fVerified = MoneyRange(Amount(requestAmount));
939 if (!fVerified) {
940 qWarning() << QString("PaymentServer::%1: Payment request amount out "
941 "of allowed range (%2, allowed 0 - %3).")
942 .arg(__func__)
943 .arg(requestAmount / SATOSHI)
944 .arg(MAX_MONEY / SATOSHI);
945 }
946 return fVerified;
947}
948
949X509_STORE *PaymentServer::getCertStore() {
950 return certStore.get();
951}
952#endif
bool MoneyRange(const Amount nValue)
Definition: amount.h:166
static constexpr Amount SATOSHI
Definition: amount.h:143
static constexpr Amount MAX_MONEY
No amount larger than this (in satoshi) is valid.
Definition: amount.h:165
ArgsManager gArgs
Definition: args.cpp:40
std::string EncodeCashAddr(const CTxDestination &dst, const CChainParams &params)
Definition: cashaddrenc.cpp:90
std::unique_ptr< const CChainParams > CreateChainParams(const ArgsManager &args, const ChainType chain)
Creates and returns a std::unique_ptr<CChainParams> of the chosen chain.
Definition: chainparams.cpp:33
void SelectParams(const ChainType chain)
Sets the params returned by Params() to those for the given BIP70 chain name.
Definition: chainparams.cpp:50
const CChainParams & Params()
Return the currently selected parameters.
Definition: chainparams.cpp:21
std::string ChainTypeToString(ChainType chain)
Definition: chaintype.cpp:11
ChainType
Definition: chaintype.h:11
fs::path GetDataDirNet() const
Get data directory path with appended network identifier.
Definition: args.h:239
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: args.cpp:463
static QString formatWithUnit(int unit, const Amount amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
CChainParams defines various tweakable parameters of a given instance of the Bitcoin system.
Definition: chainparams.h:86
std::string GetChainTypeString() const
Return the chain type string.
Definition: chainparams.h:134
const std::string & CashAddrPrefix() const
Definition: chainparams.h:143
@ MODAL
Force blocking, modal message box dialog (not just OS notification)
Definition: ui_interface.h:66
An output of a transaction.
Definition: transaction.h:128
virtual const CChainParams & GetChainParams() const =0
Interface from Qt to configuration data structure for Bitcoin client.
Definition: optionsmodel.h:48
int getDisplayUnit() const
Definition: optionsmodel.h:97
bool getProxySettings(QNetworkProxy &proxy) const
interfaces::Node & node() const
Definition: optionsmodel.h:113
QList< std::pair< CScript, Amount > > getPayTo() const
bool getMerchant(X509_STORE *certStore, QString &merchant) 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[])
QLocalServer * uriServer
void receivedPaymentRequest(SendCoinsRecipient)
bool eventFilter(QObject *object, QEvent *event) override
void handleURIOrFile(const QString &s)
bool handleURI(const CChainParams &params, const QString &s)
OptionsModel * optionsModel
Top-level interface for a bitcoin node (bitcoind process).
Definition: node.h:59
virtual CFeeRate getDustRelayFee()=0
Get dust relay fee.
Interface for accessing a wallet.
Definition: wallet.h:60
const std::string CLIENT_NAME
const Config & GetConfig()
Definition: config.cpp:40
bool IsValidDestinationString(const std::string &str, const CChainParams &params)
Definition: key_io.cpp:183
bool parseBitcoinURI(const QString &scheme, const QUrl &uri, SendCoinsRecipient *out)
Definition: guiutil.cpp:149
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:256
QString boostPathToQString(const fs::path &path)
Convert OS specific boost path to QString through UTF-8.
Definition: guiutil.cpp:791
static bool exists(const path &p)
Definition: fs.h:107
Definition: init.h:31
static QString ipcServerName()
static bool ipcCanParseCashAddrURI(const QString &arg, const ChainType chain_type)
const int BITCOIN_IPC_CONNECT_TIMEOUT
static std::string ipcParseURI(const QString &arg, const CChainParams &params, bool useCashAddr)
static bool ipcCanParseLegacyURI(const QString &arg, const ChainType chain_type)
static QSet< QString > savedPaymentRequests
static QT_END_NAMESPACE const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE
Definition: paymentserver.h:61
bool IsDust(const CTxOut &txout, const CFeeRate &dustRelayFeeIn)
Definition: policy.cpp:34
const char * name
Definition: rest.cpp:47
const char * url
Definition: rpcconsole.cpp:55
bool ExtractDestination(const CScript &scriptPubKey, CTxDestination &addressRet)
Parse a standard scriptPubKey for the destination address.
Definition: standard.cpp:158
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a Bitcoin scriptPubKey for the given CTxDestination.
Definition: standard.cpp:240
std::variant< CNoDestination, PKHash, ScriptHash > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:85
Definition: amount.h:19
int64_t GetTime()
DEPRECATED Use either ClockType::now() or Now<TimePointType>() if a cast is needed.
Definition: time.cpp:105
std::string FormatISO8601DateTime(int64_t nTime)
ISO 8601 formatting is preferred.
Definition: time.cpp:109
assert(!tx.IsCoinBase())