first commit
This commit is contained in:
102
docs/security-stack/crypto/crypto-vault/src/hd-key/hd-key.md
Normal file
102
docs/security-stack/crypto/crypto-vault/src/hd-key/hd-key.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# HD key derivation
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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.
|
||||
Reference in New Issue
Block a user