Skip to content
This repository has been archived by the owner on Jun 27, 2022. It is now read-only.

Fetch xpubkey from apps #156

Closed
passabilities opened this issue May 24, 2018 · 12 comments
Closed

Fetch xpubkey from apps #156

passabilities opened this issue May 24, 2018 · 12 comments
Labels

Comments

@passabilities
Copy link

I didn't see any functions to programmatically fetch the xpub key from the device. Could we add this feature? I am able to do this with the Trezor Connect library which I am then able to derive a bunch of addresses from the device to display to the user. Otherwise I have to loop through and call await getAddress if I want to get a bunch of addresses. Which takes a couple seconds.

@gre gre added the feature label Jun 7, 2018
@plondon
Copy link
Contributor

plondon commented Aug 8, 2018

The getAddress will return the publicKey and chainCode (along with the address). You can use these to generate the xpub via the bip32 bitcoinjs-library for the derivation path you pass to getAddress.

Something like:

publicKeyChainCodeToBip32 = (publicKey, chainCode) => {
  const compressedPublicKey = compressPublicKey(Buffer.from(publicKey, 'hex'))
  const bip32 = fromPublicKey(
    compressedPublicKey,
    Buffer.from(chainCode, 'hex')
  )
  return bip32.toBase58()
}

@destrys
Copy link

destrys commented Sep 17, 2018

I second this.

It's not as simple at @plondon says:

  1. To generate a correct xpub, you need the fingerprint of the parent of the node you're interested in. This means if you are trying to get the xpub of m/44'/0'/0'/0 you also need to pull m/44'/0'/0'.
  2. the bip32 package doesn't have a compressPublicKey function, so you have to find that elsewhere.

@plondon
Copy link
Contributor

plondon commented Sep 17, 2018

Why do you need to get the xpub for both m/44'/0'/0'/0 and m/44'/0'/0'? You should only need the xpub generated from the second path, then you can use some bitcoin library to derive further down.

@destrys
Copy link

destrys commented Sep 18, 2018

When I said "you also need to pull m/44'/0'/0'." I meant you need to pull the public key from the ledger device, not the xpub.

If you want to generate the correct xpub of m/44'/0'/0'/0 you need the public keys for both m/44'/0'/0'/0 and m/44'/0'/0' and the chaincode. This is because the xpub includes the fingerprint of the parent node.

from: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
"4 bytes: the fingerprint of the parent's key (0x00000000 if master key)"

ledgerhq has some code to generate the parent fingerprint in their webwallet repo here:
https://github.com/LedgerHQ/ledger-wallet-webtool/blob/master/src/PathFinderUtils.js#L110-L117

  var publicKey = compressPublicKey(nodeData.publicKey);
  publicKey = parseHexString(publicKey);
  var result = bitcoin.crypto.sha256(publicKey);
  result = bitcoin.crypto.ripemd160(result);
  var fingerprint =
    ((result[0] << 24) | (result[1] << 16) | (result[2] << 8) | result[3]) >>>
    0;

@plondon
Copy link
Contributor

plondon commented Sep 18, 2018

Yup you're right @destrys thanks for pointing this out.

@sha49
Copy link

sha49 commented Sep 20, 2018

It is definitely a little bit annoying to do it manually. I created that javascript example just in case is helpful:

import { map, compose, dropLast, last, length } from 'ramda'
import { crypto, HDNode } from 'bitcoinjs-lib'
import * as BIP32 from 'bip32'
import BIP39 from 'bip39'
import Transport from "@ledgerhq/hw-transport-node-hid"
import AppBtc from "@ledgerhq/hw-app-btc"
import * as bippath from 'bip32-path'

const mnemonic = 'deer scout bonus forward rubber rate embrace street tragic know wife tongue photo stool rival century cruise inspire cinnamon before sudden include strong flip'

export const compressPublicKey = publicKey => {
  let prefix = (publicKey[64] & 1) !== 0 ? 0x03 : 0x02
  let prefixBuffer = Buffer.alloc(1)
  prefixBuffer[0] = prefix
  return Buffer.concat([prefixBuffer, publicKey.slice(1, 1 + 32)])
}

export const fingerprint = publickey => {
  let pkh = compose(crypto.ripemd160, crypto.sha256)(publickey)
  return ((pkh[0] << 24) | (pkh[1] << 16) | (pkh[2] << 8) | pkh[3]) >>> 0
}

const getParentPath =
  compose(
    array => bippath.fromPathArray(array).toString(),
    dropLast(1),
    path => bippath.fromString(path).toPathArray()
  )

const createXPUB = (path, child, parent) => {
  let pathArray = bippath.fromString(path).toPathArray()
  let pkChild = compressPublicKey(Buffer.from(child.publicKey, 'hex'))
  let pkParent = compressPublicKey(Buffer.from(parent.publicKey, 'hex'))
  let hdnode = BIP32.fromPublicKey(pkChild, Buffer.from(child.chainCode, 'hex'))
  hdnode.parentFingerprint = fingerprint(pkParent)
  hdnode.depth = pathArray.length
  hdnode.index = last(pathArray)
  return hdnode.toBase58()
}

const getXPUB = async (ledger, path) => {
  let parentPath = getParentPath(path)
  let child = await ledger.getWalletPublicKey(path)
  let parent = await ledger.getWalletPublicKey(parentPath)
  return createXPUB(path, child, parent)
}

////////////////////////////////////////////////////////////////////////////////
const testXPUB = (mnemonic, i) => {
  // assume bitcoin mainnet for the exercise
  let seed = BIP39.mnemonicToSeed(mnemonic)
  let masterNode = HDNode.fromSeedBuffer(seed)
  return masterNode.deriveHardened(44)
                                  .deriveHardened(0)
                                  .deriveHardened(i)
                                  .neutered()
                                  .toBase58()
  }

const connect = async (ledger) => {
  let transport = await Transport.create()
  let btc = new AppBtc(transport)
  return btc
}

const main = async () => {
  let ledger = await connect()
  let xpub = await getXPUB(ledger, "44'/0'/0'")
  console.log('expected XPUB: ', testXPUB(mnemonic, 0))
  console.log('created XPUB: ', xpub)
}

// IT REQUIRES YOUR LEDGER CONNECTED WITH YOUR BTC APP RUNNING
main()

@passabilities
Copy link
Author

Is there any way to get this included in the packages to make it much simpler for the end users?

@gre
Copy link
Contributor

gre commented Oct 27, 2018

Before we provide such feature, I think we first need to improve our libraries and rearchitecture things a bit ( #231 ). The main goal for ledgerjs is to provide an API to communicate with the device, so it first need to come with low level primitives that exactly represent what the device returns (and maps to APDUs). These often happen to be shared cross apps (same APDU serialization living on many different apps). Then, with these functions we can compose and make higher level functions.

Typical example of function that need rework is createPaymentTransactionNew, it should be possible to split into pieces and more maintainable code.

Some usecases might not necessarily live in ledgerjs (one typical example is algorithms to chose UTXO are a bit out of scope of current libraries because implementation choice can differ – yet we can make a smooth API that takes this solver as a function). Anyway, the important part is it needs to be made more easy to build such abstraction & e.g. publish them on NPM.

For things that are very common to have, I guess it should still live in NPM. Getting the XPUB is one of this case I think. We just need to make it right so it's an opt-in (because if it needs many dependencies & if you don't need it)

@patwhite
Copy link

patwhite commented Mar 6, 2019

Hey, where did things end up on this, any movement to get xpubs generated out of the library itself? This is somewhat onerous to add (but, thanks so much to @Pernas for writing that example!)

@destrys
Copy link

destrys commented Mar 22, 2019

This would be really useful function now that the device requires confirmation when using 'unusual' BIP32 paths (LedgerHQ/app-bitcoin#90).

This means that to extract an accurate xpub at say m/45'/0'/5' a user has to click on the device 5 times, I think.

@mxaddict
Copy link

Is getting the xpub still done manually as of the moment in ledgerjs?

IE I see old references to manually getting it done like:

#187 (comment)

AND

https://github.com/LedgerHQ/ledger-live-desktop/blob/2db4cabf071d5bd0db7f36df5fcb67214124e5d2/src/internals/usb/wallet/accounts.js#L123-L139

@gre
Copy link
Contributor

gre commented Dec 22, 2019

The current status quo is this feature is beyond and out of scope of ledgerjs libs that are focused on providing a minimal bindings for apps as well as providing transport for many platforms.
This is a user land feature / can be provided by a helper "HDD" libraries because the logic of infering xpub from chain codes / pub keys is standard and beyond Ledger.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

7 participants