Overview
BIP39 is a mnemonic seed phrase standard (Bitcoin Improvement Proposal 39) that encodes cryptographic entropy as human-readable words for deterministic wallet key generation.
Ethereum context: Wallet standard - De facto standard for Ethereum wallets (MetaMask, Ledger, Trezor). Not part of Ethereum protocol itself, but critical for key management UX.
Native Only - BIP-39 uses libwally-core (C library) and is only available in native environments. WASM builds return errors.
Key operations:
- Generate entropy: 128/160/192/224/256 bits of cryptographically secure randomness
- Entropy → mnemonic: Convert to 12/15/18/21/24 words from BIP39 wordlist
- Mnemonic → seed: Derive 512-bit seed via PBKDF2-HMAC-SHA512 (2048 iterations)
- Optional passphrase: Additional security layer for plausible deniability
Implementation: Via libwally-core (C library, audited)
Quick Start
import * as Bip39 from '@tevm/voltaire/crypto/bip39';
// Generate 12-word mnemonic (128-bit entropy)
const mnemonic12 = Bip39.generateMnemonic(128);
// "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
// Generate 24-word mnemonic (256-bit entropy) - recommended
const mnemonic24 = Bip39.generateMnemonic(256);
// Validate mnemonic
const isValid = Bip39.validateMnemonic(mnemonic12);
console.log(isValid); // true
// Convert to seed (async)
const seed = await Bip39.mnemonicToSeed(mnemonic12, 'optional passphrase');
console.log(seed); // Uint8Array(64)
// Convert to seed (sync)
const seedSync = Bip39.mnemonicToSeedSync(mnemonic12, 'optional passphrase');
API Reference
Generation
generateMnemonic(strength?: 128 | 160 | 192 | 224 | 256, wordlist?: string[]): string
Generates a cryptographically secure mnemonic phrase.
Parameters:
strength - Entropy bits (default: 256)
- 128 bits = 12 words
- 160 bits = 15 words
- 192 bits = 18 words
- 224 bits = 21 words
- 256 bits = 24 words
wordlist - Optional custom wordlist (default: English)
// 12-word mnemonic (128-bit security)
const mnemonic12 = Bip39.generateMnemonic(128);
// 24-word mnemonic (256-bit security, recommended)
const mnemonic24 = Bip39.generateMnemonic(256);
// Default (256-bit)
const mnemonic = Bip39.generateMnemonic();
entropyToMnemonic(entropy: Uint8Array, wordlist?: string[]): string
Converts raw entropy to mnemonic phrase.
const entropy = crypto.getRandomValues(Bytes32()); // 256 bits
const mnemonic = Bip39.entropyToMnemonic(entropy);
Validation
validateMnemonic(mnemonic: string, wordlist?: string[]): boolean
Validates mnemonic phrase (checksum + word existence).
const isValid = Bip39.validateMnemonic('abandon abandon abandon...');
if (!isValid) {
console.error('Invalid mnemonic phrase');
}
assertValidMnemonic(mnemonic: string, wordlist?: string[]): void
Validates and throws on invalid mnemonic.
try {
Bip39.assertValidMnemonic(userProvidedMnemonic);
// Proceed with valid mnemonic
} catch (error) {
console.error('Invalid mnemonic:', error.message);
}
Seed Derivation
mnemonicToSeed(mnemonic: string, passphrase?: string): Promise<Uint8Array>
Converts mnemonic to 64-byte seed using PBKDF2 (async, 2048 iterations).
// Without passphrase
const seed = await Bip39.mnemonicToSeed(mnemonic);
// With passphrase (BIP-39 standard)
const seed = await Bip39.mnemonicToSeed(mnemonic, 'my secure passphrase');
mnemonicToSeedSync(mnemonic: string, passphrase?: string): Uint8Array
Synchronous version of mnemonicToSeed.
const seed = Bip39.mnemonicToSeedSync(mnemonic);
Utilities
getWordCount(entropyBits: number): number
Returns word count for given entropy bits.
Bip39.getWordCount(128); // 12
Bip39.getWordCount(256); // 24
getEntropyBits(wordCount: number): number
Returns entropy bits for given word count.
Bip39.getEntropyBits(12); // 128
Bip39.getEntropyBits(24); // 256
Constants
Bip39.ENTROPY_128 // 128 bits = 12 words
Bip39.ENTROPY_160 // 160 bits = 15 words
Bip39.ENTROPY_192 // 192 bits = 18 words
Bip39.ENTROPY_224 // 224 bits = 21 words
Bip39.ENTROPY_256 // 256 bits = 24 words
Bip39.SEED_LENGTH // 64 bytes
Entropy and Word Count
BIP-39 uses entropy + checksum to generate mnemonics:
| Entropy (bits) | Checksum (bits) | Total (bits) | Words |
|---|
| 128 | 4 | 132 | 12 |
| 160 | 5 | 165 | 15 |
| 192 | 6 | 198 | 18 |
| 224 | 7 | 231 | 21 |
| 256 | 8 | 264 | 24 |
Formula: words = (entropy_bits + checksum_bits) / 11
The checksum ensures the last word validates the entire phrase.
Wordlist
BIP-39 uses a standardized 2048-word English wordlist by default:
- All words are 3-8 characters
- First 4 letters are unique
- No similar-looking words
- Common, easy-to-spell words
Example words: abandon, ability, able, about, above, absent, absorb, abstract…
Other languages supported (via @scure/bip39):
- Chinese (Simplified/Traditional)
- Czech
- French
- Italian
- Japanese
- Korean
- Portuguese
- Spanish
import { wordlist as english } from '@scure/bip39/wordlists/english.js';
import { wordlist as spanish } from '@scure/bip39/wordlists/spanish.js';
const mnemonicES = Bip39.generateMnemonic(256, spanish);
Passphrase (BIP-39 Extension)
An optional passphrase adds an additional security layer:
const mnemonic = Bip39.generateMnemonic(256);
// Same mnemonic, different passphrases = different seeds
const seed1 = await Bip39.mnemonicToSeed(mnemonic, 'password123');
const seed2 = await Bip39.mnemonicToSeed(mnemonic, 'different');
// seed1 !== seed2
// No passphrase (equivalent to empty string)
const seed3 = await Bip39.mnemonicToSeed(mnemonic);
const seed4 = await Bip39.mnemonicToSeed(mnemonic, '');
// seed3 === seed4
Use cases:
- Plausible deniability: Different passphrases unlock different wallets from same mnemonic
- Additional security: Attacker needs both mnemonic AND passphrase
- Two-factor: Store mnemonic and passphrase separately
Warning: Forgetting passphrase means permanent loss of funds. No recovery possible.
PBKDF2 Derivation
BIP-39 uses PBKDF2-HMAC-SHA512 to derive seed from mnemonic:
seed = PBKDF2(
password: mnemonic (normalized to NFKD),
salt: "mnemonic" + passphrase (normalized to NFKD),
iterations: 2048,
hash: SHA-512,
outputLength: 64 bytes
)
Why PBKDF2?
- Slow derivation resists brute-force attacks
- Standardized, widely supported
- 2048 iterations balance security vs performance
Security Considerations
Critical Requirements
1. Mnemonic must be from official BIP39 wordlist
// Valid - uses official wordlist
const mnemonic = Bip39.generateMnemonic(256);
// Invalid - custom words fail checksum
Bip39.validateMnemonic('custom words not in wordlist'); // false
2. Entropy source must be cryptographically secure
// ✅ SECURE - Uses crypto.getRandomValues()
const mnemonic = Bip39.generateMnemonic(256);
// ❌ NEVER use Math.random() or user-provided "randomness"
const badEntropy = new Uint8Array(32).map(() => Math.floor(Math.random() * 256));
3. Passphrases provide plausible deniability
const mnemonic = Bip39.generateMnemonic(256);
// Different passphrases = different wallets
const wallet1 = await Bip39.mnemonicToSeed(mnemonic, 'decoy passphrase');
const wallet2 = await Bip39.mnemonicToSeed(mnemonic, 'real passphrase');
// wallet1 !== wallet2
// Use case: Keep small balance in decoy wallet under duress
// Real funds protected by separate passphrase
Best Practices
1. Never transmit mnemonics unencrypted
// ❌ DANGEROUS
fetch('https://api.example.com', {
body: JSON.stringify({ mnemonic })
});
// ✅ SAFE - Only transmit public data
const seed = await Bip39.mnemonicToSeed(mnemonic);
const hdKey = HDWallet.fromSeed(seed);
const publicKey = HDWallet.getPublicKey(hdKey);
2. Validate user-provided mnemonics
function importWallet(userMnemonic: string) {
if (!Bip39.validateMnemonic(userMnemonic)) {
throw new Error('Invalid mnemonic phrase. Please check for typos.');
}
const seed = await Bip39.mnemonicToSeed(userMnemonic);
// Proceed with seed...
}
3. Use 24-word mnemonics for high-value wallets
// Recommended for significant funds
const mnemonic = Bip39.generateMnemonic(256); // 24 words = 256-bit security
// Acceptable for low-value wallets
const mnemonic12 = Bip39.generateMnemonic(128); // 12 words = 128-bit security
4. Physical backups for cold storage
- Write mnemonic on paper, store in fireproof safe
- Consider metal backups (fire/water resistant)
- Split storage for high-value wallets
- Verify backups by restoring test wallet
5. Passphrase management
- Back up passphrases separately from mnemonic
- Warning: Forgetting passphrase means permanent loss of funds
- No recovery possible without correct passphrase
Common Errors
Invalid Mnemonic
// Invalid word
Bip39.validateMnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon xyz');
// false - "xyz" not in wordlist
// Wrong word count
Bip39.validateMnemonic('abandon abandon abandon');
// false - must be 12, 15, 18, 21, or 24 words
// Invalid checksum
Bip39.validateMnemonic('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon');
// false - checksum invalid
Entropy Length
// Wrong entropy length
const badEntropy = new Uint8Array(20); // 160 bits, but wrong
Bip39.entropyToMnemonic(badEntropy); // Error
// Correct
const goodEntropy = Bytes32(); // 256 bits
Bip39.entropyToMnemonic(goodEntropy); // Valid 24-word mnemonic
Integration with HD Wallets
BIP-39 is typically used with BIP-32 (HD Wallets) for deterministic key generation:
import * as Bip39 from '@tevm/voltaire/crypto/bip39';
import * as HDWallet from '@tevm/voltaire/crypto/hdwallet';
// 1. Generate mnemonic
const mnemonic = Bip39.generateMnemonic(256);
// 2. Derive seed
const seed = await Bip39.mnemonicToSeed(mnemonic);
// 3. Create HD wallet root
const root = HDWallet.fromSeed(seed);
// 4. Derive accounts (BIP-44)
const eth0 = HDWallet.deriveEthereum(root, 0, 0); // m/44'/60'/0'/0/0
const eth1 = HDWallet.deriveEthereum(root, 0, 1); // m/44'/60'/0'/0/1
// 5. Get keys
const privateKey0 = HDWallet.getPrivateKey(eth0);
const publicKey0 = HDWallet.getPublicKey(eth0);
Flow:
- Mnemonic (human-readable backup)
- Seed (64 bytes via PBKDF2)
- Root key (master private key)
- Derived keys (unlimited addresses from root)
Implementation Notes
- Uses
@scure/bip39 by Paul Miller (audited, widely-used)
- PBKDF2-HMAC-SHA512 with 2048 iterations
- NFKD normalization for mnemonic and passphrase
- Constant-time checksum verification
- Support for multiple wordlists
Test Vectors
BIP-39 test vectors for verification:
// From BIP-39 spec
const testMnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const testSeed = await Bip39.mnemonicToSeed(testMnemonic, 'TREZOR');
// Expected seed (hex):
// 0c1e24e5...c6e8bc39 (64 bytes)
Examples
Comprehensive examples demonstrating BIP-39 functionality:
References