Files
2026-05-27 23:47:53 +01:00

24 KiB

@suaveplan/crypto-core - Universal cryptographic primitives (formerly @suaveplan/crypto) — 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. Consumed by @suaveplan/crypto (the opinionated keychain/vault/packer layer); other packages must go through @suaveplan/crypto, not this package directly.

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

- [noble-aead-engine](./src/aead/noble-aead-engine/noble-aead-engine.md) — _(no submodules)_- [noble-aead-engine](./src/aead/noble-aead-engine/noble-aead-engine.md) — _(no submodules)_- [aead](./src/aead/aead.md) — _(no submodules)_- [noble-agree-engine](./src/agree/noble-agree-engine/noble-agree-engine.md) — _(no submodules)_- [noble-agree-engine](./src/agree/noble-agree-engine/noble-agree-engine.md) — _(no submodules)_- [x25519](./src/agree/x25519/x25519.md) — _(no submodules)_- [x25519](./src/agree/x25519/x25519.md) — _(no submodules)_- [auth-box](./src/auth-box/auth-box.md) — _(no submodules)_- [noble-auth-box-engine](./src/auth-box/noble-auth-box-engine/noble-auth-box-engine.md) — _(no submodules)_- [noble-auth-box-engine](./src/auth-box/noble-auth-box-engine/noble-auth-box-engine.md) — _(no submodules)_- [auth-box](./src/auth-box/auth-box.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)_- [constant-time-equal](./src/primitives/constant-time-equal/constant-time-equal.md) — _(no submodules)_- [constant-time-equal](./src/primitives/constant-time-equal/constant-time-equal.md) — _(no submodules)_- [random-bytes](./src/primitives/random-bytes/random-bytes.md) — _(no submodules)_- [random-bytes](./src/primitives/random-bytes/random-bytes.md) — _(no submodules)_- [zeroize](./src/primitives/zeroize/zeroize.md) — _(no submodules)_- [zeroize](./src/primitives/zeroize/zeroize.md) — _(no submodules)_- [sealed-box](./src/sealed-box/sealed-box.md) — _(no submodules)_- [noble-sealed-box-engine](./src/sealed-box/noble-sealed-box-engine/noble-sealed-box-engine.md) — _(no submodules)_- [noble-sealed-box-engine](./src/sealed-box/noble-sealed-box-engine/noble-sealed-box-engine.md) — _(no submodules)_- [sealed-box](./src/sealed-box/sealed-box.md) — _(no submodules)_- [ecdsa-p256](./src/sign/ecdsa-p256/ecdsa-p256.md) — _(no submodules)_- [ecdsa-p256](./src/sign/ecdsa-p256/ecdsa-p256.md) — _(no submodules)_- [ed25519](./src/sign/ed25519/ed25519.md) — _(no submodules)_- [ed25519](./src/sign/ed25519/ed25519.md) — _(no submodules)_- [noble-sign-engine](./src/sign/noble-sign-engine/noble-sign-engine.md) — _(no submodules)_- [noble-sign-engine](./src/sign/noble-sign-engine/noble-sign-engine.md) — _(no submodules)_- noble-aead-engine — (no submodules)- noble-aead-engine — (no submodules)- aead — (no submodules)- noble-aead-engine — <!-- beg…

  • noble-agree-engine — (no submodules)- noble-agree-engine — (no submodules)- x25519 — (no submodules)- x25519 — (no submodules)- auth-box — (no submodules)- noble-auth-box-engine — <!--…

  • codes — (no submodules)- codes — (no submodules)- crypto-error — (no submodules)- crypto-error — (no submodules)- constant-time-equal — (no submodules)- constant-time-equal — (no submodules)- random-bytes — (no submodules)- random-bytes — (no submodules)- zeroize — (no submodules)- zeroize — (no submodules)- sealed-box — (no submodules)- noble-sealed-box-engine — <!…

  • ecdsa-p256 — (no submodules)- ecdsa-p256 — (no submodules)- ed25519 — (no submodules)- ed25519 — (no submodules)- noble-sign-engine — (no submodules)- noble-sign-engine — (no submodules)- noble-aead-engine — _(no s…

  • noble-aead-engine — (no submodules)- noble-aead-engine — (no submodules)- aead — (no submodules)- noble-aead-engine — _(no s…

  • noble-agree-engine — (no submodules)- noble-agree-engine — (no submodules)- x25519 — (no submodules)- x25519 — (no submodules)- auth-box — (no submodules)- noble-auth-box-engine — _(…

  • codes — (no submodules)- codes — (no submodules)- crypto-error — (no submodules)- crypto-error — (no submodules)- constant-time-equal — (no submodules)- constant-time-equal — (no submodules)- random-bytes — (no submodules)- random-bytes — (no submodules)- zeroize — (no submodules)- zeroize — (no submodules)- sealed-box — (no submodules)- noble-sealed-box-engine — …

  • ecdsa-p256 — (no submodules)- ecdsa-p256 — (no submodules)- ed25519 — (no submodules)- ed25519 — (no submodules)- noble-sign-engine — (no submodules)- noble-sign-engine — (no submodules)- noble-aead-engine — (no submodules) - noble-aead-engine — (no submodules)- aead — (no submodules)- noble-agree-engine — (no submodules) - noble-agree-engine — (no submodules)- x25519 — (no submodules) - x25519 — (no submodules)- auth-box — (no submodules) - noble-auth-box-engine — (no submodules) - noble-auth-box-engine — (no submodules) - auth-box — (no submodules)- codes — (no submodules) - codes — (no submodules)- crypto-error — (no submodules) - crypto-error — (no submodules)- constant-time-equal — (no submodules) - constant-time-equal — (no submodules)- random-bytes — (no submodules) - random-bytes — (no submodules)- zeroize — (no submodules) - zeroize — (no submodules)- sealed-box — (no submodules) - noble-sealed-box-engine — (no submodules) - noble-sealed-box-engine — (no submodules) - sealed-box — (no submodules)- ecdsa-p256 — (no submodules) - ecdsa-p256 — (no submodules)- ed25519 — (no submodules) - ed25519 — (no submodules)- noble-sign-engine — (no submodules) - noble-sign-engine — (no submodules)

  • 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-core/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-core";
    
    // 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-core";
    
    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-core";
    
    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-core";
    
    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-core/primitives/random-bytes";
    import { constantTimeEqual } from "@suaveplan/crypto-core/primitives/constant-time-equal";
    import { zeroize } from "@suaveplan/crypto-core/primitives/zeroize";
    import { aeadEncrypt, aeadDecrypt } from "@suaveplan/crypto-core/aead";
    import { ed25519, ecdsaP256 } from "@suaveplan/crypto-core/sign";
    import { x25519 } from "@suaveplan/crypto-core/agree";
    import { sealedBox } from "@suaveplan/crypto-core/sealed-box";
    import { authBox } from "@suaveplan/crypto-core/auth-box";
    import type { AeadEngine, SignEngine } from "@suaveplan/crypto-core/engines";
    import { CryptoError, CRYPTO_CODES } from "@suaveplan/crypto-core/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