bitcoinjs / bitcoinjs-lib

A javascript Bitcoin library for node.js and browsers.
MIT License
5.62k stars 2.09k forks source link

Trezor generated xPub - 'Invalid network version' #966

Closed Katafalkas closed 6 years ago

Katafalkas commented 6 years ago

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'

dabura667 commented 6 years ago

it says ypub.

ypub is not supported in bitcoinjs-lib.

You can base58 decode, replace the version with xpub and re-do if you'd like, but getAddress will not get you the Trezor's "3" addresses.

Katafalkas commented 6 years ago

Is there some way to generate such addresses? Sorry, I am quite a noob on this topic. Not sure what topic to even search :D

dcousens commented 6 years ago

https://github.com/bitcoinjs/bitcoinjs-lib/pull/927

dabura667 commented 6 years ago
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
bmclain commented 6 years ago

@dabura667 Doing some testing with an Electrum wallet and the default option in version 3.0.5 is to use zpub and P2WSH addresses. Do you know of any good libraries similar to bitcoinjs-lib that can derive wallet addresses from a zpub?

dabura667 commented 6 years ago

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
bmclain commented 6 years ago

@dabura667 much appreciated.

dcousens commented 6 years ago

Closing in favour of https://github.com/bitcoinjs/bitcoinjs-lib/pull/927

liangfenxiaodao commented 6 years ago

@dabura667 could you please explain a little bit of hdNode.derive(0).derive(0)? I'm not sure why derives twice here. Many thanks.

dabura667 commented 6 years ago

@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

liangfenxiaodao commented 6 years ago

Hi @dabura667, thanks for your quick reply. Yeah, I've read BIP0044 and BIP0032 and things are much clear now. However, I still don't quite understand this sentence "Since xpubs and ypubs can not derive hardened layers", may I ask where can I get more information about xpub, ypub and hardened layer. Thanks a lot.

liangfenxiaodao commented 6 years ago

I found an answer here: https://bitcoin.stackexchange.com/questions/37488/eli5-whats-the-difference-between-a-child-key-and-a-hardened-child-key-in-bip3 🎉

liangfenxiaodao commented 6 years ago

btw, I tried to use @dabura667 's solution to derive address from testnet public key:

tpubDDaftv5ij9iJhHY5ECQSxNiniviKeA6t4aFQGSVKhF3f3uuJZp1sj1WqumHCxjjpVp2A4KB2thuBUaPW5tiKfdzZs4LaFaUP9zoF7SFVYT4

however, I cannot get a correct address out of it...

liangfenxiaodao commented 6 years ago

Ok, I got it now:

var bjs = require('bitcoinjs-lib')

function nodeToP2shSegwitAddressTestNet(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, bjs.networks.testnet)
}

var tpub = 'tpubDDaftv5ij9iJhHY5ECQSxNiniviKeA6t4aFQGSVKhF3f3uuJZp1sj1WqumHCxjjpVp2A4KB2thuBUaPW5tiKfdzZs4LaFaUP9zoF7SFVYT4'

var hdNode = bjs.HDNode.fromBase58(tpub, [bjs.networks.testnet])

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

console.log(address)

bjs.networks.testnet needs to be used as a parameter of address.fromOutputScript

larafale commented 5 years ago

any idea on how to make this work with latest version (4.0.1) ?

i'm having trouble with the nodeToP2shSegwitAddress(hdNode) function. hdNode.keyPair.getPublicKeyBuffer() does not exist, because the node is not created from HDNode anymore but with bip32.fromBase58 instead.

so I do I get a publickeyBuffer from a node that was created with bip32.fromBase58

thx for the feedback

larafale commented 5 years ago

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 
  })
})
Jasonvdb commented 5 years ago

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)
alexander-morris commented 5 years ago

@Jasonvdb thanks for the great example - I've been toying with these a lot recently and appreciate the clarity.

Any chance anyone has gotten this process working for the other funny extended keys trezor uses such as dogecoin (dgub...) or dash (drkp...) etc ?

Seems a bit backwards to require a separate adaptation for each token but I'm hoping I'm missing something.

junderw commented 5 years ago

@alexander-morris the function given in the example does not care what the prefix of the input is:

If you look at the function it literally is just throwing away the old prefix without checking it and adding the xpub prefix.

function ypubToXpub(ypub) {
    var data = b58.decode(ypub) // decode... this could be dgub or drkp or whatever
    data = data.slice(4) // throw away first four letters
    data = Buffer.concat([Buffer.from('0488b21e','hex'), data]) // add "xpub"
    return b58.encode(data) // re-encode
}
mareksip commented 5 years ago

@Jasonvdb @junderw thanks for examples, I have been looking for ypub solution for endless hours.

I have tried above ypubToXpub function with input:

ypub6X9uc4mFxvgtsPBYqxDfVdm15rP7jRZXJKKC42Gm4BNaqTPueZcb4LA1eaFM6sKokUJavLYzm7F5HvKPyQ7HrGHPihkVabahFVCug5HyvbS

received:

xpub6CKeJQ6LpF9R25zS1bS3HYfVutEfnoa2PCnyGdNsgAzhnMagPuT2SGVsdNHm6xftLqBnArxSJStXQdhqFhhH42bnrN44zgmCym9GHVuP69E which is not valid according to http://bip32.org/ and also fails with function bitcoinjs.bip32.fromBase58 with error: Invalid checksum

What am I missing?

Jasonvdb commented 5 years ago

@mareksip looks like that maybe a P2WPKH of a ypub format but you're looking for a P2WSH version. Not sure of the difference myself but Jameson Lopp made a handy open source convertor. You should be able to input your version and get what you're looking for: https://jlopp.github.io/xpub-converter/

junderw commented 5 years ago

@mareksip b58 in the example is the package bs58check and not bs58

junderw commented 5 years ago

You used: var b58 = require('bs58') When you should have used var b58 = require('bs58check')

mareksip commented 5 years ago

@Jasonvdb thanks for the reply, I was using bs58 instead bs58check lib.