Files
suaveplan/openspec/archive/2026-05-10-enterprise-srp/tasks.md
2026-05-27 23:29:53 +01:00

171 lines
12 KiB
Markdown

# Tasks — `@suaveplan/srp` (SRP-6a zero-knowledge password proof)
> Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify.
## 1. Spec authoring
- [x] 1.1 Read `../suaveplan/enterprise/srp.md` end-to-end
- [x] 1.2 Read RFC 5054 §2.2, §2.5, §2.6, §3, Appendix A (group constants), Appendix B (1024-bit test vectors)
- [x] 1.3 Read RFC 2945 §3 (M1 / M2 proof formulae)
- [x] 1.4 Read Wu's original SRP paper §3, §6 (constant-time-compare guidance)
- [x] 1.5 Read `openspec/AGENTS.md` Sections 13, 14, 14a
- [x] 1.6 Read `CLAUDE.md` (root + crypto family entries — `crypto-hash`, `crypto-utils`, `crypto-vault`, `vault`)
- [x] 1.7 Draft `proposal.md` from `openspec/templates/proposal.md`
- [x] 1.8 Fill in `## Source mapping` table — every requirement cites an RFC section or `enterprise/srp.md` line range
- [x] 1.9 Draft `specs/srp/spec.md` from `openspec/templates/spec-delta.md`
- [x] 1.10 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles
## 2. Package scaffold
- [x] 2.1 `packages/vault/srp/` directory + `src/{groups,params,math,verifier,client,server,pake,hash,errors,observability,schemas,__benchmarks__}/` skeleton
- [x] 2.2 `package.json` — `@suaveplan/srp@0.0.0`, `type: "module"`, `sideEffects: false`, dependencies pinned via workspace + catalog refs
- [x] 2.3 `tsconfig.lib.json` extends `@suaveplan/typescript-config/library`
- [x] 2.4 `vite.config.ts` + `vitest.config.ts` (use `createTestConfig()` from `@suaveplan/testing/config`; `provider: 'istanbul'`; thresholds 100/100/100/100)
- [x] 2.5 `README.md` with package description, install command, quick-start handshake snippet, Modules table linking every co-located `.md`
- [x] 2.6 Subpath exports map covers `.`, `./groups`, `./groups/{1024,1536,3072,4096,6144,8192}`, `./params`, `./client`, `./server`, `./verifier`, `./pake`, `./hash`, `./errors`
## 3. Implementation — groups
- [x] 3.1 `src/groups/groups-2048.ts` — RFC 5054 Appendix A 2048-bit safe prime `N_2048` (256-byte `Uint8Array`), `g_2048 = 2`, `BIT_LENGTH = 2048`
- [x] 3.2 `src/groups/1024/groups-1024.ts` — RFC 5054 Appendix A 1024-bit group (required for Appendix B test vectors)
- [x] 3.3 `src/groups/1536/groups-1536.ts` — RFC 5054 Appendix A 1536-bit group
- [x] 3.4 `src/groups/3072/groups-3072.ts` — RFC 5054 Appendix A 3072-bit group
- [x] 3.5 `src/groups/4096/groups-4096.ts` — RFC 5054 Appendix A 4096-bit group
- [x] 3.6 `src/groups/6144/groups-6144.ts` — RFC 5054 Appendix A 6144-bit group
- [x] 3.7 `src/groups/8192/groups-8192.ts` — RFC 5054 Appendix A 8192-bit group
- [x] 3.8 Sibling `.md` for each group file documenting the source RFC section, byte length, and intended use case
## 4. Implementation — params + schemas
- [x] 4.1 `src/params/params.ts` — `SrpParams` type, `params2048Sha256(hash)` factory, `SrpParamsSchema` Zod boundary (`SRP.INVALID_PARAMS` on mismatch)
- [x] 4.2 `src/schemas/schemas.ts` — `SrpParamsSchema`, `SrpHandshakeStateSchema`, `VerifierSchema` (`{ I: string; salt: Uint8Array; v: Uint8Array }`)
- [x] 4.3 Reject non-multiple-of-4 `N.byteLength` and `g >= N` in `SrpParamsSchema.refine`
## 5. Implementation — math primitives
- [x] 5.1 `src/math/pad.ts` — `PAD(x, N)` left-zero-extension; throws `SRP.INVALID_PARAMS` on `x.byteLength > N.byteLength`
- [x] 5.2 `src/math/modpow.ts` — wraps `@noble/curves` big-int modpow; never `BigInt ** BigInt % N`
- [x] 5.3 `src/math/k.ts` — `computeK(params)` per RFC 5054 §2.5.3 (`k = H(N | PAD(g))`)
- [x] 5.4 `src/math/u.ts` — `computeU(A, B, params)` per RFC 5054 §2.5.4; throws `SRP.PUBLIC_KEY_INVALID` on `u ≡ 0 mod N`
- [x] 5.5 `src/math/x.ts` — `computeX(I, P, salt, params)` per RFC 5054 §2.6 (`x = H(salt | H(I | ":" | P))`)
## 6. Implementation — verifier
- [x] 6.1 `src/verifier/verifier.ts` — `computeVerifier(I, P, salt, params): { x, v }`; `v = g^x mod N` via `modPow`
## 7. Implementation — client role
- [x] 7.1 `src/client/client-start.ts` — `clientStartHandshake(rng, params)`; `a` ≥ 256 random bits via injectable `Rng` from `@suaveplan/types`; `A = g^a mod N`
- [x] 7.2 `src/client/client-finish.ts` — `clientFinishHandshake(I, P, salt, A, B, a, params)`; full `S = (B - k * g^x)^(a + u * x) mod N`; throws `SRP.PUBLIC_KEY_INVALID` when `B mod N == 0`
- [x] 7.3 `src/client/client-verify.ts` — `clientVerifyServerProof(M1, M2Server, K, A, params)`; constant-time compare via `@suaveplan/crypto-utils`; throws `SRP.MUTUAL_AUTH_FAILED` on mismatch
## 8. Implementation — server role
- [x] 8.1 `src/server/server-start.ts` — `serverStartHandshake(v, rng, params)`; `b` ≥ 256 random bits; `B = (k * v + g^b) mod N`; re-rolls on `B ≡ 0 mod N`
- [x] 8.2 `src/server/server-finish.ts` — `serverFinishHandshake(I, salt, A, B, b, v, M1Client, params)`; verifies `A mod N != 0`; computes `S = (A * v^u)^b mod N`; constant-time M1 compare; throws `SRP.AUTH_FAILED` on mismatch
- [x] 8.3 `src/server/server-m2.ts` — `serverComputeM2(A, M1, K)` for split-roundtrip transports
## 9. Implementation — augmented PAKE bridge
- [x] 9.1 `src/pake/pake-provider.types.ts` — `PakeProvider`, `PakeRegistration`, `PakeClientHandshake`, `PakeServerHandshake`, `PakeOutcome`
- [x] 9.2 `src/pake/pake-provider.ts` — `srpPakeProvider(params): PakeProvider` SRP-6a implementation of the seam
## 10. Implementation — hash bridge
- [x] 10.1 `src/hash/hash-bridge.ts` — `nobleHashBridge(algorithm)` (sync `@noble/hashes`), `subtleHashBridge(algorithm, signal?)` (async `crypto.subtle.digest`), `defaultHashBridge(algorithm)` (platform selector)
- [x] 10.2 SHA-256 default + SHA-1 legacy opt-in; unsupported algorithm → `SRP.HASH_UNSUPPORTED`
- [x] 10.3 AbortSignal threaded through `subtleHashBridge` — pre-aborted signal short-circuits before any `crypto.subtle.digest` call
## 11. Implementation — errors + observability
- [x] 11.1 `src/errors/errors.ts` — `defineCodes("SRP", ["INVALID_PARAMS", "PUBLIC_KEY_INVALID", "AUTH_FAILED", "MUTUAL_AUTH_FAILED", "GROUP_UNSUPPORTED", "HASH_UNSUPPORTED"])`; `SrpError extends SuaveplanError<SrpErrorCode>`
- [x] 11.2 Every code thrown by ≥1 production path in `src/`
- [x] 11.3 `src/observability/metrics.ts` — `srp.handshake_duration_ms{role,phase}` histogram, `srp.outcome_total{role,outcome}` counter, `srp.errors_total{code}` counter
## 12. Root barrel + subpath exports
- [x] 12.1 `src/index.ts` — explicit named re-exports (no `export *`)
- [x] 12.2 `package.json` exports map matches subpath plan in §2.6
- [x] 12.3 Verify subpath count matches feature module count (§13 step 8)
## 13. Test-vector generator
- [x] 13.1 `scripts/generate-srp-vectors.ts` — derives `(I, P, salt, params) → { x, v, A, B, K, M1, M2 }` for any `(group, hash)` pair; deterministic given fixed RNG seed
- [x] 13.2 Generated 2048/SHA-256 vector committed under `src/__fixtures__/vectors-2048-sha256.json`
- [x] 13.3 Script idempotent — re-running produces byte-identical output
## 14. Tests (runner-neutral imports — `@suaveplan/testing/runner`)
- [x] 14.1 `groups/groups-2048.test.ts` — RFC 5054 Appendix A byte-exact match
- [x] 14.2 `groups/1024/groups-1024.test.ts` — RFC 5054 Appendix A 1024-bit byte-exact match
- [x] 14.3 `groups/groups.spec-conformance.test.ts` — every subpath resolves; no `BigInt ** BigInt %` pattern in source
- [x] 14.4 `params/params.test.ts` — factory + Zod boundary (rejects bad N, bad g)
- [x] 14.5 `math/pad.test.ts` — left-zero-extension, overflow throws `SRP.INVALID_PARAMS`
- [x] 14.6 `math/k.test.ts` — RFC 5054 Appendix B `k` reference
- [x] 14.7 `math/u.test.ts` — RFC 5054 Appendix B `u` reference; `u ≡ 0` throws `SRP.PUBLIC_KEY_INVALID`
- [x] 14.8 `math/x.test.ts` — RFC 5054 Appendix B `x` reference
- [x] 14.9 `math/modpow.test.ts` — agrees with naive `BigInt` reference on small inputs
- [x] 14.10 `verifier/verifier.test.ts` — RFC 5054 Appendix B `v` reference + locally-derived 2048/SHA-256 vector
- [x] 14.11 `client/client-start.test.ts` — `a` length ≥ 32 bytes; `A` reproducible from injected RNG
- [x] 14.12 `client/client-finish.test.ts` — RFC Appendix B `K` reference; `B ≡ 0` rejects
- [x] 14.13 `client/client-verify.test.ts` — `M2` accept + bit-flipped reject (`SRP.MUTUAL_AUTH_FAILED`); constant-time compare exercised
- [x] 14.14 `server/server-start.test.ts` — `B` reproducible; re-roll on synthetic `B ≡ 0`
- [x] 14.15 `server/server-finish.test.ts` — RFC Appendix B `M1` accept + bit-flip reject (`SRP.AUTH_FAILED`); `A ≡ 0` rejects
- [x] 14.16 `server/server-m2.test.ts` — split-roundtrip equals inline finish output
- [x] 14.17 `pake/pake-provider.test.ts` — `register → clientHandshake → serverHandshake` round-trip; client `K` equals server `K`
- [x] 14.18 `hash/hash-bridge.test.ts` — noble + subtle agree; SHA-1 path; AbortSignal pre-abort; unsupported algo throws `SRP.HASH_UNSUPPORTED`
- [x] 14.19 `errors/errors.test.ts` — every code thrown by ≥1 production path (audit table)
- [x] 14.20 `observability/metrics.test.ts` — every histogram + counter asserted with real in-memory `MeterProvider`
- [x] 14.21 `schemas/schemas.test.ts` — `SrpParamsSchema` + `SrpHandshakeStateSchema` boundary checks
- [x] 14.22 `srp.spec-conformance.test.ts` — RFC Appendix B end-to-end; 2048/SHA-256 end-to-end; property test 1000 triples → `K_client === K_server`; every "MUST abort" path
- [x] 14.23 `index.test.ts` — every documented subpath resolves
- [x] 14.24 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for genuinely async modpow on 8192-bit group)
## 15. Documentation
- [x] 15.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference)
- [x] 15.2 README links every co-located `.md` under `## Modules`
- [x] 15.3 Update root `CLAUDE.md` package listing under "Crypto family" or new "Vaulted" section
## 16. Benchmarks
- [x] 16.1 `src/__benchmarks__/srp.bench.ts` — mitata: `client_handshake_2048_sha256`, `server_handshake_2048_sha256`, `compute_verifier_2048_sha256`
- [x] 16.2 Baseline numbers published in README
## 17. Pre-archive verification (the 5-gate triple)
- [x] 17.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info
- [x] 17.2 `bunx turbo lint typecheck test --filter=@suaveplan/srp` — green
- [x] 17.3 `bunx turbo test:coverage --filter=@suaveplan/srp` — 100/100/100/100
- [x] 17.4 `bun scripts/openspec-conformance.ts @suaveplan/srp` — exit 0, no `error` findings
- [x] 17.5 `bunx turbo build --filter=@suaveplan/srp` — green
## 18. Pre-archive 7-row checklist (AGENTS.md Section 13)
- [x] 18.1 Generics threaded; `expectTypeOf` assertions on `PakeProvider` and `HashLike` injection sites
- [x] 18.2 Zod schemas wired at every declared boundary (`SrpParamsSchema`, `SrpHandshakeStateSchema`, `VerifierSchema`)
- [x] 18.3 Every error code in `errors/errors.ts` thrown by at least one production path
- [x] 18.4 Strict-mode behaviors tested both on and off (constant-time-compare path, RNG-injection determinism)
- [x] 18.5 OTel instruments emitted from production paths and asserted with a real in-memory `MeterProvider`
- [x] 18.6 Lifecycle dedup — N/A (handshake functions are stateless; document explicitly)
- [x] 18.7 No retroactive tick — every box above reflects work done in this change
## 19. Root-doc refresh + markdownlint (§13b)
> Deferred — root docs (MANIFEST.md, MISSING.yml, STATUS.md, SUMMARY.md,
> CLAUDE.md) are refreshed by the orchestrator after all parallel
> migrations land. This change does not touch them.
- [ ] 19.1 `bun scripts/folder-reorg/generate-manifest.ts` — refresh `MANIFEST.md` (orchestrator)
- [ ] 19.2 `bun scripts/generate-missing.ts` — refresh `MISSING.yml` (orchestrator)
- [ ] 19.3 `STATUS.md` — move `srp` from Pending → Completed (orchestrator)
- [ ] 19.4 `SUMMARY.md` — add per-package paragraph (orchestrator)
- [ ] 19.5 `bunx markdownlint-cli2 --fix` — apply auto-fixes (orchestrator)
- [ ] 19.6 `bunx markdownlint-cli2` (no --fix) — exit 0 (orchestrator)
## 20. Archive
- [x] 20.1 Fold spec delta into `openspec/specs/srp/spec.md`
- [x] 20.2 Move `openspec/changes/enterprise-srp/` → `openspec/archive/<yyyy-mm-dd>-enterprise-srp/`
- [ ] 20.3 Refresh `MANIFEST.md` if package set or deps changed (orchestrator)