12 KiB
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.mdend-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.mdSections 13, 14, 14a - 1.6 Read
CLAUDE.md(root + crypto family entries —crypto-hash,crypto-utils,crypto-vault,vault) - 1.7 Draft
proposal.mdfromopenspec/templates/proposal.md - 1.8 Fill in
## Source mappingtable — every requirement cites an RFC section orenterprise/srp.mdline range - 1.9 Draft
specs/srp/spec.mdfromopenspec/templates/spec-delta.md - 1.10 Every requirement in the spec delta carries a
**Verified by:**line naming concreteit(...)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.jsonextends@suaveplan/typescript-config/library - 2.4
vite.config.ts+vitest.config.ts(usecreateTestConfig()from@suaveplan/testing/config;provider: 'istanbul'; thresholds 100/100/100/100) - 2.5
README.mdwith 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 primeN_2048(256-byteUint8Array),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
.mdfor each group file documenting the source RFC section, byte length, and intended use case
4. Implementation — params + schemas
- 4.1
src/params/params.ts—SrpParamstype,params2048Sha256(hash)factory,SrpParamsSchemaZod boundary (SRP.INVALID_PARAMSon mismatch) - 4.2
src/schemas/schemas.ts—SrpParamsSchema,SrpHandshakeStateSchema,VerifierSchema({ I: string; salt: Uint8Array; v: Uint8Array }) - 4.3 Reject non-multiple-of-4
N.byteLengthandg >= NinSrpParamsSchema.refine
5. Implementation — math primitives
- 5.1
src/math/pad.ts—PAD(x, N)left-zero-extension; throwsSRP.INVALID_PARAMSonx.byteLength > N.byteLength - 5.2
src/math/modpow.ts— wraps@noble/curvesbig-int modpow; neverBigInt ** 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; throwsSRP.PUBLIC_KEY_INVALIDonu ≡ 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 NviamodPow
7. Implementation — client role
- 7.1
src/client/client-start.ts—clientStartHandshake(rng, params);a≥ 256 random bits via injectableRngfrom@suaveplan/types;A = g^a mod N - 7.2
src/client/client-finish.ts—clientFinishHandshake(I, P, salt, A, B, a, params); fullS = (B - k * g^x)^(a + u * x) mod N; throwsSRP.PUBLIC_KEY_INVALIDwhenB mod N == 0 - 7.3
src/client/client-verify.ts—clientVerifyServerProof(M1, M2Server, K, A, params); constant-time compare via@suaveplan/crypto-utils; throwsSRP.MUTUAL_AUTH_FAILEDon 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 onB ≡ 0 mod N - 8.2
src/server/server-finish.ts—serverFinishHandshake(I, salt, A, B, b, v, M1Client, params); verifiesA mod N != 0; computesS = (A * v^u)^b mod N; constant-time M1 compare; throwsSRP.AUTH_FAILEDon 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): PakeProviderSRP-6a implementation of the seam
10. Implementation — hash bridge
- 10.1
src/hash/hash-bridge.ts—nobleHashBridge(algorithm)(sync@noble/hashes),subtleHashBridge(algorithm, signal?)(asynccrypto.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 anycrypto.subtle.digestcall
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 (noexport *) - 12.2
package.jsonexports 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; noBigInt ** 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 throwsSRP.INVALID_PARAMS - 14.6
math/k.test.ts— RFC 5054 Appendix Bkreference - 14.7
math/u.test.ts— RFC 5054 Appendix Bureference;u ≡ 0throwsSRP.PUBLIC_KEY_INVALID - 14.8
math/x.test.ts— RFC 5054 Appendix Bxreference - 14.9
math/modpow.test.ts— agrees with naiveBigIntreference on small inputs - 14.10
verifier/verifier.test.ts— RFC 5054 Appendix Bvreference + locally-derived 2048/SHA-256 vector - 14.11
client/client-start.test.ts—alength ≥ 32 bytes;Areproducible from injected RNG - 14.12
client/client-finish.test.ts— RFC Appendix BKreference;B ≡ 0rejects - 14.13
client/client-verify.test.ts—M2accept + bit-flipped reject (SRP.MUTUAL_AUTH_FAILED); constant-time compare exercised - 14.14
server/server-start.test.ts—Breproducible; re-roll on syntheticB ≡ 0 - 14.15
server/server-finish.test.ts— RFC Appendix BM1accept + bit-flip reject (SRP.AUTH_FAILED);A ≡ 0rejects - 14.16
server/server-m2.test.ts— split-roundtrip equals inline finish output - 14.17
pake/pake-provider.test.ts—register → clientHandshake → serverHandshakeround-trip; clientKequals serverK - 14.18
hash/hash-bridge.test.ts— noble + subtle agree; SHA-1 path; AbortSignal pre-abort; unsupported algo throwsSRP.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-memoryMeterProvider - 14.21
schemas/schemas.test.ts—SrpParamsSchema+SrpHandshakeStateSchemaboundary 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
.mdfor every implementation.ts(≥200 words, basic + advanced examples, full API reference) - 15.2 README links every co-located
.mdunder## Modules - 15.3 Update root
CLAUDE.mdpackage 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, noerrorfindings - 17.5
bunx turbo build --filter=@suaveplan/srp— green
18. Pre-archive 7-row checklist (AGENTS.md Section 13)
- 18.1 Generics threaded;
expectTypeOfassertions onPakeProviderandHashLikeinjection sites - 18.2 Zod schemas wired at every declared boundary (
SrpParamsSchema,SrpHandshakeStateSchema,VerifierSchema) - 18.3 Every error code in
errors/errors.tsthrown 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— refreshMANIFEST.md(orchestrator) - 19.2
bun scripts/generate-missing.ts— refreshMISSING.yml(orchestrator) - 19.3
STATUS.md— movesrpfrom 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.mdif package set or deps changed (orchestrator)