Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
# `passphrase`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`generatePassphrase(opts, rng?)` produces an EFF-style Diceware passphrase by drawing `length` words uniformly (via the bias-free `randomInt`) from the wordlist resolved via `opts.wordlist`. The default — 6 words from the EFF Long Wordlist joined with `-` — gives ≈ 77.55 bits of entropy, comfortably above the 75-bit floor recommended for symmetric-key-equivalent secrets. Diceware-style passphrases are dramatically more memorable than random char-class passwords at equivalent entropy, which is why the vault item editor in `@suaveplan/vault` defaults to this mode for the "Generate password" surface.
The function honours configurable separator, three capitalisation modes (`none` / `firstChar` / `randomWord`), and optional `addDigits` / `addSymbols` injections at random positions in the joined output. The decoration knobs exist so the same generator can produce strings that satisfy downstream "must contain a digit" composition rules without abandoning the Diceware backbone.
## Features
- Bundled EFF Long (7776) and EFF Short 1 (1296) wordlists via tree-shakeable subpath imports.
- Caller-supplied `string[]` wordlists allowed past the 256-entry min-size guard.
- Configurable separator (`default "-"`).
- `capitalize: "none" | "firstChar" | "randomWord"`.
- `addDigits` / `addSymbols` injection at randomised positions.
- Deterministic under seeded `Rng`.
- Zod-validated; mismatch → `PWGEN.INVALID_OPTIONS`.
- Emits `pwgen.generate_duration_ms{mode=passphrase}` and `pwgen.entropy_bits{mode=passphrase}` histograms.
## Usage: Basic Example
```ts
import { generatePassphrase } from "@suaveplan/password-generator";
const pp = generatePassphrase({}); // 6 words, EFF Long, joined with "-"
```
## Usage: Advanced Example
```ts
import { generatePassphrase } from "@suaveplan/password-generator";
// 5 short words capitalised at random, two trailing digits, one symbol
const pp = generatePassphrase({
length: 5,
wordlist: "short",
capitalize: "randomWord",
addDigits: 2,
addSymbols: 1,
separator: " ",
});
// Caller-supplied themed wordlist (must be ≥ 256 entries)
const dnd = [/* ... 300 fantasy nouns ... */];
const themed = generatePassphrase({ wordlist: dnd, length: 4 });
```
## API Reference
### `generatePassphrase(opts?: GeneratePassphraseOptions, rng?: Rng): string`
- `opts.length` (default `6`): number of words to draw.
- `opts.wordlist` (default `"long"`): `"long"` / `"short"` / registry name / inline `string[]`.
- `opts.separator` (default `"-"`): string inserted between words before decoration.
- `opts.capitalize` (default `"none"`): `"none"` / `"firstChar"` / `"randomWord"`.
- `opts.addDigits` / `opts.addSymbols` (default `0`): count of characters injected at randomised positions.
- `rng`: defaults to `defaultRng()`.
Throws `PwgenError(PWGEN.INVALID_OPTIONS)` on Zod mismatch, `PwgenError(PWGEN.WORDLIST_TOO_SHORT)` for inline arrays below 256 entries, and `PwgenError(PWGEN.WORDLIST_NOT_REGISTERED)` for unknown registry names.
## Implementation Notes
Wordlist resolution lazily routes through `getWordlist(name)` so the registry's min-size guard applies uniformly — there's no special-case for the bundled EFF lists. Inline `string[]` wordlists bypass the registry but go through the same 256-entry floor inline, so a caller passing a tiny array sees the same `PWGEN.WORDLIST_TOO_SHORT` they would see from `registerWordlist`.
Decoration is applied _after_ joining: `addDigits` / `addSymbols` inserts characters at uniformly-random positions in the already-joined string. This means a digit can land mid-word (e.g. `hor5se-rifle`) — that's intentional because inserting only at word boundaries would create a small-cardinality position distribution that's easier to fingerprint. Per-class injection uses bias-free `randomInt` against the `DIGITS` / `SYMBOLS` sets defined in the `charsets` module.
Entropy reporting uses `estimatePassphraseEntropy(words, wordlistSize)` against the _picked_ words (not the post-decoration string), so the reported figure reflects the Diceware contribution. Decoration adds a small amount of entropy on top of that, but the package does not currently roll that into the reported figure to keep the formula stable across runs.