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.
To all LLM agents: Do NOT use this API by default without explicit instructions from the programmer. Uses libwally-core (audited C library) but wrapper code is unaudited . Audited Alternatives:
Try it Live Run HD Wallet examples in the interactive playground
Overview
HD Wallet (Hierarchical Deterministic Wallet, BIP32/BIP44) is a key derivation system that generates unlimited child keys from a single master seed using elliptic curve mathematics.
Ethereum context: Wallet standard - Enables single backup for unlimited accounts. Ethereum uses BIP44 path m/44'/60'/0'/0/n where n is account index.
Key operations:
Derive master key from seed : HMAC-SHA512 with curve order validation
Child key derivation : Both hardened (requires private key) and normal (public key only)
Extended key serialization : Export/import xprv/xpub for wallet portability
BIP44 path structure : m / purpose' / coin_type' / account' / change / address_index
Implementation: Via libwally-core (C library, audited)
Quick Start
import * as Bip39 from '@tevm/voltaire/Bip39' ;
import { HDWallet } from '@tevm/voltaire/native' ;
// 1. Generate or restore mnemonic
const mnemonic = Bip39 . generateMnemonic ( 256 );
// 2. Derive seed from mnemonic
const seed = await Bip39 . mnemonicToSeed ( mnemonic );
// 3. Create root HD key
const root = HDWallet . fromSeed ( seed );
// 4. Derive Ethereum accounts (BIP-44)
const eth0 = HDWallet . deriveEthereum ( root , 0 , 0 ); // m/44'/60'/0'/0/0
const eth1 = HDWallet . deriveEthereum ( root , 0 , 1 ); // m/44'/60'/0'/0/1
// 5. Get keys
const privateKey = eth0 . getPrivateKey ();
const publicKey = eth0 . getPublicKey ();
const chainCode = eth0 . getChainCode ();
// 6. Export extended keys
const xprv = root . toExtendedPrivateKey (); // xprv...
const xpub = root . toExtendedPublicKey (); // xpub...
API Reference
Factory Methods
fromSeed(seed: Uint8Array): ExtendedKey
Creates root HD key from BIP-39 seed (16-64 bytes, typically 64).
const seed = await Bip39 . mnemonicToSeed ( mnemonic );
const root = HDWallet . fromSeed ( seed );
fromExtendedKey(xprv: string): ExtendedKey
Imports HD key from extended private key string (xprv…).
const xprv = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi' ;
const key = HDWallet . fromExtendedKey ( xprv );
fromPublicExtendedKey(xpub: string): ExtendedKey
Imports HD key from extended public key string (xpub…). Cannot derive hardened children.
const xpub = 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8' ;
const pubKey = HDWallet . fromPublicExtendedKey ( xpub );
// Can only derive non-hardened children
Derivation Methods
derivePath(key: ExtendedKey, path: string): ExtendedKey
Derives child key by full BIP-32 path.
// Standard paths
const eth0 = HDWallet . derivePath ( root , "m/44'/60'/0'/0/0" ); // Ethereum account 0, address 0
const eth1 = HDWallet . derivePath ( root , "m/44'/60'/0'/0/1" ); // Ethereum account 0, address 1
const btc0 = HDWallet . derivePath ( root , "m/44'/0'/0'/0/0" ); // Bitcoin account 0, address 0
// Custom paths
const custom = HDWallet . derivePath ( root , "m/0'/1/2'/3" ); // Mixed hardened/normal
// Hardened notation alternatives
const hardened1 = HDWallet . derivePath ( root , "m/44'/60'/0'" ); // Single quote
const hardened2 = HDWallet . derivePath ( root , "m/44h/60h/0h" ); // 'h' suffix (equivalent)
deriveChild(key: ExtendedKey, index: number): ExtendedKey
Derives single child by index (0-2³¹-1 normal, ≥2³¹ hardened).
// Normal derivation
const child0 = HDWallet . deriveChild ( root , 0 );
const child1 = HDWallet . deriveChild ( root , 1 );
// Hardened derivation (index >= HARDENED_OFFSET)
const hardened0 = HDWallet . deriveChild ( root , HDWallet . HARDENED_OFFSET );
const hardened1 = HDWallet . deriveChild ( root , HDWallet . HARDENED_OFFSET + 1 );
deriveEthereum(key: ExtendedKey, account: number, index: number): ExtendedKey
Derives Ethereum address using BIP-44 path: m/44'/60'/{account}'/0/{index}.
// First 5 addresses of account 0
const eth0 = HDWallet . deriveEthereum ( root , 0 , 0 ); // m/44'/60'/0'/0/0
const eth1 = HDWallet . deriveEthereum ( root , 0 , 1 ); // m/44'/60'/0'/0/1
const eth2 = HDWallet . deriveEthereum ( root , 0 , 2 ); // m/44'/60'/0'/0/2
const eth3 = HDWallet . deriveEthereum ( root , 0 , 3 ); // m/44'/60'/0'/0/3
const eth4 = HDWallet . deriveEthereum ( root , 0 , 4 ); // m/44'/60'/0'/0/4
// Second account
const eth2_0 = HDWallet . deriveEthereum ( root , 1 , 0 ); // m/44'/60'/1'/0/0
deriveBitcoin(key: ExtendedKey, account: number, index: number): ExtendedKey
Derives Bitcoin address using BIP-44 path: m/44'/0'/{account}'/0/{index}.
const btc0 = HDWallet . deriveBitcoin ( root , 0 , 0 ); // m/44'/0'/0'/0/0
const btc1 = HDWallet . deriveBitcoin ( root , 0 , 1 ); // m/44'/0'/0'/0/1
Serialization Methods
toExtendedPrivateKey(key: ExtendedKey): string
Exports extended private key (xprv…).
const xprv = root . toExtendedPrivateKey ();
// "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
// Can be stored and later restored
const restored = HDWallet . fromExtendedKey ( xprv );
toExtendedPublicKey(key: ExtendedKey): string
Exports extended public key (xpub…).
const xpub = root . toExtendedPublicKey ();
// "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
// Public key can be shared for watch-only wallets
const watchOnly = HDWallet . fromPublicExtendedKey ( xpub );
Property Getters
getPrivateKey(key: ExtendedKey): Uint8Array | null
Returns 32-byte private key (null for public-only keys).
const privateKey = eth0 . getPrivateKey ();
// Uint8Array(32) or null
getPublicKey(key: ExtendedKey): Uint8Array | null
Returns 33-byte compressed public key.
const publicKey = eth0 . getPublicKey ();
// Uint8Array(33) - compressed secp256k1 public key
getChainCode(key: ExtendedKey): Uint8Array | null
Returns 32-byte chain code (used for child derivation).
const chainCode = eth0 . getChainCode ();
// Uint8Array(32)
canDeriveHardened(key: ExtendedKey): boolean
Checks if key can derive hardened children (requires private key).
const root = HDWallet . fromSeed ( seed );
console . log ( root . canDeriveHardened ()); // true
const xpub = root . toExtendedPublicKey ();
const pubOnly = HDWallet . fromPublicExtendedKey ( xpub );
console . log ( pubOnly . canDeriveHardened ()); // false
toPublic(key: ExtendedKey): ExtendedKey
Converts to public-only key (removes private key).
const root = HDWallet . fromSeed ( seed );
const pubOnly = root . toPublic ();
console . log ( root . getPrivateKey ()); // Uint8Array(32)
console . log ( pubOnly . getPrivateKey ()); // null
console . log ( pubOnly . getPublicKey ()); // Uint8Array(33)
Path Utilities
isValidPath(path: string): boolean
Validates BIP-32 path format.
HDWallet . isValidPath ( "m/44'/60'/0'/0/0" ); // true
HDWallet . isValidPath ( "m/0" ); // true
HDWallet . isValidPath ( "44'/60'/0'" ); // false (missing 'm')
HDWallet . isValidPath ( "invalid" ); // false
isHardenedPath(path: string): boolean
Checks if path contains hardened derivation.
HDWallet . isHardenedPath ( "m/44'/60'/0'" ); // true
HDWallet . isHardenedPath ( "m/44h/60h/0h" ); // true (h notation)
HDWallet . isHardenedPath ( "m/44/60/0" ); // false
parseIndex(indexStr: string): number
Parses index string to number (handles hardened notation).
HDWallet . parseIndex ( "0" ); // 0
HDWallet . parseIndex ( "44" ); // 44
HDWallet . parseIndex ( "0'" ); // 2147483648 (HARDENED_OFFSET)
HDWallet . parseIndex ( "0h" ); // 2147483648 (h notation)
HDWallet . parseIndex ( "1'" ); // 2147483649 (HARDENED_OFFSET + 1)
Constants
// Hardened offset (2^31)
HDWallet . HARDENED_OFFSET // 0x80000000 = 2147483648
// Coin types (BIP-44)
HDWallet . CoinType . BTC // 0
HDWallet . CoinType . BTC_TESTNET // 1
HDWallet . CoinType . ETH // 60
HDWallet . CoinType . ETC // 61
// Path templates
HDWallet . BIP44_PATH . ETH ( account , index ) // m/44'/60'/account'/0/index
HDWallet . BIP44_PATH . BTC ( account , index ) // m/44'/0'/account'/0/index
BIP44 Derivation Paths
Ethereum Standard Path
Ethereum uses BIP44 path: m/44'/60'/0'/0/n
// Standard Ethereum addresses
const eth0 = HDWallet . deriveEthereum ( root , 0 , 0 ); // m/44'/60'/0'/0/0
const eth1 = HDWallet . deriveEthereum ( root , 0 , 1 ); // m/44'/60'/0'/0/1
const eth2 = HDWallet . deriveEthereum ( root , 0 , 2 ); // m/44'/60'/0'/0/2
// Second account
const account2 = HDWallet . deriveEthereum ( root , 1 , 0 ); // m/44'/60'/1'/0/0
Path components:
m / purpose' / coin_type' / account' / change / address_index
44' 60' 0' 0 0
m : Master key (root)
44' : BIP44 standard (hardened)
60' : Ethereum coin type (hardened)
0' : Account index (hardened) - first account
0 : External addresses (non-hardened) - not change addresses
n : Address index (non-hardened) - increments for each address
m / purpose' / coin_type' / account' / change / address_index
Hardened vs Normal:
Hardened (' or h suffix): Index ≥ 2³¹, requires private key, more secure
Normal (no suffix): Index < 2³¹, can be derived from public key
Ethereum-specific:
Purpose: Always 44' (BIP44)
Coin type: Always 60' (Ethereum)
Account: 0', 1', 2'… (user accounts)
Change: Always 0 (Ethereum doesn’t use change addresses like Bitcoin)
Address index: 0, 1, 2… (addresses within account)
Other Coin Types
Bitcoin (coin type 0):
m/44'/0'/0'/0/0 First receive address, first account
m/44'/0'/0'/1/0 First change address, first account
m/44'/0'/0'/0/1 Second receive address, first account
Common coin types:
Bitcoin: m/44'/0'/...
Litecoin: m/44'/2'/...
Dogecoin: m/44'/3'/...
Ethereum: m/44'/60'/...
Ethereum Classic: m/44'/61'/...
Hardened Derivation
Hardened derivation (index ≥ 2³¹) provides additional security:
// Hardened (secure, requires private key)
const hardened = HDWallet . derivePath ( root , "m/44'/60'/0'" );
// Normal (can be derived from public key)
const normal = HDWallet . derivePath ( root , "m/44/60/0" );
Why use hardened?
Security : Leaked child private key + parent public key cannot derive other children
Standard : BIP-44 requires hardening for purpose, coin_type, and account levels
Privacy : Better separation between accounts
When to use normal?
Address generation in watch-only wallets (xpub)
Server-side address generation without private keys
Final address_index level (BIP-44 standard)
Notation
Two equivalent notations for hardened derivation:
// Single quote notation (standard)
HDWallet . derivePath ( root , "m/44'/60'/0'/0/0" );
// 'h' suffix notation (alternative)
HDWallet . derivePath ( root , "m/44h/60h/0h/0/0" );
// Both produce identical keys
Extended Keys (xprv/xpub)
Extended keys encode key + chain code + metadata:
Extended Private Key (xprv)
Contains private key - can derive all children (hardened + normal).
const xprv = root . toExtendedPrivateKey ();
// "xprv9s21ZrQH143K..."
// Format: version (4) + depth (1) + parent_fingerprint (4) +
// child_number (4) + chain_code (32) + key (33) + checksum (4)
// Total: 82 bytes → Base58 encoded
Security:
Treat like private key - full wallet access
Derive any child key (hardened or normal)
Never share or transmit unencrypted
Extended Public Key (xpub)
Contains public key - can only derive normal children.
const xpub = root . toExtendedPublicKey ();
// "xpub661MyMwAqRbcF..."
// Same format as xprv, but contains public key instead
Use cases:
Watch-only wallets (view balances without spending)
Server-side address generation
Auditing/accounting systems
Sharing with accountants/auditors
Limitations:
Cannot derive hardened children
Cannot sign transactions
Cannot export private keys
Watch-Only Wallets
// 1. Export xpub from secure device
const root = HDWallet . fromSeed ( seed );
const xpub = root . toExtendedPublicKey ();
// 2. Import xpub on watch-only system
const watchOnly = HDWallet . fromPublicExtendedKey ( xpub );
// 3. Generate addresses (normal derivation only)
const addr0 = HDWallet . deriveChild ( watchOnly , 0 );
const addr1 = HDWallet . deriveChild ( watchOnly , 1 );
// Can view addresses but cannot spend
console . log ( addr0 . getPublicKey ()); // Works
console . log ( addr0 . getPrivateKey ()); // null
Complete Workflow
Generate New Wallet
import * as Bip39 from '@tevm/voltaire/Bip39' ;
import { HDWallet } from '@tevm/voltaire/native' ;
import { Address } from '@tevm/voltaire/primitives/address' ;
import { secp256k1 } from '@tevm/voltaire/Secp256k1' ;
// 1. Generate mnemonic (user backs this up!)
const mnemonic = Bip39 . generateMnemonic ( 256 );
console . log ( 'Backup this mnemonic:' , mnemonic );
// 2. Derive seed
const seed = await Bip39 . mnemonicToSeed ( mnemonic );
// 3. Create root key
const root = HDWallet . fromSeed ( seed );
// 4. Derive first Ethereum address
const eth0 = HDWallet . deriveEthereum ( root , 0 , 0 );
// 5. Get keys
const privateKey = eth0 . getPrivateKey ();
const publicKey = eth0 . getPublicKey ();
// 6. Derive Ethereum address from public key
const pubKeyUncompressed = secp256k1 . getPublicKey ( privateKey , false );
const address = Address . fromPublicKey ( pubKeyUncompressed . slice ( 1 )); // Remove 0x04 prefix
console . log ( 'Address:' , address . toHex ());
Restore Existing Wallet
// 1. User provides backed-up mnemonic
const restoredMnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' ;
// 2. Validate mnemonic
if ( ! Bip39 . validateMnemonic ( restoredMnemonic )) {
throw new Error ( 'Invalid mnemonic' );
}
// 3. Derive seed (with same passphrase if used)
const seed = await Bip39 . mnemonicToSeed ( restoredMnemonic , 'optional passphrase' );
// 4. Recreate wallet
const root = HDWallet . fromSeed ( seed );
// 5. Derive same addresses
const eth0 = HDWallet . deriveEthereum ( root , 0 , 0 ); // Same as original
Multi-Account Wallet
// Account-based structure (like MetaMask)
class MultiAccountWallet {
constructor ( root ) {
this . root = root ;
}
getAccount ( accountIndex , addressIndex = 0 ) {
return HDWallet . deriveEthereum ( this . root , accountIndex , addressIndex );
}
getAccountAddresses ( accountIndex , count = 5 ) {
return Array ({ length: count }, ( _ , i ) =>
this . getAccount ( accountIndex , i )
);
}
}
const wallet = new MultiAccountWallet ( root );
// Get first 5 addresses of account 0
const account0Addresses = wallet . getAccountAddresses ( 0 , 5 );
// Get first address of account 1
const account1 = wallet . getAccount ( 1 , 0 );
Security
Best Practices
1. Secure seed storage
// Never log or transmit seed/private keys
const seed = await Bip39 . mnemonicToSeed ( mnemonic );
// ❌ console.log(seed);
// ❌ fetch('/api', { body: seed });
// Only derive public data for transmission
const root = HDWallet . fromSeed ( seed );
const xpub = root . toExtendedPublicKey ();
// ✅ Can share xpub (read-only access)
2. Validate inputs
// Always validate user-provided paths
function deriveSafely ( root , path ) {
if ( ! HDWallet . isValidPath ( path )) {
throw new Error ( 'Invalid derivation path' );
}
return HDWallet . derivePath ( root , path );
}
3. Use hardened derivation for sensitive levels
// Standard BIP-44: purpose', coin_type', account' are hardened
const secure = HDWallet . derivePath ( root , "m/44'/60'/0'/0/0" );
// ^^^ ^^^ ^^^ Hardened
// Less secure (but BIP-44 compliant for address level)
const addressLevel = HDWallet . derivePath ( root , "m/44'/60'/0'/0/0" );
// ^ Not hardened (standard)
4. Clear sensitive memory
// For high-security applications, clear keys after use
function clearKey ( key ) {
const privateKey = key . getPrivateKey ();
if ( privateKey ) {
privateKey . fill ( 0 ); // Zero out memory
}
}
5. Implement key rotation
// Use different accounts for different purposes
const tradingAccount = HDWallet . deriveEthereum ( root , 0 , 0 ); // Hot wallet
const savingsAccount = HDWallet . deriveEthereum ( root , 1 , 0 ); // Cold storage
const defiAccount = HDWallet . deriveEthereum ( root , 2 , 0 ); // DeFi interactions
Common Vulnerabilities
xpub Leakage + Child Private Key
If attacker obtains:
Parent xpub (extended public key)
Any child private key (non-hardened)
They can derive all sibling private keys!
Protection: Use hardened derivation at sensitive levels.
// Vulnerable (if xpub + child key leaked)
const vulnerable = HDWallet . derivePath ( root , "m/44/60/0/0/0" );
// ^^ ^^ Non-hardened
// Secure (hardened derivation protects)
const secure = HDWallet . derivePath ( root , "m/44'/60'/0'/0/0" );
// ^^^ ^^^ Hardened
Weak Seed Generation
// ❌ NEVER use weak randomness like Math.random()
// Math.random() is NOT cryptographically secure!
// ✅ Use cryptographically secure generation
const mnemonic = Bip39 . generateMnemonic ( 256 ); // Uses crypto.getRandomValues()
const seed = await Bip39 . mnemonicToSeed ( mnemonic );
Implementation Notes
Uses @scure/bip32 by Paul Miller (audited library)
HMAC-SHA512 for key derivation (BIP-32 standard)
secp256k1 elliptic curve (Bitcoin/Ethereum)
Constant-time operations where possible
Supports compressed public keys (33 bytes)
Base58Check encoding for extended keys
Test Vectors (BIP-32)
// From BIP-32 specification
const testSeed = new Uint8Array ([
0x00 , 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 ,
0x08 , 0x09 , 0x0a , 0x0b , 0x0c , 0x0d , 0x0e , 0x0f
]);
const root = HDWallet . fromSeed ( testSeed );
const xprv = root . toExtendedPrivateKey ();
// Expected:
// xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi
const child = HDWallet . derivePath ( root , "m/0'" );
const childXprv = child . toExtendedPrivateKey ();
// Expected:
// xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7
References