Creating Bitcoin Wallets in JS
In this series, we will implement the core protocols of Bitcoin in JavaScript. By the end of series, we will be able to run a connected network of nodes and send transactions across the network — just like Bitcoin!
To start, we’ll create a wallet. Bitcoin wallets are not stored in the blockchain. Rather, they are managed by each individual user and referenced in individual transactions. A wallet consists of the following parts, which are generated in the same order:
- Private key
- Public key
- Public key hash (PKH)
- Public address
- Private key WIF (wallet import format)
Wallets Are Not Online
An important concept to understand is that you do not need to be online to generate a Bitcoin wallet address. Because the methods to generate an address rely on math, there is no need to connect to a separate server, website or other service.
Let’s look at the code to generate a private key:
const secureRandom = require('secure-random');
let privateKey = secureRandom.randomBuffer(32); console.log('> Private key created: ', privateKey.toString('hex'));
That’s it! You generated a Bitcoin private key!
Now, what is it? Well, you might have been able to tell that it is a 32-bytearray in binary. Since there are 8 bits in a byte, that makes 256 bits.
If you want to know the probability of someone else generating the same private key as you, then it is 2²⁵⁶, which is an astronomically² large number.
2²⁵⁶, but not quite
Okay, there’s one catch that I didn’t explain. Not every private key like this is valid. Only private keys that are less than the following value (in hexadecimal) work with Bitcoin: 0xFFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFF FFFE BAAE DCE6 AF48 A03B BFD2 5E8C D036 4140
.
This is because Bitcoin uses elliptic curve cryptography (ECC) and can only accept private keys below that number. In case that wasn’t enough, the version of elliptic curve cryptography that Bitcoin uses is called secp256k1
, because, well, you know…
Our revised code now looks like this:
const max = Buffer.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140", 'hex'); let isInvalid = true; let privateKey; while (isInvalid) { privateKey = secureRandom.randomBuffer(32); if (Buffer.compare(max, privateKey) === 1) { isInvalid = false; } } console.log('> Private key: ', privateKey.toString('hex'));
We use a while
loop to keep generating private keys until we find one that is below that max
amount. That shouldn’t be hard, since max
is fairly close to 2²⁵⁶.
Generating Public Keys
Once we have a valid private key, we can use the elliptic
library to create a public key for us. Elliptic curve cryptography is amazing — I can use my private key to create a signature and a random person can verify it, without knowing my private key. Pretty amazing!
Elliptic curve cryptography gets heavy in the math department, so I won’t delve too deep here. If you’re interested in learning more about how it works, I would recommend the following resources. Hint: it involves complex spatial geometry and absurdly large prime numbers.
- Wikipedia on Elliptic Curve Cryptography
- Original RFC for Elliptic Curve Digital Signature Algorithm
- Thesis paper on ECC digital signatures
Here’s how the author of “Mastering Bitcoin” sums up ECC:
Elliptic curve cryptography is a type of asymmetric or public key cryptography based on the discrete logarithm problem as expressed by addition and multiplication on the points of an elliptic curve.¹
Now we’ll make use of the elliptic
JavaScript library to generate our public key:
const keys = ecdsa.keyFromPrivate(privateKey); const publicKey = keys.getPublic('hex'); console.log('> Public key created: ', publicKey);
Making Nuggets
John Oliver recently aired a clip of a man making an analogy for Bitcoin — that it is like trying to make a live chicken from a chicken nugget. While the analogy sounds ridiculous, it is correct — hashing is a one-way process that you cannot reverse.
While our current private and public key are enough to create digital signatures, Bitcoin takes security even further. Rather than sending money to someone’s public key and exposing that key, Bitcoin users send money to a hashed version of the public key. This can either be as a public key hash or a public address, both of which are acceptable. Let’s go over how to do both:
- To create a public key hash, we run the public key through both the SHA-256 hashing algorithm and the RIPEMD-160 hashing algorithm. It looks like this:
const sha256 = require('js-sha256'); const ripemd160 = require('ripemd160');
let hash = sha256(Buffer.from(msg, 'hex')); let publicKeyHash = new ripemd160().update(Buffer.from(hash, 'hex')).digest();
- To create a public address, there are more steps involved. We first add the prefix “00” to our
publicKeyHash
. Then we derive the SHA-256 hash of the extended public key hash. Then we derive the SHA-256 hash of that and store the first byte as a “checksum.” We add the checksum to the extended public key hash and encode it with base 58. Whew! I promise it will look more understandable in the code:
function createPublicAddress(publicKeyHash) { // step 1 - add prefix "00" in hex const step1 = Buffer.from("00" + publicKeyHash, 'hex'); // step 2 - create SHA256 hash of step 1 const step2 = sha256(step1); // step 3 - create SHA256 hash of step 2 const step3 = sha256(Buffer.from(step2, 'hex')); // step 4 - find the 1st byte of step 3 - save as "checksum" const checksum = step3.substring(0, 8); // step 5 - add step 1 + checksum const step4 = step1.toString('hex') + checksum; // return base 58 encoding of step 5 const address = base58.encode(Buffer.from(step4, 'hex')); return address; }
Generating Private Key WIF
WIF stands for “wallet import format.” It was a standard introduced to make it easier and more secure for users to migrate wallets from different services.
The process of generating a private key WIF is not very different from generating a public address. Here are the steps:
- We add a prefix to the private key. In this case it is “80” (in hexadecimal)
- We derive the SHA-256 hash of the extended private key.
- We derive the SHA-256 hash of that, and then save the first byte as the checksum.
- We add the checksum to the extended private key and encode it to base 58.
Here’s the code:
function createPrivateKeyWIF(privateKey) { const step1 = Buffer.from("80" + privateKey, 'hex'); const step2 = sha256(step1); const step3 = sha256(Buffer.from(step2, 'hex')); const checksum = step3.substring(0, 8); const step4 = step1.toString('hex') + checksum; const privateKeyWIF = base58.encode(Buffer.from(step4, 'hex')); return privateKeyWIF; }
Checking our Work
Now we have our own Bitcoin wallet that we can use to make transactions. We can check our address in blockchain.info to see our balance. Notice how it is able to infer our public key hash (“Hash 160″) from our address.
There are some other tools to verify our work. At this site we can verify that we are generating the public address properly. At another we can verify that the private key WIF is being generated correctly.
It’s important to verify that your wallet code works correctly, because there are a number mistakes that can be made. One common one is to hash the ascii private key or public key, instead of the actual number, as seen in this Stack Overflow thread. This is why we use Buffer
s to hash.
Conclusion
That’s all for creating Bitcoin wallets. There are other improvements not covered here that have been made over time, such as HD wallets, compressed keys, and passphrase-protected private keys.
Here are a few more links to learn more about Bitcoin wallet creation:
- Stack Overflow thread on Bitcoin address creation
- Bitcoin wiki on base 58 encoding
- Bitcoin wiki on Wallet Import Format (WIF)
Happy coding! If you liked the article, please clap .
¹ Antonopoulos, Andreas M.. Mastering Bitcoin: Programming the Open Blockchain (p. 60). O’Reilly Media. Kindle Edition.
² Literally. There are an estimated 2⁸⁰ atoms in the known universe — much smaller than the number of potential Bitcoin private keys.