BIP32/BIP39 hierarchical deterministic wallets for secure key management and mnemonic seed phrases.
// ES6 imports
import { Bip32, Bip39 } from 'bsv'
// CommonJS imports
const { Bip32, Bip39 } = require('bsv')Implementation of BIP32 hierarchical deterministic wallets for deriving multiple keys from a single seed.
class Bip32 {
constructor(
versionBytesNum?: number, // Version bytes (network-specific)
depth?: number, // Derivation depth (0 = master)
parentFingerPrint?: Buffer, // Parent key fingerprint
childIndex?: number, // Child key index
chainCode?: Buffer, // Chain code (32 bytes)
privKey?: PrivKey, // Private key (if present)
pubKey?: PubKey, // Public key
constants?: object, // Network constants
PrivKey?: typeof PrivKey // Private key class
)
// Core properties
versionBytesNum: number // Network version bytes
depth: number // Derivation depth
parentFingerPrint: Buffer // Parent fingerprint (4 bytes)
childIndex: number // Child index
chainCode: Buffer // Chain code (32 bytes)
privKey: PrivKey // Private key (optional)
pubKey: PubKey // Public key
Constants: object // Network constants
// Creation methods
static fromRandom(): Bip32 // Generate random master key
static fromSeed(seedBuf: Buffer): Bip32 // Create from seed
static fromString(str: string): Bip32 // Parse xprv/xpub string
static fromBuffer(buf: Buffer): Bip32 // Parse from buffer
// Serialization methods
toString(): string // Export as xprv/xpub string
toBuffer(): Buffer // Export as buffer
toHex(): string // Export as hex
toJSON(): object // Export as JSON
// Key derivation
derive(path: string): Bip32 // Derive key at BIP32 path
deriveChild(i: number): Bip32 // Derive single child key
// Utility methods
toPublic(): Bip32 // Convert to public key only (xpub)
isPrivate(): boolean // Check if private key present
fingerPrint(): Buffer // Calculate key fingerprint
// Network variants
static Mainnet: typeof Bip32
static Testnet: typeof Bip32
// Async variants
static asyncFromSeed(seedBuf: Buffer): Promise<Bip32>
static asyncFromString(str: string): Promise<Bip32>
asyncToString(): Promise<string>
asyncDerive(path: string): Promise<Bip32>
asyncDeriveChild(i: number): Promise<Bip32>
}// Generate random master HD key
const masterKey = Bip32.fromRandom()
console.log('Master xprv:', masterKey.toString())
// Create HD wallet from seed
const seedBuf = Buffer.from('000102030405060708090a0b0c0d0e0f', 'hex')
const hdKey = Bip32.fromSeed(seedBuf)
// Parse extended key from string
const xprv = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'
const hdKey = Bip32.fromString(xprv)
// Key derivation using BIP32 paths
const account0 = hdKey.derive("m/44'/0'/0'") // Account 0
const external = account0.derive('0') // External chain
const address0 = external.derive('0') // First address
// Derive multiple addresses
const addresses = []
for (let i = 0; i < 10; i++) {
const addressKey = external.derive(i.toString())
const address = Address.fromPubKey(addressKey.pubKey)
addresses.push(address.toString())
}
// Create public-only wallet (xpub)
const pubOnlyWallet = hdKey.toPublic()
console.log('xpub:', pubOnlyWallet.toString())
// Child key derivation
const child0 = hdKey.deriveChild(0) // Non-hardened
const child0Hard = hdKey.deriveChild(0x80000000) // Hardened (0')
// Check if key has private component
const hasPrivate = hdKey.isPrivate()
// Get key fingerprint
const fingerprint = hdKey.fingerPrint()
// Network-specific HD keys
const mainnetKey = Bip32.Mainnet.fromRandom()
const testnetKey = Bip32.Testnet.fromRandom()
// Async operations
const hdKey = await Bip32.asyncFromSeed(seedBuf)
const derived = await hdKey.asyncDerive("m/44'/0'/0'/0/0")// Standard BIP44 derivation paths
const paths = {
// Purpose 44 (BIP44), Coin type 0 (Bitcoin), Account 0
account: "m/44'/0'/0'",
// External chain (receiving addresses)
external: "m/44'/0'/0'/0",
// Internal chain (change addresses)
internal: "m/44'/0'/0'/1",
// Specific addresses
firstReceive: "m/44'/0'/0'/0/0",
firstChange: "m/44'/0'/0'/1/0"
}
// Derive keys for different purposes
const masterKey = Bip32.fromSeed(seedBuf)
const accountKey = masterKey.derive(paths.account)
const receiveKey = masterKey.derive(paths.firstReceive)
const changeKey = masterKey.derive(paths.firstChange)Implementation of BIP39 mnemonic seed phrases for human-readable wallet backups.
class Bip39 {
constructor(
mnemonic?: string, // Mnemonic phrase
seed?: Buffer, // Derived seed
wordlist?: string[] // Word list for language
)
// Core properties
mnemonic: string // Mnemonic phrase
seed: Buffer // Derived seed (64 bytes)
Wordlist: string[] // BIP39 word list
// Creation methods
static fromRandom(bits?: number): Bip39 // Generate random mnemonic
static fromEntropy(entropyBuf: Buffer): Bip39 // Create from entropy
static fromString(mnemonic: string): Bip39 // Set mnemonic phrase
// Serialization methods
toString(): string // Get mnemonic phrase
toSeed(passphrase?: string): Buffer // Derive seed from mnemonic
// Entropy and mnemonic conversion
entropy2Mnemonic(entropyBuf: Buffer): string
mnemonic2Seed(mnemonic: string, passphrase?: string): Buffer
// Validation methods
check(): Bip39 // Validate mnemonic checksum
isValid(passphrase?: string): boolean // Check if mnemonic is valid
static isValid(mnemonic: string, passphrase?: string): boolean
// Async variants
static asyncFromRandom(bits?: number): Promise<Bip39>
static asyncFromEntropy(entropyBuf: Buffer): Promise<Bip39>
asyncToSeed(passphrase?: string): Promise<Buffer>
}// Generate random 12-word mnemonic (128 bits entropy)
const bip39_12 = Bip39.fromRandom(128)
console.log('12-word mnemonic:', bip39_12.toString())
// Generate 24-word mnemonic (256 bits entropy)
const bip39_24 = Bip39.fromRandom(256)
console.log('24-word mnemonic:', bip39_24.toString())
// Create from existing mnemonic
const mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
const bip39 = Bip39.fromString(mnemonic)
// Validate mnemonic
const isValid = Bip39.isValid(mnemonic)
if (isValid) {
console.log('Valid mnemonic')
}
// Derive seed from mnemonic (with optional passphrase)
const seed = bip39.toSeed() // No passphrase
const seedWithPassphrase = bip39.toSeed('my-passphrase')
// Create HD wallet from mnemonic
const hdWallet = Bip32.fromSeed(seed)
console.log('HD root xprv:', hdWallet.toString())
// Custom entropy to mnemonic
const entropy = Buffer.from('00000000000000000000000000000000', 'hex')
const bip39FromEntropy = Bip39.fromEntropy(entropy)
// Check mnemonic checksum
try {
bip39.check() // Throws if invalid checksum
console.log('Checksum valid')
} catch (error) {
console.error('Invalid checksum:', error.message)
}
// Async operations
const bip39 = await Bip39.asyncFromRandom(256)
const seed = await bip39.asyncToSeed('passphrase')// Different entropy levels and resulting mnemonic lengths
const mnemonics = {
// 128 bits = 12 words
weak: Bip39.fromRandom(128),
// 160 bits = 15 words
medium: Bip39.fromRandom(160),
// 192 bits = 18 words
strong: Bip39.fromRandom(192),
// 224 bits = 21 words
stronger: Bip39.fromRandom(224),
// 256 bits = 24 words (recommended)
strongest: Bip39.fromRandom(256)
}
console.log('12 words:', mnemonics.weak.toString())
console.log('24 words:', mnemonics.strongest.toString())// 1. Generate mnemonic seed phrase
const mnemonic = Bip39.fromRandom(256) // 24 words
console.log('Mnemonic:', mnemonic.toString())
console.log('Store this safely - it can recover your entire wallet!')
// 2. Derive seed from mnemonic
const seed = mnemonic.toSeed() // Optional passphrase for extra security
// 3. Create master HD key from seed
const masterKey = Bip32.fromSeed(seed)
console.log('Master key:', masterKey.toString())
// 4. Derive account key (BIP44)
const accountKey = masterKey.derive("m/44'/0'/0'")
// 5. Derive external chain for receiving addresses
const externalChain = accountKey.derive('0')
// 6. Derive internal chain for change addresses
const internalChain = accountKey.derive('1')
// 7. Generate multiple receiving addresses
const receivingAddresses = []
for (let i = 0; i < 20; i++) {
const addressKey = externalChain.derive(i.toString())
const address = Address.fromPubKey(addressKey.pubKey)
receivingAddresses.push({
index: i,
path: `m/44'/0'/0'/0/${i}`,
address: address.toString(),
privKey: addressKey.privKey.toWif(),
pubKey: addressKey.pubKey.toHex()
})
}
// 8. Generate change addresses
const changeAddresses = []
for (let i = 0; i < 5; i++) {
const changeKey = internalChain.derive(i.toString())
const address = Address.fromPubKey(changeKey.pubKey)
changeAddresses.push({
index: i,
path: `m/44'/0'/0'/1/${i}`,
address: address.toString(),
privKey: changeKey.privKey.toWif()
})
}
console.log('First receiving address:', receivingAddresses[0].address)
console.log('First change address:', changeAddresses[0].address)// Recover wallet from mnemonic phrase
const recoveryMnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
// 1. Validate mnemonic
if (!Bip39.isValid(recoveryMnemonic)) {
throw new Error('Invalid mnemonic phrase')
}
// 2. Create Bip39 instance and derive seed
const bip39 = Bip39.fromString(recoveryMnemonic)
const seed = bip39.toSeed() // Use same passphrase as original
// 3. Recreate master key
const masterKey = Bip32.fromSeed(seed)
// 4. Derive account and address keys (same paths as original)
const accountKey = masterKey.derive("m/44'/0'/0'")
const firstAddressKey = accountKey.derive('0/0')
const firstAddress = Address.fromPubKey(firstAddressKey.pubKey)
console.log('Recovered first address:', firstAddress.toString())// Create watch-only wallet using extended public key
const masterKey = Bip32.fromRandom()
const accountKey = masterKey.derive("m/44'/0'/0'")
// Get extended public key (xpub) - safe to share
const xpub = accountKey.toPublic().toString()
console.log('Account xpub:', xpub)
// Create watch-only wallet from xpub
const watchOnlyWallet = Bip32.fromString(xpub)
// Can derive addresses but not private keys
const externalChain = watchOnlyWallet.derive('0')
const firstAddress = Address.fromPubKey(externalChain.derive('0').pubKey)
console.log('Watch-only address:', firstAddress.toString())
console.log('Has private key:', watchOnlyWallet.isPrivate()) // false// BIP44 multi-account structure
const masterKey = Bip32.fromSeed(seed)
// Derive multiple accounts
const accounts = []
for (let accountIndex = 0; accountIndex < 3; accountIndex++) {
const accountPath = `m/44'/0'/${accountIndex}'`
const accountKey = masterKey.derive(accountPath)
// Derive first address for each account
const firstAddressKey = accountKey.derive('0/0')
const firstAddress = Address.fromPubKey(firstAddressKey.pubKey)
accounts.push({
index: accountIndex,
path: accountPath,
xprv: accountKey.toString(),
xpub: accountKey.toPublic().toString(),
firstAddress: firstAddress.toString()
})
}
console.log('Account 0 first address:', accounts[0].firstAddress)
console.log('Account 1 first address:', accounts[1].firstAddress)const masterKey = Bip32.fromSeed(seed)
// Hardened derivation (index >= 0x80000000)
// More secure - cannot derive child private keys from parent public key
const hardenedChild = masterKey.deriveChild(0x80000000) // Same as derive("0'")
const account0 = masterKey.derive("m/44'/0'/0'") // All hardened path
// Non-hardened derivation
// Less secure but allows public key derivation
const nonHardenedChild = masterKey.deriveChild(0)
const addressKey = account0.derive('0/0') // Non-hardened address derivation
// BIP44 standard uses hardened derivation for purpose, coin type, and account
// Non-hardened for chain and address index
const standardPath = "m/44'/0'/0'/0/0"
// | | | | |
// | | | | +-- Address index (non-hardened)
// | | | +---- Chain (0=external, 1=internal) (non-hardened)
// | | +------- Account index (hardened)
// | +---------- Coin type (0=Bitcoin) (hardened)
// +------------- Purpose (44=BIP44) (hardened)HD wallet operations include comprehensive validation:
// Mnemonic validation
try {
const bip39 = Bip39.fromString('invalid mnemonic phrase')
bip39.check() // Will throw
} catch (error) {
console.error('Invalid mnemonic:', error.message)
}
// HD key derivation errors
try {
const hdKey = Bip32.fromString('invalid-xprv')
} catch (error) {
console.error('Invalid extended key:', error.message)
}
// Path validation
try {
const derived = hdKey.derive('invalid/path')
} catch (error) {
console.error('Invalid derivation path:', error.message)
}
// Seed validation
try {
const hdKey = Bip32.fromSeed(Buffer.alloc(16)) // Too short
} catch (error) {
console.error('Invalid seed:', error.message)
}// Generate high-entropy mnemonic
const mnemonic = Bip39.fromRandom(256) // 256 bits = 24 words (recommended)
// Add passphrase for additional security
const passphrase = 'strong-passphrase-here'
const seed = mnemonic.toSeed(passphrase)
// Never store mnemonic and passphrase together
// Mnemonic = written backup, Passphrase = memorized// Use hardened derivation for account level
const accountKey = masterKey.derive("m/44'/0'/0'") // Hardened
// Non-hardened derivation only for address level
const addressKey = accountKey.derive('0/0') // Non-hardened
// Never derive hardened children from public keys
const pubOnlyAccount = accountKey.toPublic()
// pubOnlyAccount.derive("0'") // This would fail - hardened derivation requires private key