feat(base): the changes of SimpleLogin accounts' keys, each with the password and a fresh sign-in (ADR 0020, D20)

Every change takes the password's proof by its own proof key (the
recovery phrase's for a lost password) and a SimpleLogin sign-in of the
last ten minutes on the device (`KeysOwnerProof`, its attempt), as ADR
0017's take the password and a code. The sign-in is checked before the
proof and taken with the change, so a wrong proof leaves it standing and
each sign-in serves one change.

- `keys.set(attempt, setup, handle?)`: the first keys of an account that
  SimpleLogin was added to (anonymous before, or signing in with GitHub
  or a code), with the handle where it has none; the sign-in alone, as
  there is no password to prove yet.
- `keys.changePassword`, `keys.beginPhrase` and `keys.resetPassword` (a
  lost password, with the phrase): every other session ends, in every
  service, as ADR 0017's change has it.
- `keys.setRecoveryKeys`: a new phrase.
- `account.delete(keysProof)`: an account without SRP's verifier deletes
  itself with the proof and the sign-in; `requireOwnerToDelete` no longer
  asks such an account for SRP.
- `newKeysPasswordDigest` on both sides, held to each other by a test; a
  challenge purpose of its own for the phrase's proof while signed in.
- Members ask the register for all of it; `RemoteRegister` and the
  instance API carry the five new operations and the deletion's proof.

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-23 22:11:49 +02:00
commit 7c58d23424
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -74,6 +74,15 @@ final class KeysDevice {
recoveryProofKey: ByteData.sublistView(recoveryProofKey),
);
/// The password the setup made, as a change sends it: this device's
/// password as another's new one.
KeysPassword password() => KeysPassword(
salt: ByteData.sublistView(salt),
costs: newPasswordCosts,
passwordProofKey: ByteData.sublistView(passwordProofKey),
passwordKeys: passwordKeys,
);
/// The proof of the password for [challenge] and [purpose] (with the
/// [digest] of what it sets) on [instanceUrl], by the password's proof
/// key or [keys] where given; claiming [claimedUrl] (the same unless
@ -85,19 +94,65 @@ final class KeysDevice {
SimpleKeyPair? keys,
String instanceUrl = testInstanceUrl,
String? claimedUrl,
}) => _proof(
challenge.challenge,
challenge.nonce,
purpose: purpose,
digest: digest,
keys: keys ?? passwordProofKeys,
instanceUrl: instanceUrl,
claimedUrl: claimedUrl,
);
/// A change's proof (D20): the password's proof for [challenge] (or the
/// recovery phrase's for [phrase], by its proof key, where given), with
/// the SimpleLogin sign-in under [attempt].
Future<KeysOwnerProof> ownerProof(
UuidValue attempt, {
KeysChallenge? challenge,
KeyProofChallenge? phrase,
required KeyProofPurpose purpose,
List<int> digest = const [],
SimpleKeyPair? keys,
String instanceUrl = testInstanceUrl,
}) async {
final proof = await _proof(
challenge?.challenge ?? phrase!.challenge,
challenge?.nonce ?? phrase!.nonce,
purpose: purpose,
digest: digest,
keys: keys ?? (challenge != null ? passwordProofKeys : recoveryProofKeys),
instanceUrl: instanceUrl,
);
return KeysOwnerProof(
challenge: proof.challenge,
signature: proof.signature,
instanceUrl: proof.instanceUrl,
attempt: attempt,
);
}
Future<KeysProof> _proof(
UuidValue challenge,
ByteData nonce, {
required KeyProofPurpose purpose,
required List<int> digest,
required SimpleKeyPair keys,
required String instanceUrl,
String? claimedUrl,
}) async {
final signature = await Ed25519().sign(
keyProofMessage(
challenge.challenge,
Uint8List.sublistView(challenge.nonce),
challenge,
Uint8List.sublistView(nonce),
instanceUrl: instanceUrl,
purpose: purpose,
digest: digest,
),
keyPair: keys ?? passwordProofKeys,
keyPair: keys,
);
return KeysProof(
challenge: challenge.challenge,
challenge: challenge,
signature: ByteData.sublistView(Uint8List.fromList(signature.bytes)),
instanceUrl: claimedUrl ?? instanceUrl,
);