Skip to main content

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
128413212
160516515
192619818
224723121
256826424
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:
  1. Mnemonic (human-readable backup)
  2. Seed (64 bytes via PBKDF2)
  3. Root key (master private key)
  4. 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