Files
skills/docs/security-stack/security/srp/src/server/server-m2.md
2026-05-29 08:20:56 +01:00

3.4 KiB

server-m2

Purpose

Split-roundtrip helper that computes the server proof M2 = H(A | M1 | K) independently of serverFinishHandshake. Useful for transports that ack M1 over one HTTP request (server validates client proof, persists the session) and emit M2 from a different worker or route (perhaps a follow-up acknowledgement endpoint, or a fan-out broadcast confirming successful authentication). The second-half route does not need to redo the modPow + KDF chain; it just needs (A, M1, K) (which it can fetch from a session store) and this helper.

Features

  • Single function call returning H(A | M1 | K).
  • Byte-identical to the M2 field returned by serverFinishHandshake for the same (A, M1, K) triple.
  • Deterministic — same inputs always produce the same bytes.
  • Wraps the shared computeM2 helper in math/proof.ts so the proof formula lives in exactly one place.

Usage: Basic Example

import { serverComputeM2 } from "@suaveplan/srp/server";
import { params2048Sha256 } from "@suaveplan/srp/params";
import { nobleHashBridge } from "@suaveplan/srp/hash";

const params = params2048Sha256(nobleHashBridge("SHA256"));
const A = new Uint8Array(256);
const M1 = new Uint8Array(32);
const K = new Uint8Array(32);
const M2 = serverComputeM2(A, M1, K, params);
console.log(M2.byteLength); // 32

Usage: Advanced Example

import { serverComputeM2, serverFinishHandshake } from "@suaveplan/srp/server";
import { clientFinishHandshake, clientStartHandshake } from "@suaveplan/srp/client";
import { serverStartHandshake } from "@suaveplan/srp/server";
import { computeVerifier } from "@suaveplan/srp/verifier";
import { params2048Sha256 } from "@suaveplan/srp/params";
import { nobleHashBridge } from "@suaveplan/srp/hash";

const params = params2048Sha256(nobleHashBridge("SHA256"));
const salt = new Uint8Array(16);
const rng = {
    nextUint32: () => 0,
    nextFloat: () => 0,
    nextBytes: (n: number) => crypto.getRandomValues(new Uint8Array(n)),
};
const { v } = computeVerifier("alice", "password123", salt, params);
const { a, A } = clientStartHandshake(rng, params);
const { b, B } = serverStartHandshake(v, rng, params);
const client = clientFinishHandshake("alice", "password123", salt, A, B, a, params);
const inline = serverFinishHandshake("alice", salt, A, B, b, v, client.M1, params);
const split = serverComputeM2(A, client.M1, inline.K, params);
console.log(Buffer.from(inline.M2).equals(Buffer.from(split)));

API Reference

serverComputeM2(A, M1, K, params): Uint8Array

Returns H(A | M1 | K) of length params.hash.digestSize.

Implementation Notes

The split-roundtrip pattern matters operationally: many real deployments separate the "validate client proof" step (which needs the full SRP state machine — v, b, B) from the "ack mutual authentication" step (which only needs A, M1, and the session key K). The first step can run on a stateful auth node with the verifier DB; the second can run on any edge that has been handed the session-key blob. serverComputeM2 is the API surface that supports that split without forcing the second step to re-derive S and K.

The function is intentionally trivial — a single forward to computeM2. The wrapper exists for API stability: if RFC 2945 ever extends M2 (e.g. adds a session-binding field), this entry point is where the change lands, and serverFinishHandshake is updated to call into the same helper.