Remove obsolete protobuf payment requests

This commit is contained in:
root
2026-05-03 05:30:25 +00:00
parent acf8b3ccb6
commit 9852dad996
34 changed files with 196 additions and 1288 deletions
-46
View File
@@ -1,46 +0,0 @@
//
// Simple Bitcoin Payment Protocol messages
//
// Use fields 100+ for extensions;
// to avoid conflicts, register extensions at:
// https://en.bitcoin.it/wiki/Payment_Request
//
package payments;
option java_package = "org.bitcoin.protocols.payments";
option java_outer_classname = "Protos";
// Generalized form of "send payment to this/these bitcoin addresses"
message Output {
optional uint64 amount = 1 [default = 0]; // amount is integer-number-of-satoshis
required bytes script = 2; // usually one of the standard Script forms
}
message PaymentDetails {
optional string network = 1 [default = "main"]; // "main" or "test"
repeated Output outputs = 2; // Where payment should be sent
required uint64 time = 3; // Timestamp; when payment request created
optional uint64 expires = 4; // Timestamp; when this request should be considered invalid
optional string memo = 5; // Human-readable description of request for the customer
optional string payment_url = 6; // URL to send Payment and get PaymentACK
optional bytes merchant_data = 7; // Arbitrary data to include in the Payment message
}
message PaymentRequest {
optional uint32 payment_details_version = 1 [default = 1];
optional string pki_type = 2 [default = "none"]; // none / x509+sha256 / x509+sha1
optional bytes pki_data = 3; // depends on pki_type
required bytes serialized_payment_details = 4; // PaymentDetails
optional bytes signature = 5; // pki-dependent signature
}
message X509Certificates {
repeated bytes certificate = 1; // DER-encoded X.509 certificate chain
}
message Payment {
optional bytes merchant_data = 1; // From PaymentDetails.merchant_data
repeated bytes transactions = 2; // Signed transactions that satisfy PaymentDetails.outputs
repeated Output refund_to = 3; // Where to send refunds, if a refund is necessary
optional string memo = 4; // Human-readable message for the merchant
}
message PaymentACK {
required Payment payment = 1; // Payment message that triggered this ACK
optional string memo = 2; // human-readable message for customer
}
+12 -184
View File
@@ -3,214 +3,42 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
//
// Wraps dumb protocol buffer paymentRequest
// with some extra methods
//
#include "paymentrequestplus.h"
#include <stdexcept>
#include <openssl/x509_vfy.h>
#include <QDateTime>
#include <QDebug>
#include <QSslCertificate>
using namespace std;
class SSLVerifyError : public std::runtime_error
{
public:
SSLVerifyError(std::string err) : std::runtime_error(err) {}
};
bool PaymentRequestPlus::parse(const QByteArray& data)
{
bool parseOK = paymentRequest.ParseFromArray(data.data(), data.size());
if (!parseOK) {
qWarning() << "PaymentRequestPlus::parse : Error parsing payment request";
return false;
}
if (paymentRequest.payment_details_version() > 1) {
qWarning() << "PaymentRequestPlus::parse : Received up-version payment details, version=" << paymentRequest.payment_details_version();
return false;
}
parseOK = details.ParseFromString(paymentRequest.serialized_payment_details());
if (!parseOK) {
qWarning() << "PaymentRequestPlus::parse : Error parsing payment details";
paymentRequest.Clear();
return false;
}
return true;
Q_UNUSED(data);
qWarning() << "PaymentRequestPlus::parse : BIP70 payment requests are no longer supported";
return false;
}
bool PaymentRequestPlus::SerializeToString(string* output) const
bool PaymentRequestPlus::SerializeToString(std::string* output) const
{
return paymentRequest.SerializeToString(output);
if (output)
output->clear();
return false;
}
bool PaymentRequestPlus::IsInitialized() const
{
return paymentRequest.IsInitialized();
return false;
}
QString PaymentRequestPlus::getPKIType() const
{
if (!IsInitialized()) return QString("none");
return QString::fromStdString(paymentRequest.pki_type());
return QString("none");
}
bool PaymentRequestPlus::getMerchant(X509_STORE* certStore, QString& merchant) const
{
Q_UNUSED(certStore);
merchant.clear();
if (!IsInitialized())
return false;
// One day we'll support more PKI types, but just
// x509 for now:
const EVP_MD* digestAlgorithm = NULL;
if (paymentRequest.pki_type() == "x509+sha256") {
digestAlgorithm = EVP_sha256();
} else if (paymentRequest.pki_type() == "x509+sha1") {
digestAlgorithm = EVP_sha1();
} else if (paymentRequest.pki_type() == "none") {
qWarning() << "PaymentRequestPlus::getMerchant : Payment request: pki_type == none";
return false;
} else {
qWarning() << "PaymentRequestPlus::getMerchant : Payment request: unknown pki_type " << QString::fromStdString(paymentRequest.pki_type());
return false;
}
payments::X509Certificates certChain;
if (!certChain.ParseFromString(paymentRequest.pki_data())) {
qWarning() << "PaymentRequestPlus::getMerchant : Payment request: error parsing pki_data";
return false;
}
std::vector<X509*> certs;
const QDateTime currentTime = QDateTime::currentDateTime();
for (int i = 0; i < certChain.certificate_size(); i++) {
QByteArray certData(certChain.certificate(i).data(), certChain.certificate(i).size());
QSslCertificate qCert(certData, QSsl::Der);
if (currentTime < qCert.effectiveDate() || currentTime > qCert.expiryDate()) {
qWarning() << "PaymentRequestPlus::getMerchant : Payment request: certificate expired or not yet active: " << qCert;
return false;
}
if (qCert.isBlacklisted()) {
qWarning() << "PaymentRequestPlus::getMerchant : Payment request: certificate blacklisted: " << qCert;
return false;
}
const unsigned char *data = (const unsigned char *)certChain.certificate(i).data();
X509 *cert = d2i_X509(nullptr, &data, certChain.certificate(i).size());
if (cert)
certs.push_back(cert);
}
if (certs.empty()) {
qWarning() << "PaymentRequestPlus::getMerchant : Payment request: empty certificate chain";
return false;
}
// The first cert is the signing cert, the rest are untrusted certs that chain
// to a valid root authority. OpenSSL needs them separately.
STACK_OF(X509)* chain =
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
reinterpret_cast<STACK_OF(X509)*>(OPENSSL_sk_new_null());
#else
sk_X509_new_null();
#endif
for (int i = certs.size() - 1; i > 0; i--) {
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
OPENSSL_sk_push(reinterpret_cast<OPENSSL_STACK*>(chain), reinterpret_cast<void*>(certs[i]));
#else
sk_X509_push(chain, certs[i]);
#endif
}
X509* signing_cert = certs[0];
// Now create a "store context", which is a single use object for checking,
// load the signing cert into it and verify.
X509_STORE_CTX* store_ctx = X509_STORE_CTX_new();
if (!store_ctx) {
qWarning() << "PaymentRequestPlus::getMerchant : Payment request: error creating X509_STORE_CTX";
return false;
}
char* website = NULL;
bool fResult = true;
try {
if (!X509_STORE_CTX_init(store_ctx, certStore, signing_cert, chain)) {
int error = X509_STORE_CTX_get_error(store_ctx);
throw SSLVerifyError(X509_verify_cert_error_string(error));
}
// Now do the verification!
int result = X509_verify_cert(store_ctx);
if (result != 1) {
int error = X509_STORE_CTX_get_error(store_ctx);
throw SSLVerifyError(X509_verify_cert_error_string(error));
}
X509_NAME* certname = X509_get_subject_name(signing_cert);
// Valid cert; check signature:
payments::PaymentRequest rcopy(paymentRequest); // Copy
rcopy.set_signature(std::string(""));
std::string data_to_verify; // Everything but the signature
rcopy.SerializeToString(&data_to_verify);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
if (!ctx) throw SSLVerifyError("Error allocating OpenSSL context.");
#else
EVP_MD_CTX _ctx;
EVP_MD_CTX *ctx;
ctx = &_ctx;
EVP_MD_CTX_init(ctx);
#endif
EVP_PKEY* pubkey = X509_get_pubkey(signing_cert);
if (!EVP_VerifyInit_ex(ctx, digestAlgorithm, NULL) ||
!EVP_VerifyUpdate(ctx, data_to_verify.data(), data_to_verify.size()) ||
!EVP_VerifyFinal(ctx, (const unsigned char*)paymentRequest.signature().data(), paymentRequest.signature().size(), pubkey)) {
throw SSLVerifyError("Bad signature, invalid PaymentRequest.");
}
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
EVP_MD_CTX_free(ctx);
#endif
// OpenSSL API for getting human printable strings from certs is baroque.
int textlen = X509_NAME_get_text_by_NID(certname, NID_commonName, NULL, 0);
website = new char[textlen + 1];
if (X509_NAME_get_text_by_NID(certname, NID_commonName, website, textlen + 1) == textlen && textlen > 0) {
merchant = website;
} else {
throw SSLVerifyError("Bad certificate, missing common name.");
}
// TODO: detect EV certificates and set merchant = business name instead of unfriendly NID_commonName ?
} catch (SSLVerifyError& err) {
fResult = false;
qWarning() << "PaymentRequestPlus::getMerchant : SSL error: " << err.what();
}
if (website)
delete[] website;
X509_STORE_CTX_free(store_ctx);
for (unsigned int i = 0; i < certs.size(); i++)
X509_free(certs[i]);
return fResult;
return false;
}
QList<std::pair<CScript, CAmount> > PaymentRequestPlus::getPayTo() const
{
QList<std::pair<CScript, CAmount> > result;
for (int i = 0; i < details.outputs_size(); i++) {
const unsigned char* scriptStr = (const unsigned char*)details.outputs(i).script().data();
CScript s(scriptStr, scriptStr + details.outputs(i).script().size());
result.append(make_pair(s, details.outputs(i).amount()));
}
return result;
return QList<std::pair<CScript, CAmount> >();
}
+7 -21
View File
@@ -6,12 +6,11 @@
#ifndef BITCOIN_QT_PAYMENTREQUESTPLUS_H
#define BITCOIN_QT_PAYMENTREQUESTPLUS_H
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include "paymentrequest.pb.h"
#pragma GCC diagnostic pop
#include "amount.h"
#include "script/script.h"
#include "base58.h"
#include <string>
#include <utility>
#include <openssl/x509.h>
@@ -19,11 +18,9 @@
#include <QList>
#include <QString>
//
// Wraps dumb protocol buffer paymentRequest
// with extra methods
//
// BIP70 payment requests were removed from the wallet to eliminate the
// obsolete protobuf dependency. The class stays as a compatibility shim for
// serialized wallet/order-form code paths that check for payment requests.
class PaymentRequestPlus
{
public:
@@ -31,21 +28,10 @@ public:
bool parse(const QByteArray& data);
bool SerializeToString(std::string* output) const;
bool IsInitialized() const;
QString getPKIType() const;
// Returns true if merchant's identity is authenticated, and
// returns human-readable merchant identity in merchant
bool getMerchant(X509_STORE* certStore, QString& merchant) const;
// Returns list of outputs, amount
QList<std::pair<CScript, CAmount> > getPayTo() const;
const payments::PaymentDetails& getDetails() const { return details; }
private:
payments::PaymentRequest paymentRequest;
payments::PaymentDetails details;
};
#endif // BITCOIN_QT_PAYMENTREQUESTPLUS_H
+29 -241
View File
@@ -48,14 +48,7 @@ using namespace std;
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
const QString BITCOIN_IPC_PREFIX("agrarian:");
// BIP70 payment protocol messages
const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest";
// BIP71 payment protocol media types
const char* BIP71_MIMETYPE_PAYMENT = "application/agrarian-payment";
const char* BIP71_MIMETYPE_PAYMENTACK = "application/agrarian-paymentack";
const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/agrarian-paymentrequest";
// BIP70 max payment request size in bytes (DoS protection)
// BIP70 max payment request size in bytes (kept for compatibility tests).
const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE = 50000;
struct X509StoreDeleter {
@@ -210,16 +203,7 @@ void PaymentServer::ipcParseCommandLine(int argc, char* argv[])
}
} else if (QFile::exists(arg)) // Filename
{
savedPaymentRequests.append(arg);
PaymentRequestPlus request;
if (readPaymentRequestFromFile(arg, request)) {
if (request.getDetails().network() == "main") {
SelectParams(CBaseChainParams::MAIN);
} else if (request.getDetails().network() == "test") {
SelectParams(CBaseChainParams::TESTNET);
}
}
qWarning() << "PaymentServer::ipcSendCommandLine : BIP70 payment request files are no longer supported: " << arg;
} else {
// Printing to debug.log is about the best we can do here, the
// GUI hasn't started yet so we can't pop up a message box.
@@ -271,10 +255,6 @@ PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : QObject(p
netManager(0),
optionsModel(0)
{
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
// Install global event filter to catch QFileOpenEvents
// on Mac: sent when you click agrarian: links
// other OSes: helpful when dealing with payment request files (in the future)
@@ -295,14 +275,12 @@ PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : QObject(p
tr("Cannot start agrarian: click-to-pay handler"));
} else {
connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection()));
connect(this, SIGNAL(receivedPaymentACK(QString)), this, SLOT(handlePaymentACK(QString)));
}
}
}
PaymentServer::~PaymentServer()
{
google::protobuf::ShutdownProtobufLibrary();
}
//
@@ -374,21 +352,10 @@ void PaymentServer::handleURIOrFile(const QString& s)
QUrlQuery uri((QUrl(s)));
if (uri.hasQueryItem("r")) // payment request URI
{
QByteArray temp;
temp.append(uri.queryItemValue("r").toUtf8());
QString decoded = QUrl::fromPercentEncoding(temp);
QUrl fetchUrl(decoded, QUrl::StrictMode);
if (fetchUrl.isValid()) {
qDebug() << "PaymentServer::handleURIOrFile : fetchRequest(" << fetchUrl << ")";
fetchRequest(fetchUrl);
} else {
qWarning() << "PaymentServer::handleURIOrFile : Invalid URL: " << fetchUrl;
emit message(tr("URI handling"),
tr("Payment request fetch URL is invalid: %1").arg(fetchUrl.toString()),
CClientUIInterface::ICON_WARNING);
}
qWarning() << "PaymentServer::handleURIOrFile : BIP70 payment request URLs are no longer supported.";
emit message(tr("URI handling"),
tr("BIP70 payment request URLs are no longer supported. Use a standard agrarian: URI with address and amount."),
CClientUIInterface::ICON_WARNING);
return;
} else // normal URI
{
@@ -411,15 +378,10 @@ void PaymentServer::handleURIOrFile(const QString& s)
if (QFile::exists(s)) // payment request file
{
PaymentRequestPlus request;
SendCoinsRecipient recipient;
if (!readPaymentRequestFromFile(s, request)) {
emit message(tr("Payment request file handling"),
tr("Payment request file cannot be read! This can be caused by an invalid payment request file."),
CClientUIInterface::ICON_WARNING);
} else if (processPaymentRequest(request, recipient))
emit receivedPaymentRequest(recipient);
qWarning() << "PaymentServer::handleURIOrFile : BIP70 payment request files are no longer supported: " << s;
emit message(tr("Payment request file handling"),
tr("BIP70 payment request files are no longer supported. Use a standard agrarian: URI instead."),
CClientUIInterface::ICON_WARNING);
return;
}
}
@@ -451,219 +413,45 @@ void PaymentServer::handleURIConnection()
//
bool PaymentServer::readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request)
{
QFile f(filename);
if (!f.open(QIODevice::ReadOnly)) {
qWarning() << QString("PaymentServer::%1: Failed to open %2").arg(__func__).arg(filename);
return false;
}
Q_UNUSED(filename);
Q_UNUSED(request);
// BIP70 DoS protection
if (f.size() > BIP70_MAX_PAYMENTREQUEST_SIZE) {
qWarning() << QString("PaymentServer::%1: Payment request %2 is too large (%3 bytes, allowed %4 bytes).")
.arg(__func__)
.arg(filename)
.arg(f.size())
.arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
return false;
}
QByteArray data = f.readAll();
return request.parse(data);
qWarning() << "PaymentServer::readPaymentRequestFromFile : BIP70 payment request files are no longer supported.";
return false;
}
bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, SendCoinsRecipient& recipient)
{
if (!optionsModel)
return false;
Q_UNUSED(request);
Q_UNUSED(recipient);
if (request.IsInitialized()) {
const payments::PaymentDetails& details = request.getDetails();
// Payment request network matches client network?
if (details.network() != Params().NetworkIDString()) {
emit message(tr("Payment request rejected"), tr("Payment request network doesn't match client network."),
CClientUIInterface::MSG_ERROR);
return false;
}
// Expired payment request?
if (details.has_expires() && (int64_t)details.expires() < GetTime()) {
emit message(tr("Payment request rejected"), tr("Payment request has expired."),
CClientUIInterface::MSG_ERROR);
return false;
}
} else {
emit message(tr("Payment request error"), tr("Payment request is not initialized."),
CClientUIInterface::MSG_ERROR);
return false;
}
recipient.paymentRequest = request;
recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo());
request.getMerchant(certStore.get(), recipient.authenticatedMerchant);
QList<std::pair<CScript, CAmount> > sendingTos = request.getPayTo();
QStringList addresses;
for (const auto& sendingTo : sendingTos) {
// Extract and check destination addresses
CTxDestination dest;
if (ExtractDestination(sendingTo.first, dest)) {
// Append destination address
addresses.append(QString::fromStdString(CBitcoinAddress(dest).ToString()));
} else if (!recipient.authenticatedMerchant.isEmpty()) {
// Insecure payments to custom agrarian addresses are not supported
// (there is no good way to tell the user where they are paying in a way
// they'd have a chance of understanding).
emit message(tr("Payment request rejected"),
tr("Unverified payment requests to custom payment scripts are unsupported."),
CClientUIInterface::MSG_ERROR);
return false;
}
// Extract and check amounts
CTxOut txOut(sendingTo.second, sendingTo.first);
if (txOut.IsDust(::minRelayTxFee)) {
emit message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).").arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)),
CClientUIInterface::MSG_ERROR);
return false;
}
recipient.amount += sendingTo.second;
}
// Store addresses and format them to fit nicely into the GUI
recipient.address = addresses.join("<br />");
if (!recipient.authenticatedMerchant.isEmpty()) {
qDebug() << "PaymentServer::processPaymentRequest : Secure payment request from " << recipient.authenticatedMerchant;
} else {
qDebug() << "PaymentServer::processPaymentRequest : Insecure payment request to " << addresses.join(", ");
}
return true;
emit message(tr("Payment request rejected"),
tr("BIP70 payment requests are no longer supported."),
CClientUIInterface::ICON_WARNING);
return false;
}
void PaymentServer::fetchRequest(const QUrl& url)
{
QNetworkRequest netRequest;
netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTREQUEST);
netRequest.setUrl(url);
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
netManager->get(netRequest);
Q_UNUSED(url);
emit message(tr("Payment request rejected"),
tr("BIP70 payment request URLs are no longer supported."),
CClientUIInterface::ICON_WARNING);
}
void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction)
{
const payments::PaymentDetails& details = recipient.paymentRequest.getDetails();
if (!details.has_payment_url())
return;
QNetworkRequest netRequest;
netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
netRequest.setUrl(QString::fromStdString(details.payment_url()));
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BIP71_MIMETYPE_PAYMENT);
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK);
payments::Payment payment;
payment.set_merchant_data(details.merchant_data());
payment.add_transactions(transaction.data(), transaction.size());
// Create a new refund address, or re-use:
QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
std::string strAccount = account.toStdString();
set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount);
if (!refundAddresses.empty()) {
CScript s = GetScriptForDestination(*refundAddresses.begin());
payments::Output* refund_to = payment.add_refund_to();
refund_to->set_script(&s[0], s.size());
} else {
CPubKey newKey;
if (wallet->GetKeyFromPool(newKey)) {
CKeyID keyID = newKey.GetID();
wallet->SetAddressBook(keyID, strAccount, "refund");
CScript s = GetScriptForDestination(keyID);
payments::Output* refund_to = payment.add_refund_to();
refund_to->set_script(&s[0], s.size());
} else {
// This should never happen, because sending coins should have
// just unlocked the wallet and refilled the keypool.
qWarning() << "PaymentServer::fetchPaymentACK : Error getting refund key, refund_to not set";
}
}
int length = payment.ByteSize();
netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
QByteArray serData(length, '\0');
if (payment.SerializeToArray(serData.data(), length)) {
netManager->post(netRequest, serData);
} else {
// This should never happen, either.
qWarning() << "PaymentServer::fetchPaymentACK : Error serializing payment message";
}
Q_UNUSED(wallet);
Q_UNUSED(recipient);
Q_UNUSED(transaction);
}
void PaymentServer::netRequestFinished(QNetworkReply* reply)
{
reply->deleteLater();
// BIP70 DoS protection
if (reply->size() > BIP70_MAX_PAYMENTREQUEST_SIZE) {
QString msg = tr("Payment request %1 is too large (%2 bytes, allowed %3 bytes).")
.arg(reply->request().url().toString())
.arg(reply->size())
.arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
qWarning() << QString("PaymentServer::%1:").arg(__func__) << msg;
emit message(tr("Payment request DoS protection"), msg, CClientUIInterface::MSG_ERROR);
return;
}
if (reply->error() != QNetworkReply::NoError) {
QString msg = tr("Error communicating with %1: %2")
.arg(reply->request().url().toString())
.arg(reply->errorString());
qWarning() << "PaymentServer::netRequestFinished: " << msg;
emit message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
return;
}
QByteArray data = reply->readAll();
QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
if (requestType == BIP70_MESSAGE_PAYMENTREQUEST) {
PaymentRequestPlus request;
SendCoinsRecipient recipient;
if (!request.parse(data)) {
qWarning() << "PaymentServer::netRequestFinished : Error parsing payment request";
emit message(tr("Payment request error"),
tr("Payment request cannot be parsed!"),
CClientUIInterface::MSG_ERROR);
} else if (processPaymentRequest(request, recipient))
emit receivedPaymentRequest(recipient);
return;
} else if (requestType == BIP70_MESSAGE_PAYMENTACK) {
payments::PaymentACK paymentACK;
if (!paymentACK.ParseFromArray(data.data(), data.size())) {
QString msg = tr("Bad response from server %1")
.arg(reply->request().url().toString());
qWarning() << "PaymentServer::netRequestFinished : " << msg;
emit message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
} else {
emit receivedPaymentACK(GUIUtil::HtmlEscape(paymentACK.memo()));
}
}
qWarning() << "PaymentServer::netRequestFinished : Ignoring unexpected network reply after BIP70 removal.";
}
void PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError>& errs)
+4 -41
View File
@@ -6,7 +6,6 @@
#include "paymentservertests.h"
#include "optionsmodel.h"
#include "paymentrequestdata.h"
#include "random.h"
#include "util.h"
@@ -65,50 +64,14 @@ void PaymentServerTests::paymentServerTests()
SelectParams(CBaseChainParams::MAIN);
OptionsModel optionsModel;
PaymentServer* server = new PaymentServer(NULL, false);
X509_STORE* caStore = X509_STORE_new();
X509_STORE_add_cert(caStore, parse_b64der_cert(caCert_BASE64));
PaymentServer::LoadRootCAs(caStore);
server->setOptionsModel(&optionsModel);
server->uiReady();
// Now feed PaymentRequests to server, and observe signals it produces:
std::vector<unsigned char> data = DecodeBase64(paymentrequest1_BASE64);
// BIP70 payment requests are intentionally unsupported in Agrarian 2.0.
// Standard agrarian: URI handling remains covered by uritests.
std::vector<unsigned char> data(128, 0);
SendCoinsRecipient r = handleRequest(server, data);
QString merchant;
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString("testmerchant.org"));
// Version of the above, with an expired certificate:
data = DecodeBase64(paymentrequest2_BASE64);
r = handleRequest(server, data);
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString(""));
// Long certificate chain:
data = DecodeBase64(paymentrequest3_BASE64);
r = handleRequest(server, data);
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString("testmerchant8.org"));
// Long certificate chain, with an expired certificate in the middle:
data = DecodeBase64(paymentrequest4_BASE64);
r = handleRequest(server, data);
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString(""));
// Validly signed, but by a CA not in our root CA list:
data = DecodeBase64(paymentrequest5_BASE64);
r = handleRequest(server, data);
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString(""));
// Try again with no root CA's, verifiedMerchant should be empty:
caStore = X509_STORE_new();
PaymentServer::LoadRootCAs(caStore);
data = DecodeBase64(paymentrequest1_BASE64);
r = handleRequest(server, data);
r.paymentRequest.getMerchant(caStore, merchant);
QCOMPARE(merchant, QString(""));
QCOMPARE(r.paymentRequest.IsInitialized(), false);
unsigned long lDoSProtectionTrigger = (unsigned long) BIP70_MAX_PAYMENTREQUEST_SIZE + 1;
std::string randData(lDoSProtectionTrigger, '\0');
+1 -14
View File
@@ -8,13 +8,13 @@
#include "bitcoinunits.h"
#include "guiutil.h"
#include "paymentserver.h"
#include "transactionrecord.h"
#include "base58.h"
#include "db.h"
#include "main.h"
#include "script/script.h"
#include "swifttx.h"
#include "timedata.h"
#include "guiinterface.h"
#include "util.h"
@@ -258,19 +258,6 @@ QString TransactionDesc::toHTML(CWallet* wallet, CWalletTx& wtx, TransactionReco
if (r.first == "Message")
strHTML += "<br><b>" + tr("Message") + ":</b><br>" + GUIUtil::HtmlEscape(r.second, true) + "<br>";
//
// PaymentRequest info:
//
for (const auto& r : wtx.vOrderForm) {
if (r.first == "PaymentRequest") {
PaymentRequestPlus req;
req.parse(QByteArray::fromRawData(r.second.data(), r.second.size()));
QString merchant;
if (req.getMerchant(PaymentServer::getCertStore(), merchant))
strHTML += "<b>" + tr("Merchant") + ":</b> " + GUIUtil::HtmlEscape(merchant) + "<br>";
}
}
if (wtx.IsCoinBase()) {
quint32 numBlocksToMaturity = Params().COINBASE_MATURITY() + 1;
strHTML += "<br>" + tr("Generated coins must mature %1 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, its state will change to \"not accepted\" and it won't be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.").arg(QString::number(numBlocksToMaturity)) + "<br>";
+18 -45
View File
@@ -284,36 +284,19 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
// Pre-check input data for validity
foreach (const SendCoinsRecipient& rcp, recipients) {
if (rcp.paymentRequest.IsInitialized()) { // PaymentRequest...
CAmount subtotal = 0;
const payments::PaymentDetails& details = rcp.paymentRequest.getDetails();
for (int i = 0; i < details.outputs_size(); i++) {
const payments::Output& out = details.outputs(i);
if (out.amount() <= 0) continue;
subtotal += out.amount();
const unsigned char* scriptStr = (const unsigned char*)out.script().data();
CScript scriptPubKey(scriptStr, scriptStr + out.script().size());
vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, out.amount()));
}
if (subtotal <= 0) {
return InvalidAmount;
}
total += subtotal;
} else { // User-entered agrarian address / amount:
if (!validateAddress(rcp.address)) {
return InvalidAddress;
}
if (rcp.amount <= 0) {
return InvalidAmount;
}
setAddress.insert(rcp.address);
++nAddresses;
CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, rcp.amount));
total += rcp.amount;
if (!validateAddress(rcp.address)) {
return InvalidAddress;
}
if (rcp.amount <= 0) {
return InvalidAmount;
}
setAddress.insert(rcp.address);
++nAddresses;
CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get());
vecSend.push_back(std::pair<CScript, CAmount>(scriptPubKey, rcp.amount));
total += rcp.amount;
}
if (setAddress.size() != nAddresses) {
return DuplicateAddress;
@@ -381,15 +364,9 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran
CWalletTx* newTx = transaction.getTransaction();
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
// Store PaymentRequests in wtx.vOrderForm in wallet.
// Store message from normal agrarian:URI (agrarian:XyZ...?message=example).
foreach (const SendCoinsRecipient& rcp, recipients) {
if (rcp.paymentRequest.IsInitialized()) {
std::string key("PaymentRequest");
std::string value;
rcp.paymentRequest.SerializeToString(&value);
newTx->vOrderForm.push_back(make_pair(key, value));
} else if (!rcp.message.isEmpty()) // Message from normal agrarian:URI (agrarian:XyZ...?message=example)
{
if (!rcp.message.isEmpty()) {
newTx->vOrderForm.push_back(make_pair("Message", rcp.message.toStdString()));
}
}
@@ -410,15 +387,11 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran
// Add addresses / update labels that we've sent to to the address book,
// and emit coinsSent signal for each recipient
foreach (const SendCoinsRecipient& rcp, transaction.getRecipients()) {
// Don't touch the address book when we have a payment request
if (!rcp.paymentRequest.IsInitialized()) {
std::string strAddress = rcp.address.toStdString();
CTxDestination dest = CBitcoinAddress(strAddress).Get();
std::string strLabel = rcp.label.toStdString();
std::string strAddress = rcp.address.toStdString();
CTxDestination dest = CBitcoinAddress(strAddress).Get();
std::string strLabel = rcp.label.toStdString();
updateAddressBookLabels(dest, strLabel, "send");
}
updateAddressBookLabels(dest, strLabel, "send");
emit coinsSent(wallet, rcp, transaction_array);
}
checkBalanceChanged(); // update balance immediately, otherwise there could be a short noticeable delay until pollBalanceChanged hits
+2 -7
View File
@@ -54,12 +54,11 @@ public:
AvailableCoinsType inputType;
bool useSwiftTX;
CAmount amount;
// If from a payment request, this is used for storing the memo
// Message from a standard agrarian: URI.
QString message;
// If from a payment request, paymentRequest.IsInitialized() will be true
// Obsolete BIP70 compatibility field. The shim is intentionally never initialized.
PaymentRequestPlus paymentRequest;
// Empty if no authentication or invalid signature/cert/etc.
QString authenticatedMerchant;
static const int CURRENT_VERSION = 1;
@@ -74,8 +73,6 @@ public:
std::string sLabel = label.toStdString();
std::string sMessage = message.toStdString();
std::string sPaymentRequest;
if (!ser_action.ForRead() && paymentRequest.IsInitialized())
paymentRequest.SerializeToString(&sPaymentRequest);
std::string sAuthenticatedMerchant = authenticatedMerchant.toStdString();
READWRITE(this->nVersion);
@@ -91,8 +88,6 @@ public:
address = QString::fromStdString(sAddress);
label = QString::fromStdString(sLabel);
message = QString::fromStdString(sMessage);
if (!sPaymentRequest.empty())
paymentRequest.parse(QByteArray::fromRawData(sPaymentRequest.data(), sPaymentRequest.size()));
authenticatedMerchant = QString::fromStdString(sAuthenticatedMerchant);
}
}