feat(keys): the password's own proof key (ADR 0020, D19)
A third subkey of the password's Argon2id (context `hlkpass1`, subkey 3) seeds an Ed25519 pair: `PasswordKeys.proofKeyPair` and `proofPublicKey`. An account that keeps no SRP verifier proves its password by signing the register's nonce with it, as it proves the recovery phrase; neither SRP's verifier nor the unwrapped account keys a signed-in device keeps can make it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
b4c93e03de
commit
e8953d1560
1 changed files with 114 additions and 10 deletions
|
|
@ -54,6 +54,72 @@ Future<void> main() async {
|
|||
expect(keys.srpPrivateValue, greaterThan(BigInt.zero));
|
||||
});
|
||||
|
||||
group("the password's proof key (Halkyon Learn's ADR 0020, D19)", () {
|
||||
test('is the same for the same password and salt, another for another '
|
||||
'password or salt', () {
|
||||
final keys = keysOf('correct horse battery staple');
|
||||
expect(
|
||||
keysOf('correct horse battery staple').proofPublicKey,
|
||||
keys.proofPublicKey,
|
||||
);
|
||||
expect(keys.proofPublicKey, hasLength(sodium.crypto.sign.publicKeyBytes));
|
||||
expect(
|
||||
keysOf('correct horse battery stapler').proofPublicKey,
|
||||
isNot(keys.proofPublicKey),
|
||||
);
|
||||
expect(
|
||||
keysOf(
|
||||
'correct horse battery staple',
|
||||
withSalt: Uint8List.fromList(List.generate(16, (i) => i + 1)),
|
||||
).proofPublicKey,
|
||||
isNot(keys.proofPublicKey),
|
||||
);
|
||||
});
|
||||
|
||||
test("is unrelated to SRP's secret and the key-encryption key, so "
|
||||
'neither the register nor a device holding the unwrapped keys can '
|
||||
'make it', () {
|
||||
final keys = keysOf('a password');
|
||||
final pair = keys.proofKeyPair();
|
||||
final seed = sodium.crypto.sign.skToSeed(pair.secretKey).extractBytes();
|
||||
expect(seed, hasLength(32));
|
||||
expect(seed, isNot(keys.srpSecret.extractBytes()));
|
||||
expect(seed, isNot(keys.keyEncryptionKey.extractBytes()));
|
||||
expect(
|
||||
pair.publicKey,
|
||||
isNot(AccountKeys.generate(sodium).signingPublicKey),
|
||||
);
|
||||
pair.secretKey.dispose();
|
||||
});
|
||||
|
||||
test('signs what the register checks against its public half', () {
|
||||
final keys = keysOf('a password');
|
||||
final pair = keys.proofKeyPair();
|
||||
final message = Uint8List.fromList([1, 2, 3]);
|
||||
final signature = sodium.crypto.sign.detached(
|
||||
message: message,
|
||||
secretKey: pair.secretKey,
|
||||
);
|
||||
pair.secretKey.dispose();
|
||||
expect(
|
||||
sodium.crypto.sign.verifyDetached(
|
||||
message: message,
|
||||
signature: signature,
|
||||
publicKey: keys.proofPublicKey,
|
||||
),
|
||||
isTrue,
|
||||
);
|
||||
expect(
|
||||
sodium.crypto.sign.verifyDetached(
|
||||
message: message,
|
||||
signature: signature,
|
||||
publicKey: keysOf('another password').proofPublicKey,
|
||||
),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
'a salt of another length, as an instance might send one, is refused',
|
||||
() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue