# `client-start` ## Purpose Kick off the SRP-6a handshake on the client side. Draws a private exponent `a ≥ 256` bits from an injectable `Rng` and emits the public value `A = g^a mod N`. Per RFC 5054 §2.5.4 the function rejects (re-draws) any `a` whose resulting `A ≡ 0 mod N`; in practice a real CSPRNG over a safe-prime group never trips this gate, but the gate is wired so synthetic / adversarial RNGs cannot escape the protocol invariant. ## Features - 256-bit private exponent — equal to the RFC 5054 §2.5.4 minimum. - Re-draw on `A ≡ 0 mod N` up to 16 attempts before surrendering with `SRP.PUBLIC_KEY_INVALID`. - Deterministic from a seeded `Rng` — same seed yields identical `(a, A)`. - Returns `A` zero-padded to `byteLength(N)`. - Emits `srp.handshake_duration_ms{role=client,phase=start}` and on the abort path `srp.outcome_total{role=client,outcome=public_key_invalid}` + `srp.errors_total{code=SRP.PUBLIC_KEY_INVALID}`. ## Usage: Basic Example ```ts import { clientStartHandshake } from "@suaveplan/srp/client"; import { params2048Sha256 } from "@suaveplan/srp/params"; import { nobleHashBridge } from "@suaveplan/srp/hash"; const params = params2048Sha256(nobleHashBridge("SHA256")); const rng = { nextUint32: () => 0x12345678, nextFloat: () => 0.5, nextBytes: (n: number) => crypto.getRandomValues(new Uint8Array(n)), }; const { a, A } = clientStartHandshake(rng, params); console.log(a.byteLength, A.byteLength); // 32 256 ``` ## Usage: Advanced Example ```ts import { clientStartHandshake } from "@suaveplan/srp/client"; import { params2048Sha256 } from "@suaveplan/srp/params"; import { nobleHashBridge } from "@suaveplan/srp/hash"; import type { Rng } from "@suaveplan/types"; const params = params2048Sha256(nobleHashBridge("SHA256")); // Seeded deterministic RNG — useful for replaying a known handshake in tests. function seededRng(seed: number): Rng { let state = seed >>> 0; return { nextUint32: () => { state = (state * 1664525 + 1013904223) >>> 0; return state; }, nextFloat: () => 0.5, }; } const { a: a1, A: A1 } = clientStartHandshake(seededRng(1), params); const { a: a2, A: A2 } = clientStartHandshake(seededRng(1), params); console.log(Buffer.from(A1).equals(Buffer.from(A2))); // true ``` ## API Reference ### `clientStartHandshake(rng: Rng, params: SrpParams): { a: Uint8Array; A: Uint8Array }` Returns `{ a, A }`. `a` is exactly 32 bytes (256 bits) of fresh entropy from `rng`. `A` is `params.N.byteLength` bytes, zero-padded on the left. ## Implementation Notes The 16-attempt re-draw cap exists so a pathological RNG cannot trap the function in an unbounded loop. With a real CSPRNG the probability of `A ≡ 0 mod N` per attempt is `≤ 1/N`, which for the bundled 2048-bit group is `≤ 2^-2048`; the 16-attempt cap is purely a synthetic-test safety net. `a.byteLength === 32` is the RFC 5054 §2.5.4 floor ("at least 256 bits"). Drawing more bits gains no security headroom because `a` is implicitly reduced modulo `N` inside `modPow`; we draw exactly the minimum to keep allocations small. The function is CPU-bound — one `modPow` over a 2048-bit safe prime takes ~5–20 ms on contemporary hardware. It is intentionally synchronous; consumers that want to run it off the main thread should schedule it via a Web Worker or similar.