archy/docs/nostr-identity-import-plan.md
archipelago 9e264611e2 docs: Nostr signer-login research + identity-import UX plan
Research report on the most frictionless "sign in with your Nostr signer"
flow for node login (NIP-07 extension + NIP-46 QR scan with Amber, password
always kept as fallback), and a plan for adding existing Nostr identities
(nsec import / npub watch-only / browser extension) to the Nostr Identities
screen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:59:54 -04:00

4.3 KiB

Add an existing Nostr identity to the node — UX & implementation plan

Status: plan only (2026-07-16), no code. Companion research: docs/nostr-signer-login-research.md.

Where it lives

The Nostr Identities screen (Web5Identities.vue, backed by identity.list / identity.create). Today every identity is seed-derived (identity_manager.rs derives ed25519 + nostr keys from the BIP-39 master seed at an index). "Add existing" introduces a second class of identity: one whose key material comes from outside the seed.

Two import kinds (both needed, different guarantees)

  1. Full import (nsec) — the node holds the secret key. The identity behaves exactly like a seed-derived one (can sign in embedded apps, publish, encrypt). NOT covered by seed backup — flag it visibly and include it in the encrypted node backup.
  2. Linked signer (npub only) — the node stores just the public key; signing is delegated to the user's own signer (browser extension NIP-07, or a NIP-46 remote signer later). Zero key custody; some features (background publishing) unavailable — the UI should badge what works.

The UX (matching the house style)

Entry point: next to "Create identity" on Nostr Identities, an "Add existing" glass-button. Opens a modal with three tabs (same tab pattern as the send/receive modals):

  1. Browser extension (default when window.nostr exists)
    • One button: "Connect with extension". Flow: getPublicKey() → show the npub + resolved profile (kind-0 fetched via the node's relays: avatar, name — instant recognition) → "Add this identity".
    • Creates a linked signer identity. A challenge signature (signEvent on a throwaway event) proves key possession before adding — never add an unverified npub as "yours".
  2. Secret key (nsec)
    • Paste field (masked, nsec1… or hex), inline validation + derived npub preview with the same kind-0 profile card before confirming.
    • Scary-clear copy: "Your key will be stored on this node, encrypted at rest. It is NOT part of your seed backup — back it up separately." Confirm step requires the profile card to load or an explicit "add anyway".
    • Creates a full identity.
  3. Public key (npub) — watch-only
    • Paste an npub for a linked identity without any signer attached yet (useful to reserve the profile, upgrade to extension/NIP-46 signing later).

After adding: the identity appears in the same grid with a small origin badge — seed / imported / linked — and the imported profile picture/name pulled from relays. Everything else (picker in apps, rename, avatar) behaves uniformly.

Removal: existing delete flow; for imported identities the confirm dialog warns the key is destroyed unless exported first (offer "Export nsec" in the identity's detail sheet, gated behind password re-entry).

Backend work

  • identity_manager.rs: identity records gain origin: Seed { index } | Imported | Linked, optional nostr_secret_hex absent for Linked. Storage: reuse the existing encrypted identity file; imported secrets included in node backup.
  • New RPCs:
    • identity.import-nostr { nsec | npub, name?, verify_sig? } → validates, derives npub, rejects duplicates (same pubkey as any existing identity), returns the new identity.
    • identity.fetch-profile { pubkey } → kind-0 lookup via nostr_relays.rs for the preview card (frontend could also do this, but the node already has relay plumbing and avoids CORS).
  • identity.nostr-sign (used by the iframe NIP-07 bridge): for Linked identities return a typed error the bridge translates into "ask the user's extension instead" — phase 2; phase 1 simply hides linked identities from the in-app signer picker.

Demo mode

Mock identity.import-nostr + identity.fetch-profile in mock-backend.js (canned profile: picture + name for any pasted npub) so the whole add-existing flow is demoable without real relays.

Phasing

  1. Phase 1 (small): nsec + npub tabs, origin badges, backup inclusion, mock.
  2. Phase 2: extension tab with possession-proof + kind-0 preview cards everywhere.
  3. Phase 3: NIP-46 remote-signer identities + login integration (shares the QR plumbing from the signer-login work).