I'm trying to learn how HD wallets work internally by writing javascript code that can derive child keys from parent keys. So far, the only thing I've been able to do is convert a BIP39-generated seed to a master key and chain code. I can do that successfully.

Now I would like to convert the master key to an extended key, the long string that starts with xprv. I'd also like to know how to derive child keys from these, and obtain the private key for signing with any of the accounts.

I've been using this confusing BIP32 document as a guide so far, but I'm getting nowhere with it. As far as I can tell, before you derive any keys, you have to serialize the ones you already have.

The serialization process is described like this:

Extended public and private keys are serialized as follows:

  • 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private)
  • 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ....
  • 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
  • 4 bytes: child number. This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key)
  • 32 bytes: the chain code
  • 33 bytes: the public key or private key data (serP(K) for public keys, 0x00 || ser256(k) for private keys)

This 78 byte structure can be encoded like other Bitcoin data in Base58, by first adding 32 checksum bits (derived from the double SHA-256 checksum), and then converting to the Base58 representation. This results in a Base58-encoded string of up to 112 characters. Because of the choice of the version bytes, the Base58 representation will start with "xprv" or "xpub" on mainnet, "tprv" or "tpub" on testnet.

Here is the javascript code I wrote in attempting to follow these instructions.

const crypto = require ('crypto');
const base58 = require ('base58');

const master_key_hex = '5a02ee5cb622f15a4e7ab3dfe7faa9861738da2ae7bb936c685af4dd7b1fbdee';
const chain_code_hex = 'abfbc73691eaad7ca4eb5e25bc51c5804af221cc27a23460634498497f92c92c';

// the 78 bytes of data
var data = '0488ade4';
data += '00';
data += '00000000';
data += '00000000';
data += chain_code_hex;
data += '00' + master_key_hex;

// add the checksum
const hash_1 = crypto.createHash ('sha256');
var sha_256_1 = hash_1.update (data, 'hex').digest ('hex');
const hash_2 = crypto.createHash ('sha256');
var sha_256_2 = hash_2.update (sha_256_1, 'hex').digest ('hex');

var data_with_checksum = data + sha_256_2;

//data = Buffer.from (data_with_checksum, 'hex');
var result = base58.encode (data_with_checksum);
console.log (result);

I tried it with a string and with a Buffer and it gave me the same error message both times. The error message is:

Error: Value passed is not a non-negative safe integer.

I have a feeling I am not even close to doing this right. Does anyone know how it should be coded to derive child keys from a master key?


The base58 module is designed for Flickr; it uses a different alphabet ordering than Bitcoin, and only handles fairly small numbers not the size Bitcoin needs. For Bitcoin use bs58 module plus one other fix: the checksum is not the whole SHA256d but only the first 4 bytes, or since you store it in hex the first 8 chars:

const bs58 = require ('bs58');
... same except 
var data_with_checksum = data + sha_256_2.substring(0,8);
var result = bs58.encode(Buffer.from(data_with_checksum,'hex'));

However, as the bs58 page says, there is a bs58check module that combines the checksum and encode for you:

const bs58check = require ('bs58check');
... first two parts only, computing data
var result = bs58check.encode(Buffer.from(data,'hex'));
-> same

And the same github user also has bip32 and bip32-utils modules that probably do the whole job for you, although the readme's are fairly limited and you'll probably need to look at the code (I didn't have time).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.