59 lines
3.1 KiB
Markdown
59 lines
3.1 KiB
Markdown
# Change: enterprise-crypto
|
|
|
|
## Why
|
|
|
|
`@suaveplan/crypto` is the single surface every other package uses for hashing, HMAC, HKDF/PBKDF2/Argon2id key derivation, AEAD symmetric encryption, and public-key signing. Having one canonical implementation with identical semantics across Bun, Node, and browser means consumers never pick between `node:crypto` and `crypto.subtle` — they import from one place, get structured errors, constant-time comparisons, buffer zeroing, and injectable RNG/clock for tests.
|
|
|
|
Per the foundation-100-coverage rule, it ships at 100% coverage.
|
|
|
|
## What Changes
|
|
|
|
### New package: `packages/universal/crypto`
|
|
|
|
Pure-TypeScript cryptographic substrate. Runtime dependencies: `@noble/ciphers`, `@noble/curves`, `@noble/hashes` (audited pure-TS crypto libraries), plus `@suaveplan/error`, `@suaveplan/types`, `tslib`, `zod`.
|
|
|
|
#### Primitives
|
|
|
|
- **`randomBytes(n, { rng? })`** — cryptographic random bytes; default uses `crypto.getRandomValues`
|
|
- **`constantTimeEqual(a, b)`** — constant-time byte comparison
|
|
- **`zeroize(buf)`** — overwrite a `Uint8Array` with zeros
|
|
|
|
#### Hashes
|
|
|
|
- **`sha256(data)`**, **`sha384(data)`**, **`sha512(data)`** — one-shot digests returning `Uint8Array`
|
|
|
|
#### Keyed MACs + KDFs
|
|
|
|
- **`hmac(algo, key, data)`** — HMAC-SHA-256/384/512
|
|
- **`hkdf(algo, ikm, { salt, info, length })`** — HKDF extract-and-expand
|
|
- **`pbkdf2(algo, password, { salt, iterations, length })`** — PBKDF2
|
|
- **`argon2id(password, { salt, timeCost, memoryCost, parallelism, length })`** — Argon2id (memory-hard)
|
|
|
|
#### AEAD
|
|
|
|
- **`aeadEncrypt(algo, key, plaintext, { nonce, aad? })`** — AES-256-GCM and ChaCha20-Poly1305
|
|
- **`aeadDecrypt(algo, key, ciphertext, { nonce, aad? })`** — paired decrypter; throws on tag mismatch
|
|
|
|
#### Signing + key agreement
|
|
|
|
- **`ed25519.generateKeypair({ rng? })`**, **`ed25519.sign(secret, message)`**, **`ed25519.verify(public, signature, message)`**
|
|
- **`x25519.generateKeypair({ rng? })`**, **`x25519.agree(mySecret, theirPublic)`** — ECDH shared-secret
|
|
- **`ecdsaP256.generateKeypair({ rng? })`**, **`ecdsaP256.sign(secret, message)`**, **`ecdsaP256.verify(public, signature, message)`**
|
|
|
|
#### Error surface
|
|
|
|
- **`CryptoError`** extends `SuaveplanError<CryptoCode>` with codes like `CRYPTO.AEAD.DECRYPT_FAILED`, `CRYPTO.KEY.INVALID_LENGTH`, `CRYPTO.SIGN.VERIFY_FAILED`, `CRYPTO.KDF.INVALID_PARAMS`, `CRYPTO.RNG.UNAVAILABLE`
|
|
- Errors never leak plaintext, key bytes, or tag values in messages/context
|
|
|
|
#### Determinism
|
|
|
|
- All randomness flows through an injectable RNG param (`{ rng?: Rng }`) — the default is a thin wrapper over `crypto.getRandomValues`, located in exactly one file so tests can swap it
|
|
- No module-scope `Math.random()` / `Date.now()` — guarded by `tests/no-disallowed.test.ts`
|
|
|
|
## Impact
|
|
|
|
- Unblocks every downstream package that needs crypto (auth, jwt-tenant, keys, network-encryption, surrealdb keys, etc.)
|
|
- Depends on `@suaveplan/error`, `@suaveplan/types`, `@noble/ciphers`, `@noble/curves`, `@noble/hashes`
|
|
- One new catalog group (`crypto`) in the root `package.json`
|
|
- Non-goal for this change: streaming AEAD (needs `@suaveplan/streams` first), hardware-backed key storage, post-quantum algorithms, HSM integration
|