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