113 lines
3.9 KiB
TypeScript
113 lines
3.9 KiB
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* Deterministic SRP test-vector generator.
|
|
*
|
|
* Runs the full SRP-6a handshake (computeVerifier → clientStartHandshake →
|
|
* serverStartHandshake → clientFinishHandshake → serverFinishHandshake)
|
|
* for the bundled 2048-bit group with SHA-256, using a fixed-seed
|
|
* deterministic RNG, and writes the resulting `(x, v, A, B, K, M1, M2)`
|
|
* tuple to `src/__fixtures__/vectors-2048-sha256.json`.
|
|
*
|
|
* Re-running the script with the same seed produces a byte-identical
|
|
* JSON fixture. The conformance test replays the same handshake against
|
|
* the committed fixture to anchor the modern 2048/SHA-256 default.
|
|
*
|
|
* Usage:
|
|
* bun scripts/generate-srp-vectors.ts [--seed=42]
|
|
*/
|
|
|
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import type { Rng } from "@suaveplan/types";
|
|
import { clientFinishHandshake } from "../src/client/client-finish.js";
|
|
import { clientStartHandshake } from "../src/client/client-start.js";
|
|
import { nobleHashBridge } from "../src/hash/hash-bridge.js";
|
|
import { params2048Sha256 } from "../src/params/params.js";
|
|
import { serverFinishHandshake } from "../src/server/server-finish.js";
|
|
import { serverStartHandshake } from "../src/server/server-start.js";
|
|
import { computeVerifier } from "../src/verifier/verifier.js";
|
|
|
|
function seededRng(seed: number): Rng {
|
|
let state = seed >>> 0;
|
|
return {
|
|
nextUint32: () => {
|
|
state = (state * 1664525 + 1013904223) >>> 0;
|
|
return state;
|
|
},
|
|
nextFloat: () => 0.5,
|
|
nextBytes: (n: number) => {
|
|
const out = new Uint8Array(n);
|
|
let i = 0;
|
|
while (i < n) {
|
|
state = (state * 1664525 + 1013904223) >>> 0;
|
|
out[i++] = state & 0xff;
|
|
if (i < n) out[i++] = (state >>> 8) & 0xff;
|
|
if (i < n) out[i++] = (state >>> 16) & 0xff;
|
|
if (i < n) out[i++] = (state >>> 24) & 0xff;
|
|
}
|
|
return out;
|
|
},
|
|
};
|
|
}
|
|
|
|
function toHex(bytes: Uint8Array): string {
|
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
}
|
|
|
|
function parseSeed(): number {
|
|
const arg = process.argv.find((a) => a.startsWith("--seed="));
|
|
if (arg) return Number.parseInt(arg.slice("--seed=".length), 10);
|
|
return 42;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const seed = parseSeed();
|
|
const params = params2048Sha256(nobleHashBridge("SHA256"));
|
|
const I = "alice@example.com";
|
|
const P = "correct horse battery staple";
|
|
const salt = seededRng(seed + 1).nextBytes!(16);
|
|
const { x, v } = computeVerifier(I, P, salt, params);
|
|
const { a, A } = clientStartHandshake(seededRng(seed + 2), params);
|
|
const { b, B } = serverStartHandshake(v, seededRng(seed + 3), params);
|
|
const client = clientFinishHandshake(I, P, salt, A, B, a, params);
|
|
const server = serverFinishHandshake(I, salt, A, B, b, v, client.M1, params);
|
|
|
|
const fixture = {
|
|
meta: {
|
|
group: "RFC 5054 Appendix A 2048-bit",
|
|
hash: "SHA-256",
|
|
seed,
|
|
generator: "scripts/generate-srp-vectors.ts",
|
|
},
|
|
inputs: {
|
|
I,
|
|
P,
|
|
salt_hex: toHex(salt),
|
|
a_hex: toHex(a),
|
|
b_hex: toHex(b),
|
|
},
|
|
outputs: {
|
|
x_hex: toHex(x),
|
|
v_hex: toHex(v),
|
|
A_hex: toHex(A),
|
|
B_hex: toHex(B),
|
|
K_hex: toHex(client.K),
|
|
M1_hex: toHex(client.M1),
|
|
M2_hex: toHex(server.M2),
|
|
S_hex: toHex(client.S),
|
|
},
|
|
};
|
|
const outPath = join(
|
|
dirname(new URL(import.meta.url).pathname),
|
|
"..",
|
|
"src",
|
|
"__fixtures__",
|
|
"vectors-2048-sha256.json"
|
|
);
|
|
mkdirSync(dirname(outPath), { recursive: true });
|
|
writeFileSync(outPath, `${JSON.stringify(fixture, null, 2)}\n`);
|
|
console.log(`wrote ${outPath}`);
|
|
}
|
|
|
|
await main();
|