feat(identity): switch social identity, all from the one backup
Adds pseudonymous, switchable social identities derived from the SAME root seed via an account index (NostrKeyDerivation.deriveFromSeed(seed, account)). HKDF is one-way so accounts are unlinkable to the Ğ1 key; account 0 is the original identity, byte-for-byte unchanged (no rotation for current users), and every account regenerates from the single seed — so switching adds nothing to back up. - SocialAccountStore: keystore-backed active account + max created. - Per-identity stores (chats, profile, name cache) namespaced by account scope (account 0 = legacy keys, no migration) so identities never mix. - switchSocialAccount() re-derives the identity, re-scopes the stores and restarts the inbox listener; RestartWidget rebuilds the tree to pick up the new social singletons. DB/inventory untouched. - Profile 'Your identities' switcher: list, create, switch (with a note that messages/contacts are kept separate per identity). i18n en/es/pt/ast. Tests: account-indexed derivation (legacy 0 unchanged, accounts distinct yet deterministic, negatives rejected); SocialAccountStore; per-identity store scope isolation. Resolves the flagged 'change identity' decision (open-decisions §B). Known: switching resets navigation to home (full tree rebuild).
This commit is contained in:
parent
d36fd05741
commit
993f7b37ab
25 changed files with 597 additions and 33 deletions
|
|
@ -51,12 +51,25 @@ class NostrKeyDerivation {
|
|||
radix: 16,
|
||||
);
|
||||
|
||||
static Future<NostrIdentity> deriveFromSeed(List<int> seed) async {
|
||||
/// Derives the identity for a given [account] (default 0). Different accounts
|
||||
/// yield unlinkable pseudonymous identities from the SAME root seed — so the
|
||||
/// user can switch social identity while still backing up only the one seed
|
||||
/// (every account regenerates from it). Account 0 is the legacy identity and
|
||||
/// its derivation is byte-for-byte unchanged, so existing users keep their key.
|
||||
static Future<NostrIdentity> deriveFromSeed(
|
||||
List<int> seed, {
|
||||
int account = 0,
|
||||
}) async {
|
||||
if (account < 0) throw ArgumentError.value(account, 'account', 'must be >= 0');
|
||||
// Account 0 keeps the original info string (no rotation for current users);
|
||||
// higher accounts branch into a distinct, domain-separated sub-path.
|
||||
final base =
|
||||
account == 0 ? derivationInfo : '$derivationInfo/account/$account';
|
||||
var counter = 0;
|
||||
while (true) {
|
||||
final key = await _hkdf.deriveKey(
|
||||
secretKey: SecretKey(seed),
|
||||
info: '$derivationInfo/$counter'.codeUnits,
|
||||
info: '$base/$counter'.codeUnits,
|
||||
nonce: const [],
|
||||
);
|
||||
final bytes = await key.extractBytes();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue