Files
amethyst/commons
Claude 04ef41281a refactor: move the location-chat identity into the encrypted DataStore
GeohashChatIdentityState was the last thing in the app still reading and
writing EncryptedSharedPreferences outside the four deliberate mirrors. It
kept two keys — the seed its per-geohash throwaway keys derive from, and
the handle the user posts under — and neither was covered by the 112-key
migration, because neither is in PrefKeys: they are private constants in
the state object, so LegacyKeyCoverageTest, which reflects over PrefKeys,
structurally could not see them.

They were also in a file nothing else uses. The writer passed
`signer.pubKey`, which is hex, where every other caller passes an npub, so
the identity lived in `secret_keeper_<pubkey hex>` while the account's own
secrets live in `secret_keeper_<npub>`. That makes it an orphan rather than
a hazard: LegacyPreferenceCleanup enumerates and deletes the npub file, so
it never saw these keys, and deleting that file could not have lost them.
It also means nothing will ever clean the hex file up on its own.

So: read the hex file once, copy into the account's npub-keyed encrypted
DataStore, prefer the new store on read, and keep mirroring the legacy
write until the legacy writes are retired app-wide — the same terms as
AccountSecrets.

GeohashIdentitySecrets is its own group with its own migrated marker, and
that is load-bearing rather than tidy. Every account save mirrors a whole
AccountSecrets built field by field from AccountSettings, which does not
hold these — they belong to the state object. Folded into that group, each
save would write null over them, and the group save uses putOrRemove, so
null removes: the seed would disappear on the next unrelated save and every
geohash identity the user has would silently change. Its own marker for the
same reason the group is separate — the two migrate out of different files,
so neither marker can speak for the other.

The keys are deliberately NOT added to LegacyAccountSecretNames.all. That
set is what the cleanup gate treats as claimed in the npub file, and these
never appear in it; listing them would be inert and would suggest the gate
handles them.

Shape changes this forced: nickname() and keyPair() are suspend (every call
site was already inside withContext(Dispatchers.IO)); setNickname stays
fire-and-forget on the account scope, as the SharedPreferences edit {} it
replaces already was; and seed creation moved from synchronized to a Mutex,
because the store reads it guards are suspending and two racers minting
different seeds would strand one caller's identities.

Tests: seven, covering the round trip, a seed with no handle, an empty
identity still counting as migrated, the two markers staying independent in
both directions, and anAccountSaveLeavesTheGeohashIdentityAlone — which
pins the wipe this design exists to prevent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXvKXakvup4inNFfAhhr4L
2026-09-24 22:53:59 +00:00
..