ebellocchia / bip_utils

Generation of mnemonics, seeds, private/public keys and addresses for different types of cryptocurrencies
MIT License
292 stars 83 forks source link

Derive BTC bip32 address from mnemonic #127

Closed tajelp closed 2 months ago

tajelp commented 2 months ago

Hello, if I have for example the following mnemonic (randomly generated)

turn action economy still dose proof fuel way sword auto boring breeze spice juice uniform

by using the m/44'/0'/0' bip32 derivation path, I get this m/0/0 address from the iancoleman website

1EXEAGqbzG9V5mRLrA4j28YBe7QfvT5Qm4

However, if I want to do the same with the lib, referring to this example, I have:

from bip_utils import Bip32Slip10Secp256k1, Bip39SeedGenerator, Bip39WordsNum

mnemonic = 'turn action economy still dose proof fuel way sword auto boring breeze spice juice uniform'
seed_bytes = Bip39SeedGenerator(mnemonic).Generate()
bip32_mst_ctx = Bip32Slip10Secp256k1.FromSeed(seed_bytes)
bip32_der_ctx = bip32_mst_ctx.DerivePath("m/44'/00'/0'/0/0")

But then how can I derive the public BTC address?

ebellocchia commented 2 months ago

Hi First of all, with that mnemonic the address will be 16KPybAKQpD3wzjeTsWLZjQPx2J18rPCK5.

Anyway, if you want to derive a BIP44 path then you should use the BIP44 example, there is no need to use the BIP32 library unless you want to derive a custom path.

With BIP44 you can either derive the entire path in one shot:

from bip_utils import *

mnemonic = "turn action economy still dose proof fuel way sword auto boring breeze spice juice uniform"
seed_bytes = Bip39SeedGenerator(mnemonic).Generate()

# Construct BIP44 object from seed
bip_44_ctx = Bip44.FromSeed(seed_bytes, Bip44Coins.BITCOIN)

# Derive m/44'/0'/0'/0/0 in one shot
bip44_def_ctx = bip_44_ctx.DeriveDefaultPath()

# Print: 16KPybAKQpD3wzjeTsWLZjQPx2J18rPCK5
print(bip44_def_ctx.PublicKey().ToAddress())

Or element by element, in case you want to change the account or address indexes:

from bip_utils import *

mnemonic = "turn action economy still dose proof fuel way sword auto boring breeze spice juice uniform"
seed_bytes = Bip39SeedGenerator(mnemonic).Generate()

# Construct BIP44 object from seed
bip_44_ctx = Bip44.FromSeed(seed_bytes, Bip44Coins.BITCOIN)

# Derive m/44'/0'/0'/0 element by element
bip44_chg_ctx = bip_44_ctx.Purpose().Coin().Account(0).Change(Bip44Changes.CHAIN_EXT)

# Print the first 5 addresses:
# m/44'/0'/0'/0/0 : 16KPybAKQpD3wzjeTsWLZjQPx2J18rPCK5
# m/44'/0'/0'/0/1 : 12DKMEjeDoGnjgovGFvQRTRo4KHfwP6jtA
# m/44'/0'/0'/0/2 : 1623sKHQN2Aen9Ti3WT1ybrUW4b3wxftQA
# ...
for i in range(5):
    print(bip44_chg_ctx.AddressIndex(i).PublicKey().ToAddress())

If you want to use BIP32 (but there is no need to do it), you have to use the correct encoding class to get the address from the public key, depending on the coin you want:

from bip_utils import *

mnemonic = "turn action economy still dose proof fuel way sword auto boring breeze spice juice uniform"
seed_bytes = Bip39SeedGenerator(mnemonic).Generate()

bip32_mst_ctx = Bip32Slip10Secp256k1.FromSeed(seed_bytes)
bip32_der_ctx = bip32_mst_ctx.DerivePath("m/44'/0'/0'/0/0")

# Bitcoin BIP44 uses P2PKH algorithm
# Print: 16KPybAKQpD3wzjeTsWLZjQPx2J18rPCK5
print(
    P2PKHAddr.EncodeKey(
        bip32_der_ctx.PublicKey().KeyObject(),
        net_ver=CoinsConf.BitcoinMainNet.ParamByKey("p2pkh_net_ver")
    )
)
tajelp commented 2 months ago

why there is no need to use bip32? If I want to generate bip32 addresses, that's the only way, right?

ebellocchia commented 2 months ago

Because that's how things work, I suggest you to read BIP32 and BIP44 documents to clarify your ideas:

Very briefly, BIP32 describes what a derivation path is and how to derive keys using it. Therefore, with BIP32 you can use any derivation path you want. For example, you can use a path like m/0/2147483647'/1/2147483646'/2 or m/0'/1'/2'/3'/4'/5'/6/7/8/9/10 to derive your keys, it's perfectly fine for BIP32. However, this is a problem for compatibility between wallets, because in principle every wallet can use its own derivation path so your mnemonic will lead to different keys (and so addresses) when you recover it with a different wallet.

That's why BIP44 exists. BIP44 extends BIP32 by specifying a standard derivation path to be used by wallets, so that different wallets will be compatible. The BIP44 derivation path is the following:

m / purpose' / coin_type' / account' / change / address_index

The purpose' is set to 44' because it's BIP44. And it's exactly the same path you are using (i.e. m/44'/0'/0'/0/0), that's why it's easier for you to use the Bip44 class. If you want use Bip32Slip10Secp256k1 do it, it's just more complicated to use and write, so there is no need unless you use a non-BIP44 derivation path. BIP49/BIP84/BIP86 are the same, they just change the purpose value.

To summarize, a BIP44 derivation path is always a BIP32 path, just in a standard format.