managarten/apps
Till JS 2f48f867f1 feat(crypto): phase 9 milestone 1 — recovery code primitives
Foundation for the zero-knowledge opt-in. New crypto/recovery.ts
provides the user-held secret half of the Phase 9 design:

  - generateRecoverySecret() — 32 random bytes (256 bits) from Web
    Crypto CSPRNG
  - formatRecoveryCode() — renders raw bytes as 16 dash-separated
    groups of 4 uppercase hex chars: "1A2B-3C4D-5E6F-..." (79 chars
    total). Copy-pasteable, password-manager-friendly, no language
    dependency.
  - parseRecoveryCode() — tolerant inverse: strips whitespace + any
    dash placement, accepts mixed case, throws RecoveryCodeFormatError
    on wrong length / non-hex (no position-leaking errors)
  - deriveRecoveryWrapKey() — HKDF-SHA256 with empty salt + versioned
    info "mana-recovery-v1" → non-extractable AES-GCM-256 wrap key.
    HKDF (not PBKDF2/scrypt) because the input already has full 256
    bits of entropy — no slow KDF needed.
  - wrapMasterKeyWithRecovery() — exports the master key bytes,
    AES-GCM-encrypts with the recovery wrap key, returns base64
    ciphertext + IV ready for the server. Wipes the raw MK reference
    immediately after sealing.
  - unwrapMasterKeyWithRecovery() — inverse, returns a non-extractable
    CryptoKey. Throws uniformly on wrong code / tampered ciphertext —
    the UI maps both to "wrong recovery code" so an attacker gets no
    side-channel signal about which check failed.

Why hex over BIP-39?
  - No 2048-word wordlist to bundle (~17 KB even gzipped)
  - 32 random bytes have full 256 bits of entropy on their own — no
    checksum word needed because there's nothing to "validate"
  - Trivially copy-pasteable into any password manager, no language
    dependency, no autocomplete-confusing dictionary words
  - Survives autocorrect (no spaces)

22 tests in recovery.test.ts cover:
  - generation (length, randomness)
  - format (16 groups, uppercase, total 79 chars, wrong-length input)
  - parse (roundtrip, lowercase, whitespace, missing dashes, extra
    dashes, error cases, no position leakage)
  - key derivation (non-extractable, deterministic, wrong-length input)
  - wrap/unwrap roundtrip (with and without format/parse trip)
  - failure modes (wrong code, tampered ciphertext)
  - IV uniqueness (no reuse on repeated wraps)

This is the self-contained foundation. Server-side schema, vault
service extensions, vault-client wire-up and the settings UI all
build on these primitives in subsequent commits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:00:43 +02:00
..
api feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
calc/packages/shared chore: delete 25 web-archived directories, remove stale stubs, clean workspace config 2026-04-03 13:03:49 +02:00
calendar chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
cards chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
chat chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
citycorners chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
contacts chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
context chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
docs chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
guides chore: delete 25 web-archived directories, remove stale stubs, clean workspace config 2026-04-03 13:03:49 +02:00
inventar chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
mana feat(crypto): phase 9 milestone 1 — recovery code primitives 2026-04-07 22:00:43 +02:00
manacore/apps/web/src/lib feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
manavoxel chore(workspace): unify vitest to ^4.1.2 across all packages 2026-04-07 13:58:29 +02:00
matrix chore(workspace): unify vitest to ^4.1.2 across all packages 2026-04-07 13:58:29 +02:00
memoro chore(workspace): unify vitest to ^4.1.2 across all packages 2026-04-07 13:58:29 +02:00
moodlit feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
mukke feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
news chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
nutriphi chore(workspace): unify vitest to ^4.1.2 across all packages 2026-04-07 13:58:29 +02:00
photos chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
picture chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
planta chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
presi chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
questions feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
skilltree chore: delete 25 web-archived directories, remove stale stubs, clean workspace config 2026-04-03 13:03:49 +02:00
storage chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
times chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
todo chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
traces feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
uload chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
zitare/packages/content chore: delete 25 web-archived directories, remove stale stubs, clean workspace config 2026-04-03 13:03:49 +02:00