8#include <chainparams.h>
21#include <openssl/x509_vfy.h>
24#include <QApplication>
30#include <QFileOpenEvent>
33#include <QLocalServer>
34#include <QLocalSocket>
36#include <QNetworkAccessManager>
37#include <QNetworkProxy>
38#include <QNetworkReply>
39#include <QNetworkRequest>
40#include <QSslCertificate>
41#include <QSslConfiguration>
45#include <QTextDocument>
54const char *BIP70_MESSAGE_PAYMENTACK =
"PaymentACK";
55const char *BIP70_MESSAGE_PAYMENTREQUEST =
"PaymentRequest";
57const char *BIP71_MIMETYPE_PAYMENT =
"application/ecash-payment";
58const char *BIP71_MIMETYPE_PAYMENTACK =
"application/ecash-paymentack";
59const char *BIP71_MIMETYPE_PAYMENTREQUEST =
"application/ecash-paymentrequest";
68 QString
name(
"BitcoinQt");
74 name.append(QString::number(qHash(ddir)));
87 const QString scheme = QString::fromStdString(params.
CashAddrPrefix());
88 if (!arg.startsWith(scheme +
":", Qt::CaseInsensitive)) {
101 const std::string &network) {
103 std::string addr =
ipcParseURI(arg, *tempChainParams,
true);
108 const std::string &network) {
110 std::string addr =
ipcParseURI(arg, *tempChainParams,
false);
123 std::array<const std::string *, 3> networks = {
127 const std::string *chosenNetwork =
nullptr;
129 for (
int i = 1; i < argc; i++) {
130 QString arg(argv[i]);
131 if (arg.startsWith(
"-")) {
135 const std::string *itemNetwork =
nullptr;
138 for (
auto net : networks) {
154 if (readPaymentRequestFromFile(arg, request)) {
155 for (
auto net : networks) {
164 if (itemNetwork ==
nullptr) {
167 qWarning() <<
"PaymentServer::ipcSendCommandLine: Payment request "
168 "file or URI does not exist or is invalid: "
174 if (chosenNetwork && chosenNetwork != itemNetwork) {
175 qWarning() <<
"PaymentServer::ipcSendCommandLine: Payment request "
177 << QString(itemNetwork->c_str())
178 <<
" does not match already chosen network "
179 << QString(chosenNetwork->c_str());
188 chosenNetwork = itemNetwork;
202 bool fResult =
false;
204 QLocalSocket *socket =
new QLocalSocket();
205 socket->connectToServer(
ipcServerName(), QIODevice::WriteOnly);
213 QDataStream out(&block, QIODevice::WriteOnly);
214 out.setVersion(QDataStream::Qt_4_0);
216 out.device()->seek(0);
218 socket->write(block);
221 socket->disconnectFromServer();
232 : QObject(parent), saveURIs(true), uriServer(nullptr), optionsModel(nullptr)
242 GOOGLE_PROTOBUF_VERIFY_VERSION;
249 parent->installEventFilter(
this);
255 QLocalServer::removeServer(
name);
257 if (startLocalServer) {
263 QMessageBox::critical(
nullptr, tr(
"Payment request error"),
264 tr(
"Cannot start click-to-pay handler"));
266 connect(
uriServer, &QLocalServer::newConnection,
this,
269 connect(
this, &PaymentServer::receivedPaymentACK,
this,
270 &PaymentServer::handlePaymentACK);
278 google::protobuf::ShutdownProtobufLibrary();
288 if (event->type() == QEvent::FileOpen) {
289 QFileOpenEvent *fileEvent =
static_cast<QFileOpenEvent *
>(event);
290 if (!fileEvent->file().isEmpty()) {
292 }
else if (!fileEvent->url().isEmpty()) {
299 return QObject::eventFilter(
object, event);
315 const QString scheme = QString::fromStdString(params.
CashAddrPrefix());
316 if (!s.startsWith(scheme +
":", Qt::CaseInsensitive)) {
320 QUrlQuery uri((QUrl(s)));
323 if (uri.hasQueryItem(
"r")) {
325 temp.append(uri.queryItemValue(
"r").toUtf8());
326 QString decoded = QUrl::fromPercentEncoding(temp);
327 QUrl fetchUrl(decoded, QUrl::StrictMode);
329 if (fetchUrl.isValid()) {
330 qDebug() <<
"PaymentServer::handleURIOrFile: fetchRequest("
332 fetchRequest(fetchUrl);
334 qWarning() <<
"PaymentServer::handleURIOrFile: Invalid URL: "
336 Q_EMIT
message(tr(
"URI handling"),
337 tr(
"Payment request fetch URL is invalid: %1")
338 .arg(fetchUrl.toString()),
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."),
361 tr(
"Invalid payment address %1").arg(recipient.
address),
369 tr(
"URI cannot be parsed! This can be caused by an invalid "
370 "Bitcoin address or malformed URI parameters."),
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)) {
404 Q_EMIT
message(tr(
"Payment request file handling"),
405 tr(
"Cannot process payment request because BIP70 "
406 "support was not compiled in."),
413 QLocalSocket *clientConnection =
uriServer->nextPendingConnection();
415 while (clientConnection->bytesAvailable() < (
int)
sizeof(quint32)) {
416 clientConnection->waitForReadyRead();
419 connect(clientConnection, &QLocalSocket::disconnected, clientConnection,
420 &QLocalSocket::deleteLater);
422 QDataStream in(clientConnection);
423 in.setVersion(QDataStream::Qt_4_0);
424 if (clientConnection->bytesAvailable() < (
int)
sizeof(quint16)) {
438struct X509StoreDeleter {
439 void operator()(X509_STORE *b) { X509_STORE_free(b); }
443 void operator()(X509 *b) { X509_free(b); }
448std::unique_ptr<X509_STORE, X509StoreDeleter> certStore;
451static void ReportInvalidCertificate(
const QSslCertificate &cert) {
452 qDebug() << QString(
"%1: Payment server found an invalid certificate: ")
454 << cert.serialNumber()
455 << cert.subjectInfo(QSslCertificate::CommonName)
456 << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier)
457 << cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
463void PaymentServer::LoadRootCAs(X509_STORE *_store) {
466 certStore.reset(_store);
471 certStore.reset(X509_STORE_new());
477 QString::fromStdString(
gArgs.
GetArg(
"-rootcertificates",
"-system-"));
480 if (certFile.isEmpty()) {
481 qDebug() << QString(
"PaymentServer::%1: Payment request authentication "
482 "via X.509 certificates disabled.")
487 QList<QSslCertificate> certList;
489 if (certFile !=
"-system-") {
490 qDebug() << QString(
"PaymentServer::%1: Using \"%2\" as trusted root "
495 certList = QSslCertificate::fromPath(certFile);
497 QSslConfiguration::defaultConfiguration().setCaCertificates(certList);
499 certList = QSslConfiguration::systemCaCertificates();
503 const QDateTime currentTime = QDateTime::currentDateTime();
505 for (
const QSslCertificate &cert : certList) {
512 if (currentTime < cert.effectiveDate() ||
513 currentTime > cert.expiryDate()) {
514 ReportInvalidCertificate(cert);
519 if (cert.isBlacklisted()) {
520 ReportInvalidCertificate(cert);
524 QByteArray certData = cert.toDer();
525 const uint8_t *data = (
const uint8_t *)certData.data();
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())) {
534 ReportInvalidCertificate(cert);
538 qInfo() <<
"PaymentServer::LoadRootCAs: Loaded " << nRootCerts
539 <<
" root certificates";
551void PaymentServer::initNetManager() {
555 if (netManager !=
nullptr) {
560 netManager =
new QNetworkAccessManager(
this);
566 netManager->setProxy(proxy);
568 qDebug() <<
"PaymentServer::initNetManager: Using SOCKS5 proxy"
569 << proxy.hostName() <<
":" << proxy.port();
572 <<
"PaymentServer::initNetManager: No active proxy server found.";
575 connect(netManager, &QNetworkAccessManager::finished,
this,
576 &PaymentServer::netRequestFinished);
577 connect(netManager, &QNetworkAccessManager::sslErrors,
this,
578 &PaymentServer::reportSslErrors);
585bool PaymentServer::readPaymentRequestFromFile(
const QString &filename,
588 if (!f.open(QIODevice::ReadOnly)) {
589 qWarning() << QString(
"PaymentServer::%1: Failed to open %2")
596 if (!verifySize(f.size())) {
600 QByteArray data = f.readAll();
602 return request.
parse(data);
615 tr(
"Payment request rejected"),
616 tr(
"Payment request network doesn't match client network."),
626 Q_EMIT
message(tr(
"Payment request rejected"),
627 tr(
"Payment request expired."),
633 Q_EMIT
message(tr(
"Payment request error"),
634 tr(
"Payment request is not initialized."),
640 recipient.paymentRequest = request;
645 QList<std::pair<CScript, Amount>> sendingTos = request.
getPayTo();
646 QStringList addresses;
648 for (
const std::pair<CScript, Amount> &sendingTo : sendingTos) {
659 Q_EMIT
message(tr(
"Payment request rejected"),
660 tr(
"Unverified payment requests to custom payment "
661 "scripts are unsupported."),
670 if (!verifyAmount(sendingTo.second)) {
671 Q_EMIT
message(tr(
"Payment request rejected"),
672 tr(
"Invalid payment request."),
678 CTxOut txOut(
Amount(sendingTo.second), sendingTo.first);
681 tr(
"Payment request error"),
682 tr(
"Requested payment amount of %1 is too small (considered "
691 recipient.
amount += sendingTo.second;
694 if (!verifyAmount(recipient.
amount)) {
695 Q_EMIT
message(tr(
"Payment request rejected"),
696 tr(
"Invalid payment request."),
702 recipient.
address = addresses.join(
"<br />");
705 qDebug() <<
"PaymentServer::processPaymentRequest: Secure payment "
709 qDebug() <<
"PaymentServer::processPaymentRequest: Insecure payment "
711 << addresses.join(
", ");
717void PaymentServer::dataDownloaded() {
719 qWarning() <<
"PaymentServer::dataDownloaded: Payment request size "
720 "bigger than expected - aborting";
724 paymentRequestReply->finished();
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);
737 paymentRequestReply = netManager->get(netRequest);
738 assert(paymentRequestReply);
745 connect(paymentRequestReply, &QIODevice::readyRead,
this,
746 &PaymentServer::dataDownloaded);
751 QByteArray transaction) {
752 const payments::PaymentDetails &details =
753 recipient.paymentRequest.getDetails();
754 if (!details.has_payment_url()) {
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);
766 payments::Payment payment;
767 payment.set_merchant_data(details.merchant_data());
768 payment.add_transactions(transaction.data(), transaction.size());
779 std::string label = tr(
"Refund from %1")
782 wallet.setAddressBook(dest, label,
"refund");
785 payments::Output *refund_to = payment.add_refund_to();
786 refund_to->set_script(&s[0], s.size());
790 qWarning() <<
"PaymentServer::fetchPaymentACK: Error getting refund "
791 "key, refund_to not set";
795#ifdef USE_PROTOBUF_MESSAGE_BYTESIZELONG
796 length.setValue(payment.ByteSizeLong());
798 length.setValue(payment.ByteSize());
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);
807 qWarning() <<
"PaymentServer::fetchPaymentACK: Error serializing "
812void PaymentServer::netRequestFinished(QNetworkReply *reply) {
813 reply->deleteLater();
820 if (!verifySize(reply->size())) {
822 tr(
"Payment request rejected"),
823 tr(
"Payment request %1 is larger than the max allowed %2 bytes).")
824 .arg(reply->request().url().toString())
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());
835 qWarning() <<
"PaymentServer::netRequestFinished: " << msg;
836 Q_EMIT
message(tr(
"Payment request error"), msg,
841 QByteArray data = reply->readAll();
843 QString requestType =
844 reply->request().attribute(QNetworkRequest::User).toString();
845 if (requestType == BIP70_MESSAGE_PAYMENTREQUEST) {
848 if (!request.
parse(data)) {
849 qWarning() <<
"PaymentServer::netRequestFinished: Error parsing "
851 Q_EMIT
message(tr(
"Payment request error"),
852 tr(
"Payment request cannot be parsed!"),
854 }
else if (processPaymentRequest(request, recipient)) {
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());
865 qWarning() <<
"PaymentServer::netRequestFinished: " << msg;
866 Q_EMIT
message(tr(
"Payment request error"), msg,
874void PaymentServer::reportSslErrors(QNetworkReply *reply,
875 const QList<QSslError> &errs) {
879 for (
const QSslError &err : errs) {
880 qWarning() <<
"PaymentServer::reportSslErrors: " << err;
881 errString += err.errorString() +
"\n";
883 Q_EMIT
message(tr(
"Network request error"), errString,
887void PaymentServer::handlePaymentACK(
const QString &paymentACKMsg) {
889 Q_EMIT
message(tr(
"Payment acknowledged"), paymentACKMsg,
894bool PaymentServer::verifyNetwork(
896 const std::string clientNetwork =
898 bool fVerified = requestDetails.network() == clientNetwork;
900 qWarning() << QString(
"PaymentServer::%1: Payment request network "
901 "\"%2\" doesn't match client network \"%3\".")
903 .arg(QString::fromStdString(requestDetails.network()))
904 .arg(QString::fromStdString(clientNetwork));
909bool PaymentServer::verifyExpired(
910 const payments::PaymentDetails &requestDetails) {
911 bool fVerified = (requestDetails.has_expires() &&
912 (int64_t)requestDetails.expires() <
GetTime());
914 const QString requestExpires = QString::fromStdString(
916 qWarning() << QString(
917 "PaymentServer::%1: Payment request expired \"%2\".")
919 .arg(requestExpires);
924bool PaymentServer::verifySize(qint64 requestSize) {
927 qWarning() << QString(
"PaymentServer::%1: Payment request too large "
928 "(downloaded %2 bytes, max allowed %3 bytes).")
936bool PaymentServer::verifyAmount(
const Amount requestAmount) {
939 qWarning() << QString(
"PaymentServer::%1: Payment request amount out "
940 "of allowed range (%2, allowed 0 - %3).")
948X509_STORE *PaymentServer::getCertStore() {
949 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.
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.
const CChainParams & Params()
Return the currently selected parameters.
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)
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.
std::variant< CNoDestination, PKHash, ScriptHash > CTxDestination
A txout script template with a specific destination.
int64_t GetTime()
DEPRECATED Use either ClockType::now() or Now<TimePointType>() if a cast is needed.
std::string FormatISO8601DateTime(int64_t nTime)
ISO 8601 formatting is preferred.