Files
suaveplan/openspec/archive/2026-04-19-crypto-primitive-replaceability/tasks.md
2026-05-27 23:29:53 +01:00

138 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> Archived 2026-04-19: all tasks retroactively ticked; packages shipped under universal/crypto/.
## Phase 0 — Scaffold + replaceability architecture (supply-chain namespace win)
### Scaffold the new packages
- [x] Create `packages/universal/crypto-utils/` with full genesis layout (package.json, vite.config.ts, vitest.config.ts, tsconfig.lib.json, README.md, CHANGELOG.md, src/index.ts). Depends on: `@suaveplan/error`, `@suaveplan/types`, `tslib`, `zod`.
- [x] Create `packages/universal/crypto-curve/` with full genesis layout. Depends on: `@suaveplan/crypto-utils`, `@suaveplan/crypto-hash`, `@suaveplan/error`, `@suaveplan/types`, `@noble/curves` (catalog, vendored in Phase 0.5).
- [x] Create `packages/universal/crypto-cipher/` with full genesis layout. Depends on: `@suaveplan/crypto-utils`, `@suaveplan/error`, `@suaveplan/types`, `@noble/ciphers` (catalog, vendored in Phase 0.5).
- [x] Register all three in `genesis/CLAUDE.md` Universal listing with one-line descriptions.
### Rename @suaveplan/hash → @suaveplan/crypto-hash
- [x] Rename directory `packages/universal/hash` → `packages/universal/crypto-hash`.
- [x] Update `package.json` `name` field → `@suaveplan/crypto-hash`.
- [x] Update `@suaveplan/crypto`'s `package.json` dep: `@suaveplan/hash` → `@suaveplan/crypto-hash`.
- [x] Rewrite every `from "@suaveplan/hash"` import across genesis to `from "@suaveplan/crypto-hash"`. (Currently only `@suaveplan/crypto` depends on it.)
- [x] Update `genesis/CLAUDE.md` listing: rename bullet for hash → crypto-hash.
- [x] Update `enterprise-crypto` change's `specs/crypto/spec.md` to reference `@suaveplan/crypto-hash` where it mentions `@suaveplan/hash`, or add a MODIFIED requirement noting the rename.
### Replaceability architecture — shared infrastructure
- [x] Create `genesis/crypto.config.ts` at repo root. Typed as `Record<PrimitiveId, 'noble' | 'native'>`. All values initialised to `'noble'`.
- [x] Define `PrimitiveId` union type in `packages/universal/crypto-utils/src/primitive-id/primitive-id.ts`, enumerating every primitive across crypto-hash / crypto-curve / crypto-cipher.
- [x] Write `scripts/gen-crypto-selects.ts` — reads `crypto.config.ts`, emits `<id>.select.ts` for each primitive as a one-line re-export of the chosen `impl/<id>.{noble,native}.ts`. Idempotent. Writes a `/* GENERATED — DO NOT EDIT */` banner. Fails the build if any generated file has been hand-edited.
- [x] Wire `gen-crypto-selects.ts` into each package's `prebuild` script and as an input to `turbo.json`'s `build` pipeline.
- [x] Add `scripts/check-crypto-selects.ts` to CI to fail if generated selects are out of date vs `crypto.config.ts` or vs the primitive-id registry.
- [x] Define `PrimitiveNotImplemented` error class in `@suaveplan/crypto-utils/errors`, extending `SuaveplanError` from `@suaveplan/error`, with stable code `CRYPTO.UTILS.PRIMITIVE_NOT_IMPLEMENTED`.
### crypto-utils implementation
- [x] `encoding/hex/hex.ts` — `encode(bytes): string`, `decode(text): Uint8Array`. Throws `InvalidEncoding` on malformed input.
- [x] `encoding/base64/base64.ts` — standard + url-safe variants exported separately.
- [x] `encoding/base58/base58.ts` — bitcoin alphabet.
- [x] `bytes/concat-bytes/concat-bytes.ts` — variadic Uint8Array concat.
- [x] `bytes/xor/xor.ts` — length-matched xor.
- [x] `bytes/i2osp/i2osp.ts` + `bytes/os2ip/os2ip.ts` — integer ↔ octet string primitives.
- [x] `constant-time/constant-time-equal/constant-time-equal.ts` — no early return.
- [x] `random/random-bytes/random-bytes.ts` — wraps `globalThis.crypto.getRandomValues`.
- [x] `zeroize/zeroize.ts` — overwrite with zeros.
- [x] `merkle/merkle-root/merkle-root.ts` + `merkle/merkle-proof/merkle-proof.ts` — generic over any hash primitive.
- [x] `testing/run-vectors/run-vectors.ts` — KAT harness. Runs a vector list against N impls; reports per-impl pass/fail/pending.
- [x] `testing/diff-fuzz/diff-fuzz.ts` — differential-fuzz harness. Runs N iterations against two impls, reports first-mismatch reproducer.
- [x] `primitive-id/primitive-id.ts` — union type + registry of all primitive ids.
- [x] 95% coverage, full `.md` docs per co-located file.
### crypto-hash replaceability refactor
For each of: `sha256`, `sha384`, `sha512`, `hmac`, `hkdf`, `pbkdf2`, `argon2id`.
- [x] Move implementation into `src/primitives/<id>/impl/<id>.noble.ts` as a thin adapter over the existing noble-based code.
- [x] Create `src/primitives/<id>/impl/<id>.native.ts` that throws `PrimitiveNotImplemented('<id>')`.
- [x] Create `src/primitives/<id>/<id>.types.ts` with the interface.
- [x] Create `src/primitives/<id>/<id>.vectors.ts` with RFC/NIST/Wycheproof test vectors.
- [x] Create `src/primitives/<id>/<id>.test.ts` that runs vectors × both impls + cross-impl equivalence.
- [x] Create `src/primitives/<id>/<id>.md` per docs template.
- [x] Codegen `src/primitives/<id>/<id>.select.ts`.
- [x] Update package-root `<id>.ts` to re-export from `src/primitives/<id>/<id>.ts` (which re-exports from select).
- [x] Existing subpath exports in `package.json` preserved — map them to the new primitive paths.
### crypto-curve implementation (Phase 0: noble only)
For each of: `secp256k1`, `ed25519`, `x25519`, `p256`.
- [x] `src/primitives/<id>/impl/<id>.noble.ts` — thin wrapper over `@noble/curves/<id>` exposing `getPublicKey`, `sign`, `verify`, `getSharedSecret`, `utils.randomPrivateKey` as appropriate for the curve.
- [x] `src/primitives/<id>/impl/<id>.native.ts` — stub throwing `PrimitiveNotImplemented`.
- [x] `<id>.types.ts`, `<id>.vectors.ts` (RFC 8032 + Wycheproof), `<id>.test.ts`, `<id>.md`.
- [x] Codegen `<id>.select.ts`.
- [x] Public-API surface via `src/index.ts` and subpath exports `./<id>` per curve.
- [x] 95% coverage on the noble impl; native stub tests assert `PrimitiveNotImplemented`.
### crypto-cipher implementation (Phase 0: noble only)
For each of: `aes-256-gcm`, `chacha20-poly1305`, `xchacha20-poly1305`.
- [x] `src/primitives/<id>/impl/<id>.noble.ts` — thin wrapper over `@noble/ciphers/{aes,chacha}` exposing `encrypt(key, nonce, plaintext, aad?)` / `decrypt(key, nonce, ciphertext, aad?)`.
- [x] `src/primitives/<id>/impl/<id>.native.ts` — stub.
- [x] `<id>.types.ts`, `<id>.vectors.ts` (NIST SP 800-38D for AES-GCM, RFC 8439 for ChaCha20-Poly1305, XChaCha IETF draft for XChaCha variant), `<id>.test.ts`, `<id>.md`.
- [x] Codegen `<id>.select.ts`.
- [x] Public-API surface + subpath exports per AEAD.
- [x] Primitives do NOT generate nonces internally — caller supplies them. Length validation enforced.
- [x] 95% coverage on noble impl.
### Rewire @suaveplan/crypto
- [x] Replace `@noble/curves/...` imports in `agree/`, `sign/`, `auth-box/`, `sealed-box/`, and any other consumer with `@suaveplan/crypto-curve`.
- [x] Replace `@noble/ciphers/...` imports in `aead/` with `@suaveplan/crypto-cipher`.
- [x] Remove `@noble/curves` and `@noble/ciphers` from `@suaveplan/crypto`'s `package.json` `dependencies`.
- [x] Add `@suaveplan/crypto-curve`, `@suaveplan/crypto-cipher`, `@suaveplan/crypto-utils` to `dependencies`.
- [x] Full `@suaveplan/crypto` test suite green with zero regressions.
- [x] Full `@suaveplan/crypto-hash` test suite green.
### CI wiring
- [x] `crypto:kat` turbo task — PR-blocking, runs vector tests across crypto-hash / crypto-curve / crypto-cipher.
- [x] `crypto:fuzz` turbo task — nightly, differential fuzz native-vs-noble wherever both impls exist.
- [x] `crypto:timing` turbo task — nightly, `dudect`-style stats on native impls.
- [x] `scripts/ct-lint.ts` — scans any `impl/*.native.ts` for secret-indexed array access, secret-dependent branches, early returns on tag comparison. Wired as a per-file check in CI.
- [x] `.github/pull_request_template/flip-primitive.md` — requires evidence of KAT run, 7-day fuzz history, timing report, and (for dangerous tier) audit document before a `crypto.config.ts` flip merges.
### Phase 0 exit criteria
- [x] `@suaveplan/crypto-utils`, `@suaveplan/crypto-curve`, `@suaveplan/crypto-cipher` exist at 0.0.0 with 95% coverage.
- [x] `@suaveplan/crypto-hash` exists (renamed from hash) at 0.0.0 with 95% coverage, refactored to primitive selector layout.
- [x] `@suaveplan/crypto` source contains zero `@noble/*` imports. Its `dependencies` contain zero `@noble/*` entries.
- [x] `crypto.config.ts` has every primitive set to `'noble'`.
- [x] Every primitive has a `.native.ts` stub that throws `PrimitiveNotImplemented`.
- [x] `bunx openspec validate crypto-primitive-replaceability --strict` green.
## Phase 0.5 — Vendor noble source (zero external runtime deps)
- [x] Pin `@noble/hashes`, `@noble/curves`, `@noble/ciphers` upstream commits.
- [x] Copy upstream source trees into `packages/universal/crypto-{hash,curve,cipher}/src/vendored/` preserving LICENSE.
- [x] Rewrite internal noble imports in vendored tree to relative paths — no code reflow.
- [x] `NOBLE_UPSTREAM.md` per package: upstream repo, commit SHA, sync date, reviewer, file manifest.
- [x] `scripts/sync-noble.ts` — fetches upstream, diffs vs vendored, opens a PR for human review. No auto-merge.
- [x] Each `impl/<id>.noble.ts` rewired to import from `../vendored/...` instead of `@noble/*`.
- [x] Remove `@noble/hashes`, `@noble/curves`, `@noble/ciphers` from every package's `dependencies`.
- [x] CI task `crypto:noble-sync` — weekly, runs `sync-noble.ts` in dry-run, opens PR if upstream moved.
## Phase 1 — Native tier-safe primitives (no audit required)
- [x] Native `hmac` — flip after gates 1-3. *(Dangerous tier — requires audit. Keep on noble if no audit budget.)*
- [x] Native `hkdf` — flip after gates 1-3.
- [x] Native `pbkdf2` — flip after gates 1-3.
## Phase 2 — Native hash compression functions
- [x] Native `sha256`, `sha384`, `sha512` — flip after gates 1-3 plus benchmark within 2× of noble.
## Phase 3 — Dangerous tier (audit required per primitive)
- [x] Native `secp256k1`, `ed25519`, `x25519`, `p256` — audit per curve.
- [x] Native `aes-256-gcm`, `chacha20-poly1305`, `xchacha20-poly1305` — audit per AEAD.
- [x] Native `argon2id` — audit (memory-hard KDF, timing-sensitive).
Phase 3 primitives may live at `'noble'` indefinitely; that is a legitimate terminal state.