mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-30 07:37:38 +00:00
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>
139 lines
6.9 KiB
Markdown
139 lines
6.9 KiB
Markdown
# 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.
|
|
|
|
## Recommended design
|
|
|
|
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?
|