2.6 KiB
x
Purpose
Compute the SRP-6a private exponent x = H(salt | H(I | ":" | P)) per RFC 5054 §2.6. x ties username + password + salt to a deterministic exponent in Z_N, producing the verifier v = g^x mod N that the server stores. Both I (username) and P (password) are encoded as UTF-8 bytes before hashing. The colon separator is fixed by RFC 5054 and is consumed verbatim — consumers MUST NOT pre-quote or escape colons inside I or P; that is the consumer's responsibility upstream.
Features
- Pure function of
(I, P, salt, params). - UTF-8 encoding of
IandPso non-ASCII usernames work. - Two-stage hash: inner
H(I | ":" | P)then outerH(salt | innerHash). - Deterministic — same inputs always produce the same
x.
Usage: Basic Example
import { computeX } from "@suaveplan/srp";
import { params2048Sha256 } from "@suaveplan/srp/params";
import { nobleHashBridge } from "@suaveplan/srp/hash";
const params = params2048Sha256(nobleHashBridge("SHA256"));
const salt = new Uint8Array(16);
const x = computeX("alice@example.com", "correct horse battery staple", salt, params);
console.log(x.byteLength); // 32
Usage: Advanced Example
import { computeX } from "@suaveplan/srp";
import { params2048Sha256 } from "@suaveplan/srp/params";
import { nobleHashBridge } from "@suaveplan/srp/hash";
const params = params2048Sha256(nobleHashBridge("SHA256"));
const salt = new Uint8Array([1, 2, 3, 4]);
// Determinism — both calls return byte-identical x for identical inputs.
const x1 = computeX("alice", "secret", salt, params);
const x2 = computeX("alice", "secret", salt, params);
console.log(x1.toString() === x2.toString());
API Reference
computeX(I: string, P: string, salt: Uint8Array, params: SrpParams): Uint8Array
Returns H(salt | H(I | ":" | P)). Result length equals params.hash.digestSize.
Implementation Notes
The colon separator in I | ":" | P is part of the SRP-6a definition; without it, an attacker could pivot a credential with username "ab" + password "cd" against a credential with username "abc" + password "d" (same I | P byte stream). RFC 5054 §2.6 fixes the separator to 0x3a (":"); we encode it as UTF-8 once at module load and reuse the cached buffer across calls.
x is only ever derived on the client side — the server stores the resulting verifier v = g^x mod N and never sees x directly. The function therefore lives in math/ rather than client/ because it is a primitive used by both computeVerifier and clientFinishHandshake; co-locating it under either of those modules would obscure the dependency relationship.