# 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 ```ts 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 ```ts 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.