Generating a P2PKH address with Node.js

Posted by Trevin Hofmann

How to Generate a Pay-to-Public-Key-Hash Bitcoin Address

Install 21

A Bitcoin address functions like an invoice number, identifying a unique request for payment on the blockchain. There are currently two major versions of Bitcoin addresses, Pay-to-Public-Key-Hash (P2PKH) and Pay-to-Script-Hash (P2SH). In this tutorial, we will be generating the simpler kind, P2PKH, using Node.js.

This tutorial assumes only basic knowledge of cryptographic concepts, such as hashes. To follow along with the Node.js demonstration, first install Node.js and NPM

Then create a new folder, enter it, and install the two libraries that we'll be working with today:

mkdir genp2pkh
cd genp2pkh
npm install eccrypto
npm install bs58

Note: when installing eccrypto, an error message may be produced during the installation of the optional dependency secp256k1. You can ignore this.

Generate a private key

We begin by randomly generating a 256-bit ECDSA private key. It is very important that this key is generated with a cryptographically secure random number generator; a biased random number generator would produce a weaker private key. For the secp256k1 ECDSA curve used in Bitcoin, private keys must be in the range of 0x01 to 0xffff ffff ffff ffff ffff ffff ffff fffe baae dce6 af48 a03b bfd2 5e8c d036 4140.

Use a text editor such as vim, emacs, or nano to open the file p2pkh.js:

nano p2pkh.js

Copy and paste in the following code:

var crypto = require('crypto');

var MIN_PRIVATE_KEY = new Buffer('0000000000000000000000000000000000000000000000000000000000000001', 'hex');
var MAX_PRIVATE_KEY = new Buffer('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140', 'hex');

function validatePrivateKey(privateKey) {
    var isValid = true;

    // must be at least the minimum
    if (privateKey.compare(MIN_PRIVATE_KEY) < 0) {
        isValid = false;
    }

    // must not exceed the maximum
    if (privateKey.compare(MAX_PRIVATE_KEY) > 0) {
        isValid = false;
    }

    return isValid;
}

var privateKey;

do {
    privateKey = crypto.randomBytes(32);
} while (!validatePrivateKey(privateKey));

console.log(privateKey);
//

Now save the file, exit the editor, and then run the code:

nodejs p2pkh.js

It should print something like this (yours will be different because it's based on random data):

<Buffer 82 5e 70 0c 62 77 1c f6 1c f7 4b bc 79 e8 50 25 a1 43 6f 66 d9 7d f9 e0 9a 61 e6 64 e0 a0 ca 2e>

Generate a public key

To continue, we will use the eccrypto library to derive the public key corresponding to our private key. Re-open the file and add the following lines to the end:

var eccrypto = require('eccrypto');

var publicKey = eccrypto.getPublic(privateKey);

console.log(publicKey);
//

Run the script again:

nodejs p2pkh.js

The first line printed is a new private key because the script uses random input. However, this time it also prints that private key's public key.

This public key is the one referred to in Pay-to-Public-Key-Hash. Next, we will calculate the hash in Pay-to-Public-Key-Hash. The hash is one round of SHA256 followed by one round of RIPEMD-160. It is interesting to note that by revealing only this hash in an address, observers are unable to determine the public key until after a payment has been submitted to the network for mining. This helps protect funds in an unredeemed P2PKH output from unknown weaknesses in elliptic curve cryptography.

Generate the key hash

Add the following lines to the end of your file, and re-run the code:

var hash = crypto.createHash('sha256').update(publicKey).digest();

hash = crypto.createHash('ripemd160').update(hash).digest();

console.log(hash);
//

This hash will be the largest piece of the Bitcoin address.

Generate the checksum

First, we need to add a version byte and calculate a 32-bit checksum. The version byte for a P2PKH address is 0x00. Add the following lines to the end of your file and re-run the code:

var version = new Buffer('00', 'hex');

var checksum = Buffer.concat([version, hash]);

checksum = crypto.createHash('sha256').update(checksum).digest();

checksum = crypto.createHash('sha256').update(checksum).digest();

checksum = checksum.slice(0, 4);

console.log(checksum);
//

This checksum is used by clients to validate the address. With a size of 32 bits, it leaves a chance of only 1 out of 232 that a typo or similar error would go undetected.

Output the address

We are now ready to combine the pieces into one address. The address consists of 8 version bits, 160 public key hash bits, and 32 checksum bits. Add the following lines to the end of your file and re-run the code:

var address = Buffer.concat([version, hash, checksum]);

console.log(address);
//

The final step is to encode the 200-bit address into a format easier for humans to read and type. To do this, we use base-58 encoding. Base-58 encoding includes the set of characters 0-9, a-z, and A-Z, but omits the homoglyphs 0, O, I, and l. One more time, add the following lines to the end of your file and re-run the code.

var bs58 = require('bs58');

address = bs58.encode(address);

console.log(address);
//

Conclusion

Congratulations! We now have a usable Bitcoin address. Any payment sent to this Bitcoin address can be redeemed with the private key printed on the first line.

The full code is also available for download in a single file.