I had recently been tasked with building an application that could produce a BIP70 payment request, much like BitPay now does for all its invoices, the link above describes the payment protocol.

During my quest, I came across a medium article by Sjors Provoost on Decoding a BIP-70 Payment Request where he dissects BitPay’s implementation of the improvement proposal in question. I found this article quite useful as a reference in my endeavours, as bitcore’s documentation on implementing this is… lacking, so cheers for that Sjors!

The implementation I am going to cover today will be done in good ‘ol JavaScript, using bitcore-lib, the following guide can be easily tweaked to work for Bitcoin Cash as well! I’ll note the areas that require these tweaks for it to work for BCH (or BCC, as some exchanges have it listed).

Prerequisites

You will need to have the following setup before proceeding:

  • NodeJS
  • Yarn
  • A domain (for SSL cert)
  • A server running apache or nginx with the domain above pointing to it (for SSL cert + optional deployment)

Getting Started

Setup a new project directory and install bitcore-lib, as well as bitcore-payment-provider.

mkdir my-awesome-payments
cd my-awesome-payments
npm init -y
yarn add bitcore-lib bitcore-payment-protocol

Note: If you’re attempting to build for BCH, install bitcore-lib-cash instead.

Next we’ll need to get ourselves some SSL certificates, why? The BIP70 Payment Protocol uses X.509 certificates to authenticate payment requests, its a security feature to provide “resistance from man-in-the-middle attacks that replace a merchant’s bitcoin address with an attacker’s address before a transaction is authorized with a hardware wallet.”

I will be using Letsencypt to generate free, trusted SSL certificates. If you already have SSL certificates handy that you’d like to use, you can skip ahead to the Implementation section below. Otherwise ssh into your server, then head over to https://certbot.eff.org/ select the webserver you’re running, as well as the OS and version and follow the steps to generate a certificate. If all goes well and you manage to generate a certificate, the new cert (on ubuntu systems) will be located under /etc/letsencrypt/live/{yourdomain}/. One last thing we need to do is to convert the cert.pem file generated for us by letsencrypt to a DER file.

On your server, get into the directory containing your certs and run the following command to convert the file.

cd /etc/letsencrypt/live/yourdomain.com/
openssl x509 -outform der -in cert.pem -out cert.der

Implementation

Head on over back to your project directory and create an index.js file for us to dump our poorly written code into.

First off, lets import a few modules:

const fs = require('fs');
const bitcore = require('bitcore-lib');
const PaymentProtocol = require('bitcore-payment-protocol');

Note: Again for BCH, import bitcore-lib-cash instead. Also note that there’s a bug with bitcore-payment-protocol when used withbitcore-lib-cash, I’ll explain this further at the end.

Lets pull in those certs generated earlier:

const file_with_x509_der_cert = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/cert.der');
const file_with_x509_private_key = fs.readFileSync('/etc/letsencrypt/live/yourdomain.com/privkey.pem', 'utf8');

… and create an instance of PaymentProtocol :

const pp = new PaymentProtocol();

Note: For BCH, add 'BCH' as the first argument for the PaymentProtocolconstructor.

Now for some outputs on our transaction — show me the money!

I’m just going to generate some random addresses for the purposes of this tutorial.

const privateKey = new bitcore.PrivateKey();
const publicKey = bitcore.PublicKey(privateKey);
const address = new bitcore.Address(publicKey);

Now a p2pkh script, this doesn’t have to be a p2pkh, it could be a p2pk or p2ms or even a p2sh. The important part being that we need to convert this script to a buffer.

const script = bitcore.Script.buildPublicKeyHashOut(address).toBuffer();

Create the actual output using our pp from earlier and set the amount as well as the script buffer from above.

const output = pp.makeOutput();
output.set('amount', 100);
output.set('script', script);

Here comes the fun part! Now we build the actual Payment Request:

const now = Date.now() / 1000 | 0;
const details = pp.makePaymentDetails();
details.set('network', 'main');
details.set('outputs', output.message);
details.set('time', now);
details.set('expires', now + 60 * 60 * 24);
details.set('memo', 'A payment request from the merchant.');
details.set('payment_url', 'https://yourwebsite.com/payment');
details.set('merchant_data', Buffer.from(JSON.stringify({"foo": "bar"}));

Just to quickly break down all the properties listed above:

  • network: either main or test, depending on your requirements.
  • outputs: can be an array of outputs, but many wallet clients only support reading one output. Note that only the output.message should be passed through here.
  • time: the time now, when the payment request is created.
  • expires: date and time at which the invoice should expire, I’ve just chucked on a bit of time to the current for demonstration.
  • memo: A string of text describing the transaction.
  • payment_url: A sort of “callback” url that the wallet client sends the payment to upon completion of signing the invoice (see conclusion).
  • merchant_data: An arbitrary string for use internally.

And now we make and sign our payment request using the SSL certificates we obtained and imported earlier:

const certificates = pp.makeX509Certificates();
certificates.set('certificate', [file_with_x509_der_cert]);
const request = pp.makePaymentRequest();
request.set('payment_details_version', 1);
request.set('pki_type', 'x509+sha256');
request.set('pki_data', certificates.serialize());
request.set('serialized_payment_details', details.serialize());
request.sign(file_with_x509_private_key);

Finally, we return our payment request, signed and serialized — to the wallet client:

const rawbody = request.serialize();
return rawbody;
// Example HTTP Response Headers:
// Content-Type: PaymentProtocol.LEGACY_PAYMENT.BTC.REQUEST_CONTENT_TYPE
// Content-Length: request.length
// Content-Transfer-Encoding: 'binary'

Note: For BCH change the Content-Type response header to PaymentProtocol.LEGACY_PAYMENT.BCH.REQUEST_CONTENT_TYPE

Conculsion

I had quite a bit of fun implementing the BIP70 payment request. I hope this article helps out anyone that might have trouble figuring out how to implement this, I for one pulled my hair out on the certificate formats, as well as the script buffer.

This guide does not cover the entire BIP70 protocol implementation, there’s still the handling of the actual payment request thats made back to the server from the client, that needs to be implemented, as well as broadcasting the payment and sending back a payment ack to the client. I’m hoping to cover that in another post.

Thanks for reading!

A final note on BCH implementation with the above, bitcore-payment-protocolconsumes bitcore-lib internally and may cause issues with your application, as after this library is required, bitcore-lib now becomes the reference to bitcore, overriding bitcore-lib-cash . Importing bitcore-payment-protocolbefore bitcore-lib-cash may resolve the issue.