feat(crypto): SRP-6a, the register's half and the device's

ADR 0017's step 1. Generic over group and hash (SrpGroup); production
takes RFC 5054's 3072-bit group with SHA-256. k = H(N | PAD(g)),
u = H(PAD(A) | PAD(B)), K = H(PAD(S)),
M1 = H(H(N) xor H(g) | H(I) | s | PAD(A) | PAD(B) | K),
M2 = H(PAD(A) | M1 | K). A and B must lie in 1..N-1 and u must not be
0; a and b are 256 bits from Random.secure(); proofs are compared in
constant time. x comes from the caller (halkyon_keys makes it); RFC
5054's own x is there for the vectors only.

- The register's half in halkyon_base_server (base.dart: srpChallenge,
  srpVerify), the device's written by hand in halkyon_base_client
  (srp.dart: SrpClient, srpVerifier). Serverpod's generator cleans and
  writes only a client's lib/src/protocol/ (serverpod_cli 4.0.4's
  _getDirectoriesRequiringCleaning; a generate leaves srp/ untouched).
- Tests: RFC 5054 Appendix B on both halves; the client against the
  register, succeeding and every refusal.
- The module's tests depend on its client, and pub resolves every
  member's dev dependencies, so Learn's server image takes the module's
  client into its workspace too.
- The format check covers the hand-written half.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-20 12:01:52 +02:00
commit bc499713b8
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -2,8 +2,8 @@
library;
/// Code generators' output, which is never formatted by hand: Serverpod's
/// client packages from top to bottom, a server's generated sources and test
/// tools, and gen-l10n's localizations.
/// client packages from top to bottom (but for [_written]), a server's
/// generated sources and test tools, and gen-l10n's localizations.
final _generated = RegExp(
'(^|/)[a-z_]+_client/'
'|/lib/src/generated/'
@ -11,10 +11,17 @@ final _generated = RegExp(
r'|/lib/l10n/[a-z_]*localizations[a-z_]*\.dart$',
);
/// What is written by hand in a client package: the module's SRP client
/// half and its tests (the generator writes only its `lib/src/protocol/`).
final _written = RegExp(
r'^packages/halkyon_base_client/(lib/srp\.dart$|lib/src/srp/|test/)',
);
/// Whether [path] (relative to the repository's root) is a Dart file of our
/// own, which the formatting check covers.
bool isOwnDartFile(String path) =>
path.endsWith('.dart') && !_generated.hasMatch(path);
path.endsWith('.dart') &&
(_written.hasMatch(path) || !_generated.hasMatch(path));
/// The root's files every package's resolution or analysis depends on.
const _rootFiles = {'pubspec.yaml', 'pubspec.lock', 'analysis_options.yaml'};