CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-bsv

JavaScript library for Bitcoin SV (BSV) blockchain development with cryptographic primitives, transaction handling, and protocol implementations

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

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

Install with Tessl CLI

npx tessl i tessl/npm-bsv

docs

core-bitcoin.md

cryptography.md

encryption.md

hd-wallets.md

index.md

transaction-building.md

utilities.md

tile.json