3.1 KiB
3.1 KiB
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 usescrypto.getRandomValuesconstantTimeEqual(a, b)— constant-time byte comparisonzeroize(buf)— overwrite aUint8Arraywith zeros
Hashes
sha256(data),sha384(data),sha512(data)— one-shot digests returningUint8Array
Keyed MACs + KDFs
hmac(algo, key, data)— HMAC-SHA-256/384/512hkdf(algo, ikm, { salt, info, length })— HKDF extract-and-expandpbkdf2(algo, password, { salt, iterations, length })— PBKDF2argon2id(password, { salt, timeCost, memoryCost, parallelism, length })— Argon2id (memory-hard)
AEAD
aeadEncrypt(algo, key, plaintext, { nonce, aad? })— AES-256-GCM and ChaCha20-Poly1305aeadDecrypt(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-secretecdsaP256.generateKeypair({ rng? }),ecdsaP256.sign(secret, message),ecdsaP256.verify(public, signature, message)
Error surface
CryptoErrorextendsSuaveplanError<CryptoCode>with codes likeCRYPTO.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 overcrypto.getRandomValues, located in exactly one file so tests can swap it - No module-scope
Math.random()/Date.now()— guarded bytests/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 rootpackage.json - Non-goal for this change: streaming AEAD (needs
@suaveplan/streamsfirst), hardware-backed key storage, post-quantum algorithms, HSM integration