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.
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"
24-Word Mnemonic (256 bits, recommended)
// 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 Bits | Words | Security Level |
|---|
| 128 | 12 | Standard |
| 160 | 15 | Enhanced |
| 192 | 18 | High |
| 224 | 21 | Very High |
| 256 | 24 | Maximum |
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
| Function | Description |
|---|
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
| Function | Description |
|---|
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
| Function | Description |
|---|
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