bitcoinjs-lib: Trezor generated xPub - 'Invalid network version'

Hi. So I am trying to generate HD addresses from xPub that is generated inside the Trezor.

const network = bitcoin.networks.mainnet;
const xpub = 'ypub6XTWVFLfqkFYarn9NArqtBLziffJttqf1Utaur3sTbTGtgfNaTzkGcRpFgiiieBjQ6rV1rJ7iJ9r9oXGpPXZpkq71yfss2mrKLaauxhjXD4';
const node = bitcoin.HDNode.fromBase58(xpub, network).neutered();
const address = node.derive(0).derive(0).getAddress();

console.log('address', address);
# node index.js 
/vagrant/btc-js/node_modules/bitcoinjs-lib/src/hdnode.js:78
    version !== network.bip32.public) throw new Error('Invalid network version')

Any clues why? It does work with const xpub = 'xpub661MyMwAqRbcG4PQrRAT3N2uxTkXWeRq5kpjyDvStBQP7eW65Lu5rZ3MLoBZJQuZFS9FC7mZZEcgxFZxccRdnqSxopraUB6wVjTqp8ZsS4H'

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 24 (9 by maintainers)

Most upvoted comments

var bjs = require('bitcoinjs-lib')
var b58 = require('bs58check')

// this function takes ypub and turns into xpub
function ypubToXpub(ypub) {
  var data = b58.decode(ypub)
  data = data.slice(4)
  data = Buffer.concat([Buffer.from('0488b21e','hex'), data])
  return b58.encode(data)
}

// this function takes an HDNode, and turns the pubkey of that node into a Segwit P2SH address
function nodeToP2shSegwitAddress(hdNode) {
  var pubkeyBuf = hdNode.keyPair.getPublicKeyBuffer()
  var hash = bjs.crypto.hash160(pubkeyBuf)
  var redeemScript = bjs.script.witnessPubKeyHash.output.encode(hash)
  var hash2 = bjs.crypto.hash160(redeemScript)
  var scriptPubkey = bjs.script.scriptHash.output.encode(hash2)
  return bjs.address.fromOutputScript(scriptPubkey)
}

// convert ypub string into xpub string
var xpub = ypubToXpub('ypub6XTWVFLfqkFYarn9NArqtBLziffJttqf1Utaur3sTbTGtgfNaTzkGcRpFgiiieBjQ6rV1rJ7iJ9r9oXGpPXZpkq71yfss2mrKLaauxhjXD4')

// grab the HDNode object from the xpub
var hdNode = bjs.HDNode.fromBase58(xpub)

// generate as usual, but instead of getAddress, feed into above function
var address = nodeToP2shSegwitAddress(hdNode.derive(0).derive(0))
// 3FkFtj43U6UDZ7wberPtZwUrR3GLMw2S6x

You can basically do the same thing as above. Slightly modified.

var bjs = require('bitcoinjs-lib')
var b58 = require('bs58check')

// this function takes zpub and turns into xpub
function zpubToXpub(zpub) {
  var data = b58.decode(zpub)
  data = data.slice(4)
  data = Buffer.concat([Buffer.from('0488b21e','hex'), data])
  return b58.encode(data)
}

// this function takes an HDNode, and turns the pubkey of that node into a Segwit bech32 address
function nodeToP2wpkhSegwitAddress(hdNode) {
  var pubkeyBuf = hdNode.keyPair.getPublicKeyBuffer()
  var hash = bjs.crypto.hash160(pubkeyBuf)
  var scriptPubkey = bjs.script.witnessPubKeyHash.output.encode(hash)
  return bjs.address.fromOutputScript(scriptPubkey)
}

// convert zpub string into xpub string
var xpub = zpubToXpub('zpub6oFHEbYeAMTVmmmcZA5KJinUFVoGZfQhc4dBfzCMwrL1AsxopkyLv9zCKHezgHJNskU8pRUQ9AZqZjAjdZeM1ehkALJE1UNPboorWDfhPSB')

// grab the HDNode object from the xpub
var hdNode = bjs.HDNode.fromBase58(xpub)

// generate as usual, but instead of getAddress, feed into above function
var address = nodeToP2wpkhSegwitAddress(hdNode.derive(0).derive(0))
// bc1qr8mrj8lsmnl5rpewfphj0rrprs7gkqccmn4z4l

If anyone is still looking for a complete example of how to get addresses from the ypub trezor provides.

Consolidated from off all the answers above (working with version 4.0.2 of bitcoinjs-lib):

var bjs = require('bitcoinjs-lib')
var b58 = require('bs58check')
var bip32 = require('bip32')

//Assuming it's mainnet for trezors
var network = bjs.networks.bitcoin;

function ypubToXpub(ypub) {
    var data = b58.decode(ypub)
    data = data.slice(4)
    data = Buffer.concat([Buffer.from('0488b21e','hex'), data])
    return b58.encode(data)
}

var ypub = "ypub6XTWVFLfqkFYarn9NArqtBLziffJttqf1Utaur3sTbTGtgfNaTzkGcRpFgiiieBjQ6rV1rJ7iJ9r9oXGpPXZpkq71yfss2mrKLaauxhjXD4"

//If you have a ybup address, convert it first
var xpub = ypubToXpub(ypub)

//First address created for account
var addressIndex = 0;

var payment = bjs.payments.p2sh({ 
    redeem: bjs.payments.p2wpkh({ 
        pubkey: bip32.fromBase58(xpub, network).derive(0).derive(addressIndex).publicKey 
    })
  })

var address = payment.address;

console.log(address)

ok I was able to do this with

bjs.payments.p2sh({ 
  redeem: bjs.payments.p2wpkh({ 
    pubkey: bip32.fromBase58(pubkey, network).derive(0).derive(0).publicKey 
  })
})

@jackylimel Every derivation scheme has two non-hardened laters after the last hardened layer.

Since xpubs and ypubs can not derive hardened layers, usually apps show the xpub of the last hardened layer.

In Electrum’s case, they use BIP44 which is

H H H S S (hard hard hard soft soft)

The last two softs are 0|1 for is_change and the address index.

So to derive the “first non-change address” is derive(0).derive(0)

first change would be 1 0

Second non-change is 0 1

Tenth non-change is 0 9