3.0 KiB
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 chosenparams.hashdigest size. - UTF-8 encode
Ionce per call.
Usage: Basic Example
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
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.