138 lines
10 KiB
Markdown
138 lines
10 KiB
Markdown
> 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.
|