1

Summary

I'm pulling my hair out trying to understand a couple of concepts. I would like to use a Segwit wallet in my Ledger Nano S to derive unhardened(?) child public keys (and thus segwit addresses) that match those that my Nano produces. I want to do this using Python 3. I believe creating an address like this is called a "watch wallet", or at least it is described in the Bip32 docs like this:

This permits for example a webshop business to let its webserver generate fresh addresses (public key hashes) for each order or for each customer, without giving the webserver access to the corresponding private keys (which are required for spending the received funds). https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Motivation

This is precisely what I'm trying to do using Python for my Django application. I do not want to use a third party service as I'm told not to trust giving out my extended public key (xPub). I want to use the xPub from my Ledger Nano S. Actually, I'm not entirely sure if my Nano S uses an xpub or a zpub, but I have asked this in a separate question.

Does Ledger Nano S Have an xPub or a zPub for Bitcoin wallets?

Regardless, I have converted the "xpub" key to a "zpub" key and attempted to derive new keys from both using MANY different python modules and with zero success in creating an address that matches or even looks similar to that which my Nano produces.

What I've Tried

For the sake of eliminating answers that suggest methods I've already tried here is a complete and exhaustive list of every method I have tried, with links to all similar questions on Stack Exchange.

At first I tried btclib along with Pycoin.

Note: in all examples I'm attempting to use the Bitcoin Testnet

from btclib import bip32
from pycoin.networks.registry import network_for_netcode

network = network_for_netcode("XTN") # XTN for the Testnet, BTC for the Mainnet
xpub = "x(z/t)pub-big-long-key-that-starts-with-tpub-zpub-xpub"

child_xpub_key = bip32.derive(xpub, "84'/1'/0'/0/0")
key = network.parse.bip32(child_xpub_key)

print(str("Key: {}").format(child_xpub_key))
print(str("Address: {}").format(key.address())) 

Similar to this question: Derive public key from xpub? I get an error using the "hardened" derivation path and in order for the error to go away I can only use the path 84/1/0/0/0. I have even tried path 0 and 0/0. Of course the path my Nano gives me includes the ('). From my understanding this derivation path is for zpub keys but even if I convert to zpub it doesn't give me an address that matches my Nano. I assume this is because I should be using Bip44 or Bip84 to derive addresses? Correct?

The next thing I attempted to use was bip_utils

from bip_utils import Bip44, Bip44Coins
key_str = "x(z/t)pub-big-long-key-that-starts-with-tpub-zpub-xpub"
bip44_ctx = Bip44.FromExtendedKey(key_str, Bip44Coins.BITCOIN_TESTNET)

This code works, however the example code given at the bip_utils github page suggests that the next line of code should be:

# Derive account 0 for Bitcoin: m/44'/0'/0'
bip44_acc = bip44_ctx.Purpose().Coin().Account(0)

Which gives me the error

Bip44DepthError: Current depth (3) is not suitable for deriving purpose

I already know that I don't want m/44'/0'/0' but the docs give no clue how to change the path and looking at the source code didn't help me either.

Then I tried using pybitcointools by the famous Vitalik Buterin. He even has a part in the docs about "watch wallets":

For security reasons the seed and xprv should ideally be held in cold storage only. If a web application needs to be able to provide addresses on demand, the solution is to use a watch wallet, generated from the xpub. For example, let's take the Dash xpub from a previous example:

from cryptos import *
coin = Dash()
xpub = 'xpub-some-xpub-key'
wallet = coin.watch_wallet(xpub)
wallet.is_watching_only
wallet.new_receiving_address()
wallet.new_change_address()

I'm thinking "this is it, Vitalik is my hero! He know's what I'm looking for!" But NO, this sample code only works for Dash, apparently. When I change the class to Bitcoin(), none of the "watch_wallet" methods described in the docs work, or rather they don't exist. No where is an example of how to make this work with Bitcoin.

Conclusion

Of all the methods I tried, at least btclib didn't give me any errors. I could be using these python modules incorrectly or perhaps there is a better python module/class that I should be using. Any direction would be helpful. Thank you in advance.

1
1

Since you can't traverse through hardened paths with xPubs ledger is displaying not the Master xpub but the xpub at the derivation address 84/0'/0' or whatever your account number is. From there you need two additional derivations of 0/0 to get the final 84/0'/0'/0/0 public key.

The resulting public key can be used to derive either a P2PKH address (non-segwit) or a P2WPKH address (segwit). Xpub and Zpub are both encodings of the same key but Zpub additionally instructs the wallet to derive a P2WPKH address from it. Ledger doesn't make use of this standard and prefers to encode it with the more general Xpub encoding (which provides no instruction).

Using my toy Python library you can use the following script to derive your addresses:

from cryptotools import Xpub

MY_XPUB = 'xpub-as-shown-in-ledger'

key = Xpub.decode(MY_XPUB)

pubkey0 = key/0/0
print(pubkey0.address('P2WPKH'))  # Tell the wallet that you want a P2WPKH address

if you want testnet addresses set the envvar CRYPTOTOOLS_NETWOK = 'test'

1
  • It worked like a charm! Thank you. I took a good look through the source code and I forked it as well. I understand that you have a disclaimer about security. Is there anything I should be aware of?
    – mike-a
    Oct 5 at 17:34
0

Pycoin now supports segwit xpub after this commit. It is able to parse xpub/ypub/zpub and derive addresses. Ledger is still using incorrect prefix for segwit xpub and you may have to convert the xpub to ypub/zpub appropriately

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.