mirror of
https://github.com/seigler/dash-docs
synced 2025-07-27 09:46:12 +00:00
* Remove the QR Code error corrections subsection. * Remove the non-example Payment Protocol text from developer examples. * Update reference links and autocrossrefs to reflect above deletions * Fix CSS padding problems reported by @saivann * Remove all HTML comments from autocrossref text to allow easy checking for broken link definitions: find _site -name '*.html' | xargs grep '\]\[' * Add PNG versions of guide and example SVG icons. (optipng run)
452 lines
17 KiB
Markdown
452 lines
17 KiB
Markdown
## Payment Processing
|
|
|
|
### Payment Protocol
|
|
|
|
{% autocrossref %}
|
|
|
|
To request payment using the payment protocol, you use an extended (but
|
|
backwards-compatible) `bitcoin:` URI. For example:
|
|
|
|
~~~
|
|
bitcoin:mjSk1Ny9spzU2fouzYgLqGUD8U41iR35QN\
|
|
?amount=0.10\
|
|
&label=Example+Merchant\
|
|
&message=Order+of+flowers+%26+chocolates\
|
|
&r=http://example.com/pay.php/invoice%3Dda39a3ee
|
|
~~~
|
|
|
|
The browser, QR code reader, or other program processing the URI opens
|
|
the spender's Bitcoin wallet program on the URI. If the wallet program is
|
|
aware of the payment protocol, it accesses the URL specified in the `r`
|
|
parameter, which should provide it with a serialized PaymentRequest
|
|
served with the [MIME][] type `application/bitcoin-paymentrequest`<!--noref-->.
|
|
|
|
{% endautocrossref %}
|
|
|
|
#### PaymentRequest & PaymentDetails
|
|
|
|
{% autocrossref %}
|
|
|
|
The [PaymentRequest][]{:#term-paymentrequest}{:.term} is created with data structures built using
|
|
Google's Protocol Buffers. BIP70 describes these data
|
|
structures in the non-sequential way they're defined in the payment
|
|
request protocol buffer code, but the text below will describe them in
|
|
a more linear order using a simple (but functional) Python CGI
|
|
program. (For brevity and clarity, many normal CGI best practices are
|
|
not used in this program.)
|
|
|
|
The full sequence of events is illustrated below, starting with the
|
|
spender clicking a `bitcoin:` URI or scanning a `bitcoin:` QR code.
|
|
|
|

|
|
|
|
For the script to use the protocol buffer, you will need a copy of
|
|
Google's Protocol Buffer compiler (`protoc`), which is available in most
|
|
modern Linux package managers and [directly from Google.][protobuf] Non-Google
|
|
protocol buffer compilers are available for a variety of
|
|
programming languages. You will also need a copy of the PaymentRequest
|
|
[Protocol Buffer description][core paymentrequest.proto] from the Bitcoin Core source code.
|
|
|
|
{% endautocrossref %}
|
|
|
|
##### Initialization Code
|
|
|
|
{% autocrossref %}
|
|
|
|
With the Python code generated by `protoc`, we can start our simple
|
|
CGI program.
|
|
|
|
{% highlight python %}
|
|
#!/usr/bin/env python
|
|
|
|
## This is the code generated by protoc --python_out=./ 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()
|
|
request = PaymentRequest()
|
|
x509 = X509Certificates()
|
|
{% endhighlight %}
|
|
|
|
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`.
|
|
|
|
{% endautocrossref %}
|
|
|
|
##### Configuration Code
|
|
|
|
{% autocrossref %}
|
|
|
|
Next, we'll set configuration settings which will typically only change
|
|
when the receiver wants to do something differently. The code pushes a
|
|
few settings into the `request` (PaymentRequest) and `details`
|
|
(PaymentDetails) objects. When we serialize them,
|
|
[PaymentDetails][]{:#term-paymentdetails}{:.term} will be contained
|
|
within the PaymentRequest.
|
|
|
|
{% highlight python %}
|
|
## SSL Signature method
|
|
request.pki_type = "x509+sha256" ## Default: none
|
|
|
|
## Mainnet or Testnet?
|
|
details.network = "test" ## Default: main
|
|
|
|
## Postback URL
|
|
details.payment_url = "https://example.com/pay.py"
|
|
|
|
## PaymentDetails version number
|
|
request.payment_details_version = 1 ## Default: 1
|
|
|
|
## Certificate chain
|
|
x509.certificate.append(file("/etc/apache2/example.com-cert.der", "r").read())
|
|
#x509.certificate.append(file("/some/intermediate/cert.der", "r").read())
|
|
|
|
## Load private SSL key into memory for signing later
|
|
priv_key = "/etc/apache2/example.com-key.pem"
|
|
pw = "test" ## Key password
|
|
private_key = load_privatekey(FILETYPE_PEM, file(priv_key, "r").read(), pw)
|
|
{% endhighlight %}
|
|
|
|
Each line is described below.
|
|
|
|
{% highlight python %}
|
|
request.pki_type = "x509+sha256" ## Default: none
|
|
{% endhighlight %}
|
|
|
|
`pki_type`: (optional) tell the receiving wallet program what [Public-Key
|
|
Infrastructure][PKI]{:#term-pki}{:.term} (PKI) type you're using to
|
|
cryptographically sign your PaymentRequest so that it can't be modified
|
|
by a man-in-the-middle attack.
|
|
|
|
If you don't want to sign the PaymentRequest, you can choose a
|
|
[`pki_type`][pp pki type]{:#term-pp-pki-type}{:.term} of `none`
|
|
(the default).
|
|
|
|
If you do choose the sign the PaymentRequest, you currently have two
|
|
options defined by BIP70: `x509+sha1` and `x509+sha256`. Both options
|
|
use the X.509 certificate system, the same system used for HTTP Secure
|
|
(HTTPS). To use either option, you will need a certificate signed by a
|
|
certificate authority or one of their intermediaries. (A self-signed
|
|
certificate will not work.)
|
|
|
|
Each wallet program may choose which certificate authorities to trust,
|
|
but it's likely that they'll trust whatever certificate authorities their
|
|
operating system trusts. If the wallet program doesn't have a full
|
|
operating system, as might be the case for small hardware wallets, BIP70
|
|
suggests they use the [Mozilla Root Certificate Store][mozrootstore]. In
|
|
general, if a certificate works in your web browser when you connect to
|
|
your webserver, it will work for your PaymentRequests.
|
|
|
|
{% highlight python %}
|
|
details.network = "test" ## Default: main
|
|
{% endhighlight %}
|
|
|
|
`network`:<!--noref--> (optional) tell the spender's wallet program what Bitcoin network you're
|
|
using; BIP70 defines "main" for mainnet (actual payments) and "test" for
|
|
testnet (like mainnet, but fake satoshis are used). If the wallet
|
|
program doesn't run on the network you indicate, it will reject the
|
|
PaymentRequest.
|
|
|
|
{% highlight python %}
|
|
details.payment_url = "https://example.com/pay.py"
|
|
{% endhighlight %}
|
|
|
|
`payment_url`: (required) tell the spender's wallet program where to send the Payment
|
|
message (described later). This can be a static URL, as in this example,
|
|
or a variable URL such as `https://example.com/pay.py?invoice=123.`
|
|
It should usually be an HTTPS address to prevent man-in-the-middle
|
|
attacks from modifying the message.
|
|
|
|
{% highlight python %}
|
|
request.payment_details_version = 1 ## Default: 1
|
|
{% endhighlight %}
|
|
|
|
`payment_details_version`: (optional) tell the spender's wallet program what version of the
|
|
PaymentDetails you're using. As of this writing, the only version is
|
|
version 1.
|
|
|
|
{% highlight python %}
|
|
## This is the pubkey/certificate corresponding to the private SSL key
|
|
## that we'll use to sign:
|
|
x509.certificate.append(file("/etc/apache2/example.com-cert.der", "r").read())
|
|
{% endhighlight %}
|
|
|
|
`x509certificates`:<!--noref--> (required for signed PaymentRequests) you must
|
|
provide the public SSL key/certificate corresponding to the private SSL
|
|
key you'll use to sign the PaymentRequest. The certificate must be in
|
|
ASN.1/DER format.
|
|
|
|
{% highlight python %}
|
|
## If the pubkey/cert above didn't have the signature of a root
|
|
## certificate authority, we'd then append the intermediate certificate
|
|
## which signed it:
|
|
#x509.certificate.append(file("/some/intermediate/cert.der", "r").read())
|
|
{% endhighlight %}
|
|
|
|
You must also provide any intermediate certificates necessary to link
|
|
your certificate to the root certificate of a certificate authority
|
|
trusted by the spender's software, such as a certificate from the
|
|
Mozilla root store.
|
|
|
|
The certificates must be provided in a specific order---the same order
|
|
used by Apache's `SSLCertificateFile` directive and other server
|
|
software. The figure below shows the [certificate chain][]{:#term-certificate-chain}{:.term} of the
|
|
www.bitcoin.org X.509 certificate and how each certificate (except the
|
|
root certificate) would be loaded into the [X509Certificates][]{:#term-x509certificates}{:.term} protocol
|
|
buffer message.
|
|
|
|

|
|
|
|
To be specific, the first certificate provided must be the
|
|
X.509 certificate corresponding to the private SSL key which will make the
|
|
signature<!--noref-->, called the [leaf certificate][]{:#term-leaf-certificate}{:.term}. Any [intermediate
|
|
certificates][intermediate certificate]{:#term-intermediate-certificate}{:.term} necessary to link that signed public SSL
|
|
key to the [root
|
|
certificate][]{:#term-root-certificate}{:.term} (the certificate authority) are attached separately, with each
|
|
certificate in DER format bearing the signature<!--noref--> of the certificate that
|
|
follows it all the way to (but not including) the root certificate.
|
|
|
|
{% highlight python %}
|
|
priv_key = "/etc/apache2/example.com-key.pem"
|
|
pw = "test" ## Key password
|
|
private_key = load_privatekey(FILETYPE_PEM, file(priv_key, "r").read(), pw)
|
|
{% endhighlight %}
|
|
|
|
(Required for signed PaymentRequests) you will need a private SSL key in
|
|
a format your SSL library supports (DER format is not required). In this
|
|
program, we'll load it from a PEM file. (Embedding your passphrase in
|
|
your CGI code, as done here, is obviously a bad idea in real life.)
|
|
|
|
The private SSL key will not be transmitted with your request. We're
|
|
only loading it into memory here so we can use it to sign the request
|
|
later.
|
|
|
|
{% endautocrossref %}
|
|
|
|
##### Code Variables
|
|
|
|
{% autocrossref %}
|
|
|
|
Now let's look at the variables your CGI program will likely set for
|
|
each payment.
|
|
|
|
{% highlight python %}
|
|
## Amount of the request
|
|
amount = 10000000 ## In satoshis
|
|
|
|
## P2PKH pubkey hash
|
|
pubkey_hash = "2b14950b8d31620c6cc923c5408a701b1ec0a020"
|
|
## P2PKH output script entered as hex and 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 = hex_script.decode("hex")
|
|
|
|
## Load amount and script into PaymentDetails
|
|
details.outputs.add(amount = amount, script = serialized_script)
|
|
|
|
## Memo to display to the spender
|
|
details.memo = "Flowers & chocolates"
|
|
|
|
## Data which should be returned to you with the payment
|
|
details.merchant_data = "Invoice #123"
|
|
{% endhighlight python %}
|
|
|
|
Each line is described below.
|
|
|
|
{% highlight python %}
|
|
amount = 10000000 ## In satoshis (=100 mBTC)
|
|
{% endhighlight %}
|
|
|
|
`amount`: (optional) the [amount][pp amount]{:#term-pp-amount}{:.term} you want the spender to pay. You'll probably get
|
|
this value from your shopping cart application or fiat-to-BTC exchange
|
|
rate conversion tool. If you leave the amount blank, the wallet
|
|
program will prompt the spender how much to pay (which can be useful
|
|
for donations).
|
|
|
|
{% highlight python %}
|
|
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 = hex_script.decode("hex")
|
|
{% endhighlight %}
|
|
|
|
`script`: (required) You must specify the output script you want the spender to
|
|
pay---any valid script is acceptable. In this example, we'll request
|
|
payment to a P2PKH output script.
|
|
|
|
First we get a pubkey hash. The hash above is the hash form of the
|
|
address used in the URI examples throughout this section,
|
|
mjSk1Ny9spzU2fouzYgLqGUD8U41iR35QN.
|
|
|
|
Next, we plug that hash into the standard P2PKH output script using hex,
|
|
as illustrated by the code comments.
|
|
|
|
Finally, we convert the output script from hex into its serialized form.
|
|
|
|
{% highlight python %}
|
|
details.outputs.add(amount = amount, script = serialized_script)
|
|
{% endhighlight %}
|
|
|
|
`outputs`:<!--noref--> (required) add the output script and (optional) amount to the
|
|
PaymentDetails outputs<!--noref--> array.
|
|
|
|
It's possible to specify multiple [`scripts`][pp
|
|
script]{:#term-pp-script}{:.term} and `amounts` as part of a merge
|
|
avoidance strategy, described later in the [Merge Avoidance
|
|
subsection][]. However, effective merge avoidance is not possible under
|
|
the base BIP70 rules in which the spender pays each `script` the exact
|
|
amount specified by its paired `amount`. If the amounts are omitted from
|
|
all `amount`/`script` pairs, the spender will be prompted to choose an
|
|
amount to pay.
|
|
|
|
{% highlight python %}
|
|
details.memo = "Flowers & chocolates"
|
|
{% endhighlight %}
|
|
|
|
`memo`: (optional) add a memo which will be displayed to the spender as
|
|
plain UTF-8 text. Embedded HTML or other markup will not be processed.
|
|
|
|
{% highlight python %}
|
|
details.merchant_data = "Invoice #123"
|
|
{% endhighlight %}
|
|
|
|
`merchant_data`: (optional) add arbitrary data which should be sent back to the
|
|
receiver when the invoice is paid. You can use this to track your
|
|
invoices, although you can more reliably track payments by generating a
|
|
unique address for each payment and then tracking when it gets paid.
|
|
|
|
The [`memo`][pp memo]{:#term-pp-memo}{:.term} field and the [`merchant_data`][pp merchant data]{:#term-pp-merchant-data}{:.term} field can be arbitrarily long,
|
|
but if you make them too long, you'll run into the 50,000 byte limit on
|
|
the entire PaymentRequest, which includes the often several kilobytes
|
|
given over to storing the certificate chain. As will be described in a
|
|
later subsection, the `memo` field can be used by the spender after
|
|
payment as part of a cryptographically-proven receipt.
|
|
|
|
{% endautocrossref %}
|
|
|
|
##### Derivable Data
|
|
|
|
{% autocrossref %}
|
|
|
|
Next, let's look at some information your CGI program can
|
|
automatically derive.
|
|
|
|
{% highlight python %}
|
|
## 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 is complete; serialize it and store it in PaymentRequest
|
|
request.serialized_payment_details = details.SerializeToString()
|
|
|
|
## Serialized certificate chain
|
|
request.pki_data = x509.SerializeToString()
|
|
|
|
## Initialize signature field so we can sign the full PaymentRequest
|
|
request.signature = ""
|
|
|
|
## Sign PaymentRequest
|
|
request.signature = sign(private_key, request.SerializeToString(), "sha256")
|
|
{% endhighlight %}
|
|
|
|
Each line is described below.
|
|
|
|
{% highlight python %}
|
|
details.time = int(time()) ## Current epoch (Unix) time
|
|
{% endhighlight %}
|
|
|
|
`time`: (required) PaymentRequests must indicate when they were created
|
|
in number of seconds elapsed since 1970-01-01T00:00 UTC (Unix
|
|
epoch time format).
|
|
|
|
{% highlight python %}
|
|
details.expires = int(time()) + 60 * 10 ## 10 minutes from now
|
|
{% endhighlight %}
|
|
|
|
`expires`: (optional) the PaymentRequest may also set an [`expires`][pp
|
|
expires]{:#term-pp-expires}{:.term} time after
|
|
which they're no longer valid. You probably want to give receivers
|
|
the ability to configure the expiration time delta; here we used the
|
|
reasonable choice of 10 minutes. If this request is tied to an order
|
|
total based on a fiat-to-satoshis exchange rate, you probably want to
|
|
base this on a delta from the time you got the exchange rate.
|
|
|
|
{% highlight python %}
|
|
request.serialized_payment_details = details.SerializeToString()
|
|
{% endhighlight %}
|
|
|
|
`serialized_payment_details`: (required) we've now set everything we need to create the
|
|
PaymentDetails, so we'll use the SerializeToString function from the
|
|
protocol buffer code to store the PaymentDetails in the appropriate
|
|
field of the PaymentRequest.
|
|
|
|
{% highlight python %}
|
|
request.pki_data = x509.SerializeToString()
|
|
{% endhighlight %}
|
|
|
|
`pki_data`: (required for signed PaymentRequests) serialize the certificate chain
|
|
[PKI data][pp PKI data]{:#term-pp-pki-data}{:.term} and store it in the
|
|
PaymentRequest
|
|
|
|
{% highlight python %}
|
|
request.signature = ""
|
|
{% endhighlight %}
|
|
|
|
We've filled out everything in the PaymentRequest except the signature,
|
|
but before we sign it, we have to initialize the signature field by
|
|
setting it to a zero-byte placeholder.
|
|
|
|
{% highlight python %}
|
|
request.signature = sign(private_key, request.SerializeToString(), "sha256")
|
|
{% endhighlight %}
|
|
|
|
`signature`:<!--noref--> (required for signed PaymentRequests) now we
|
|
make the [signature][ssl signature]{:#term-ssl-signature}{:.term} by
|
|
signing the completed and serialized PaymentRequest. We'll use the
|
|
private key we stored in memory in the configuration section and the
|
|
same hashing formula we specified in `pki_type` (sha256 in this case)
|
|
|
|
{% endautocrossref %}
|
|
|
|
##### Output Code
|
|
|
|
{% autocrossref %}
|
|
|
|
Now that we have PaymentRequest all filled out, we can serialize it and
|
|
send it along with the HTTP headers, as shown in the code below.
|
|
|
|
{% highlight python %}
|
|
print "Content-Type: application/bitcoin-paymentrequest"
|
|
print "Content-Transfer-Encoding: binary"
|
|
print ""
|
|
{% endhighlight %}
|
|
|
|
(Required) BIP71 defines the content types for PaymentRequests,
|
|
Payments, and PaymentACKs.
|
|
|
|
{% highlight python %}
|
|
file.write(stdout, request.SerializeToString())
|
|
{% endhighlight %}
|
|
|
|
`request`: (required) now, to finish, we just dump out the serialized
|
|
PaymentRequest (which contains the serialized PaymentDetails). The
|
|
serialized data is in binary, so we can't use Python's print()
|
|
because it would add an extraneous newline.
|
|
|
|
The following screenshot shows how the authenticated PaymentDetails
|
|
created by the program above appears in the GUI from Bitcoin Core 0.9.
|
|
|
|

|
|
|
|
{% endautocrossref %}
|