Skip to main content

Try it Live

Run BIP39 examples in the interactive playground

Overview

BIP-39 uses standardized 2048-word lists to convert entropy into human-readable mnemonics. Multiple languages are supported, enabling global wallet recovery.

English Wordlist (Default)

Properties

  • Size: 2048 words (2^11, fits 11 bits)
  • Length: 3-8 characters per word
  • Uniqueness: First 4 letters unique
  • Character set: Lowercase a-z only
  • Format: Alphabetically sorted

Word Requirements

1. Unique Prefix First 4 letters distinguish all words:
// These are valid BIP-39 words
'abandon' // aban
'ability' // abil
'able'    // able
'about'   // abou
'above'   // abov
'absent'  // abse

// No collisions in first 4 letters
2. Length Constraints
// Shortest words (3 characters)
['act', 'add', 'age', 'aim', 'air', 'all', 'and', 'ant', 'any', 'ape', 'app', 'arc', 'are', 'ark', 'arm', 'art', 'ask']

// Longest words (8 characters)
['absolute', 'abstract', 'accident', 'accurate', 'acoustic', 'activity', 'actually', ...]
3. Common Words BIP-39 prioritizes common, easy-to-spell English words:
// Easy to spell and remember
['abandon', 'ability', 'able', 'about', 'above', 'absent', 'absorb', 'abstract', 'absurd', 'abuse', 'access', 'accident']

// Avoids:
// - Proper nouns
// - Technical jargon
// - Obscure vocabulary

Using English Wordlist

Default Usage

import * as Bip39 from '@tevm/voltaire/Bip39';

// English is default
const mnemonic = Bip39.generateMnemonic(256);
console.log(mnemonic);
// "abandon ability able about above absent absorb abstract absurd abuse access accident account accuse achieve acid acoustic acquire across act action actor actress actual"

Explicit English

import { wordlist as english } from '@scure/bip39/wordlists/english.js';

const mnemonic = Bip39.generateMnemonic(256, english);

Other Language Wordlists

Supported Languages

BIP-39 supports 9 languages:
  1. English (default)
  2. Chinese (Simplified)
  3. Chinese (Traditional)
  4. Czech
  5. French
  6. Italian
  7. Japanese
  8. Korean
  9. Portuguese
  10. Spanish

Language-Specific Generation

import { wordlist as spanish } from '@scure/bip39/wordlists/spanish.js';
import { wordlist as french } from '@scure/bip39/wordlists/french.js';
import { wordlist as japanese } from '@scure/bip39/wordlists/japanese.js';

// Spanish mnemonic
const mnemonicES = Bip39.generateMnemonic(256, spanish);

// French mnemonic
const mnemonicFR = Bip39.generateMnemonic(256, french);

// Japanese mnemonic
const mnemonicJA = Bip39.generateMnemonic(256, japanese);

Example Mnemonics

English:
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
Spanish:
ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco ábaco abierto
French:
abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abaisser abeille
Japanese:
あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいこくしん あいさつ

Wordlist Format

Structure

Each wordlist is a sorted array of 2048 strings:
const wordlist = [
  'abandon',  // Index 0
  'ability',  // Index 1
  'able',     // Index 2
  'about',    // Index 3
  // ... 2044 more words
  'zone',     // Index 2047
];

Index to Word Mapping

// 11-bit groups map to wordlist index
const index = 0b00000000000; // 0 in binary
const word = wordlist[index]; // 'abandon'

const index2 = 0b11111111111; // 2047 in binary
const word2 = wordlist[index2]; // 'zoo' (last word in English)

Custom Wordlist

// Create custom wordlist (must be 2048 words)
const customWordlist = [
  // 2048 unique words
  'word0', 'word1', 'word2', /* ... */, 'word2047'
];

// Use with BIP-39
const mnemonic = Bip39.generateMnemonic(256, customWordlist);

Language-Specific Considerations

Japanese Wordlist

Japanese uses ideographic space (U+3000):
import { wordlist as japanese } from '@scure/bip39/wordlists/japanese.js';

const mnemonicJA = Bip39.generateMnemonic(256, japanese);
console.log(mnemonicJA);
// Words separated by ideographic space

Chinese Wordlists

Simplified vs Traditional:
import { wordlist as simplifiedChinese } from '@scure/bip39/wordlists/simplified-chinese.js';
import { wordlist as traditionalChinese } from '@scure/bip39/wordlists/traditional-chinese.js';

const mnemonicCN = Bip39.generateMnemonic(256, simplifiedChinese);
const mnemonicTW = Bip39.generateMnemonic(256, traditionalChinese);

Czech Diacritics

Czech uses diacritical marks:
import { wordlist as czech } from '@scure/bip39/wordlists/czech.js';

const mnemonicCZ = Bip39.generateMnemonic(256, czech);
// Contains characters like: á, č, ď, é, ě, í, ň, ó, ř, š, ť, ú, ů, ý, ž

Cross-Language Compatibility

Same Entropy, Different Languages

const entropy = Bytes32().fill(0);

import { wordlist as english } from '@scure/bip39/wordlists/english.js';
import { wordlist as spanish } from '@scure/bip39/wordlists/spanish.js';

const mnemonicEN = Bip39.entropyToMnemonic(entropy, english);
const mnemonicES = Bip39.entropyToMnemonic(entropy, spanish);

// Different words, same entropy, same seed
const seedEN = await Bip39.mnemonicToSeed(mnemonicEN);
const seedES = await Bip39.mnemonicToSeed(mnemonicES);

console.log(seedEN.every((byte, i) => byte === seedES[i])); // true

Language Detection

function detectLanguage(mnemonic: string): string {
  const words = mnemonic.split(' ');

  const wordlists = {
    english: require('@scure/bip39/wordlists/english.js').wordlist,
    spanish: require('@scure/bip39/wordlists/spanish.js').wordlist,
    french: require('@scure/bip39/wordlists/french.js').wordlist,
    // ... more languages
  };

  for (const [lang, wordlist] of Object.entries(wordlists)) {
    if (words.every(word => wordlist.includes(word))) {
      return lang;
    }
  }

  return 'unknown';
}

const mnemonic = Bip39.generateMnemonic(256);
console.log(detectLanguage(mnemonic)); // 'english'

Wordlist Validation

Checking Word Existence

import { wordlist } from '@scure/bip39/wordlists/english.js';

function isValidWord(word: string): boolean {
  return wordlist.includes(word.toLowerCase());
}

console.log(isValidWord('abandon')); // true
console.log(isValidWord('bitcoin')); // false (not in BIP-39 wordlist)

Finding Invalid Words

function findInvalidWords(mnemonic: string): string[] {
  const words = mnemonic.split(' ');
  return words.filter(word => !wordlist.includes(word));
}

const invalid = findInvalidWords('abandon bitcoin ethereum about');
console.log(invalid); // ['bitcoin', 'ethereum']

Autocomplete Implementation

Prefix Matching

function autocomplete(prefix: string, limit = 10): string[] {
  const lower = prefix.toLowerCase();
  return wordlist
    .filter(word => word.startsWith(lower))
    .slice(0, limit);
}

console.log(autocomplete('aba'));
// ['abandon', 'ability', 'able', 'about', 'above']

console.log(autocomplete('aban'));
// ['abandon']

Typo Correction

function findClosestWord(input: string): string {
  const lower = input.toLowerCase();

  // Check exact match
  if (wordlist.includes(lower)) {
    return lower;
  }

  // Check prefix (first 4 letters unique in BIP-39)
  const prefix = lower.slice(0, 4);
  const matches = wordlist.filter(w => w.startsWith(prefix));

  if (matches.length === 1) {
    return matches[0];
  }

  // Levenshtein distance for typos
  // ... implementation
  return input; // Fallback
}

console.log(findClosestWord('aband')); // 'abandon'
console.log(findClosestWord('aban'));  // 'abandon'

Word Selection Properties

Even Distribution

All 2048 words equally probable:
// Each word has 1/2048 chance
const probability = 1 / 2048; // ~0.049%

// For 12-word mnemonic:
const combinations = Math.pow(2048, 12);
console.log(combinations); // 5.44e39 (2^132)

// For 24-word mnemonic:
const combinations24 = Math.pow(2048, 24);
console.log(combinations24); // 2.96e71 (2^264)

No Semantic Meaning

Word order has no semantic meaning (just encodes entropy):
// These are both valid but completely different wallets:
const mnemonic1 = 'abandon ability able about above absent absorb abstract absurd abuse access accident';
const mnemonic2 = 'accident access abuse absurd abstract absorb absent above about able ability abandon';

// Different orders = different entropy = different wallets

Performance

Word Lookup

Wordlist is array - O(n) lookup without indexing:
// Slow (linear search)
function slowLookup(word: string): number {
  return wordlist.indexOf(word); // O(n)
}

// Fast (binary search, since sorted)
function fastLookup(word: string): number {
  let left = 0;
  let right = wordlist.length - 1;

  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    const comparison = word.localeCompare(wordlist[mid]);

    if (comparison === 0) return mid;
    if (comparison < 0) right = mid - 1;
    else left = mid + 1;
  }

  return -1; // Not found
}

Indexing for Speed

// Create index for O(1) lookup
const wordIndex = new Map(
  wordlist.map((word, index) => [word, index])
);

function instantLookup(word: string): number {
  return wordIndex.get(word) ?? -1; // O(1)
}

Security Implications

Wordlist Standardization

Using non-standard wordlist reduces compatibility:
// ✅ Standard - works everywhere
import { wordlist } from '@scure/bip39/wordlists/english.js';
const mnemonic = Bip39.generateMnemonic(256, wordlist);

// ❌ Custom - may not work in other wallets
const customWordlist = ['apple', 'banana', /* 2046 more */];
const customMnemonic = Bip39.generateMnemonic(256, customWordlist);

Language Consistency

Always use same language for recovery:
// Generate in English
import { wordlist as english } from '@scure/bip39/wordlists/english.js';
const mnemonic = Bip39.generateMnemonic(256, english);

// ❌ Cannot validate with different language
import { wordlist as spanish } from '@scure/bip39/wordlists/spanish.js';
Bip39.validateMnemonic(mnemonic, spanish); // false (different wordlist)

// ✅ Must use same language
Bip39.validateMnemonic(mnemonic, english); // true

Best Practices

1. Use English for maximum compatibility
// Most widely supported
const mnemonic = Bip39.generateMnemonic(256); // English by default
2. Store language metadata
interface StoredMnemonic {
  mnemonic: string;
  language: 'english' | 'spanish' | 'french' | /* ... */;
}

const stored: StoredMnemonic = {
  mnemonic: Bip39.generateMnemonic(256),
  language: 'english'
};
3. Validate against correct wordlist
async function recoverWallet(mnemonic: string, language: string) {
  const wordlist = await import(`@scure/bip39/wordlists/${language}.js`);

  if (!Bip39.validateMnemonic(mnemonic, wordlist.wordlist)) {
    throw new Error(`Invalid ${language} mnemonic`);
  }

  return await Bip39.mnemonicToSeed(mnemonic);
}

References