Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
# `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.