Files
skills/docs/security-stack/crypto/crypto/README.md
2026-05-29 08:20:56 +01:00

32 KiB

@suaveplan/crypto - Universal cryptographic primitives — AEAD (AES-256-GCM, ChaCha20-Poly1305, XChaCha20-Poly1305), Ed25519 / X25519 / ECDSA-P256, sealed-box, auth-box, constant-time compare, zeroize. Engine-pluggable via noble defaults.

Summary

Universal cryptographic primitives — engine-pluggable AEAD, Ed25519/ECDSA-P256 signing, X25519 key agreement, sealed-box, and auth-box. Built on audited @noble/* libraries with zero WASM.

Documentation tree

- [authority](./src/authority/authority.md) — _(no submodules)_- [chain-bundle](./src/bundles/chain-bundle/chain-bundle.md) — _(no submodules)_- [chain-bundle](./src/bundles/chain-bundle/chain-bundle.md) — _(no submodules)_- [public-bundle](./src/bundles/public-bundle/public-bundle.md) — _(no submodules)_- [public-bundle](./src/bundles/public-bundle/public-bundle.md) — _(no submodules)_- [coordinate](./src/derivation/coordinate/coordinate.md) — _(no submodules)_- [coordinate](./src/derivation/coordinate/coordinate.md) — _(no submodules)_- [layouts](./src/derivation/layouts/layouts.md) — _(no submodules)_- [layouts](./src/derivation/layouts/layouts.md) — _(no submodules)_- [purposes](./src/derivation/purposes/purposes.md) — _(no submodules)_- [purposes](./src/derivation/purposes/purposes.md) — _(no submodules)_- [domain-tags](./src/domain-tags/domain-tags.md) — _(no submodules)_- [domain-tags](./src/domain-tags/domain-tags.md) — _(no submodules)_- [codes](./src/errors/codes/codes.md) — _(no submodules)_- [codes](./src/errors/codes/codes.md) — _(no submodules)_- [crypto-error](./src/errors/crypto-error/crypto-error.md) — _(no submodules)_- [crypto-error](./src/errors/crypto-error/crypto-error.md) — _(no submodules)_- [codes](./src/errors/keys-errors/codes/codes.md) — _(no submodules)_- [codes](./src/errors/keys-errors/codes/codes.md) — _(no submodules)_- [crypto-keys-error](./src/errors/keys-errors/crypto-keys-error/crypto-keys-error.md) — _(no submodules)_- [crypto-keys-error](./src/errors/keys-errors/crypto-keys-error/crypto-keys-error.md) — _(no submodules)_- [codes](./src/errors/vault-errors/codes/codes.md) — _(no submodules)_- [codes](./src/errors/vault-errors/codes/codes.md) — _(no submodules)_- [crypto-vault-error](./src/errors/vault-errors/crypto-vault-error/crypto-vault-error.md) — _(no submodules)_- [crypto-vault-error](./src/errors/vault-errors/crypto-vault-error/crypto-vault-error.md) — _(no submodules)_- [keychain](./src/keychain/keychain.md) — _(no submodules)_- [algorithms](./src/keychain/algorithms/algorithms.md) — _(no submodules)_- [algorithms](./src/keychain/algorithms/algorithms.md) — _(no submodules)_- [key-slot](./src/keychain/key-slot/key-slot.md) — _(no submodules)_- [key-slot](./src/keychain/key-slot/key-slot.md) — _(no submodules)_- [generate](./src/keychain/keypair/generate/generate.md) — _(no submodules)_- [generate](./src/keychain/keypair/generate/generate.md) — _(no submodules)_- [validate](./src/keychain/keypair/validate/validate.md) — _(no submodules)_- [validate](./src/keychain/keypair/validate/validate.md) — _(no submodules)_- [memory](./src/keychain/storage/adapters/memory/memory.md) — _(no submodules)_- [memory](./src/keychain/storage/adapters/memory/memory.md) — _(no submodules)_- [serialized](./src/keychain/storage/adapters/serialized/serialized.md) — _(no submodules)_- [serialized](./src/keychain/storage/adapters/serialized/serialized.md) — _(no submodules)_- [validity](./src/keychain/validity/validity.md) — _(no submodules)_- [validity](./src/keychain/validity/validity.md) — _(no submodules)_- [keychain](./src/keychain/keychain.md) — _(no submodules)_- [derive-key-from-password](./src/password/derive-key-from-password.md) — `derive-key-from-password.ts` is the low-level password-to-key derivation helper in `@suaveplan/crypto`. It accepts a p…

Install

bun add @suaveplan/crypto

Primitives

Surface What
randomBytes(n, { byteSource? }) CSPRNG bytes (WebCrypto default; injectable for tests)
constantTimeEqual(a, b) Constant-time byte comparison
zeroize(buf) Fill a Uint8Array with zeros
aeadEncrypt / aeadDecrypt AES-256-GCM, ChaCha20-Poly1305, XChaCha20-Poly1305
ed25519.{generateKeypair, sign, verify} Ed25519 signing
ecdsaP256.{generateKeypair, sign, verify} ECDSA-P256 signing
x25519.{generateKeypair, agree} X25519 ECDH key agreement
sealedBox.{seal, open} Anonymous-sender sealed box (X25519 + XChaCha20-Poly1305)
authBox.{seal, open} Authenticated box (X25519 + XChaCha20-Poly1305)

Hashing (SHA-2, HMAC, HKDF, PBKDF2, Argon2id) lives in @suaveplan/crypto-hash.

Engine interfaces

Every operation accepts an optional { engine?: <EngineType> } argument. The default engines use @noble/ciphers and @noble/curves. Swap in a WebCrypto-backed or libsodium-backed engine without changing call sites.

import type { AeadEngine } from "@suaveplan/crypto/engines";

const webCryptoEngine: AeadEngine = { /* ... */ };
aeadEncrypt("AES-256-GCM", key, plaintext, { nonce }, { engine: webCryptoEngine });

ByteSource

ByteSource = (n: number) => Uint8Array — the injected randomness type used for key generation. Replaces the old Rng alias (which conflicted with @suaveplan/types's Rng).

import { randomBytes, type ByteSource } from "@suaveplan/crypto";

// Production
const key = randomBytes(32);

// Tests — deterministic
const seeded: ByteSource = (n) => new Uint8Array(n).fill(0x42);
const kp = ed25519.generateKeypair({ byteSource: seeded });

AEAD

import { aeadEncrypt, aeadDecrypt, randomBytes } from "@suaveplan/crypto";

const key = randomBytes(32);
const nonce = randomBytes(12); // 12 for AES-GCM/ChaCha20, 24 for XChaCha20
const plaintext = new TextEncoder().encode("hello");

const ct = aeadEncrypt("AES-256-GCM", key, plaintext, { nonce });
const pt = aeadDecrypt("AES-256-GCM", key, ct, { nonce });

Signing

import { ed25519, ecdsaP256 } from "@suaveplan/crypto";

const kp = ed25519.generateKeypair();
const sig = ed25519.sign(kp.secretKey, message);
ed25519.verify(kp.publicKey, sig, message); // true

Key Agreement + Sealed Box

import { x25519, sealedBox, authBox, randomBytes } from "@suaveplan/crypto";

const alice = x25519.generateKeypair();
const bob = x25519.generateKeypair();

// Anonymous sender (sealed box)
const sealed = sealedBox.seal(bob.publicKey, plaintext);
const opened = sealedBox.open(bob.secretKey, bob.publicKey, sealed);

// Authenticated (auth box)
const nonce = randomBytes(24);
const ct = authBox.seal(alice.secretKey, bob.publicKey, plaintext, nonce);
const pt = authBox.open(alice.publicKey, bob.secretKey, ct, nonce);

Error surface

Every failure surfaces as CryptoError (extends SuaveplanError) with a stable CRYPTO.* code. Error context never includes raw key bytes, plaintext, or authentication tags.

Code When
CRYPTO.KEY.INVALID_LENGTH Key is not 32 bytes
CRYPTO.AEAD.INVALID_NONCE Nonce length wrong for algorithm
CRYPTO.AEAD.DECRYPT_FAILED Authentication tag mismatch
CRYPTO.SIGN.INVALID_KEY Key is malformed
CRYPTO.SIGN.VERIFY_FAILED Signature verification failed
CRYPTO.RNG.INVALID_LENGTH randomBytes(n) called with negative/non-integer n
CRYPTO.RNG.UNAVAILABLE No CSPRNG available

Subpath exports

import { randomBytes } from "@suaveplan/crypto/primitives/random-bytes";
import { constantTimeEqual } from "@suaveplan/crypto/primitives/constant-time-equal";
import { zeroize } from "@suaveplan/crypto/primitives/zeroize";
import { aeadEncrypt, aeadDecrypt } from "@suaveplan/crypto/aead";
import { ed25519, ecdsaP256 } from "@suaveplan/crypto/sign";
import { x25519 } from "@suaveplan/crypto/agree";
import { sealedBox } from "@suaveplan/crypto/sealed-box";
import { authBox } from "@suaveplan/crypto/auth-box";
import type { AeadEngine, SignEngine } from "@suaveplan/crypto/engines";
import { CryptoError, CRYPTO_CODES } from "@suaveplan/crypto/errors";

Co-located docs

Foundation rules

  • Runtime deps: @noble/ciphers, @noble/curves, @suaveplan/crypto-hash, @suaveplan/error, @suaveplan/types, tslib, zod
  • sideEffects: false, per-primitive subpath exports
  • Zero Math.random, Date.now, or crypto.getRandomValues outside src/primitives/random-bytes.ts (guarded by tests/no-disallowed.test.ts)
  • 100% statement / branch / function / line coverage (Layer 1/2 foundation rule)
  • fast-check property tests for every primitive

Testing

bunx turbo lint typecheck test:coverage --filter=@suaveplan/crypto