Files
suaveplan/openspec/changes/complete-rng-unification/specs/crypto-core/spec.md
2026-05-27 23:29:53 +01:00

4.1 KiB

Capability Spec Delta: crypto-core (crypto-core)

Package: @suaveplan/crypto-core Status: PROPOSED

Every requirement below carries a **Verified by:** line at authoring time. No exceptions.

MODIFIED Requirements

Requirement: ByteSource and Rng are distinct contracts

@suaveplan/crypto-core SHALL declare ByteSource as the package's callable byte producer (type ByteSource = (n: number) => Uint8Array), exported from src/primitives/random-bytes/random-bytes.ts. The package SHALL NOT alias Rng to ByteSource or to any callable shape — the canonical Rng (from @suaveplan/types) is an object with nextUint32 / nextFloat, fundamentally different from a callable byte producer. The previous type Rng = ByteSource alias and the defaultRng: ByteSource = defaultByteSource constant SHALL both be removed.

@suaveplan/crypto-core MAY re-export the canonical Rng from @suaveplan/types under the name Rng as a back-compat forwarder for one release cycle, with a prose comment naming the canonical import path and the removal change ID (remove-deprecated-rng-aliases). The forwarder MUST live at src/primitives/random-bytes/random-bytes.ts and be re-surfaced through the three barrels (src/primitives/random-bytes/index.ts, src/primitives/index.ts, src/index.ts). The defaultRng re-export SHALL NOT survive in any barrel — consumers use defaultByteSource directly.

The RandomBytesOptions shape SHALL drop the legacy rng?: ByteSource field; only byteSource?: ByteSource remains. The internal selector simplifies from options.byteSource ?? options.rng ?? defaultByteSource to options.byteSource ?? defaultByteSource. Repo-internal grep confirms zero callsites pass { rng: ... } to randomBytes today.

@suaveplan/crypto-core SHALL preserve every other ByteSource-typed surface verbatim — engine constructors (AeadEngine, SignEngine, AgreeEngine, SealedBoxEngine, AuthBoxEngine), the byteSource injection on generateKeypair (ed25519, ecdsaP256, x25519), and the defaultByteSource constant. None of these reference Rng; they reference ByteSource by its real name.

Verified by: packages/crypto/crypto-core/src/primitives/random-bytes/random-bytes.ts (no type Rng = ByteSource; no defaultRng; presence of export type { Rng } from "@suaveplan/types" back-compat re-export) AND packages/crypto/crypto-core/src/primitives/random-bytes/random-bytes.test.ts::"randomBytes accepts byteSource option" AND packages/crypto/crypto-core/src/primitives/random-bytes/random-bytes.test.ts::"randomBytes rejects negative or non-integer n" AND bunx turbo typecheck test --filter=@suaveplan/crypto-core (green with zero assertion edits) AND bunx turbo test --filter=...@suaveplan/crypto-core (every consumer green) AND repo-internal grep grep -rn "defaultRng\b\|\.rng:\|, rng:" packages/crypto/crypto-core/src/ --include="*.ts" returns zero results outside the documented back-compat re-export comment.

Scenario: Rng re-export forwards canonical

  • WHEN a consumer writes import type { Rng } from "@suaveplan/crypto-core"
  • THEN the imported type is structurally identical to import type { Rng } from "@suaveplan/types" (an object, NOT a callable)

Scenario: ByteSource remains the callable contract

  • WHEN a consumer writes import type { ByteSource } from "@suaveplan/crypto-core"
  • THEN the imported type is (n: number) => Uint8Array

Scenario: defaultRng is removed

  • WHEN a consumer writes import { defaultRng } from "@suaveplan/crypto-core"
  • THEN tsc rejects with "has no exported member defaultRng"

Scenario: RandomBytesOptions.rng field is removed

  • WHEN a consumer calls randomBytes(32, { rng: customSource })
  • THEN tsc rejects with "Object literal may only specify known properties, and rng does not exist in type RandomBytesOptions"

Scenario: engines preserve ByteSource injection

  • WHEN a consumer calls ed25519.generateKeypair({ byteSource: customSource })
  • THEN the keypair is generated using customSource(32) (behavior unchanged)