Generate Bitcoin Wallet Addresses from Extended Public Key with PHP

Reusing the same Bitcoin wallet address is a big privacy issue.

If you have a simple e-shop or a website which asks for donations you may want to consider generating unique addresses for each transaction instead.

There are numerous payment systems such as Bitpay that do all the hard work for you. The drawback is that they are in charge of your private keys.

You may implement your own simple solution using an extended public key (XPUB) from a hierarchically deterministic (HD) wallet, however.

The whole process is explained in BIP 32. I suggest you read it first to get a general idea of how addresses are derived.

For this tutorial, we will use Electrum, OS X Sierra, Apache 2.4, PHP 7.1 and Bit-Wasp/bitcoin-php.

When it comes to a Bitcoin wallet, any HD wallet (such as Mycelium) will work. The setup process should be the same on any UNIX-like system, especially Linux.

The PHP library and its dependencies require PHP 5.6+. Open a terminal and check your current version:

php -v

In my case the output is:

PHP 7.1.0 (cli) (built: Jan 2 2017 20:09:35) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.1.0-dev, Copyright (c) 1998-2016 Zend Technologies

If your version is smaller than 5.6 you have to upgrade your PHP first.

Install Composer (globally)

Before we can install bitcoin-php library we need to make sure composer is installed.

Open a terminal and type in:

composer -V

If it says something like: Composer version 1.3.0 2016-12-24 00:47:03 you can safely skip this step.

Install it otherwise:

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

Run composer -V again to check whether it was successfully installed.

Install the Bitcoin PHP library

Go to your web server document root (a directory where your websites are stored) and create folders bitcoin/hdkeys.

In my case, the document root is ~/Sites but it may also be /var/www on other UNIX-like systems. If you're unsure check your server settings.

cd ~/Sites
mkdir bitcoin bitcoin/hdkeys
cd bitcoin/hdkeys

Install Bit-Wasp/bitcoin-php library:

composer require bitwasp/bitcoin

It will download the library and dependencies. This process may take a few minutes.

If it didn't output any errors move to the next step.

Generate Wallet Addresses from xpub, ypux and zpub

I wrote a little class that loads all the necessary bitcoin-php classes and wraps certain methods for easy use.

Download it to the directory:

wget https://gist.githubusercontent.com/mariodian/5b67a1f315a74a7753a6f23d0198ec48/raw/2742a7909dd2621381de53209e85348a078df470/HD.php

Firstly, we need to get an extended public key.

Open Electrum, click Wallet, then Master Public Key and copy the string.

In Electrum 3.x, go to Wallet -> Information -> Master Public Key instead.

Legacy address (p2pkh)

Open your favorite text editor, create a file called generate.php, and copy & paste the following code:

<?php
require_once('./HD.php');

$xpub = 'xpub661MyMwAqRbcGYcu6n1FmV1TbE8EwnSKecRZLvKAMyj4qLf15qXsoNryiKNvCkRq3z5kBCeZG8115jj28eVqmeKBJZPqjAfwRD3TGx1w5hY';
$path = '0/0'; // 1st receiving address
// $path = '0/2'; // 3rd receiving address
// $path = '1/0'; // 1st change address
// $path = '1/1'; // 2nd change address

$hd = new HD();
$hd->set_xpub($xpub);
$address = $hd->address_from_master_pub($path);

echo $address;

Make sure to edit $xpub variable according to your own key (you can also use mine).

Open your browser and type in http://localhost/hdkeys/generate.php (or whatever your path is).

The output should be the same as the first address in your Electrum wallet.

Comment/uncomment different paths in the code to see the address changing.

In case you use Mycelium you have to edit the $path variable a little:

$path = "44'/0'/0'/0/0";

This will show you the first wallet address. To increment the address index, edit the last digit.

Native SegWit address (p2wpkh)

For native SegWit addresses (p2wkh) that start with bc1..., please use the following code:

<?php
require_once('./HD.php');

$zpub = 'zpub.........';
$path = '0/0'; // 1st receiving address

$hd = new HD();
$hd->set_zpub($zpub);
$address = $hd->address_from_master_pub($path);

echo $address;

Don't forget to edit the $zpub variable.

Non-native SegWit address (p2sh-p2wpkh)

Most SegWit wallets currently use pay-to-witness-public-key-hash addresses wrapped in p2sh instead.

The example code would be:

<?php
require_once('./HD.php');

$ypub = 'ypub.........';
$path = '0/0'; // 1st receiving address

$hd = new HD();
$hd->set_ypub($ypub);
$address = $hd->address_from_master_pub($path);

echo $address;

Again, change the $ypub variable for your own one.

Multi-signature Address (p2sh)

For this next example, I created a 2-of-2 multi-signature wallet in Electrum.

It means that 2 signatures (out of 2) are needed to sign and broadcast a transaction. The second signature was created from the extended key from the previous example.

Please refer to Electrum documentation if you're struggling to create the wallet.

Go back to your text editor, create a new file called generate_multisig.php and copy & paste the following:

<?php
require_once('./HD.php');

$xpubs = array( 'xpub661MyMwAqRbcGgbfj3mCXkHkx4VUTrvTQQH19ehsU3gEgvSu2MrSwSuvRw8hWAQNdTuG9zYbbXZVP3Er1zpaicGpbJXUptZSsyaLQVD44BW',
'xpub661MyMwAqRbcGYcu6n1FmV1TbE8EwnSKecRZLvKAMyj4qLf15qXsoNryiKNvCkRq3z5kBCeZG8115jj28eVqmeKBJZPqjAfwRD3TGx1w5hY'
);
$m = 2; // how many signatures are needed
$path = '0/0'; // 1st receiving address
// $path = '0/2'; // 3rd receiving address
// $path = '1/0'; // 1st change address
// $path = '0/1'; // 2nd change address

$hd = new HD();
$hd->set_multisig_xpubs($xpubs);
$address = $hd->multisig_address_from_xpub($m, $path);

echo $address;

Again, edit $xpubs accordingly or use my keys. Don't forget to change the $path if you use a different wallet.

Open http://localhost/hdkeys/generate_multisig.php in your browser and you should see the first multi-signature wallet address.

How to Use the Code

Every time you receive a new order, fetch the last address index from the database, increment it and generate a new address.

Save the new wallet address, timestamp and index with the new order.

You can also check for existing addresses that haven't been funded in days/weeks. If you find such address you can assign it to the new order instead.

This will prevent from generating too many addresses.

However, if you do generate lots of addresses, you will need to raise the wallet's gap limit.

Go to Electrum console and run the following command and restart the wallet:

wallet.gap_limit = 100

When you go to Addresses tab you should see more wallets now (highlighted in red).


As you can see the whole solution is just a few lines of code and the main advantage is that your private keys stay with you.

You are not dependent on any 3rd party which is the whole purpose of Bitcoin.