Guide: HD Wallets & Mnemonics¶
This guide covers BIP39 mnemonics and BIP32 hierarchical deterministic wallets, including watch-only (xpub) derivation.
From mnemonic to addresses¶
from libcrypto import HDWallet, Wallet, generate_mnemonic
mnemonic = generate_mnemonic(12)
hd = HDWallet.from_mnemonic(mnemonic, passphrase="")
# BIP44 Ethereum account
eth_node = hd.derive_from_path("m/44'/60'/0'/0/0")
eth_wallet = Wallet(eth_node.private_key)
print("ETH:", eth_wallet.get_address("ethereum"))
# BIP84 native SegWit Bitcoin account
btc_node = hd.derive_from_path("m/84'/0'/0'/0/0")
btc_wallet = Wallet(btc_node.private_key)
print("BTC:", btc_wallet.get_address("bitcoin", "p2wpkh"))
Common derivation paths¶
| Path | Purpose |
|---|---|
m/44'/0'/0'/0/0 |
Bitcoin legacy (BIP44). |
m/49'/0'/0'/0/0 |
Bitcoin nested SegWit (BIP49). |
m/84'/0'/0'/0/0 |
Bitcoin native SegWit (BIP84). |
m/86'/0'/0'/0/0 |
Bitcoin Taproot (BIP86). |
m/44'/60'/0'/0/0 |
Ethereum / EVM. |
m/44'/195'/0'/0/0 |
TRON. |
Extended keys (xprv / xpub)¶
from libcrypto import HDWallet, HDNode
hd = HDWallet(bytes.fromhex("000102030405060708090a0b0c0d0e0f"))
account = hd.derive_from_path("m/0'")
xprv = account.serialize_private()
xpub = account.serialize_public()
# Round-trip
restored_priv = HDNode.deserialize(xprv)
restored_pub = HDNode.deserialize(xpub)
assert restored_pub.is_private is False
Watch-only wallets (CKDpub)¶
A watch-only wallet can derive receiving addresses without ever holding a
private key. neuter() produces a public-only node, and non-hardened children
are derived through elliptic-curve point addition (CKDpub):
from libcrypto import HDWallet, PublicKey
hd = HDWallet(bytes.fromhex("000102030405060708090a0b0c0d0e0f"))
account = hd.derive_from_path("m/44'/0'/0'") # has a private key
# Export only the public account node to an online system.
xpub = account.neuter().serialize_public()
# On the online system, import the xpub and derive receive addresses.
from libcrypto import HDNode
watch = HDNode.deserialize(xpub)
for i in range(3):
child = watch.derive_child(0).derive_child(i) # m/.../0/i
address = PublicKey(child.public_key).get_address("p2pkh", "bitcoin")
print(i, address)
Public derivation reproduces exactly the same public key (and therefore the same
address) that private derivation would produce. Hardened derivation from a
watch-only node is impossible by design and raises BIP32Error.
Security reminders¶
- Treat mnemonics, seeds, and xprv strings as top-secret material.
- Only ever export xpub (never xprv) to online or third-party systems.
- Remember that leaking an xpub plus any single child private key can expose the parent private key for non-hardened branches — keep account roots hardened.