Payment Processing
Payment Protocol
To request a payment through the payment protocol, an extended (and backwards-compatible) “bitcoinevo:” URI can be used. For example:Warning: The payment protocol is considered outdated and is scheduled to be removed in a future version of BitcoinEvo Core. It has several security design and implementation weaknesses found in some wallets. Deprecation notices will start appearing in BitcoinEvo Core version 0.18 when using BIP70 URIs. Merchants are advised to shift from BIP70 to safer alternatives like BIP21. BIP70 payments should not be mandatory, and merchants must offer BIP21 alternatives.
When a user clicks this URI, the browser, QR code scanner, or another relevant program launches the BitcoinEvo wallet linked to the URI. If the wallet recognizes the payment protocol, it will fetch the URL in the “r” parameter, which should contain a serialized PaymentRequest, delivered with the MIME type application/bitcoinevo-paymentrequest.bitcoinevo:mjSk1Ny9spzU2fouzYgLqGUD8U41iR35QN\
?amount=0.10\
&label=Example+Merchant\
&message=Order+of+flowers+%26+chocolates\
&r=https://example.com/pay.php/invoice%3Dda39a3ee
PaymentRequest & PaymentDetails
The PaymentRequest is generated using data structures based on Google’s Protocol Buffers. BIP70 defines these structures in a non-linear way through the payment request protocol buffer code, but the description below provides a simplified and more linear explanation using a basic Python CGI program. (Please note, standard CGI best practices are not fully implemented here for simplicity.)The entire process starts when the spender interacts with a “bitcoinevo:” URI or scans a bitcoinevo: QR code.
BIP70 Payment Protocol
Initialization Code
With the Python code generated byprotoc
, we can start our simple CGI program.#!/usr/bin/env python
## This is the code generated by protoc --python_out=./ [paymentrequest][paymentrequest].proto
from paymentrequest_pb2 import *
## Load some functions
from time import time
from sys import stdout
from OpenSSL.crypto import FILETYPE_PEM, load_privatekey, sign
## Copy three of the classes created by protoc into objects we can use
details = [PaymentDetails][paymentdetails]()
request = [PaymentRequest][paymentrequest]()
x509 = [X509Certificates][x509certificates]()
The startup code above is quite simple, requiring nothing but the epoch (Unix date) time function, the standard out file descriptor, a few functions from the OpenSSL library, and the data structures and functions created by protoc
.
Configuration Code
Next, we’ll define the configuration settings, which generally only change when the receiver modifies something. The code inputs a few configurations into thePaymentRequest
and PaymentDetails
objects. Once serialized, the PaymentDetails
will be embedded within the PaymentRequest
.# Specify SSL signature method
request.pki_type = "x509+sha256" # Default: none
# Mainnet or testnet?
details.network = "test" # Default: main
# Postback URL for payment processing
details.payment_url = "https://example.com/pay.py"
# PaymentDetails version number
request.payment_details_version = 1 # Default: 1
# SSL certificate chain
x509.certificate.append(open("/etc/apache2/example.com-cert.der", "r").read())
#x509.certificate.append(open("/path/to/intermediate/cert.der", "r").read())
# Load the private SSL key for signing purposes
priv_key = "/etc/apache2/example.com-key.pem"
pw = "test" # Key password
private_key = load_privatekey(FILETYPE_PEM, open(priv_key, "r").read(), pw)
Each line is explained below:
Therequest.pki_type = "x509+sha256" ## Default: none
pki_type
(optional) informs the recipient wallet program which Public-Key Infrastructure (PKI) type you are using to sign the PaymentRequest cryptographically, protecting it from tampering during transit.If you opt not to sign the PaymentRequest, you can set the
pki_type
to none (default).When signing the PaymentRequest, BIP70 currently defines two options: x509+sha1 and x509+sha256. Both utilize the X.509 certificate system, the same system used by HTTPS. To use either, you need a certificate issued by a certificate authority or one of its intermediaries (self-signed certificates are not accepted).
Wallet programs may select which certificate authorities they trust, generally relying on the same authorities trusted by their operating system. For small hardware wallets without full operating systems, BIP70 suggests using the Mozilla Root Certificate Store. If a certificate works in your web browser, it will most likely work for your PaymentRequests as well.
details.network = "test" ## Default: main
The network
field (optional) specifies to the spender’s wallet which BitcoinEvo network is being used. According to BIP70, “main” represents the mainnet (real payments) and “test” represents the testnet (a testing environment where fake satoshis are used). If the wallet is not configured to operate on the indicated network, it will decline the PaymentRequest.Thedetails.payment_url = "https://example.com/pay.py"
payment_url
(required) instructs the spender’s wallet program where to send the Payment message (which will be explained later). This can either be a fixed URL, like the example here, or a dynamic one such as https://example.com/pay.py?invoice=123
. Typically, an HTTPS address should be used to safeguard against man-in-the-middle attacks altering the message.Therequest.payment_details_version = 1 ## Default: 1
payment_details_version
(optional) communicates to the spender’s wallet what version of the PaymentDetails is being utilized. Currently, only version 1 exists.The## This is the pubkey/certificate corresponding to the private SSL key
## that we'll use to sign:x509.certificate.append(open("/etc/apache2/example.com-cert.der", "r").read())
x509certificates
field (required for signed PaymentRequests) must contain the public SSL certificate associated with the private SSL key that will be used to sign the PaymentRequest. This certificate must be in ASN.1/DER format.## If the pubkey/cert above didn't have the signature of a root
## certificate authority, we'd then append the [intermediate certificate][intermediate certificate]
## which signed it:
#x509.certificate.append(open("/some/intermediate/cert.der", "r").read())
If the public key or certificate specified above is not directly signed by a root certificate authority, an intermediate certificate must also be appended to link it to the root authority trusted by the spender’s wallet. This intermediate certificate can come from trusted sources like the Mozilla root store.
Certificates must be supplied in a particular order, which mirrors how software like Apache’s SSLCertificateFile directive handles them. The diagram below demonstrates the certificate chain for the www.bitcoinevo.org X.509 certificate and shows how each certificate (except for the root certificate) would be loaded into the X509Certificates protocol buffer message.
X509Certificates Loading Order
When handling signed PaymentRequests, you will require a private SSL key in a format supported by your SSL library (though DER format is not mandatory). In this example, the key is loaded from a PEM file. (It’s important to note that embedding a passphrase directly in your CGI code, as shown here, is not recommended in a real-world scenario.)priv_key = "/etc/apache2/example.com-key.pem"
pw = "test" ## Password for key
private_key = load_privatekey(FILETYPE_PEM, open(priv_key, "r").read(), pw)
The private SSL key itself will not be sent along with the PaymentRequest. Instead, it is loaded into memory solely for the purpose of signing the request later on.
Code Variables
Now, let’s explore the variables your CGI program will typically configure for each payment.## Payment Amount
amount = 10000000 ## In satoshis
## Public Key Hash (P2PKH)
pubkey_hash = "2b14950b8d31620c6cc923c5408a701b1ec0a020"
## P2PKH pubkey script encoded as hexadecimal, then converted to binary
## OP_DUP OP_HASH160 <push 20 bytes> <pubKey hash> OP_EQUALVERIFY OP_CHECKSIG
## 76 a9 14 <pubKey hash> 88 ac
hex_script = "76" + "a9" + "14" + pubkey_hash + "88" + "ac"
serialized_script = bytes.fromhex(hex_script)
## Load Amount and Pubkey Script into PaymentDetails
details.outputs.add(amount=amount, script=serialized_script)
## Memo Displayed to Spender
details.memo = "Flowers & chocolates"
## Merchant Data to Return with Payment
Each line is explained below:details.merchant_data = "Invoice #123"
“amount”: (optional) the amount the spender is required to pay. This value is generally provided by your shopping cart system or a tool that converts fiat currency to BTCE. If this field is left empty, the wallet program will prompt the spender to input an amount (useful for donations).amount = 10000000 # In satoshis (=100 mBTCE)
“script”: (required) This field specifies the pubkey script that the spender will pay to — any valid pubkey script can be used. In this example, the payment is directed to a P2PKH pubkey script.pubkey_hash = "2b14950b8d31620c6cc923c5408a701b1ec0a020"
# OP_DUP OP_HASH160 <push 20 bytes> <pubKey hash> OP_EQUALVERIFY OP_CHECKSIG
# 76 a9 14 <pubKey hash> 88 ac
hex_script = "76" + "a9" + "14" + pubkey_hash + "88" + "ac"
serialized_script = bytes.fromhex(hex_script)
First, the pubkey hash is obtained. The hash above corresponds to the address used in previous examples,
mjSk1Ny9spzU2fouzYgLqGUD8U41iR35QN
.Then, the hash is inserted into the standard P2PKH pubkey script in hexadecimal form, as explained in the comments.
Finally, the pubkey script is transformed from its hex form into binary format.
outputs: (required) This adds the pubkey script along with the (optional) amount to the outputs array within the PaymentDetails object.details.outputs.add(amount=amount, script=serialized_script)
It’s possible to specify multiple script-amount pairs for merge avoidance, but this technique is not fully compatible with the base rules of BIP70. Under BIP70, the spender must pay the exact amount specified for each pubkey script. If no amounts are defined for the scripts, the spender will be prompted to choose how much to pay.
“memo”: (optional) A message can be added here, which will be shown to the spender in plain UTF-8 text. HTML or other markup won’t be rendered.details.memo = "Flowers & chocolates"
“merchant_data“: (optional) This field allows you to include any data that should be sent back with the payment. It’s commonly used to track invoices, though it is more reliable to track payments by generating unique addresses for each payment and monitoring when those addresses receive funds.etails.merchant_data = "Invoice #123"
The “memo” field can be of arbitrary length, but making it too long may push the PaymentRequest past the 50,000 byte limit. This limit includes space used to store the certificate chain, which can be several kilobytes. As will be covered later, the memo can also be used as part of a cryptographically verifiable receipt after payment.
Derivable Data
Now, let’s explore some information that your CGI program can automatically generate.## Request creation time
details.time = int(time()) ## Current epoch (Unix) time
## Request expiration time
details.expires = int(time()) + 60 * 10 ## 10 minutes from now
## [PaymentDetails][paymentdetails] is complete; serialize it and store it in [PaymentRequest][paymentrequest]
request.serialized_payment_details = details.SerializeToString()
## Serialized [certificate chain][certificate chain]
request.pki_data = x509.SerializeToString()
## Initialize signature field so we can sign the full [PaymentRequest][paymentrequest]
request.signature = ""
## Sign [PaymentRequest][paymentrequest]
request.signature = sign(private_key, request.SerializeToString(), "sha256")
Each line is explained below:time: (required) PaymentRequests must indicate the creation time in seconds since January 1, 1970, at midnight UTC (Unix epoch time format).details.time = int(time()) # Current Unix epoch time
details.expires = int(time()) + 60 * 10 ## 10 minutes from now
“expires”: (optional) You can set an expiration time after which the PaymentRequest becomes invalid. This value is configurable by the receiver, and in this example, we set it to expire in 10 minutes. If the request is tied to an exchange rate between fiat currency and satoshis, it’s recommended to set the expiration time relative to when the exchange rate was retrieved.serialized_payment_details: (required) At this point, all necessary information for the PaymentDetails has been set, so we use therequest.serialized_payment_details = details.SerializeToString()
SerializeToString
function from the protocol buffer code to save the PaymentDetails in the appropriate field of the PaymentRequest.pki_data: (required for signed PaymentRequests) This field stores the serialized Public-Key Infrastructure PKI data, specifically the certificate chain, in the PaymentRequest.request.pki_data = x509.SerializeToString()
Before we can sign the PaymentRequest, we must initialize the signature field with an empty string as a placeholder.request.signature = ""
request.signature = sign(private_key, request.SerializeToString(), "sha256")
signature: (required for signed PaymentRequests) Now, we create the signature by signing the fully serialized PaymentRequest. We use the private key that was loaded earlier and apply the sha256 hashing algorithm, as specified in the pki_type
field.
Output Code
Now that thePaymentRequest
has been fully populated, we can serialize it and transmit it along with the required HTTP headers, as demonstrated in the code below.(Required) BIP71 defines the correct content types for PaymentRequests, Payments, and PaymentACKs.print "Content-Type: application/bitcoinevo-paymentrequest"
print "Content-Transfer-Encoding: binary"
print ""
request: (required) Finally, we output the serializedfile.write(stdout, request.SerializeToString())
PaymentRequest
(which includes the serialized PaymentDetails
). Since the serialized data is in binary format, we cannot use Python’s print()
function, as it would inadvertently insert a newline.The following screenshot illustrates the authenticated
PaymentDetails
generated by the program.BitcoinEvo Core Showing Validated Payment Request