Archipelago — open-source initial import
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user