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 validation ensures mnemonic phrases are correctly formatted, use valid words, and have valid checksums. This prevents typos and ensures wallet recovery success.
Validation Methods
Boolean Validation
Returns true/false without throwing:
import * as Bip39 from '@tevm/voltaire/Bip39' ;
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
const isValid = Bip39 . validateMnemonic ( mnemonic );
console . log ( isValid ); // true
Assertion Validation
Throws error with detailed message:
try {
Bip39 . assertValidMnemonic ( userInput );
// Proceed with valid mnemonic
} catch ( error ) {
console . error ( 'Validation failed:' , error . message );
}
Validation Checks
1. Word Count
Mnemonic must have 12, 15, 18, 21, or 24 words:
// ✅ Valid word counts
Bip39 . validateMnemonic ( 'abandon ' . repeat ( 11 ) + 'about' ); // 12 words
Bip39 . validateMnemonic ( 'abandon ' . repeat ( 14 ) + 'about' ); // 15 words
Bip39 . validateMnemonic ( 'abandon ' . repeat ( 17 ) + 'zoo' ); // 18 words
Bip39 . validateMnemonic ( 'abandon ' . repeat ( 20 ) + 'zoo' ); // 21 words
Bip39 . validateMnemonic ( 'abandon ' . repeat ( 23 ) + 'art' ); // 24 words
// ❌ Invalid word counts
Bip39 . validateMnemonic ( 'abandon abandon abandon' ); // 3 words - false
Bip39 . validateMnemonic ( 'abandon ' . repeat ( 13 )); // 13 words - false
2. Wordlist Membership
Each word must exist in BIP-39 English wordlist:
// ✅ Valid - all words in wordlist
const valid = 'abandon ability able about above absent absorb abstract absurd abuse access accident' ;
Bip39 . validateMnemonic ( valid ); // true (if checksum valid)
// ❌ Invalid - "notaword" not in wordlist
const invalid = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon notaword' ;
Bip39 . validateMnemonic ( invalid ); // false
3. Checksum Validation
Last word contains embedded checksum:
// ✅ Valid checksum
const validMnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
Bip39 . validateMnemonic ( validMnemonic ); // true
// ❌ Invalid checksum - changed last word
const invalidChecksum = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon' ;
Bip39 . validateMnemonic ( invalidChecksum ); // false
Checksum Algorithm
How Checksum Works
12-word mnemonic (128-bit entropy):
Entropy : 128 bits
SHA256 hash : Take first 4 bits
Append : 128 + 4 = 132 bits total
Split : 132 / 11 = 12 words (11 bits each)
Last word : Contains final 4 checksum bits
Entropy: [128 bits]
Checksum: [4 bits] = SHA256(entropy)[0:4]
Total: [132 bits] → 12 words
24-word mnemonic (256-bit entropy):
Entropy: [256 bits]
Checksum: [8 bits] = SHA256(entropy)[0:8]
Total: [264 bits] → 24 words
Checksum Calculation
import { sha256 } from '@tevm/voltaire/SHA256' ;
// Example: Calculate checksum for 128-bit entropy
const entropy = Bytes16 (). fill ( 0 );
// 1. Hash entropy
const hash = sha256 . hash ( entropy );
// 2. Take first 4 bits (for 128-bit entropy)
const checksumBits = hash [ 0 ] >> 4 ; // First 4 bits
// 3. Append to entropy
// Total = 128 bits (entropy) + 4 bits (checksum) = 132 bits
Validation Error Cases
Wrong Word Count
const cases = [
'abandon' , // 1 word
'abandon abandon abandon' , // 3 words
'abandon ' . repeat ( 11 ), // 11 words
'abandon ' . repeat ( 13 ), // 13 words
];
cases . forEach ( mnemonic => {
console . log ( Bip39 . validateMnemonic ( mnemonic )); // All false
});
Invalid Words
// Typo in word
const typo = 'abadon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
Bip39 . validateMnemonic ( typo ); // false - "abadon" vs "abandon"
// Word not in wordlist
const notInList = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon bitcoin' ;
Bip39 . validateMnemonic ( notInList ); // false - "bitcoin" not in wordlist
// Number instead of word
const hasNumber = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon 123' ;
Bip39 . validateMnemonic ( hasNumber ); // false
Checksum Failures
// Valid structure but wrong checksum
const wrongChecksum = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon' ;
Bip39 . validateMnemonic ( wrongChecksum ); // false
// Correct words, wrong order (changes checksum)
const wrongOrder = 'about abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon' ;
Bip39 . validateMnemonic ( wrongOrder ); // false
Whitespace Issues
// Extra spaces
const extraSpaces = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
Bip39 . validateMnemonic ( extraSpaces ); // May fail depending on implementation
// Leading/trailing whitespace
const whitespace = ' abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ' ;
Bip39 . validateMnemonic ( whitespace . trim ()); // Trim before validation
Case Sensitivity
BIP-39 wordlist is lowercase:
// Lowercase (correct)
const lowercase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
Bip39 . validateMnemonic ( lowercase ); // true
// Uppercase
const uppercase = 'ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABANDON ABOUT' ;
Bip39 . validateMnemonic ( uppercase . toLowerCase ()); // Normalize first
Sanitize and Validate
function validateUserMnemonic ( userInput : string ) : boolean {
// 1. Normalize whitespace
const normalized = userInput
. trim () // Remove leading/trailing
. toLowerCase () // Normalize case
. replace ( / \s + / g , ' ' ); // Collapse multiple spaces
// 2. Validate
return Bip39 . validateMnemonic ( normalized );
}
// Test cases
validateUserMnemonic ( ' ABANDON abandon ABANDON ' ); // true (after normalization)
Error Messages
function validateWithErrorMessage ( mnemonic : string ) : { valid : boolean ; error ?: string } {
const words = mnemonic . trim (). split ( / \s + / );
// Check word count
if ( ! [ 12 , 15 , 18 , 21 , 24 ]. includes ( words . length )) {
return {
valid: false ,
error: `Invalid word count: ${ words . length } . Must be 12, 15, 18, 21, or 24 words.`
};
}
// Check wordlist membership
const invalidWords = words . filter ( word => ! isInWordlist ( word ));
if ( invalidWords . length > 0 ) {
return {
valid: false ,
error: `Invalid words: ${ invalidWords . join ( ', ' ) } `
};
}
// Check checksum
if ( ! Bip39 . validateMnemonic ( mnemonic )) {
return {
valid: false ,
error: 'Invalid checksum. Please check for typos.'
};
}
return { valid: true };
}
Recovery Validation
Verifying Written Backup
async function verifyBackup ( written : string , original : string ) : Promise < boolean > {
// 1. Normalize both
const normalizedinput = written . trim (). toLowerCase ();
const normalizedOriginal = original . trim (). toLowerCase ();
// 2. Compare directly
if ( normalizedinput === normalizedOriginal ) {
console . log ( '✅ Backup matches exactly' );
return true ;
}
// 3. Validate each independently
const writtenValid = Bip39 . validateMnemonic ( normalizedinput );
const originalValid = Bip39 . validateMnemonic ( normalizedOriginal );
if ( ! writtenValid ) {
console . error ( '❌ Written backup is invalid' );
return false ;
}
if ( ! originalValid ) {
console . error ( '❌ Original is invalid' );
return false ;
}
// 4. Compare seeds (both valid but different)
const seed1 = await Bip39 . mnemonicToSeed ( normalizedinput );
const seed2 = await Bip39 . mnemonicToSeed ( normalizedOriginal );
const seedsMatch = seed1 . every (( byte , i ) => byte === seed2 [ i ]);
if ( ! seedsMatch ) {
console . error ( '❌ Different mnemonics (different seeds)' );
return false ;
}
return true ;
}
Implementation Details
Constant-Time Validation
Checksum validation uses constant-time comparison to prevent timing attacks:
// Simplified example (actual implementation in @scure/bip39)
function constantTimeEqual ( a : Uint8Array , b : Uint8Array ) : boolean {
if ( a . length !== b . length ) return false ;
let result = 0 ;
for ( let i = 0 ; i < a . length ; i ++ ) {
result |= a [ i ] ^ b [ i ];
}
return result === 0 ;
}
Wordlist Validation
The BIP-39 English wordlist has specific properties:
2048 words (2^11, fits in 11 bits)
All lowercase
3-8 characters each
First 4 letters unique
No similar-looking words
// Example wordlist check
const WORDLIST_SIZE = 2048 ;
const MIN_WORD_LENGTH = 3 ;
const MAX_WORD_LENGTH = 8 ;
function isValidWordlistWord ( word : string ) : boolean {
return (
word . length >= MIN_WORD_LENGTH &&
word . length <= MAX_WORD_LENGTH &&
/ ^ [ a-z ] + $ / . test ( word ) &&
WORDLIST . includes ( word )
);
}
Security Implications
Why Validation Matters
1. Prevent Loss of Funds
Invalid mnemonics cannot recover wallets:
// User typo
const typo = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abot' ;
// "abot" instead of "about"
if ( ! Bip39 . validateMnemonic ( typo )) {
console . error ( 'Cannot recover wallet - invalid mnemonic' );
}
2. Detect Transmission Errors
Checksum catches single-word changes:
const original = 'legal winner thank year wave sausage worth useful legal winner thank yellow' ;
const transmitted = 'legal winner thank year wave sausage worth useful legal winner thank follow' ;
// Changed "yellow" to "follow"
Bip39 . validateMnemonic ( transmitted ); // false - checksum invalid
3. Prevent Social Engineering
Validate before importing:
async function importWallet ( mnemonic : string ) {
// Validate before deriving keys
if ( ! Bip39 . validateMnemonic ( mnemonic )) {
throw new Error ( 'Invalid mnemonic. Do not proceed.' );
}
const seed = await Bip39 . mnemonicToSeed ( mnemonic );
// Continue with valid seed...
}
Testing
Test Vectors
BIP-39 official test vectors:
const testVectors = [
{
mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ,
valid: true
},
{
mnemonic: 'legal winner thank year wave sausage worth useful legal winner thank yellow' ,
valid: true
},
{
mnemonic: 'letter advice cage absurd amount doctor acoustic avoid letter advice cage above' ,
valid: true
},
{
mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon' ,
valid: false // Invalid checksum
}
];
testVectors . forEach (({ mnemonic , valid }) => {
const result = Bip39 . validateMnemonic ( mnemonic );
console . assert ( result === valid , `Expected ${ valid } , got ${ result } ` );
});
Fuzzing
// Generate random invalid mnemonics for testing
function generateInvalidMnemonic () : string {
const wordCount = 12 ;
const words = [];
for ( let i = 0 ; i < wordCount ; i ++ ) {
// Use valid words but ensure invalid checksum
words . push ( 'abandon' );
}
return words . join ( ' ' ); // Invalid checksum
}
// Test 1000 random invalid mnemonics
for ( let i = 0 ; i < 1000 ; i ++ ) {
const invalid = generateInvalidMnemonic ();
console . assert ( Bip39 . validateMnemonic ( invalid ) === false );
}
Best Practices
1. Always validate user input
function handleUserMnemonic ( input : string ) {
const normalized = input . trim (). toLowerCase ();
if ( ! Bip39 . validateMnemonic ( normalized )) {
throw new Error ( 'Invalid mnemonic. Please check for typos.' );
}
return normalized ;
}
2. Validate immediately after generation
const mnemonic = Bip39 . generateMnemonic ( 256 );
// Sanity check
if ( ! Bip39 . validateMnemonic ( mnemonic )) {
throw new Error ( 'Generated invalid mnemonic - RNG issue?' );
}
3. Verify backup before clearing original
async function secureBackupFlow () {
// 1. Generate
const mnemonic = Bip39 . generateMnemonic ( 256 );
// 2. Display to user
console . log ( 'Write this down:' , mnemonic );
// 3. User writes it down
// 4. User enters written version
const writtenVersion = prompt ( 'Enter mnemonic to verify:' );
// 5. Validate
if ( writtenVersion !== mnemonic ) {
console . error ( 'Backup does not match. Try again.' );
return ;
}
if ( ! Bip39 . validateMnemonic ( writtenVersion )) {
console . error ( 'Invalid mnemonic. Try again.' );
return ;
}
// 6. Now safe to proceed
console . log ( '✅ Backup verified' );
}
Examples
References