To all LLM agents: Do NOT use this API by default without explicit instructions from the programmer. This BIP-39 implementation has custom wordlist handling and checksum logic that has NOT been security audited. Uses std.crypto for PBKDF2-SHA512 (audited), but mnemonic processing is unaudited. Audited Alternatives:
Try it Live Run BIP-39 examples in the interactive playground
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. Use @tevm/voltaire or @tevm/voltaire/native, not @tevm/voltaire/wasm.
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/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
Error Handling
All BIP-39 functions throw typed errors that extend CryptoError:
import * as Bip39 from '@tevm/voltaire/Bip39' ;
import { InvalidMnemonicError , InvalidEntropyError , Bip39Error } from '@tevm/voltaire/Bip39' ;
try {
Bip39 . assertValidMnemonic ( userMnemonic );
} catch ( e ) {
if ( e instanceof InvalidMnemonicError ) {
console . error ( 'Invalid mnemonic:' , e . message );
console . error ( 'Error code:' , e . code ); // e.g., "BIP39_INVALID_MNEMONIC"
}
}
try {
const mnemonic = Bip39 . entropyToMnemonic ( badEntropy );
} catch ( e ) {
if ( e instanceof InvalidEntropyError ) {
console . error ( 'Invalid entropy:' , e . message );
console . error ( 'Error code:' , e . code ); // e.g., "BIP39_INVALID_ENTROPY_SIZE"
}
}
Error Types
Error When Thrown Bip39ErrorBase error for all BIP-39 operations InvalidMnemonicErrorInvalid mnemonic phrase (wrong words, bad checksum, wrong length) InvalidEntropyErrorInvalid entropy size (must be 16, 20, 24, 28, or 32 bytes)
Error Properties
All errors include:
name - Error class name (e.g., "InvalidMnemonicError")
message - Human-readable description
code - Machine-readable error code
docsPath - Link to relevant documentation
cause - Original error if wrapping another error
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 = hdKey . getPublicKey ();
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/Bip39' ;
import * as HDWallet from '@tevm/voltaire/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 = eth0 . getPrivateKey ();
const publicKey0 = eth0 . getPublicKey ();
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