Skip to main content
Skill — Copyable reference implementation. Use as-is or customize. See Skills Philosophy.

Overview

Generate Ethereum wallets using BIP-39 mnemonics and BIP-32/BIP-44 hierarchical deterministic key derivation. This guide covers the complete workflow from mnemonic generation to Ethereum address derivation.

Quick Start

import * as Bip39 from '@voltaire/crypto/Bip39';
import * as HDWallet from '@voltaire/crypto/HDWallet';
import * as Secp256k1 from '@voltaire/crypto/Secp256k1';
import { Address } from '@voltaire/primitives/Address';

// 1. Generate a 24-word mnemonic
const mnemonic = Bip39.generateMnemonic(256);
console.log('Backup this mnemonic:', mnemonic);

// 2. Derive seed from mnemonic
const seed = await Bip39.mnemonicToSeed(mnemonic);

// 3. Create HD wallet root key
const root = HDWallet.fromSeed(seed);

// 4. Derive first Ethereum account (m/44'/60'/0'/0/0)
const account = HDWallet.deriveEthereum(root, 0, 0);

// 5. Get private key
const privateKey = HDWallet.getPrivateKey(account);

// 6. Derive Ethereum address
const publicKey = Secp256k1.derivePublicKey(privateKey);
const address = Address.fromPublicKey(publicKey);

console.log('Address:', address.toHex());

Generate Mnemonic

BIP-39 mnemonics provide human-readable backup for wallet seeds.

12-Word Mnemonic (128 bits)

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

// 128 bits = 12 words
const mnemonic12 = Bip39.generateMnemonic(128);
console.log(mnemonic12);
// "abandon ability able about above absent absorb abstract absurd abuse access accident"
// 256 bits = 24 words (maximum security)
const mnemonic24 = Bip39.generateMnemonic(256);
console.log(mnemonic24);
// "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art"

Entropy to Word Count

Entropy BitsWordsSecurity Level
12812Standard
16015Enhanced
19218High
22421Very High
25624Maximum

Derive Seed from Mnemonic

Convert mnemonic to 64-byte seed using PBKDF2-HMAC-SHA512.

Async Derivation

import * as Bip39 from '@voltaire/crypto/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.length); // 64

// With passphrase (adds extra security)
const seedWithPass = await Bip39.mnemonicToSeed(mnemonic, 'my secret passphrase');
// Different seed than without passphrase

Sync Derivation

// Synchronous version (blocks execution)
const seed = Bip39.mnemonicToSeedSync(mnemonic);
console.log(seed.length); // 64

Create HD Wallet

Create hierarchical deterministic wallet from seed.
import * as Bip39 from '@voltaire/crypto/Bip39';
import * as HDWallet from '@voltaire/crypto/HDWallet';

const mnemonic = Bip39.generateMnemonic(256);
const seed = await Bip39.mnemonicToSeed(mnemonic);

// Create root HD key
const root = HDWallet.fromSeed(seed);

// Export extended keys for backup/recovery
const xprv = HDWallet.toExtendedPrivateKey(root);
const xpub = HDWallet.toExtendedPublicKey(root);

console.log(xprv); // "xprv9s21ZrQH143K..."
console.log(xpub); // "xpub661MyMwAqRbcF..."

Derive Ethereum Accounts

Use BIP-44 path m/44'/60'/account'/0/index for Ethereum.

Single Account

import * as HDWallet from '@voltaire/crypto/HDWallet';

// Derive first Ethereum address (account 0, index 0)
const eth0 = HDWallet.deriveEthereum(root, 0, 0);

// Get private key (32 bytes)
const privateKey = HDWallet.getPrivateKey(eth0);
console.log(privateKey.length); // 32

// Get compressed public key (33 bytes)
const publicKeyCompressed = HDWallet.getPublicKey(eth0);
console.log(publicKeyCompressed.length); // 33

Multiple Addresses

// Derive multiple addresses from same account
const addresses = [];

for (let i = 0; i < 5; i++) {
  const key = HDWallet.deriveEthereum(root, 0, i);
  const privateKey = HDWallet.getPrivateKey(key);
  addresses.push({
    path: `m/44'/60'/0'/0/${i}`,
    privateKey
  });
}

console.log(`Derived ${addresses.length} addresses`);

Multiple Accounts

// Derive from different accounts
const account0 = HDWallet.deriveEthereum(root, 0, 0); // m/44'/60'/0'/0/0
const account1 = HDWallet.deriveEthereum(root, 1, 0); // m/44'/60'/1'/0/0
const account2 = HDWallet.deriveEthereum(root, 2, 0); // m/44'/60'/2'/0/0

Get Ethereum Address

Convert private key to Ethereum address.
import * as Secp256k1 from '@voltaire/crypto/Secp256k1';
import { Address } from '@voltaire/primitives/Address';

// Get private key from HD derivation
const privateKey = HDWallet.getPrivateKey(eth0);

// Derive uncompressed public key (64 bytes)
const publicKey = Secp256k1.derivePublicKey(privateKey);
console.log(publicKey.length); // 64

// Create Ethereum address from public key
const address = Address.fromPublicKey(publicKey);
console.log(address.toHex()); // "0x9858EfFD232B4033E47d90003D41EC34EcaEda94"

Import Existing Mnemonic

Restore wallet from backed-up mnemonic.
import * as Bip39 from '@voltaire/crypto/Bip39';
import * as HDWallet from '@voltaire/crypto/HDWallet';
import * as Secp256k1 from '@voltaire/crypto/Secp256k1';
import { Address } from '@voltaire/primitives/Address';

// User provides backed-up mnemonic
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';

// Validate mnemonic first
if (!Bip39.validateMnemonic(mnemonic)) {
  throw new Error('Invalid mnemonic');
}

// Restore wallet (use same passphrase if one was used)
const seed = await Bip39.mnemonicToSeed(mnemonic);
const root = HDWallet.fromSeed(seed);

// Derive same addresses as before
const eth0 = HDWallet.deriveEthereum(root, 0, 0);
const privateKey = HDWallet.getPrivateKey(eth0);
const publicKey = Secp256k1.derivePublicKey(privateKey);
const address = Address.fromPublicKey(publicKey);

console.log('Restored address:', address.toHex());

Import from Extended Key

Restore from xprv/xpub string.
import * as HDWallet from '@voltaire/crypto/HDWallet';

// From extended private key (full access)
const xprv = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi';
const key = HDWallet.fromExtendedKey(xprv);

// Can derive any child (hardened or normal)
const child = HDWallet.derivePath(key, "m/44'/60'/0'/0/0");

// From extended public key (watch-only)
const xpub = 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8';
const pubOnly = HDWallet.fromPublicExtendedKey(xpub);

// Can only derive non-hardened children
const watchAddress = HDWallet.deriveChild(pubOnly, 0);
console.log(HDWallet.getPrivateKey(watchAddress)); // null (no private key)
console.log(HDWallet.getPublicKey(watchAddress));  // Uint8Array(33)

Custom Derivation Paths

Derive keys using any BIP-32 path.
import * as HDWallet from '@voltaire/crypto/HDWallet';

// Standard Ethereum path
const eth = HDWallet.derivePath(root, "m/44'/60'/0'/0/0");

// Bitcoin path
const btc = HDWallet.derivePath(root, "m/44'/0'/0'/0/0");

// Custom path
const custom = HDWallet.derivePath(root, "m/0'/1/2'/3");

// Path validation
console.log(HDWallet.isValidPath("m/44'/60'/0'/0/0")); // true
console.log(HDWallet.isValidPath("invalid/path"));     // false

// Check for hardened derivation
console.log(HDWallet.isHardenedPath("m/44'/60'/0'")); // true
console.log(HDWallet.isHardenedPath("m/44/60/0"));    // false

Complete Wallet Class Example

import * as Bip39 from '@voltaire/crypto/Bip39';
import * as HDWallet from '@voltaire/crypto/HDWallet';
import * as Secp256k1 from '@voltaire/crypto/Secp256k1';
import { Address } from '@voltaire/primitives/Address';

class Wallet {
  private root: ReturnType<typeof HDWallet.fromSeed>;

  private constructor(root: ReturnType<typeof HDWallet.fromSeed>) {
    this.root = root;
  }

  static async create(): Promise<Wallet> {
    const mnemonic = Bip39.generateMnemonic(256);
    const seed = await Bip39.mnemonicToSeed(mnemonic);
    console.log('BACKUP YOUR MNEMONIC:', mnemonic);
    return new Wallet(HDWallet.fromSeed(seed));
  }

  static async fromMnemonic(mnemonic: string, passphrase = ''): Promise<Wallet> {
    if (!Bip39.validateMnemonic(mnemonic)) {
      throw new Error('Invalid mnemonic');
    }
    const seed = await Bip39.mnemonicToSeed(mnemonic, passphrase);
    return new Wallet(HDWallet.fromSeed(seed));
  }

  static fromExtendedKey(xprv: string): Wallet {
    return new Wallet(HDWallet.fromExtendedKey(xprv));
  }

  getAccount(accountIndex: number, addressIndex: number = 0) {
    const key = HDWallet.deriveEthereum(this.root, accountIndex, addressIndex);
    const privateKey = HDWallet.getPrivateKey(key)!;
    const publicKey = Secp256k1.derivePublicKey(privateKey);
    const address = Address.fromPublicKey(publicKey);

    return {
      path: `m/44'/60'/${accountIndex}'/0/${addressIndex}`,
      privateKey,
      publicKey,
      address: address.toHex()
    };
  }

  getAccounts(count: number, accountIndex: number = 0) {
    return Array.from({ length: count }, (_, i) =>
      this.getAccount(accountIndex, i)
    );
  }

  exportExtendedPrivateKey(): string {
    return HDWallet.toExtendedPrivateKey(this.root);
  }

  exportExtendedPublicKey(): string {
    return HDWallet.toExtendedPublicKey(this.root);
  }
}

// Usage
const wallet = await Wallet.create();
const account = wallet.getAccount(0, 0);
console.log('Address:', account.address);

// Restore from mnemonic
const restored = await Wallet.fromMnemonic(
  'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
);
console.log('Restored:', restored.getAccount(0, 0).address);

Security Best Practices

Entropy Quality

// Always use Bip39.generateMnemonic() - uses crypto.getRandomValues()
const secure = Bip39.generateMnemonic(256);

// NEVER use Math.random() or predictable sources

Passphrase Usage

// Passphrase adds extra security layer
const seed = await Bip39.mnemonicToSeed(mnemonic, 'strong passphrase');

// Warning: Forgetting passphrase = permanent loss
// Different passphrase = completely different wallet

Memory Handling

// Clear sensitive data after use
function clearKey(key: Uint8Array) {
  key.fill(0);
}

const privateKey = HDWallet.getPrivateKey(account);
// ... use privateKey ...
clearKey(privateKey); // Zero out when done

Storage Guidelines

  • Mnemonic: Write on paper, store in fireproof safe, never digital
  • Passphrase: Memorize or store separately from mnemonic
  • xprv: Treat like private key, never transmit unencrypted
  • xpub: Safe to share for watch-only access

API Reference

Bip39 Functions

FunctionDescription
generateMnemonic(bits)Generate mnemonic (128/160/192/224/256 bits)
validateMnemonic(mnemonic)Check if mnemonic is valid BIP-39
mnemonicToSeed(mnemonic, passphrase?)Async seed derivation
mnemonicToSeedSync(mnemonic, passphrase?)Sync seed derivation

HDWallet Functions

FunctionDescription
fromSeed(seed)Create root key from 64-byte seed
fromExtendedKey(xprv)Import from extended private key
fromPublicExtendedKey(xpub)Import from extended public key
derivePath(key, path)Derive child by BIP-32 path
deriveChild(key, index)Derive child by index
deriveEthereum(key, account, index)Derive Ethereum address
getPrivateKey(key)Get 32-byte private key
getPublicKey(key)Get 33-byte compressed public key
toExtendedPrivateKey(key)Export xprv string
toExtendedPublicKey(key)Export xpub string

Address Functions

FunctionDescription
Address.fromPublicKey(pubKey)Create address from 64-byte uncompressed public key
Address.fromPrivateKey(privKey)Create address from 32-byte private key
address.toHex()Get checksummed hex string

References