logo

Generating a Wallet: BIP-39 Mnemonic and BIP-32 HD Keys

A wallet illuminated by glowing lights and wires, showcasing a modern and futuristic design.

You can spot a “wallet generation” bug by how it feels. Everything looks fine in development, then a user restores on another app, and the balance is gone.

Support swears the seed phrase is correct. Your code “follows the standard.” And yet the keys don’t match. How does something as simple as “12 words in, addresses out” turn into a production incident?

Most of the confusion comes from mixing two layers that work together but solve different problems: BIP-39 gives humans a backup-friendly mnemonic and turns it into a binary seed, while BIP-32 turns that seed into a tree of deterministic keys. Once you separate those responsibilities, the whole pipeline becomes easier to implement, test, and debug.

What “generating a wallet” actually includes

A wallet isn’t a single key. It’s a repeatable process that takes secret material and expands it into many keys and addresses, often across multiple accounts and networks. BIP-39 and BIP-32 are the backbone of that process, but they don’t define everything people casually attribute to “wallet generation.”

If you’re implementing a wallet, treat these as separate modules with a clean interface: mnemonic → seed (BIP-39), then seed → HD root → derived keys (BIP-32). Mixing them into one blob of logic is where edge cases hide.

BIP-39 mnemonic: Entropy, checksum, and the seed you actually use

BIP-39 is a Bitcoin Improvement Proposal that standardizes the “seed phrase” idea used by many crypto wallets: instead of backing up a long, unreadable random number, the wallet turns that randomness into a short list of common words (the mnemonic) that a person can write down and type back in later. 

Under the hood, the wallet starts with truly random bits (entropy), adds a small checksum so many mistakes can be detected, and maps the result to words from a fixed wordlist, so the same entropy always produces the same words and the words can be validated for typos. Those words are mainly a human-friendly backup format; when a wallet actually needs to generate keys, it runs the mnemonic (and optionally a passphrase you choose) through a standard “key-stretching” function called PBKDF2 to produce a binary seed that becomes the starting point for generating all your wallet’s private keys and addresses.

Entropy is the product, not the byproduct

As was mentioned, generating a wallet starts with generating entropy. That sounds obvious until someone uses a weak RNG because “it’s just a demo,” and the demo becomes a release candidate.

BIP-39 mnemonics typically represent 128–256 bits of entropy (commonly 12–24 words), with checksum bits embedded into the last word selection. The mnemonic is reversible back to the entropy, checksum included, which is why typos can often be detected.

When you implement this step, the main engineering question isn’t “how do we map bits to words?” The question is “how do we guarantee the bits are good?”

Here’s a practical checklist engineers actually use during implementation reviews:

  • Use a cryptographically secure RNG provided by the OS, not a custom PRNG.
  • Keep entropy generation isolated from UI and networking code to reduce accidental logging.
  • Treat test entropy as radioactive: make it impossible to ship with “fixed seed” defaults.
  • Verify checksum validation logic with official test vectors before you wire it into UX.

Passphrase handling: One field, two different wallets

BIP-39 includes an optional passphrase feature that people often misunderstand because it’s presented as an “extra password.” It’s not a wallet lock. It’s part of seed derivation.

The binary seed is derived using PBKDF2-HMAC-SHA512 with 2048 iterations, where the mnemonic sentence is the password and the salt is the string “mnemonic” concatenated with the passphrase. Both inputs are normalized to UTF-8 NFKD.

Operationally, that means the same 12 words can produce completely different wallets depending on whether the passphrase is present and what it is. Forgetting the passphrase is equivalent to losing the wallet, even if the mnemonic is intact.

Normalization: The silent source of incompatibility

BIP-39’s normalization requirement is easy to implement and easy to skip, which makes it perfect for long-tail bugs. NFKD matters because visually identical strings can have different byte representations, especially with accented characters or certain keyboard layouts.

If you want one decision rule to prevent painful cross-wallet mismatches, make it this: normalize exactly as BIP-39 specifies at the boundary, before any hashing, and test with non-ASCII examples.

BIP-32 HD keys: Master keys, chain codes, and hardened choices

BIP-32 is a standard that explains how a wallet can take one secret starting point (often the seed produced from your recovery phrase) and generate a whole family of keys in a predictable way, so you don’t need a new backup every time you create a new address. It does this by creating a “master” key plus an extra companion value called a chain code (think of it as the ingredient that makes the branching process safe and consistent), and together they form an extended key that can deterministically produce “child” keys over and over. 

Those child keys can be organized into a tree, which is why wallets can have accounts, change addresses, and endless new receive addresses while still being recoverable from the same original seed. 

Extended keys and the chain code in plain terms

A BIP-32 extended key is a key plus metadata that makes derivation work: a private or public key, a chain code, depth, index, and parent fingerprint. The chain code isn’t a secret key you spend with. It’s a companion value that makes the key tree behave deterministically and prevents simple correlations.

If you’ve ever used xpub/xprv strings, you’ve seen extended keys serialized for transport. That serialization choice (version bytes, encoding) can vary by network, but the underlying idea stays the same: extended keys let you derive a family of keys predictably.

Hardened vs non-hardened: A capability you pay for with risk

BIP-32 supports two derivation modes:

  • Non-hardened derivation allows deriving child public keys from a parent public key. That enables watch-only wallets and server-side address generation without private keys.
  • Hardened derivation requires private key material, which blocks that watch-only derivation capability.

The tricky part is the security trade-off. With non-hardened branches, certain leakage scenarios become worse: if a child private key leaks and the parent extended public key is known, it can enable compromise of the parent private key in some constructions. That’s why hardened steps exist and why many standards harden at key boundaries like “account.”

The indices that bite: 31-bit space and hardened markers

BIP-32 child indexes are 32-bit, but hardened derivation uses the upper range (commonly represented by adding 2³¹ or using a ‘ marker in paths). That representation detail seems harmless until a parser treats “0′” as “0” or mishandles large unsigned integers.

If you’re building path parsing, treat it like input validation on a payment system. A single off-by-one or sign bug doesn’t fail loudly. It generates a different wallet.

A practical development workflow for wallet key generation

Most wallet bugs aren’t cryptographic failures. They’re integration failures: wrong normalization, wrong path, wrong serialization, wrong assumptions about scanning. A clean development workflow reduces those risks by forcing decisions into the open.

Step 1: Define your wallet contract before writing code

Before you generate a single address, write down what “the same wallet” means for your product. This is the compatibility contract you’ll use to judge every restore, migration, and “my balance is missing” report. 

Keep it short, yet specific: which mnemonic lengths you accept and whether you allow non-standard wordlists, how passphrases are handled and explained in UX, which derivation paths are your default versus “advanced” options, and how you serialize/store extended keys so the same wallet can be reconstructed consistently across environments.

Step 2: Implement BIP-39 as a pure function with hard boundaries

Treat BIP-39 as a pure transformation: entropy → mnemonic, mnemonic (+ optional passphrase) → binary seed. The more deterministic and isolated this layer is, the easier it is to test and the harder it is to accidentally break. Keep it separate from storage, UI, and networking so you can prove correctness in unit tests. 

Your implementation should be able to generate entropy via an injected dependency for repeatable tests, validate mnemonic checksums on import, apply UTF-8 NFKD normalization exactly as required, and confirm outputs against official test vectors so refactors don’t shift wallet results.

Step 3: Implement BIP-32 derivation with strict parsing and serialization

Your BIP-32 layer should make “wrong but plausible” outcomes difficult. That means strict parsing of derivation paths, careful handling of hardened indexes, and a clear distinction between operations that require private key material and operations that can safely run on public extended keys. 

If your API design allows a single function to derive keys both from xpub and xprv in a loosely typed way, someone will call it incorrectly later. Separate interfaces and explicit types reduce that future risk and make security reviews easier.

Step 4: Add “golden wallets” and cross-implementation checks

This step pays for itself the first time you change a dependency or add a new address type. Create a small set of test-only mnemonics and record expected outputs at several checkpoints: seed bytes, master xprv/xpub, and a handful of derived addresses at known paths. Then verify those expected outputs against at least one independent implementation so your own code isn’t the only “source of truth.” The goal is to prove you match the standard and remain compatible with the ecosystem.

Step 5: Threat model the key lifecycle

The cryptography is usually the solid part. The leaks are where wallets get hurt. Walk through the full lifecycle of sensitive material and look for mundane failure modes: debug logs that accidentally print mnemonics, crash reporters that capture memory snapshots, analytics events that include derived identifiers, UI flows that encourage copying secrets to clipboard, or backup formats that export xprv when xpub would have been enough. This isn’t about paranoia. It’s about removing the obvious escape hatches that appear in real codebases under deadline pressure.

Step 6: Ship with observability that doesn’t leak secrets

You can make the system observable without leaking sensitive data by logging decisions and states rather than key material. It’s useful to know whether a restore used a passphrase, which derivation path template was selected, which address type was active, and how far account discovery scanned before stopping. Those signals turn “wallet looks empty” into something you can reproduce and fix. 

What you should never log, regardless of environment, is the mnemonic, the seed, private keys, or raw extended private keys. If you design diagnostics with that boundary in mind, you’ll have enough visibility to support users without creating a second security problem.

Conclusion

Generating a wallet is less “create a key” and more “define a deterministic pipeline that other software can reproduce.” BIP-39 gives you a human-usable mnemonic and a precisely specified seed derivation step, including normalization and passphrase handling. BIP-32 gives you the key tree mechanics, along with a real trade-off between hardened safety and non-hardened convenience.

The teams that ship reliable wallet generation tend to do two things early: they write down their wallet contract (paths, address types, discovery rules), and they build cross-implementation tests before UX polish. That’s not an extra ceremony. It’s the difference between “restores everywhere” and “works on my device.”