Files
amethyst/desktopApp/plans/2026-08-11-key-backup-nsec-exposure-plan.md
nrobi144andClaude Opus 4.8 129bad5a02 feat: discoverable key backup + nsec exposure (Desktop + Android)
Make the secret key (nsec) discoverable and backupable without obfuscating
it, while keeping the npub/nsec asymmetry every Nostr client relies on:
npub is shareable (plain, copy, QR); nsec is an unrecoverable password
(masked, gated reveal + gated copy, NIP-49 encrypted option, never a QR).

Desktop:
- New BackupKeysCard in the settings/profile screen: npub (copy + QR),
  nsec masked-by-default with reveal/copy gated behind the existing
  PrivacyLock (new LockScope.KeyBackup), plaintext + NIP-49 encrypted copy,
  strong "cannot be recovered" warning, external-signer note when no key.
- Upgrade NewKeyWarningCard: per-key copy, encrypted copy, stronger warning,
  soft "I have saved my keys" acknowledgement.
- Extract shared copyToClipboard + best-effort copyToClipboardThenClear util.

Android:
- Soft, dismissible post-signup "Back up your keys" nudge on the home feed
  (per-account hasBackedUpKeys flag; only freshly-generated accounts nudged;
  "Back up now" opens the existing AccountBackupScreen).
- FLAG_SECURE on the key-backup screen (screenshot / app-switcher redaction).
- Best-effort clipboard auto-clear 60s after copying the plaintext nsec.

Deferred: compose paste-guard (nsec self-doxx warning) — the send path is
reimplemented across ~17 *PostViewModels with no shared choke point.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-12 11:06:33 +03:00

6.9 KiB

Key Backup & nsec Exposure — Plan

Date: 2026-08-11 Scope: Desktop (desktopApp) primary, Android (amethyst) secondary, shared strings/logic in commons/quartz.

Problem

User report (Nostr): new account creation gives no discoverable way to find/save the keypair. "Couldn't find it in settings, no option to save when generated." Keys are crucial → must reinforce, not obfuscate.

Ask: make nsec discoverable + backupable; npub sharable (copy/QR); check both Android and Desktop.

Key asymmetry (design principle — non-negotiable)

Every surveyed client agrees:

  • npub = public identity → plain by default, copy + QR, share freely.
  • nsec = password that can NEVER be reset → masked by default, gated reveal + gated copy, never rendered as a QR, prefer encrypted (NIP-49).

So "show nsec with npub for sharing" splits: npub is for sharing; nsec is for private backup only. The plan treats them differently.

Current state

Android (amethyst) — mostly done, one gap

  • ui/.../keyBackup/AccountBackupScreen.kt — full backup screen: biometric-gated copy nsec, NIP-49 encrypted (ncryptsec1) copy, plaintext + encrypted QR, strong warnings (account_backup_tips2_md/tips3_md). Route AccountBackup.
  • Gap: signup (SignUpViewModel.signup → AccountSessionManager.createNewAccount, val keyPair = KeyPair()) generates silently. No post-signup nudge to back up. Screen exists but discoverability relies on the user hunting the drawer.

Desktop (desktopApp) — large gaps

  • ui/auth/NewKeyWarningCard.kt — shows npub+nsec once at creation as plain SelectableKeyText (manual-select). No copy button, no QR, no encrypted option, weak warning.
  • ui/DevSettingsSection.kt — full copy UI but debug-mode only; normal users can't re-reach nsec.
  • ui/profile/ProfileInfoCard.kt — npub + hex only (no copy? verify), no QR.
  • No user-facing Backup Keys screen. No settings path to nsec. No NIP-49 export. No QR for npub.
  • AccountState.LoggedIn already exposes .npub, .nsec, .pubKeyHex → wiring a backup screen is trivial.
  • commons/jvmMain/.../keystorage/SecureKeyStorage.kt — OS keychain + encrypted fallback; can retrieve raw key.

Shared / quartz (reuse — don't rebuild)

  • nip19Bech32/ByteArrayExt.kt: toNsec(), toNpub().
  • nip49PrivKeyEnc/Nip49.kt: encrypt()/decrypt() ncryptsec1.
  • Android QR: ui/.../qrcode (QrCodeDrawer). Desktop QR: QrCodeCanvas.kt (currently only NIP-46/NIP-47 URIs) — reuse for npub.

Adopt the hidden-password camp (Amethyst Android's existing model) + persistent post-signup backup nudge (Snort pattern). Rationale: matches Android, keeps signup fast, avoids a hard gate, but nags until backed up.

Firm rules across both platforms:

  1. nsec masked by default; reveal is an explicit gated action.
  2. Copy nsec is itself gated (copy is the real leak vector) + shows warning.
  3. Offer NIP-49 encrypted (ncryptsec1) copy/export alongside plaintext.
  4. npub always plain, copyable, QR. nsec never QR.
  5. Hide the entire private-key section for external-signer / bunker / read-only sessions (nsec == null) — show "This account uses an external signer" note.
  6. Explicit "cannot be recovered" warning verbatim-strong (reuse Android's account_backup_tips2_md).

Platform reveal-gate:

  • Android: biometric (already wired in AccountBackupScreen).
  • Desktop: no biometric → gate reveal/copy behind a confirm dialog ("Show secret key?" YES/CANCEL) + showKeys toggle, matching DevSettings but user-facing and with the encrypted option. (Optional later: OS auth via existing PrivacyLock/master-password machinery if present — verify.)

Implementation

Phase 1 — Desktop Backup Keys screen (biggest gap)

  • New desktopApp/.../ui/settings/BackupKeysScreen.kt (or .../ui/keyBackup/):
    • npub row: plain, Copy, "Show QR" (reuse QrCodeCanvas).
    • nsec section: masked → confirm-dialog reveal → Copy (plaintext) + "Copy encrypted" (password field → Nip49().encrypt). Strong warning banner.
    • Hidden when account.nsec == null (external/read-only) → info note.
  • Add entry point in Settings sidebar/account area (near ProfileInfoCard in Main.kt ~2191). Label "Backup Keys" / "Account Keys".
  • Reuse AccountState.LoggedIn.{npub,nsec,pubKeyHex}; clipboard via existing copyToClipboard (AWT) — factor out of DevSettingsSection.

Phase 2 — Desktop NewKeyWarningCard upgrade

  • Add Copy button per key (not just selection).
  • Add "Copy encrypted (recommended)" + password field.
  • Strengthen warning copy to match Android "no recovery" strength.
  • Add "I've saved my keys" acknowledgement affordance before onContinue (soft; not a hard checkbox gate — see open Q).
  • Do NOT add nsec QR.

Phase 3 — Android post-signup backup nudge

  • After signup(), route to / surface a dismissible "Back up your keys" prompt with "Back up now" (→ AccountBackupScreen) / "I already saved them".
  • Persist "backed up" flag per account; keep nudging (home banner or settings badge) until acknowledged. No silent dismissal.

Phase 4 — Shared polish (both platforms)

  • Copy-warning string on every nsec copy ("like a password, cannot be reset").
  • FLAG_SECURE (Android) on reveal to redact screenshots/recents. Desktop: no direct equivalent — skip.
  • (Stretch) timed clipboard auto-clear after nsec copy — genuinely novel, no client does it. Verify feasibility (Android ClipboardManager, Desktop AWT).
  • (Stretch) Coracle-style paste-guard on compose: warn if a note body starts with nsec1.

Testing

  • Desktop: generate account → reveal/copy plaintext + encrypted → decrypt round-trips (Nip49) → QR shows npub not nsec → external-signer account hides nsec section. Manual sheet.
  • Android: signup → nudge appears → backup screen reachable → encrypted copy round-trips. Existing AccountBackupScreen unit coverage if any.
  • Reuse quartz Nip49 tests; add commons test for any extracted helper.

Non-goals / deferred

  • iOS (no mature target yet).
  • BIP-39 / NIP-06 mnemonic backup (Snort) — separate feature.
  • Full OS-biometric gate on Desktop (no primitive) — confirm dialog instead.

Unanswered questions

  • Design camp: confirm hidden-password + nudge (recommended) vs. keep Desktop's show-at-creation as the primary backup moment?
  • Hard "I saved it" checkbox gate at signup, or soft dismissible nudge? (survey: soft wins; hard gate largely unclaimed.)
  • Desktop reveal gate: confirm-dialog only, or wire existing PrivacyLock/master password if one exists? (verify what Desktop already has.)
  • Extract a shared commons backup composable, or keep Android + Desktop screens separate (Android biometric vs Desktop dialog diverge)?
  • Timed clipboard auto-clear: in scope now or stretch?
  • Paste-guard on compose (nsec self-doxx prevention): this feature or separate?
  • Does Desktop ProfileInfoCard already copy npub / need a QR button there too?