Bitcoinjs-lib: derive bip44 hardened addresses from xpub key

Created on 18 Oct 2017  路  10Comments  路  Source: bitcoinjs/bitcoinjs-lib

It got my issues with deriving addresses from xpub keys answered very well: #584

Unfortunately, I run into problems when deriving addresses from xpub keys with hardened indexes. I checked the unit tests (https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.js#L50) which work great, however not with only a xpub as an input.

This triggers error: TypeError: Could not derive hardened child key

bitcoin.HDNode.fromBase58('xpub...').derivePath("m/44'/0'/0/0").getAddress();

This works without problems

bitcoin.HDNode.fromBase58('xpub...').derivePath("m/0/0").getAddress();

Strangely with testnet it doesn't work, first line triggers the error: Error: Not a master node, second line works correctly

bitcoin.HDNode.fromBase58('tpub...', [bitcoin.networks.testnet]).derivePath("m/0/0").getAddress()
bitcoin.HDNode.fromBase58('tpub...', [bitcoin.networks.testnet]).derive(0).derive(0).getAddress()

My goal is to derive addresses from xpub keys exported from bip44 wallets. Testnet should also be supported. Can somebody give me an answer how this can be done? Referring to issue #584, is it only possible for non-hardened indexes?

how to / question / docs

Most helpful comment

Just to give you an idea of how the library works... here's how:

let seed16to64bytes = someMnemonicObject.toSeedBuffer(); // BIP39 gives 64 byte hash of the phrase
let xprvString = bitcoin.HDNode.fromSeedBuffer(seed16to64bytes).toBase58();
// ----------------------------------------------------
// 2 levels of 0' here as per BIP44 spec
let xpubString = bitcoin.HDNode.fromBase58(xprvString).derivePath("m/44'/0'/0'").neutered().toBase58();
// no m/ since this xpub is the 3rd layer, not the top layer of the HD tree
let address = bitcoin.HDNode.fromBase58(xpubString).derivePath("0/0").getAddress();

Edit: xpubs (neutered HDNodes) can not derive a ' path, any number with a ' after it will throw the error you got.

All 10 comments

TypeError: Could not derive hardened child key

That is your Error :), it probably shouldn't be a TypeError.

You can't derive hardened keys using an xpub.

Just to give you an idea of how the library works... here's how:

let seed16to64bytes = someMnemonicObject.toSeedBuffer(); // BIP39 gives 64 byte hash of the phrase
let xprvString = bitcoin.HDNode.fromSeedBuffer(seed16to64bytes).toBase58();
// ----------------------------------------------------
// 2 levels of 0' here as per BIP44 spec
let xpubString = bitcoin.HDNode.fromBase58(xprvString).derivePath("m/44'/0'/0'").neutered().toBase58();
// no m/ since this xpub is the 3rd layer, not the top layer of the HD tree
let address = bitcoin.HDNode.fromBase58(xpubString).derivePath("0/0").getAddress();

Edit: xpubs (neutered HDNodes) can not derive a ' path, any number with a ' after it will throw the error you got.

Thank you for your fast answers @junderw @dcousens! This helped me a lot in understanding! I checked the wallets and found out that they already export the xpub key based on the derivation path e.g. m/44'/0'.

Hello, I'm trying to derive a testnet xpub (generated with electrum --testnet).
but when passing the testnet network to HDnode i get an error.

this is my code

import { HDNode, networks } from 'bitcoinjs-lib'

const network = networks.testnet
const xpub = 'xpub661MyMwAqRbcG4PQrRAT3N2uxTkXWeRq5kpjyDvStBQP7eW65Lu5rZ3MLoBZJQuZFS9FC7mZZEcgxFZxccRdnqSxopraUB6wVjTqp8ZsS4H'
const node = HDNode.fromBase58(xpub, network).neutered()
const address = node.derive(0).derive(0).getAddress()

console.log('address', address)

and this is the error I get

/pathtomyfolder/node_modules/bitcoinjs-lib/src/hdnode.js:78
    version !== network.bip32.public) throw new Error('Invalid network version')
                                      ^

Error: Invalid network version

any idea ? thanks you very much guys

this is not a testnet xpub key! I am not sure how to get a testnet key from electrum, or rather if electrum has testnet support. However you could use android bitcoin wallet for testnet or mycelium testnet wallet to get tpub keys... It should start with tpub.

I hope this xpub is not your production key, otherwise publishing an xpub in the open has serious privacy implications...

@stefanhuber thx for the feedback. indeed it's not a testnet xpub key.
electrum has testnet support but there is a bug when they dump the xpub.
I've been able to generate a tpub following the link mentionned here https://github.com/bitcoinjs/bitcoinjs-lib/issues/949

thanks

otherwise publishing an xpub in the open has serious privacy implications...

And security implications.

Perhaps I need a better understanding of HD nodes and wallets as I don't use them in practice, but here's where I am confused. As mentioned above several times, one cannot derive hardened ' paths from an xpub, they would have to provide the seed. That said, how does one generate the seed in a cold offline environment and then derive keys from that chain without exposing the seed in an online or less secure environment? I feel that having to provide the seed every time a merchant wants to generate an address is a security concern. So what's the solution if you want the seed to remain cold, just use xpubs and non-hardened keys? But as the above 2 posts indicate using a xpubkey comes with privacy and security implications if exposed. I must be misunderstanding something...

Providing the seed is the worst case.
Providing an xpub isn't fantastic either.
Providing an address or explicit public key is probably what you want.

If they need multiple public keys... providing them with an xpub that is solely for their interaction, is probably better.

@coinables
From my earlier post (with extra comments to explain your use case):

The xpubString does not contain any private info, BUT it can derive unlimited addresses (and public keys) that can then be signed using private keys derived from the someMnemonicObject (aka your mnemonic)

// ************* THIS IS DONE OFFLINE
let seed16to64bytes = someMnemonicObject.toSeedBuffer(); // BIP39 gives 64 byte hash of the phrase
let xprvString = bitcoin.HDNode.fromSeedBuffer(seed16to64bytes).toBase58();
// ----------------------------------------------------
// 2 levels of 0' here as per BIP44 spec
let xpubString = bitcoin.HDNode.fromBase58(xprvString).derivePath("m/44'/0'/0'").neutered().toBase58();

// ************* TAKE xpubString STRING OFF THE OFFLINE PC VIA USB OR SOMETHING, BRING THE xpubString TO THE ONLINE COMPUTER

// ************* THIS IS DONE ONLINE
// no m/ since this xpub is the 3rd layer, not the top layer of the HD tree
let address = bitcoin.HDNode.fromBase58(xpubString).derivePath("0/0").getAddress();
// ************* address IS NOW AN ADDRESS THAT CAN BE CONTROLLED BY THE PRIVATE KEYS DERIVED FROM someMnemonicObject AND YOU CAN DERIVE AS MANY ADDRESSES AS YOU WANT (0/1, 0/2, 0/3 ...... 0/2348763 etc.) AND CHANGE ADDRESSES TOO ( 1/0, 1/1, 1/2, 1/3... 1/287366 etc.)
Was this page helpful?
0 / 5 - 0 ratings

Related issues

LeonYanghaha picture LeonYanghaha  路  3Comments

thrastarson picture thrastarson  路  3Comments

itsMikeLowrey picture itsMikeLowrey  路  3Comments

tuyennvtb picture tuyennvtb  路  3Comments

silence-may picture silence-may  路  3Comments