21 #include <openssl/x509_vfy.h>
24 #include <QApplication>
26 #include <QDataStream>
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>
44 #include <QStringList>
45 #include <QTextDocument>
54 const char *BIP70_MESSAGE_PAYMENTACK =
"PaymentACK";
55 const char *BIP70_MESSAGE_PAYMENTREQUEST =
"PaymentRequest";
57 const char *BIP71_MIMETYPE_PAYMENT =
"application/ecash-payment";
58 const char *BIP71_MIMETYPE_PAYMENTACK =
"application/ecash-paymentack";
59 const 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)) {
438 struct X509StoreDeleter {
439 void operator()(X509_STORE *b) { X509_STORE_free(b); }
443 void operator()(X509 *b) { X509_free(b); }
448 std::unique_ptr<X509_STORE, X509StoreDeleter> certStore;
451 static 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);
463 void 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";
551 void 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);
585 bool 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(
", ");
717 void PaymentServer::fetchRequest(
const QUrl &
url) {
718 QNetworkRequest netRequest;
719 netRequest.setAttribute(QNetworkRequest::User,
720 BIP70_MESSAGE_PAYMENTREQUEST);
721 netRequest.setUrl(
url);
722 netRequest.setRawHeader(
"User-Agent",
CLIENT_NAME.c_str());
723 netRequest.setRawHeader(
"Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
724 netManager->get(netRequest);
729 QByteArray transaction) {
730 const payments::PaymentDetails &details =
731 recipient.paymentRequest.getDetails();
732 if (!details.has_payment_url()) {
736 QNetworkRequest netRequest;
737 netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
738 netRequest.setUrl(QString::fromStdString(details.payment_url()));
739 netRequest.setHeader(QNetworkRequest::ContentTypeHeader,
740 BIP71_MIMETYPE_PAYMENT);
741 netRequest.setRawHeader(
"User-Agent",
CLIENT_NAME.c_str());
742 netRequest.setRawHeader(
"Accept", BIP71_MIMETYPE_PAYMENTACK);
744 payments::Payment payment;
745 payment.set_merchant_data(details.merchant_data());
746 payment.add_transactions(transaction.data(), transaction.size());
757 std::string label = tr(
"Refund from %1")
760 wallet.setAddressBook(dest, label,
"refund");
763 payments::Output *refund_to = payment.add_refund_to();
764 refund_to->set_script(&s[0], s.
size());
768 qWarning() <<
"PaymentServer::fetchPaymentACK: Error getting refund "
769 "key, refund_to not set";
773 #ifdef USE_PROTOBUF_MESSAGE_BYTESIZELONG
774 length.setValue(payment.ByteSizeLong());
776 length.setValue(payment.ByteSize());
779 netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
780 QByteArray serData(length.toInt(),
'\0');
781 if (payment.SerializeToArray(serData.data(), length.toInt())) {
782 netManager->post(netRequest, serData);
785 qWarning() <<
"PaymentServer::fetchPaymentACK: Error serializing "
790 void PaymentServer::netRequestFinished(QNetworkReply *reply) {
791 reply->deleteLater();
794 if (!verifySize(reply->size())) {
796 tr(
"Payment request rejected"),
797 tr(
"Payment request %1 is too large (%2 bytes, allowed %3 bytes).")
798 .arg(reply->request().url().toString())
805 if (reply->error() != QNetworkReply::NoError) {
806 QString msg = tr(
"Error communicating with %1: %2")
807 .arg(reply->request().url().toString())
808 .arg(reply->errorString());
810 qWarning() <<
"PaymentServer::netRequestFinished: " << msg;
811 Q_EMIT
message(tr(
"Payment request error"), msg,
816 QByteArray data = reply->readAll();
818 QString requestType =
819 reply->request().attribute(QNetworkRequest::User).toString();
820 if (requestType == BIP70_MESSAGE_PAYMENTREQUEST) {
823 if (!request.
parse(data)) {
824 qWarning() <<
"PaymentServer::netRequestFinished: Error parsing "
826 Q_EMIT
message(tr(
"Payment request error"),
827 tr(
"Payment request cannot be parsed!"),
829 }
else if (processPaymentRequest(request, recipient)) {
834 }
else if (requestType == BIP70_MESSAGE_PAYMENTACK) {
835 payments::PaymentACK paymentACK;
836 if (!paymentACK.ParseFromArray(data.data(), data.size())) {
837 QString msg = tr(
"Bad response from server %1")
838 .arg(reply->request().url().toString());
840 qWarning() <<
"PaymentServer::netRequestFinished: " << msg;
841 Q_EMIT
message(tr(
"Payment request error"), msg,
849 void PaymentServer::reportSslErrors(QNetworkReply *reply,
850 const QList<QSslError> &errs) {
854 for (
const QSslError &err : errs) {
855 qWarning() <<
"PaymentServer::reportSslErrors: " << err;
856 errString += err.errorString() +
"\n";
858 Q_EMIT
message(tr(
"Network request error"), errString,
862 void PaymentServer::handlePaymentACK(
const QString &paymentACKMsg) {
864 Q_EMIT
message(tr(
"Payment acknowledged"), paymentACKMsg,
869 bool PaymentServer::verifyNetwork(
871 const std::string clientNetwork =
873 bool fVerified = requestDetails.network() == clientNetwork;
875 qWarning() << QString(
"PaymentServer::%1: Payment request network "
876 "\"%2\" doesn't match client network \"%3\".")
878 .arg(QString::fromStdString(requestDetails.network()))
879 .arg(QString::fromStdString(clientNetwork));
884 bool PaymentServer::verifyExpired(
885 const payments::PaymentDetails &requestDetails) {
886 bool fVerified = (requestDetails.has_expires() &&
887 (int64_t)requestDetails.expires() <
GetTime());
889 const QString requestExpires = QString::fromStdString(
891 qWarning() << QString(
892 "PaymentServer::%1: Payment request expired \"%2\".")
894 .arg(requestExpires);
899 bool PaymentServer::verifySize(qint64 requestSize) {
902 qWarning() << QString(
"PaymentServer::%1: Payment request too large "
903 "(%2 bytes, allowed %3 bytes).")
911 bool PaymentServer::verifyAmount(
const Amount requestAmount) {
914 qWarning() << QString(
"PaymentServer::%1: Payment request amount out "
915 "of allowed range (%2, allowed 0 - %3).")
923 X509_STORE *PaymentServer::getCertStore() {
924 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.
std::variant< CNoDestination, PKHash, ScriptHash > CTxDestination
A txout script template with a specific destination.
std::string FormatISO8601DateTime(int64_t nTime)
ISO 8601 formatting is preferred.