Files
skills/docs/security-stack/crypto/crypto-vault/src/hd-key/hd-key.md
2026-05-29 08:20:56 +01:00

3.8 KiB

HD key derivation

(no submodules)

Purpose

hd-key.ts turns a BIP39 mnemonic into deterministic key material that the rest of the package can fan out into a full hierarchy. The module performs two jobs: BIP39 seed derivation (PBKDF2-HMAC-SHA512 with 2048 rounds and the mnemonic + passphrase salt scheme) and indexed child-key derivation (HMAC-SHA512 over the parent + 32-bit big-endian index). Together they provide a flat, safe, BIP32-shaped HD path without requiring callers to maintain hardened-derivation chain codes.

Features

  • deriveMasterKeyFromMnemonic(mnemonic, passphrase?) — 32-byte master key (first half of the BIP39 seed).
  • deriveSeedFromMnemonic(mnemonic, passphrase?) — full 64-byte seed for callers that need both halves.
  • deriveChildKey(parent, index) — 32-byte HMAC-SHA512 child.
  • All entry points reject invalid mnemonics (word-count, unknown word, checksum failure) with CryptoVaultError(CRYPTOVAULT.HD.INVALID_MNEMONIC).

Usage: Basic Example

import { generateMnemonic } from "@suaveplan/crypto-vault/bip39/bip39";
import { deriveMasterKeyFromMnemonic } from "@suaveplan/crypto-vault/hd-key/hd-key";

const phrase = generateMnemonic(128);
const master = deriveMasterKeyFromMnemonic(phrase);
console.log(master.length); // 32

Usage: Advanced Example

import {
    deriveChildKey,
    deriveMasterKeyFromMnemonic,
    deriveSeedFromMnemonic,
} from "@suaveplan/crypto-vault/hd-key/hd-key";

const phrase = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";

// Two devices, same mnemonic + passphrase => same master key (deterministic).
const m1 = deriveMasterKeyFromMnemonic(phrase, "shared-passphrase");
const m2 = deriveMasterKeyFromMnemonic(phrase, "shared-passphrase");
console.log(Buffer.from(m1).equals(Buffer.from(m2))); // true

// Different passphrases => different masters.
const m3 = deriveMasterKeyFromMnemonic(phrase, "different");
console.log(Buffer.from(m1).equals(Buffer.from(m3))); // false

// Indexed children.
const seed = deriveSeedFromMnemonic(phrase, "shared-passphrase");
const c0 = deriveChildKey(seed.slice(0, 32), 0);
const c1 = deriveChildKey(seed.slice(0, 32), 1);
console.log(Buffer.from(c0).equals(Buffer.from(c1))); // false

API Reference

deriveMasterKeyFromMnemonic(mnemonic, passphrase?): Uint8Array

  • mnemonic: string — must be 12 / 15 / 18 / 21 / 24 BIP39 English words with a valid checksum.
  • passphrase?: string — optional BIP39 passphrase (defaults to "").

Returns a 32-byte master key (first half of the BIP39 seed).

Throws CryptoVaultError(CRYPTOVAULT.HD.INVALID_MNEMONIC) on empty input, unsupported word counts, unknown words, or checksum failures.

deriveSeedFromMnemonic(mnemonic, passphrase?): Uint8Array

Same validation as above; returns the full 64-byte seed.

deriveChildKey(parent, index): Uint8Array

  • parent: Uint8Array — any byte string (typically a master key or another derived key).
  • index: number — non-negative integer ≤ 0xffffffff.

Returns a 32-byte child key (HMAC-SHA512(parent, uint32-be(index))[:32]).

Throws CryptoVaultError(CRYPTOVAULT.HD.INVALID_INDEX) for non-integer, negative, or > 32-bit indices.

Implementation Notes

  • BIP39 seed derivation is intentionally synchronous (mnemonicToSeedSync) so it can be called from constructors and worker init. PBKDF2 with 2048 rounds completes in ~5 ms on modern hardware.
  • The validator runs both word-list membership and the BIP39 checksum (@scure/bip39.validateMnemonic) so callers cannot bypass the checksum by re-importing words.
  • Child-key derivation deliberately does not implement BIP32 hardened paths with chain codes. The spec-driven hierarchy in key-hierarchy/ uses HKDF for fan-out and only needs a single deterministic 32-byte parent.