or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-bitcoin.mdcryptography.mdencryption.mdhd-wallets.mdindex.mdtransaction-building.mdutilities.md
tile.json

hd-wallets.mddocs/

HD Wallets (Hierarchical Deterministic)

BIP32/BIP39 hierarchical deterministic wallets for secure key management and mnemonic seed phrases.

Imports

// ES6 imports
import { Bip32, Bip39 } from 'bsv'

// CommonJS imports
const { Bip32, Bip39 } = require('bsv')

Bip32 (HD Wallets)

Implementation of BIP32 hierarchical deterministic wallets for deriving multiple keys from a single seed.

Class Definition

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>
}

Usage Examples

// 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")

BIP32 Path Format

// 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)

Bip39 (Mnemonic Seed Phrases)

Implementation of BIP39 mnemonic seed phrases for human-readable wallet backups.

Class Definition

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>
}

Usage Examples

// 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')

Mnemonic Strength Levels

// 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())

Complete HD Wallet Workflow

Wallet Generation and Address Derivation

// 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)

Wallet Recovery from Mnemonic

// 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())

Watch-Only Wallets (xpub)

// 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

Multi-Account HD Wallets

// 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)

Hardened vs Non-Hardened Derivation

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)

Error Handling

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)
}

Security Best Practices

Mnemonic Security

// 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

Key Derivation Security

// 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