# Change: crypto-primitive-replaceability ## Why `enterprise-crypto` establishes `@suaveplan/hash` + `@suaveplan/crypto` at enterprise quality but keeps a direct runtime dependency on `@noble/{hashes,curves,ciphers}`. That delivers audited primitives today but leaves two properties on the table: **supply-chain ownership** (every byte of crypto code lives under `@suaveplan/*` with no external runtime dep) and **per-primitive replaceability** (ability to swap individual primitives from the vendored `noble` impl to an owned `native` impl incrementally, under a rigorous CI gate). Those properties matter because primitive-level crypto is the one place where a single compromised upstream release — or a single npm-registry takeover — silently owns every downstream service we ship. Vendoring noble source into our tree and adding a per-primitive selector lets us: (a) freeze the exact bytes we trust; (b) review upstream diffs on our cadence instead of theirs; (c) replace individual primitives with owned implementations one at a time, without ever going through a big-bang rewrite; (d) leave cryptographically dangerous primitives (curves, AES, MACs) on the audited `noble` impl indefinitely if we choose — that is a legitimate terminal state. This change builds *on top of* `enterprise-crypto`'s primitives, it does not replace them. The public surface of `@suaveplan/crypto` does not change. What changes is where the underlying bytes come from and how we swap them. ## What Changes ### Package family (new) - **NEW** `@suaveplan/crypto-curve` — ECC primitives (secp256k1, Ed25519, X25519, P-256) as vendor-fork of `@noble/curves`, exposed behind per-primitive selector modules. Dangerous-tier CI gate. - **NEW** `@suaveplan/crypto-cipher` — AEAD primitives (AES-256-GCM, ChaCha20-Poly1305, XChaCha20-Poly1305) as vendor-fork of `@noble/ciphers`, same selector architecture. Dangerous-tier CI gate. - **NEW** `@suaveplan/crypto-utils` — pure-composition utilities (hex, base64, base64url, base58, concatBytes, xor, I2OSP / OS2IP, Merkle helpers, constant-time-equal, zeroize, KAT harness, differential-fuzz harness, constant-time lint). No vendored code. Depends only on `@suaveplan/error`, `@suaveplan/types`, `tslib`, `zod`. ### Package rename (no external consumers — genesis only) - **RENAME** `@suaveplan/hash` → `@suaveplan/crypto-hash` so the family shares the `crypto-*` prefix. The one in-tree consumer (`@suaveplan/crypto`) is updated in the same change. No other workspace imports `@suaveplan/hash`. ### Replaceability architecture (new, applied to crypto-hash / crypto-curve / crypto-cipher) Every hash, curve, and AEAD primitive is refactored to the following layout: ```text src/primitives// .types.ts # Interface .ts # Public API — re-exports from .select.ts .vectors.ts # RFC/NIST/Wycheproof KATs, used by tests .test.ts # Runs vectors × every impl + cross-impl equivalence .md .select.ts # GENERATED — do not edit impl/ .noble.ts # Vendored-noble impl (Phase 0: via @noble/* dep; Phase 0.5: vendored source) .native.ts # Owned impl (starts: throw PrimitiveNotImplemented) ``` A root `crypto.config.ts` maps `primitiveId → 'noble' | 'native'`. A pre-build codegen script (`scripts/gen-crypto-selects.ts`) emits each `.select.ts` as a one-line re-export of the chosen impl, so Vite tree-shakes the unused impl out. Flipping a primitive is a one-line config change. ### Modifications - **MODIFIED** `@suaveplan/crypto` — `@noble/curves` and `@noble/ciphers` imports are rewired to `@suaveplan/crypto-curve` and `@suaveplan/crypto-cipher`. `@noble/*` removed from `dependencies`. Public API surface unchanged. - **MODIFIED** `@suaveplan/crypto-hash` (formerly `@suaveplan/hash`) — each primitive (sha256 / sha384 / sha512 / hmac / hkdf / pbkdf2 / argon2id / encoding) refactored into the replaceability layout. Public API surface unchanged. ### CI gate for flipping a primitive to `native` A primitive default SHALL NOT change from `'noble'` to `'native'` in `crypto.config.ts` unless the flip PR demonstrates: 1. Full KAT suite (RFC / NIST / Wycheproof) passes on the native impl. 2. Nightly differential fuzz (≥ 1M inputs over 7 days) shows zero mismatches vs noble. 3. Constant-time lint: automated scan for secret-dependent branches / secret-indexed lookups + reviewer checklist + `dudect`-style statistical timing test on Bun. 4. **Dangerous tier** (every primitive in `crypto-curve` and `crypto-cipher`, plus HMAC and any MAC-like primitive in `crypto-hash`): third-party cryptographic audit sign-off attached to the flip PR, referencing the exact commit SHA of the native impl. Hashes, HKDF, and PBKDF2 can flip at gate 3 without audit. ### Vendoring hygiene (Phase 0.5 — after initial rollout) - `@noble/{hashes,curves,ciphers}` source copied into `packages/universal/crypto-{hash,curve,cipher}/src/vendored/` at a pinned upstream commit, with LICENSE preserved. - `NOBLE_UPSTREAM.md` per package records upstream repo, commit SHA, sync date, reviewer, and files copied. - `scripts/sync-noble.ts` fetches upstream, diffs, and opens a PR for human review — never auto-merges. - After vendoring: `@noble/*` removed from every package's `dependencies`; `impl/.noble.ts` imports from `../vendored/...` relative paths. ## Capabilities ### New Capabilities - `crypto-curve`: ECC primitive family (secp256k1, Ed25519, X25519, P-256) with per-primitive noble/native selector and dangerous-tier audit gate. - `crypto-cipher`: AEAD primitive family (AES-256-GCM, ChaCha20-Poly1305, XChaCha20-Poly1305) with per-primitive selector and dangerous-tier audit gate. - `crypto-utils`: encoding + byte + test-harness utilities. Zero vendored code, pure-composition only. ### Modified Capabilities - `crypto-hash` (renamed from `hash`): primitives refactored into the per-primitive selector layout. Public API unchanged. - `crypto`: curve and cipher primitive sources migrate from `@noble/*` to `@suaveplan/crypto-{curve,cipher}`. `@noble/*` removed from `dependencies`. Public API unchanged. ## Impact - **Consumers of `@suaveplan/crypto`**: no source changes required. Public API stable. - **Consumers of `@suaveplan/hash`**: one-line import rewrite `@suaveplan/hash` → `@suaveplan/crypto-hash`. Codemod provided. - **Bundle size**: neutral in Phase 0. Tree-shakes per-primitive as natives land. - **CI**: four new tasks — `crypto:kat` (PR-blocking), `crypto:fuzz` (nightly), `crypto:timing` (nightly), `crypto:noble-sync` (weekly). - **Phase 0 exit criteria**: four packages publish at 0.0.0 with 95% coverage; every primitive selector defaults to `'noble'`; every `.native.ts` stub is present; `@suaveplan/crypto` has zero `@noble/*` imports in source and zero `@noble/*` entries in its direct `dependencies`; `crypto-hash` / `crypto-curve` / `crypto-cipher` still declare `@noble/*` as catalog deps (vendoring deferred to Phase 0.5). - **Supersession**: updates the `@noble/*` dep decision documented in `enterprise-crypto` without reverting any of its other work. `enterprise-crypto` remains the enterprise-quality baseline; this change layers replaceability on top. ## Relationship to enterprise-crypto `enterprise-crypto` is the enterprise-quality baseline for `@suaveplan/crypto` + `@suaveplan/hash`: OTel, structured errors, Zod schemas, 100% coverage, benchmark suite. This change preserves every one of those properties and adds the replaceability architecture. The two changes are composable — whichever ships first, the other rebases cleanly by updating import paths (for rename) and adding the `impl/` and `select.ts` files per primitive (for architecture).