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
2 changed files with 137 additions and 8 deletions

View file

@ -51,3 +51,12 @@ Future<Uint8List> ed25519PublicKeyFromSeed(List<int> seed) async {
final pair = await Ed25519().newKeyPairFromSeed(seed);
return Uint8List.fromList((await pair.extractPublicKey()).bytes);
}
/// An X25519 public key made from [seed] (32 bytes, clamped as a scalar): a
/// decoy's, which is a point of the prime-order subgroup as an account's
/// is; random bytes would be one only one time in sixteen, telling a decoy
/// from an account.
Future<Uint8List> x25519PublicKeyFromSeed(List<int> seed) async {
final pair = await X25519().newKeyPairFromSeed(seed);
return Uint8List.fromList((await pair.extractPublicKey()).bytes);
}

View file

@ -1086,8 +1086,9 @@ Uint8List _decoyBytes(
);
/// An address nobody has's keys, as a key proof's first step shows them:
/// an X25519 public key (its top bit clear, as a point's u is), an Ed25519
/// one made from a seed, and wrapped keys of an account's length; and the
/// X25519 and Ed25519 public keys made from seeds, so each is a point a
/// device's key could be (random bytes mostly aren't, which told a decoy
/// from an account), and wrapped keys of an account's length; and the
/// phrase's proof key its second step checks against. Computed for every
/// proof begun, an account's too.
Future<
@ -1100,12 +1101,9 @@ Future<
})
>
_decoyKeys(String decoyKey, String hash) async => (
encryptionPublicKey: _decoyBytes(
decoyKey,
'encryption key',
hash,
_publicKeyLength,
)..[_publicKeyLength - 1] &= 0x7f,
encryptionPublicKey: await x25519PublicKeyFromSeed(
_decoyBytes(decoyKey, 'encryption seed', hash, 32),
),
signingPublicKey: await ed25519PublicKeyFromSeed(
_decoyBytes(decoyKey, 'signing seed', hash, 32),
),