Documentation Index Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Try it Live Run BIP39 examples in the interactive playground
Overview
BIP-39 seed derivation converts human-readable mnemonics into 64-byte binary seeds using PBKDF2-HMAC-SHA512. This seed serves as the root for HD wallet key derivation.
Basic Usage
Async Derivation
import * as Bip39 from '@tevm/voltaire/Bip39' ;
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
// Without passphrase
const seed = await Bip39 . mnemonicToSeed ( mnemonic );
console . log ( seed ); // Uint8Array(64)
// With passphrase
const seedWithPass = await Bip39 . mnemonicToSeed ( mnemonic , 'my secret passphrase' );
console . log ( seedWithPass ); // Uint8Array(64) - different from above
Sync Derivation
// Synchronous version (blocks execution)
const seed = Bip39 . mnemonicToSeedSync ( mnemonic );
console . log ( seed ); // Uint8Array(64)
// With passphrase
const seedWithPass = Bip39 . mnemonicToSeedSync ( mnemonic , 'passphrase' );
PBKDF2 Algorithm
Parameters
BIP-39 uses PBKDF2-HMAC-SHA512 with specific parameters:
Algorithm: PBKDF2
PRF: HMAC-SHA512
Password: mnemonic (NFKD normalized)
Salt: "mnemonic" + passphrase (NFKD normalized)
Iterations: 2048
Output: 64 bytes (512 bits)
Step-by-Step Process
1. Normalize Mnemonic (NFKD)
// Unicode normalization form KD (Compatibility Decomposition)
const normalized = mnemonic . normalize ( 'NFKD' );
2. Construct Salt
const salt = 'mnemonic' + ( passphrase || '' ). normalize ( 'NFKD' );
3. Apply PBKDF2
// Pseudocode
seed = PBKDF2 (
password : normalized_mnemonic ,
salt : salt ,
iterations : 2048 ,
hash : SHA512 ,
keyLength : 64
)
4. Return 64-byte Seed
console . log ( seed . length ); // 64
Passphrase Support
Why Passphrases?
Passphrases add an additional security layer:
1. Plausible Deniability
const mnemonic = Bip39 . generateMnemonic ( 256 );
// Decoy wallet (small amount)
const decoySeed = await Bip39 . mnemonicToSeed ( mnemonic , '' );
// Real wallet (main funds)
const realSeed = await Bip39 . mnemonicToSeed ( mnemonic , 'my real passphrase' );
// Different seeds, same mnemonic
console . log ( decoySeed . some (( byte , i ) => byte !== realSeed [ i ])); // true
2. Two-Factor Security
// Factor 1: Mnemonic (backed up on paper)
const mnemonic = 'abandon abandon abandon...' ;
// Factor 2: Passphrase (memorized)
const passphrase = 'never written down' ;
// Attacker needs both
const seed = await Bip39 . mnemonicToSeed ( mnemonic , passphrase );
3. Enhanced Entropy
// Even with weak mnemonic, passphrase adds entropy
const weakMnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
const strongPassphrase = 'correct horse battery staple' ;
const seed = await Bip39 . mnemonicToSeed ( weakMnemonic , strongPassphrase );
Passphrase vs No Passphrase
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
// No passphrase (empty string is default)
const seed1 = await Bip39 . mnemonicToSeed ( mnemonic );
const seed2 = await Bip39 . mnemonicToSeed ( mnemonic , '' );
// Both are equivalent
console . log ( seed1 . every (( byte , i ) => byte === seed2 [ i ])); // true
// With passphrase produces different seed
const seed3 = await Bip39 . mnemonicToSeed ( mnemonic , 'passphrase' );
console . log ( seed1 . some (( byte , i ) => byte !== seed3 [ i ])); // true (different)
Passphrase Best Practices
1. Never forget passphrase
// ❌ Forgetting passphrase = permanent loss
const mnemonic = Bip39 . generateMnemonic ( 256 );
const passphrase = 'my secret' ;
const seed = await Bip39 . mnemonicToSeed ( mnemonic , passphrase );
// Later...cannot recover without exact passphrase
const wrongPassphrase = 'my secre' ; // Typo!
const wrongSeed = await Bip39 . mnemonicToSeed ( mnemonic , wrongPassphrase );
// Completely different wallet - funds unrecoverable
2. Use strong passphrases
// ❌ Weak
const weak = '1234' ;
// ✅ Strong
const strong = 'correct horse battery staple ancient wisdom mountain' ;
3. Store separately
// Mnemonic: in fireproof safe
const mnemonic = Bip39 . generateMnemonic ( 256 );
// Passphrase: memorized or separate secure location
const passphrase = 'never stored with mnemonic' ;
// Combine only when needed
const seed = await Bip39 . mnemonicToSeed ( mnemonic , passphrase );
Unicode Normalization (NFKD)
Why NFKD?
Different Unicode representations of same string must produce same seed:
// é can be represented two ways:
// 1. Single character: U+00E9 (é)
// 2. Combining: e (U+0065) + ́ (U+0301)
const form1 = 'café' ; // Composed
const form2 = 'café' ; // Decomposed (e + combining accent)
// Without normalization: different seeds
// With NFKD: same seed
const seed1 = await Bip39 . mnemonicToSeed ( 'test mnemonic' , form1 );
const seed2 = await Bip39 . mnemonicToSeed ( 'test mnemonic' , form2 );
console . log ( seed1 . every (( byte , i ) => byte === seed2 [ i ])); // true (with NFKD)
Normalization Example
// Japanese characters in passphrase
const passphrase = 'パスワード' ;
// NFKD normalization ensures consistency
const normalized = passphrase . normalize ( 'NFKD' );
const seed = await Bip39 . mnemonicToSeed ( mnemonic , passphrase );
// Internally normalizes to NFKD
Why 2048 Iterations?
PBKDF2 iterations balance security vs performance:
Security:
2048 iterations slow down brute-force attacks
Each guess takes ~50-100ms
Testing 1 million passphrases takes ~14 hours
Performance:
Fast enough for user experience (<100ms)
Not too slow for legitimate use
console . time ( 'seed derivation' );
const seed = await Bip39 . mnemonicToSeed ( mnemonic );
console . timeEnd ( 'seed derivation' );
// Typically: 50-100ms
Async vs Sync
Async (recommended):
// Non-blocking, allows UI updates
async function deriveWallet () {
console . log ( 'Deriving seed...' );
const seed = await Bip39 . mnemonicToSeed ( mnemonic );
console . log ( 'Seed derived!' );
return seed ;
}
Sync (use with caution):
// Blocks execution, may freeze UI
function deriveWalletSync () {
console . log ( 'Deriving seed...' );
const seed = Bip39 . mnemonicToSeedSync ( mnemonic );
// UI frozen for ~100ms
console . log ( 'Seed derived!' );
return seed ;
}
Security Properties
Deterministic
Same input always produces same output:
const mnemonic = Bip39 . generateMnemonic ( 256 );
const passphrase = 'test' ;
const seed1 = await Bip39 . mnemonicToSeed ( mnemonic , passphrase );
const seed2 = await Bip39 . mnemonicToSeed ( mnemonic , passphrase );
console . log ( seed1 . every (( byte , i ) => byte === seed2 [ i ])); // true
One-Way Function
Cannot reverse seed to mnemonic:
const mnemonic = Bip39 . generateMnemonic ( 256 );
const seed = await Bip39 . mnemonicToSeed ( mnemonic );
// ❌ Impossible to recover mnemonic from seed
// PBKDF2 is one-way cryptographic function
Passphrase as Salt
Passphrase modifies the salt, creating different seed:
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
// Different passphrases = different seeds
const passphrases = [ '' , 'a' , 'b' , 'password' , 'another' ];
const seeds = await Promise . all (
passphrases . map ( p => Bip39 . mnemonicToSeed ( mnemonic , p ))
);
// All seeds different
for ( let i = 0 ; i < seeds . length ; i ++ ) {
for ( let j = i + 1 ; j < seeds . length ; j ++ ) {
const different = seeds [ i ]. some (( byte , k ) => byte !== seeds [ j ][ k ]);
console . assert ( different , `Seeds ${ i } and ${ j } should be different` );
}
}
Test Vectors
BIP-39 Official Test Vectors
const testVectors = [
{
mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ,
passphrase: '' ,
seed: '5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4'
},
{
mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ,
passphrase: 'TREZOR' ,
seed: 'c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04'
},
{
mnemonic: 'legal winner thank year wave sausage worth useful legal winner thank yellow' ,
passphrase: '' ,
seed: '878386efb78845b3355bd15ea4d39ef97d179cb712b77d5c12b6be415fffeffe5f377ba02bf3f8544ab800b955e51fbff09828f682052a20faa6addbbddfb096'
}
];
// Verify implementation
for ( const { mnemonic , passphrase , seed : expectedHex } of testVectors ) {
const actualSeed = await Bip39 . mnemonicToSeed ( mnemonic , passphrase );
const actualHex = Array ( actualSeed )
. map ( b => b . toString ( 16 ). padStart ( 2 , '0' ))
. join ( '' );
console . assert ( actualHex === expectedHex , 'Seed derivation mismatch' );
}
Advanced Usage
Parallel Derivation
// Derive multiple seeds in parallel
const mnemonics = [
Bip39 . generateMnemonic ( 256 ),
Bip39 . generateMnemonic ( 256 ),
Bip39 . generateMnemonic ( 256 ),
];
const seeds = await Promise . all (
mnemonics . map ( m => Bip39 . mnemonicToSeed ( m ))
);
console . log ( seeds . length ); // 3
Progress Indication
// For large batch operations
async function deriveWithProgress ( mnemonics : string []) {
const seeds = [];
for ( let i = 0 ; i < mnemonics . length ; i ++ ) {
const seed = await Bip39 . mnemonicToSeed ( mnemonics [ i ]);
seeds . push ( seed );
const progress = (( i + 1 ) / mnemonics . length ) * 100 ;
console . log ( `Progress: ${ progress . toFixed ( 1 ) } %` );
}
return seeds ;
}
Caching Seeds
// Cache seeds for performance (careful with security)
class SeedCache {
private cache = new Map < string , Uint8Array >();
async getSeed ( mnemonic : string , passphrase = '' ) : Promise < Uint8Array > {
const key = ` ${ mnemonic } : ${ passphrase } ` ;
if ( ! this . cache . has ( key )) {
const seed = await Bip39 . mnemonicToSeed ( mnemonic , passphrase );
this . cache . set ( key , seed );
}
return this . cache . get ( key ) ! ;
}
clear () {
// Zero out seeds before clearing
for ( const seed of this . cache . values ()) {
seed . fill ( 0 );
}
this . cache . clear ();
}
}
Common Errors
Invalid Mnemonic
try {
const seed = await Bip39 . mnemonicToSeed ( 'invalid mnemonic phrase' );
} catch ( error ) {
console . error ( 'Seed derivation failed:' , error );
// Validation error
}
Passphrase Typos
// Original
const seed1 = await Bip39 . mnemonicToSeed ( mnemonic , 'password' );
// Typo (completely different seed!)
const seed2 = await Bip39 . mnemonicToSeed ( mnemonic , 'pasword' );
// No warning - both are valid but different
console . log ( seed1 . some (( byte , i ) => byte !== seed2 [ i ])); // true
Integration with HD Wallets
Full Workflow
import * as Bip39 from '@tevm/voltaire/Bip39' ;
import { HDWallet } from '@tevm/voltaire/native' ;
// 1. Generate mnemonic
const mnemonic = Bip39 . generateMnemonic ( 256 );
// 2. Derive seed
const seed = await Bip39 . mnemonicToSeed ( mnemonic , 'optional passphrase' );
// 3. Create HD wallet
const root = HDWallet . fromSeed ( seed );
// 4. Derive accounts
const eth0 = HDWallet . deriveEthereum ( root , 0 , 0 );
const privateKey = eth0 . getPrivateKey ();
console . log ( privateKey ); // Uint8Array(32)
Examples
References