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

12 KiB

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

  • 1.1 Read ../suaveplan/enterprise/srp.md end-to-end
  • 1.2 Read RFC 5054 §2.2, §2.5, §2.6, §3, Appendix A (group constants), Appendix B (1024-bit test vectors)
  • 1.3 Read RFC 2945 §3 (M1 / M2 proof formulae)
  • 1.4 Read Wu's original SRP paper §3, §6 (constant-time-compare guidance)
  • 1.5 Read openspec/AGENTS.md Sections 13, 14, 14a
  • 1.6 Read CLAUDE.md (root + crypto family entries — crypto-hash, crypto-utils, crypto-vault, vault)
  • 1.7 Draft proposal.md from openspec/templates/proposal.md
  • 1.8 Fill in ## Source mapping table — every requirement cites an RFC section or enterprise/srp.md line range
  • 1.9 Draft specs/srp/spec.md from openspec/templates/spec-delta.md
  • 1.10 Every requirement in the spec delta carries a **Verified by:** line naming concrete it(...) titles

2. Package scaffold

  • 2.1 packages/vault/srp/ directory + src/{groups,params,math,verifier,client,server,pake,hash,errors,observability,schemas,__benchmarks__}/ skeleton
  • 2.2 package.json — @suaveplan/srp@0.0.0, type: "module", sideEffects: false, dependencies pinned via workspace + catalog refs
  • 2.3 tsconfig.lib.json extends @suaveplan/typescript-config/library
  • 2.4 vite.config.ts + vitest.config.ts (use createTestConfig() from @suaveplan/testing/config; provider: 'istanbul'; thresholds 100/100/100/100)
  • 2.5 README.md with package description, install command, quick-start handshake snippet, Modules table linking every co-located .md
  • 2.6 Subpath exports map covers ., ./groups, ./groups/{1024,1536,3072,4096,6144,8192}, ./params, ./client, ./server, ./verifier, ./pake, ./hash, ./errors

3. Implementation — groups

  • 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
  • 3.2 src/groups/1024/groups-1024.ts — RFC 5054 Appendix A 1024-bit group (required for Appendix B test vectors)
  • 3.3 src/groups/1536/groups-1536.ts — RFC 5054 Appendix A 1536-bit group
  • 3.4 src/groups/3072/groups-3072.ts — RFC 5054 Appendix A 3072-bit group
  • 3.5 src/groups/4096/groups-4096.ts — RFC 5054 Appendix A 4096-bit group
  • 3.6 src/groups/6144/groups-6144.ts — RFC 5054 Appendix A 6144-bit group
  • 3.7 src/groups/8192/groups-8192.ts — RFC 5054 Appendix A 8192-bit group
  • 3.8 Sibling .md for each group file documenting the source RFC section, byte length, and intended use case

4. Implementation — params + schemas

  • 4.1 src/params/params.ts — SrpParams type, params2048Sha256(hash) factory, SrpParamsSchema Zod boundary (SRP.INVALID_PARAMS on mismatch)
  • 4.2 src/schemas/schemas.ts — SrpParamsSchema, SrpHandshakeStateSchema, VerifierSchema ({ I: string; salt: Uint8Array; v: Uint8Array })
  • 4.3 Reject non-multiple-of-4 N.byteLength and g >= N in SrpParamsSchema.refine

5. Implementation — math primitives

  • 5.1 src/math/pad.ts — PAD(x, N) left-zero-extension; throws SRP.INVALID_PARAMS on x.byteLength > N.byteLength
  • 5.2 src/math/modpow.ts — wraps @noble/curves big-int modpow; never BigInt ** BigInt % N
  • 5.3 src/math/k.ts — computeK(params) per RFC 5054 §2.5.3 (k = H(N | PAD(g)))
  • 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
  • 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

  • 6.1 src/verifier/verifier.ts — computeVerifier(I, P, salt, params): { x, v }; v = g^x mod N via modPow

7. Implementation — client role

  • 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
  • 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
  • 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

  • 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
  • 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
  • 8.3 src/server/server-m2.ts — serverComputeM2(A, M1, K) for split-roundtrip transports

9. Implementation — augmented PAKE bridge

  • 9.1 src/pake/pake-provider.types.ts — PakeProvider, PakeRegistration, PakeClientHandshake, PakeServerHandshake, PakeOutcome
  • 9.2 src/pake/pake-provider.ts — srpPakeProvider(params): PakeProvider SRP-6a implementation of the seam

10. Implementation — hash bridge

  • 10.1 src/hash/hash-bridge.ts — nobleHashBridge(algorithm) (sync @noble/hashes), subtleHashBridge(algorithm, signal?) (async crypto.subtle.digest), defaultHashBridge(algorithm) (platform selector)
  • 10.2 SHA-256 default + SHA-1 legacy opt-in; unsupported algorithm → SRP.HASH_UNSUPPORTED
  • 10.3 AbortSignal threaded through subtleHashBridge — pre-aborted signal short-circuits before any crypto.subtle.digest call

11. Implementation — errors + observability

  • 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>
  • 11.2 Every code thrown by ≥1 production path in src/
  • 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

  • 12.1 src/index.ts — explicit named re-exports (no export *)
  • 12.2 package.json exports map matches subpath plan in §2.6
  • 12.3 Verify subpath count matches feature module count (§13 step 8)

13. Test-vector generator

  • 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
  • 13.2 Generated 2048/SHA-256 vector committed under src/__fixtures__/vectors-2048-sha256.json
  • 13.3 Script idempotent — re-running produces byte-identical output

14. Tests (runner-neutral imports — @suaveplan/testing/runner)

  • 14.1 groups/groups-2048.test.ts — RFC 5054 Appendix A byte-exact match
  • 14.2 groups/1024/groups-1024.test.ts — RFC 5054 Appendix A 1024-bit byte-exact match
  • 14.3 groups/groups.spec-conformance.test.ts — every subpath resolves; no BigInt ** BigInt % pattern in source
  • 14.4 params/params.test.ts — factory + Zod boundary (rejects bad N, bad g)
  • 14.5 math/pad.test.ts — left-zero-extension, overflow throws SRP.INVALID_PARAMS
  • 14.6 math/k.test.ts — RFC 5054 Appendix B k reference
  • 14.7 math/u.test.ts — RFC 5054 Appendix B u reference; u ≡ 0 throws SRP.PUBLIC_KEY_INVALID
  • 14.8 math/x.test.ts — RFC 5054 Appendix B x reference
  • 14.9 math/modpow.test.ts — agrees with naive BigInt reference on small inputs
  • 14.10 verifier/verifier.test.ts — RFC 5054 Appendix B v reference + locally-derived 2048/SHA-256 vector
  • 14.11 client/client-start.test.ts — a length ≥ 32 bytes; A reproducible from injected RNG
  • 14.12 client/client-finish.test.ts — RFC Appendix B K reference; B ≡ 0 rejects
  • 14.13 client/client-verify.test.ts — M2 accept + bit-flipped reject (SRP.MUTUAL_AUTH_FAILED); constant-time compare exercised
  • 14.14 server/server-start.test.ts — B reproducible; re-roll on synthetic B ≡ 0
  • 14.15 server/server-finish.test.ts — RFC Appendix B M1 accept + bit-flip reject (SRP.AUTH_FAILED); A ≡ 0 rejects
  • 14.16 server/server-m2.test.ts — split-roundtrip equals inline finish output
  • 14.17 pake/pake-provider.test.ts — register → clientHandshake → serverHandshake round-trip; client K equals server K
  • 14.18 hash/hash-bridge.test.ts — noble + subtle agree; SHA-1 path; AbortSignal pre-abort; unsupported algo throws SRP.HASH_UNSUPPORTED
  • 14.19 errors/errors.test.ts — every code thrown by ≥1 production path (audit table)
  • 14.20 observability/metrics.test.ts — every histogram + counter asserted with real in-memory MeterProvider
  • 14.21 schemas/schemas.test.ts — SrpParamsSchema + SrpHandshakeStateSchema boundary checks
  • 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
  • 14.23 index.test.ts — every documented subpath resolves
  • 14.24 Every it(...) carries { timeout: 5000 } (or 30000 for genuinely async modpow on 8192-bit group)

15. Documentation

  • 15.1 Sibling .md for every implementation .ts (≥200 words, basic + advanced examples, full API reference)
  • 15.2 README links every co-located .md under ## Modules
  • 15.3 Update root CLAUDE.md package listing under "Crypto family" or new "Vaulted" section

16. Benchmarks

  • 16.1 src/__benchmarks__/srp.bench.ts — mitata: client_handshake_2048_sha256, server_handshake_2048_sha256, compute_verifier_2048_sha256
  • 16.2 Baseline numbers published in README

17. Pre-archive verification (the 5-gate triple)

  • 17.1 bun run check — Biome 0 errors / 0 warnings / 0 info
  • 17.2 bunx turbo lint typecheck test --filter=@suaveplan/srp — green
  • 17.3 bunx turbo test:coverage --filter=@suaveplan/srp — 100/100/100/100
  • 17.4 bun scripts/openspec-conformance.ts @suaveplan/srp — exit 0, no error findings
  • 17.5 bunx turbo build --filter=@suaveplan/srp — green

18. Pre-archive 7-row checklist (AGENTS.md Section 13)

  • 18.1 Generics threaded; expectTypeOf assertions on PakeProvider and HashLike injection sites
  • 18.2 Zod schemas wired at every declared boundary (SrpParamsSchema, SrpHandshakeStateSchema, VerifierSchema)
  • 18.3 Every error code in errors/errors.ts thrown by at least one production path
  • 18.4 Strict-mode behaviors tested both on and off (constant-time-compare path, RNG-injection determinism)
  • 18.5 OTel instruments emitted from production paths and asserted with a real in-memory MeterProvider
  • 18.6 Lifecycle dedup — N/A (handshake functions are stateless; document explicitly)
  • 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

  • 20.1 Fold spec delta into openspec/specs/srp/spec.md
  • 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)