BIP-32: HD Wallets
Hierarchical Deterministic Wallets specification. Derive unlimited keys from a single seed for Bitcoin agents.
| Type | Bitcoin Improvement Proposal |
| Number | bip-32 |
| Status | Final |
| Authors | Pieter Wuille |
| Original | https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki |
BIP-32: Hierarchical Deterministic Wallets
BIP-32 defines a standard for deriving an entire tree of key pairs from a single seed. This enables wallet backup with just one secret and organized key management.
Motivation
Before HD wallets, each key was generated independently:
- Backup burden: New backup needed after each new key
- No organization: Keys had no logical structure
- Privacy risk: Key reuse to avoid backup complexity
HD wallets solve this:
- Single backup: One seed backs up all current and future keys
- Hierarchy: Organized tree structure (accounts, chains, addresses)
- Delegation: Share xpubs for watch-only access
Key Concepts
Extended Keys
An extended key contains:
- Key material: 256-bit private key or public key point
- Chain code: 256-bit entropy for child derivation
- Metadata: Depth, parent fingerprint, index
Total: 78 bytes before encoding
Serialization Format
4 bytes - version (mainnet: 0x0488ADE4 private, 0x0488B21E public)
1 byte - depth (0x00 for master)
4 bytes - parent fingerprint (0x00000000 for master)
4 bytes - child index
32 bytes - chain code
33 bytes - key (0x00 + 32-byte private, or 33-byte compressed public)
Base58Check encoded with prefix:
xprv- mainnet privatexpub- mainnet publictprv- testnet privatetpub- testnet public
Derivation Types
| Type | Notation | Index Range | Use |
|---|---|---|---|
| Normal | 0 to 2³¹-1 | 0x00000000 - 0x7FFFFFFF | Public derivation possible |
| Hardened | 0' or 0h | 0x80000000 - 0xFFFFFFFF | Private key required |
Critical: Hardened derivation prevents parent key recovery from child + parent public key.
Derivation Algorithm
Child Key Derivation Function (CKD)
Private Parent → Private Child:
if index >= 0x80000000 (hardened):
data = 0x00 || ser256(kpar) || ser32(i)
else:
data = serP(point(kpar)) || ser32(i)
I = HMAC-SHA512(Key = cpar, Data = data)
IL = I[:32] # left 256 bits
IR = I[32:] # right 256 bits
ki = parse256(IL) + kpar (mod n)
ci = IR
Public Parent → Public Child (normal only):
if index >= 0x80000000:
return failure # Cannot derive hardened from public
data = serP(Kpar) || ser32(i)
I = HMAC-SHA512(Key = cpar, Data = data)
IL = I[:32]
IR = I[32:]
Ki = point(parse256(IL)) + Kpar
ci = IR
Master Key Generation
From seed (typically 128-512 bits from BIP-39):
I = HMAC-SHA512(Key = "Bitcoin seed", Data = seed)
IL = I[:32] # master private key
IR = I[32:] # master chain code
Path Notation
Standard path format:
m / purpose' / coin_type' / account' / change / address_index
| Component | Meaning |
|---|---|
m | Master key |
/ | Derive child |
' or h | Hardened derivation |
| Number | Child index |
Example: m/84'/0'/0'/0/5
- Master → purpose 84 (hardened) → Bitcoin mainnet (hardened) → account 0 (hardened) → external chain → address 5
Implementation
JavaScript (bitcoinjs-lib)
const { BIP32Factory } = require('bip32');
const ecc = require('tiny-secp256k1');
const bip32 = BIP32Factory(ecc);
// From seed
const seed = Buffer.from('...', 'hex');
const root = bip32.fromSeed(seed);
// Derive path
const child = root.derivePath("m/84'/0'/0'/0/0");
console.log('Private:', child.privateKey.toString('hex'));
console.log('Public:', child.publicKey.toString('hex'));
// Get xpub for account
const account = root.derivePath("m/84'/0'/0'");
const xpub = account.neutered().toBase58();
console.log('xpub:', xpub);
Python (bip32)
from bip32 import BIP32
# From seed
seed = bytes.fromhex('...')
bip32 = BIP32.from_seed(seed)
# Derive
privkey = bip32.get_privkey_from_path("m/84'/0'/0'/0/0")
pubkey = bip32.get_pubkey_from_path("m/84'/0'/0'/0/0")
# Get xpub
xpub = bip32.get_xpub_from_path("m/84'/0'/0'")
Security Considerations
Hardened vs Normal Derivation
| Scenario | Risk |
|---|---|
| xpub leaked | Attacker sees all addresses (privacy loss) |
| xpub + any child private key leaked | All keys compromised (normal derivation) |
| xpub + hardened child private key | Only that child compromised |
Rule: Always use hardened derivation for sensitive levels (purpose, coin_type, account).
Key Exposure Prevention
# WRONG: Don't share full path xprv
xprv_bad = root.derivePath("m/84'/0'/0'/0/0").toBase58()
# CORRECT: Share xpub at account level
xpub_good = root.derivePath("m/84'/0'/0'").neutered().toBase58()
Invalid Keys
Derivation can (rarely) produce invalid keys:
- Private key = 0 or ≥ curve order n
- Public key is point at infinity
Implementations must handle by skipping to next index.
Extended Key Versions
Different address types use different version bytes:
| Type | Private | Public | Address |
|---|---|---|---|
| P2PKH (BIP-44) | xprv | xpub | 1… |
| P2SH-P2WPKH (BIP-49) | yprv | ypub | 3… |
| P2WPKH (BIP-84) | zprv | zpub | bc1q… |
| P2TR (BIP-86) | xprv | xpub | bc1p… |
Note: Taproot uses standard xprv/xpub (no special version).
Common Derivation Paths
| Standard | Path | Purpose |
|---|---|---|
| BIP-44 | m/44’/0’/account’/change/index | Legacy addresses |
| BIP-49 | m/49’/0’/account’/change/index | Wrapped SegWit |
| BIP-84 | m/84’/0’/account’/change/index | Native SegWit |
| BIP-86 | m/86’/0’/account’/change/index | Taproot |
Agent Best Practices
- Generate master from BIP-39 mnemonic (not raw entropy)
- Use hardened derivation for purpose/coin/account
- Share xpubs for watch-only, never xprvs
- Implement gap limit (typically 20 unused addresses)
- Backup seed phrase, not derived keys
Test Vectors
Vector 1
Seed: 000102030405060708090a0b0c0d0e0f
| Path | xpub |
|---|---|
| m | xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8 |
| m/0’ | xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw |
| m/0’/1 | xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ |
Machine-Readable Summary
{
"bip": 32,
"title": "Hierarchical Deterministic Wallets",
"status": "final",
"key_derivation": "hmac-sha512",
"derivation_types": ["normal", "hardened"],
"path_format": "m / purpose' / coin' / account' / change / index",
"libraries": {
"javascript": ["bip32", "bitcoinjs-lib"],
"python": ["bip32", "hdwallet"],
"rust": ["bitcoin", "bip32"]
}
}