fix(auth): decoy encryption keys are keys a device could have made

An address nobody has got, as the recovery phrase's first step, an
X25519 public key of 32 HMAC bytes, which is a point of Curve25519's
prime-order subgroup one time in sixteen, as every real key is: whoever
checked could tell nearly always whether an address has an account (the
second reading's M1). The decoy's key is now made from a seed, as its
Ed25519 key already was. The tests hold twenty decoys to the curve check
the review used.

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 21:04:50 +02:00
commit 009970faab
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -7,6 +7,7 @@ import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'package:test/test.dart';
import '../support/curve25519.dart';
import '../support/fixtures.dart';
import '../support/mailbox.dart';
import '../support/password_device.dart';
@ -1489,6 +1490,28 @@ void main() {
);
});
test('gives addresses nobody has keys no test tells from an '
"account's: X25519 public keys are points of Curve25519's "
'prime-order subgroup, as keys made from scalars are', () async {
// Random bytes are such a point one time in sixteen: twenty
// addresses all passing by chance is 2^-80.
for (var i = 0; i < 20; i++) {
final nobody = await atNow(
() => endpoints.accountRecovery.beginKeyProof(
notSignedIn(),
address: 'nobody$i@example.org',
),
);
expect(
isX25519PublicKey(
Uint8List.sublistView(nobody.encryptionPublicKey),
),
isTrue,
reason: 'nobody$i@example.org',
);
}
});
test('is refused for another key, an address nobody has, a missing '
'code, a used challenge, or a new password out of range, the '
'password staying', () async {