Skip to main content

Try it Live

Run BIP39 examples in the interactive playground

Overview

BIP-39 mnemonic generation converts cryptographic entropy into human-readable word sequences. The process ensures deterministic, verifiable, and secure seed phrase creation.

Generation Process

1. Entropy Generation

Generate cryptographically secure random bytes:
import * as Bip39 from '@tevm/voltaire/Bip39';

// 128 bits = 12 words
const mnemonic12 = Bip39.generateMnemonic(128);

// 256 bits = 24 words (recommended)
const mnemonic24 = Bip39.generateMnemonic(256);

2. Entropy to Mnemonic Conversion

// Custom entropy (must be 16-32 bytes)
const entropy = crypto.getRandomValues(Bytes32()); // 256 bits
const mnemonic = Bip39.entropyToMnemonic(entropy);

console.log(mnemonic.split(' ').length); // 24 words

3. Word Count Mapping

Entropy BitsChecksum BitsTotal BitsWordsSecurity Level
128413212Standard
160516515Enhanced
192619818High
224723121Very High
256826424Maximum

Algorithm Details

Step-by-Step Process

1. Generate Entropy (ENT)
ENT = 128 to 256 bits (must be multiple of 32)
2. Calculate Checksum (CS)
CS = SHA256(ENT)[0:ENT/32 bits]
3. Concatenate
Binary = ENT || CS
4. Split into 11-bit Groups
Each group = 0-2047 (maps to wordlist index)
Words = Binary / 11
5. Map to Wordlist
For each 11-bit group:
  word = WORDLIST[group_value]

Example Calculation

// 128-bit entropy
const entropy = Bytes16().fill(0x00);

// Binary representation
// 00000000 00000000 ... (128 bits of zeros)

// SHA256 checksum (first 4 bits)
// SHA256(entropy) = 374708fff7719dd5979ec875d56cd2286f6d3cf7ec317a3b25632aab28ec37bb
// First 4 bits: 0011 (3)

// Combined: 128 bits + 4 bits = 132 bits
// Split into 11-bit groups: 132 / 11 = 12 words

// First 11 bits: 00000000000 = 0 → "abandon"
// All zeros → all "abandon" except last word (includes checksum)

const result = Bip39.entropyToMnemonic(entropy);
// "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"

Entropy Sources

Browser Environment

// Cryptographically secure random
const entropy = crypto.getRandomValues(Bytes32());
const mnemonic = Bip39.entropyToMnemonic(entropy);

Node.js Environment

import { randomBytes } from 'crypto';

const entropy = randomBytes(32); // 256 bits
const mnemonic = Bip39.entropyToMnemonic(entropy);

Hardware Wallets

Hardware wallets use dedicated secure elements:
// Simulated hardware entropy (do not use in production)
// Real hardware uses secure element RNG
async function hardwareEntropy() {
  // Request entropy from hardware device
  const hwEntropy = await hardwareDevice.getRandomBytes(32);
  return Bip39.entropyToMnemonic(hwEntropy);
}

Utility Functions

Calculate Word Count

// Get word count for entropy bits
const wordCount = Bip39.getWordCount(128); // 12
const wordCount2 = Bip39.getWordCount(256); // 24

Calculate Entropy Bits

// Get entropy bits for word count
const entropy = Bip39.getEntropyBits(12); // 128
const entropy2 = Bip39.getEntropyBits(24); // 256

Security Considerations

Entropy Quality

Critical: Use cryptographically secure randomness
// ✅ SECURE - Uses crypto.getRandomValues()
const mnemonic = Bip39.generateMnemonic(256);

// ❌ INSECURE - Never use Math.random()
const badEntropy = Bytes32();
for (let i = 0; i < 32; i++) {
  badEntropy[i] = Math.floor(Math.random() * 256); // PREDICTABLE!
}

// ❌ INSECURE - Never use timestamp-based entropy
const timestamp = Date.now();
const weakEntropy = Bytes32().fill(timestamp & 0xFF);

Entropy Size Recommendations

Minimum: 128 bits (12 words)
  • Provides 2^128 possible combinations
  • Considered secure against brute force
  • Suitable for low-to-medium value wallets
Recommended: 256 bits (24 words)
  • Provides 2^256 possible combinations
  • Future-proof against quantum computers
  • Recommended for high-value wallets
// Low-value wallet (testing, small amounts)
const testWallet = Bip39.generateMnemonic(128);

// Production wallet (recommended)
const productionWallet = Bip39.generateMnemonic(256);

Deterministic Generation

Same entropy always produces same mnemonic:
const entropy = Bytes16().fill(0x42);

const mnemonic1 = Bip39.entropyToMnemonic(entropy);
const mnemonic2 = Bip39.entropyToMnemonic(entropy);

console.log(mnemonic1 === mnemonic2); // true

Offline Generation

Best practice: Generate mnemonics offline
// 1. Disconnect from network
// 2. Generate mnemonic
const mnemonic = Bip39.generateMnemonic(256);

// 3. Write on paper
// 4. Verify by restoring
const isValid = Bip39.validateMnemonic(mnemonic);

// 5. Clear browser/device memory
// 6. Reconnect to network

Advanced Usage

Custom Entropy Length

// 160 bits = 15 words
const mnemonic15 = Bip39.generateMnemonic(160);

// 192 bits = 18 words
const mnemonic18 = Bip39.generateMnemonic(192);

// 224 bits = 21 words
const mnemonic21 = Bip39.generateMnemonic(224);

Dice-Roll Entropy (Maximum Security)

For maximum paranoia, generate entropy manually:
// Roll 6-sided die 99 times for 256 bits
// Each roll contributes ~2.58 bits of entropy
function diceToEntropy(rolls: number[]): Uint8Array {
  // Convert base-6 to binary
  let binary = '';
  for (const roll of rolls) {
    binary += (roll - 1).toString(2).padStart(3, '0');
  }

  // Take first 256 bits
  const entropy = Bytes32();
  for (let i = 0; i < 32; i++) {
    entropy[i] = parseInt(binary.slice(i * 8, i * 8 + 8), 2);
  }

  return entropy;
}

// Example: 99 dice rolls
const diceRolls = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, /* ... */];
const diceEntropy = diceToEntropy(diceRolls);
const diceMnemonic = Bip39.entropyToMnemonic(diceEntropy);

Verifying Generation

// Generate mnemonic
const mnemonic = Bip39.generateMnemonic(256);

// Verify it's valid
const isValid = Bip39.validateMnemonic(mnemonic);
console.log(isValid); // true

// Verify word count
const words = mnemonic.split(' ');
console.log(words.length); // 24

// Verify deterministic
const seed1 = await Bip39.mnemonicToSeed(mnemonic);
const seed2 = await Bip39.mnemonicToSeed(mnemonic);
console.log(seed1.every((byte, i) => byte === seed2[i])); // true

Common Errors

Invalid Entropy Length

// ❌ Invalid - 20 bytes (160 bits) not supported in this example
const invalidEntropy = new Uint8Array(20);
// Use 16, 20, 24, 28, or 32 bytes

// ✅ Valid
const validEntropy = Bytes32(); // 256 bits

Non-Random Entropy

// ❌ DANGEROUS - Sequential pattern
const sequential = Bytes32();
for (let i = 0; i < 32; i++) {
  sequential[i] = i;
}

// ❌ DANGEROUS - All same value
const constant = Bytes32().fill(0xFF);

// ✅ SECURE - Cryptographic randomness
const secure = crypto.getRandomValues(Bytes32());

Implementation Details

Uses @scure/bip39 by Paul Miller:
  • Audited implementation
  • Constant-time checksum validation
  • Support for multiple wordlists
  • NFKD normalization
  • Strict BIP-39 compliance

Examples

References