feat(recovery): the key behind an identity that has no phrase, and the way to forget it
Phase 6 of docs/nsec-sign-in.md. KeyRecoveryScreen offered one backup, the
recovery phrase, whose screen reads userWallet.words out of the seed map. For
an identity signed in with a bare key there are no words, and the honest
answer that screen would give is "this device holds no phrase for the
profile". So:
KeyRecoveryScreen branches on the active identity's kind. A mnemonic identity
keeps the phrase option. A NostrSecret identity gets a nostr secret key option
in its place, routing to NostrSecretRoute, with its own status line ("you said
you stored it") and a header that no longer promises coins. The backup flags
underneath are the same two per-identity preferences the phrase screen
writes; they already mean "this identity's secret is not backed up" for
whichever secret it is.
NostrSecretScreen is RecoveryPhraseScreen with the word grid replaced by the
nsec: hidden until revealed, revealed in monospace, selectable, with a copy
action -- nobody transcribes sixty-three characters by hand -- the same two
checkboxes, hidden again on leaving. The view model reads the key from
nostr-keys.dat at reveal time, by the identity's public key, not off the
active identity, so the screen's contract -- nothing secret held longer than
it is shown -- is the phrase screen's. The disclaimer is a new string: the
old one said "...and the funds in its wallet", and this identity has no
wallet to warn about.
Forget this key. An import needs an inverse, and this is the first real "sign
out" in the app; ActiveProfileScreen's button still routes to a pending
screen, and the general case stays there, because removing a seed is a wallet
question with funds behind it. Behind a dialog that names the npub and says
the profile stays on the relays, four effects in an order that matters: the
key out of nostr-keys.dat and the identity's preference files off the disk
(IdentityWriter.forgetNostrKey, which refuses a key that is not in the file
-- a wallet's nostr key lives in the seed); the metadata entry hidden, since
the metadata store has no delete and isHidden is what the selector filters
on; the account's unsigned rows deleted (forgetLocalAccount -- the kind 0
that made it a local account and anything queued that can now never be
signed, with the requests that hang off them cascading; published events
and the profile cache stay, as anyone else's would); and only then the
caller told, which re-lists identities and clears the active one so the
navigation observer sends a null identity to startup. A failure at the first
step leaves the rest untouched -- an identity the selector lists but nothing
can open is worse than one that is still there.
Tests. IdentityWriterJvmTest runs both bare-key writers against the jvm key
store and a real directory: a key written once and refused the second time
by public key, with its preferences file created; forgetting one key leaves
the other and takes its preferences with it; a key that is not in the file is
not forgotten. Its keys are fresh per test rather than fixed, because
DataStoreManager caches each id's preferences in a companion object for the
life of the process, and a fixed key was served the UserPrefs an earlier test
had created in an earlier temp directory -- worth knowing about that cache.
NostrSecretViewModelJvmTest records the four effects and asserts their order,
and that a key that could not be removed stops the sequence at one.
ForgetLocalAccountJvmTest, against Room, asserts the account and its cascaded
requests go while an unrelated account stays.
Verified with :composeApp:compileDebugKotlinAndroid, :composeApp:jvmTest (901
tests) and :composeApp:m3Audit.
Replayed onto Mantra by docs/curated-to-mantra.md: KeyRecoveryScreen.kt: the class comment this commit rewrites is taken whole; the only base difference was the dropped rebrand capitalising the brand in one word of it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pulled-From: curated/curated@e3cc23ae31
This commit is contained in:
@@ -595,4 +595,28 @@
|
||||
<string name="it_may_be_new_or_it_may_live_on_relays_we">It may be new, or it may live on relays we do not know about. Ask again, or set one up now and publish it from here.</string>
|
||||
<string name="set_up_a_profile">Set up a profile</string>
|
||||
<string name="we_could_not_find_a_profile_for_this_key">We could not find a profile for this key on the relays we asked.</string>
|
||||
<!-- Recovery for an identity with a key and no phrase, docs/nsec-sign-in.md, phase 6. -->
|
||||
<string name="copied_the_nsec">Copied the nsec</string>
|
||||
<string name="copy_nsec">Copy nsec</string>
|
||||
<string name="could_not_forget_the_key_please_try_again">Could not forget the key. Please try again.</string>
|
||||
<string name="could_not_unlock_your_key_please_try">Could not unlock your key. Please try again.</string>
|
||||
<string name="mantra_will_delete_the_nsec_for_s_from_this">Mantra will delete the nsec for %1$s from this device. Anything not yet published is lost. The profile stays on the relays, and the key can be signed in again.</string>
|
||||
<string name="display_nostr_secret_key">Display nostr secret key</string>
|
||||
<string name="forget">Forget</string>
|
||||
<string name="forget_this_key">Forget this key</string>
|
||||
<string name="forget_this_key_question">Forget this key?</string>
|
||||
<string name="i_have_saved_my_nostr_secret_key_somewhere">I have saved my nostr secret key somewhere safe.</string>
|
||||
<string name="i_understand_that_if_i_lose_this_phone_and_my_key">I understand that if I lose this phone and my nostr secret key, I lose this profile.</string>
|
||||
<string name="keep_this_key_safe_do_not_share_it">Keep this key safe.\nDo not share it.</string>
|
||||
<string name="key_forgotten">Key forgotten</string>
|
||||
<string name="no_identity_is_open_on_this_device_so_there">No profile is open on this device, so there is no key to show.</string>
|
||||
<string name="nostr_secret_key">Nostr secret key</string>
|
||||
<string name="remove_the_key_from_this_device_the_profile">Remove the key from this device. The profile stays on the relays.</string>
|
||||
<string name="store_the_nsec_that_signs_as_this_profile">Store the nsec that signs as this profile. There is no phrase behind it: the key is the whole backup.</string>
|
||||
<string name="the_nsec_is_the_key_that_signs_as_you_this">The nsec is the key that signs as you. This profile was signed in with it rather than made from a recovery phrase, so there is no phrase to write down and no wallet attached: the key is the whole of the backup.\n\nOnly you have it. Keep it private — nobody from Mantra will ever ask you for it.\n\nDo not lose it. Store it somewhere safe that is not this phone. If you lose both the phone and the key, this profile is gone for good.</string>
|
||||
<string name="this_device_holds_no_key_for_the_profile">This device holds no nostr secret key for the profile that is open. If it was made from a recovery phrase, the phrase is its backup.</string>
|
||||
<string name="unlocking_your_key">Unlocking your key…</string>
|
||||
<string name="you_have_not_backed_up_your_nostr_secret_key">You have not backed up your nostr secret key</string>
|
||||
<string name="these_are_your_keys_keep_them_safe_no_wallet">These are your keys. Keep them safe so they can keep unlocking this profile, even when you lose or change your phone.</string>
|
||||
<string name="you_said_you_stored_it">You said you stored it</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user