# `proof` ## Purpose Internal helpers for computing the SRP-6a / RFC 2945 §3 mutual-authentication proofs `M1` and `M2`. Both client and server compute these values: the client emits `M1 = H(H(N) XOR H(g) | H(I) | salt | A | B | K)` to prove it knows the password, and the server emits `M2 = H(A | M1 | K)` to prove it knows the verifier. Co-locating the math in one module keeps the formula in exactly one place so any future correction (e.g. a hash-binding change) lands consistently across both roles. ## Features - `computeM1(I, salt, A, B, K, params)` — RFC 2945 §3 client proof. - `computeM2(A, M1, K, params)` — RFC 2945 §3 server proof. - Equal-length XOR helper for the `H(N) XOR H(g)` term — bound to the chosen `params.hash` digest size. - UTF-8 encode `I` once per call. ## Usage: Basic Example ```ts import { computeM1, computeM2 } 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 A = new Uint8Array(256); const B = new Uint8Array(256); const K = new Uint8Array(32); const M1 = computeM1("alice", salt, A, B, K, params); const M2 = computeM2(A, M1, K, params); console.log(M1.byteLength, M2.byteLength); // 32 32 ``` ## Usage: Advanced Example ```ts import { computeM1, computeM2 } from "@suaveplan/srp"; import { params2048Sha256 } from "@suaveplan/srp/params"; import { nobleHashBridge } from "@suaveplan/srp/hash"; import { constantTimeEqual } from "@suaveplan/crypto-utils"; const params = params2048Sha256(nobleHashBridge("SHA256")); const salt = new Uint8Array(16); const A = new Uint8Array(256); const B = new Uint8Array(256); const K = new Uint8Array(32); const clientM1 = computeM1("alice", salt, A, B, K, params); const serverExpectedM1 = computeM1("alice", salt, A, B, K, params); console.log(constantTimeEqual(clientM1, serverExpectedM1)); ``` ## API Reference ### `computeM1(I, salt, A, B, K, params): Uint8Array` Returns the client proof `H(H(N) XOR H(g) | H(I) | salt | A | B | K)`. Result length equals `params.hash.digestSize`. ### `computeM2(A, M1, K, params): Uint8Array` Returns the server proof `H(A | M1 | K)`. Result length equals `params.hash.digestSize`. ## Implementation Notes The `H(N) XOR H(g)` term is a deliberate design choice in RFC 2945 — it commits both ends to the chosen group AND ties the proof to the hash function. An attacker who substituted a weakened group during transport would produce a different `H(N) XOR H(g)` value than the legitimate counterpart computed, causing the proof to fail. `computeM1` deliberately accepts `A` and `B` as-is rather than re-padding them. RFC 2945's formula is over the un-padded public values, AND the values supplied to `computeM1` from the handshake functions are already produced by `bigIntToBytes(_, params.N.byteLength)` and so are already zero-padded to the modulus width. Double-padding would produce off-by-N-byte digests.