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.