first commit
This commit is contained in:
70
docs/security-stack/security/srp/src/client/client-start.md
Normal file
70
docs/security-stack/security/srp/src/client/client-start.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# `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.
|
||||
Reference in New Issue
Block a user