diff --git a/CHECKPOINT-encryption.md b/CHECKPOINT-encryption.md index f35d230..3c05400 100644 --- a/CHECKPOINT-encryption.md +++ b/CHECKPOINT-encryption.md @@ -1,4 +1,4 @@ -# Checkpoint — Compose preview + open-source docs (2026-08-04) +# Checkpoint — NIP-46 remote signer (2026-08-05) A stopping point you can return to if this session is closed. Everything below was verified green at the moment this file was written. @@ -6,119 +6,82 @@ verified green at the moment this file was written. ## Where things are - Project: `/home/avi/Projects/skills/nost-feed-manager` -- Git repo: `master` @ `3eb9fa9` ("Rewrite README for Forgejo hosting"), on top of - `dbc0974` ("Add open-source README and MIT license"), `46d5da7` ("Point checkpoint at current - HEAD"), `9ec01c9` ("Add AGENTS.md checkpoint workflow rule"), `a052851` (checkpoint refresh), - `d677125` ("Show preview images and link cards in Compose"), `03f6877` (NIP-98 upload auth), - `3e3467b` (compose preview + attachments), `17e8ace` (Profiles subtitle copy), and - `8eb6685` (reveal-secret-key). +- Git repo: `master` @ `73cb08d` ("Add NIP-46 remote signer (external signing)"), on top of + `13efee6` ("Refresh checkpoint with Forgejo README"), `3eb9fa9` ("Rewrite README for Forgejo + hosting"), and the history described in the previous checkpoint. - The working tree is **clean** — everything from this session is committed. -- `AGENTS.md` now records the standing rule: every addition must end with an up-to-date checkpoint. -- `README.md` is an open-source-ready document rewritten for **Forgejo** hosting (no GitHub - references; clone/issues/wiki links use the `[YOUR_FORGEJO_INSTANCE_URL]//` Forgejo - format; no git remote is configured in the repo yet, so the instance URL is a labeled - placeholder). The new `LICENSE` file grants MIT (Copyright (c) 2026 Avi). The vault is gitignored, - so no secret material is tracked. -- `/home/avi/Projects/nostr_backend/nostr_backendmanager.md` (old-CLI docs) has been updated - to match reality; it lives outside this repo so it is not part of any commit. -- `/home/avi/Projects/nostr_backend/vlog-website/` (separate, untouched). +- The signer feature is split across one commit (`73cb08d`) that contains both the Rust backend + and the Electron/React frontend. -## What was completed: compose preview + image attachments +## What was completed: NIP-46 remote signer (bunker role) -The Compose screen now has a **Write / Preview** tab split. The preview renders the note as it -will appear (profile avatar + npub, the text, and any images), without publishing anything. +The app can now act as a **remote signer** (NIP-46, "bunker") for another Nostr app. Instead of +pasting your nsec into other apps, they send signing requests to this app, which approves them +with the active profile's keys. Decisions made this session: the connected peer is +**auto-approved** once the handshake succeeds, the signer runs **inside the `serve` process as a +concurrent tokio task** (sharing `Arc>`), and the frontend gets a **dedicated Signer +screen** as its own sidebar nav item. -- **Photos from hyperlinks**: pasting an image URL (`.jpg/.jpeg/.png/.gif/.webp/.avif`) anywhere in - the note shows the picture in the preview. On publish, the backend detects the same URLs and tags - the event with NIP-92 `imeta` (`url`, `m`) plus the legacy `image` tag, so both new and older - Nostr clients render the images. Detection is by extension only — nothing is downloaded to decide. -- **Attach images**: an "Attach image" button opens the native file dialog (multi-select), - uploads each file to nostr.build (anonymous `POST /api/v2/upload/files`, field `fileToUpload`), - and shows a removable thumbnail chip. The hosted URLs are appended to the note on publish. -- **Where upload lives**: `pick_image` / `upload_image` are handled by the Electron main process - (native dialog + `fetch`), not the Rust backend — they need a file dialog and a one-off HTTP - upload. The renderer still calls them through the same `window.backend.request` envelope. -- The reveal-secret-key feature from the previous commit (`8eb6685`) is unchanged and still works. +- **Backend** (`src/signer.rs`): a hand-rolled NIP-46 implementation (the SDK has no ready-made + NIP-46 server). `Signer` exposes `status()`, `connect()`, `disconnect()`, `fail()`, + `set_connected()`. `parse_connect_uri` accepts `nostrconnect://` links (and clearly rejects the + opposite-role `bunker://` scheme). The async `run_sign_task` connects over the chosen relays, + listens for kind 24133 requests decrypted with a NIP-44 v2 conversation key, and answers + `get_public_key`, `sign_event`, and `nip44_encrypt`/`nip44_decrypt`. Responses are NIP-44-encrypted + and published back to the peer. Six unit tests cover URI parsing, bunker rejection, NIP-44 + round-trip, get_public_key, sign_event, and unknown-method error responses. +- **IPC** (`src/ipc.rs`): `serve`/`handle`/`run` now share `Arc>`; new variants + `SignerConnect { uri }`, `SignerDisconnect`, `SignerStatus`. The connect/disconnect/status + requests are serviced by `run()` before the (long-lived) vault lock so a slow signer task never + blocks ordinary commands. +- **CLI** (`src/main.rs`): `signer status` and `signer connect `. +- **Electron/React frontend**: new **Signer screen** (sidebar nav item with the key icon) where you + paste a `nostrconnect://` link, Connect/Disconnect, and see the phase (Not connected / + Connecting… / Connected), the connected client's hex pubkey (shortened), the relays in use, and + any error. Wired through `api.ts` (`signerConnect`/`signerDisconnect`/`signerStatus`) and + `AppProvider`. When the vault is locked, a warning explains the signer needs an unlocked vault. + Tests: `SignerScreen.test.tsx` (new) + `apiMock.ts`/`fakeBackend.ts` signer methods. ## Commits this session (newest first) -- `3eb9fa9` "Rewrite README for Forgejo hosting" — README now targets Forgejo: zero GitHub URLs; - clone / issues / wiki links use the generic Forgejo format `[YOUR_FORGEJO_INSTANCE_URL]//` - with the placeholder labeled as such (repo has no git remote configured, so the instance URL - couldn't be auto-detected). Forgejo Actions CI badge left as a commented placeholder. Full - verification suite re-run green (docs-only change): cargo 64, npm 65, clippy/fmt/typecheck/ - lint/format/builds clean. -- `dbc0974` "Add open-source README and MIT license" — `README.md` rewritten for a technical / - open-source audience (badges, feature list incl. compose preview + media, tech-stack tables, - quick start + `.env` example, CLI/IPC usage, screenshots placeholders, dev guide, contributing, - community, license). New `LICENSE` (MIT, Copyright (c) 2026 Avi). Docs-only change; full - verification suite re-run green anyway (cargo 64, npm 65, all lint/format/builds clean). -- `46d5da7` "Point checkpoint at current HEAD" — checkpoint aligned with HEAD before the docs work. -- `9ec01c9` "Add AGENTS.md checkpoint workflow rule" — new `AGENTS.md` records that every addition - must end with an up-to-date checkpoint. -- `a052851` "Refresh checkpoint with preview image/link fixes" -- `d677125` "Show preview images and link cards in Compose" — two fixes from GUI testing: - 1. Remote images never rendered because the CSP was `img-src 'self' data:` — added `https:` - (`frontend/index.html`). Attached images and pasted image URLs now appear in the preview. - 2. Link previews: a pasted web-page link (not a direct image URL) now fetches the page in the - Electron main process (`link_preview` IPC) and shows an OpenGraph/Twitter card (image, title, - description, site, hostname) in the preview. New `src/lib/media.ts::extractLinkUrls`, - `LinkPreview` type, `api.linkPreview`, `AppProvider.linkPreview`, `LinkPreviewCard` in - ComposeScreen, `styles.css` card styles. Publish is unchanged — link cards are a preview-only - feature (Nostr clients render their own link previews from the URL in the note). - Tests: `media.test.ts` (new), `ComposeScreen.test.tsx`, `apiMock.ts`, `fakeBackend.ts`. -- `03f6877` "Sign NIP-98 auth for nostr.build image uploads" — fixes the attach-image flow, which - failed with "Unauthorized, please provide a valid nip-98 token". nostr.build now requires a - NIP-98 `Authorization` header. Backend: new `src/uploads.rs` (`nip98_authorization` signs a kind - 27235 auth event with the active profile's key and returns `Nostr `), IPC method - `upload_auth { url, http_method }`, `nostr-sdk` feature `nip98` added. Electron: - `frontend/electron/main.ts` fetches the header from the backend and sends it on the upload. - Verified live: throwaway profile → IPC `upload_auth` → `curl` upload to nostr.build returned - `status: success` (temp data cleaned up). -- `3e3467b` "Add compose preview with image attachments (NIP-92 imeta)" — 11 files, 1 new. - Backend: `src/publish.rs` (`extract_image_urls`, `image_mime_from_url`, `image_tags` + tests). - Electron: `frontend/electron/main.ts` (`pickImage`, `uploadImage`, routed from `backend:request`). - Renderer: `frontend/src/lib/media.ts` (new), `api.ts`, `types.ts`, `AppProvider.tsx`, - `ComposeScreen.tsx`, `styles.css`. Tests: `ComposeScreen.test.tsx`, `apiMock.ts`, `fakeBackend.ts` - (adds request recording + `pick_image`/`upload_image`). -- `17e8ace` "Clarify the Profiles subtitle about private keys" — `ProfilesScreen.tsx` copy. -- `8eb6685` "Add reveal-secret-key after unlock (CLI + GUI)" — previous feature, see history. +- `73cb08d` "Add NIP-46 remote signer (external signing)" — everything above in one commit. + Full verification suite re-run green (below). ## How it was verified (all green) ``` -cargo test # 64 passed +cargo test # 70 passed cargo clippy --all-targets # clean cargo fmt --check # clean cargo build --release # builds (rebuilt so the GUI runs the new backend) npm run typecheck # clean (frontend/) npm run lint # clean (pre-existing module warning only) npm run format:check # clean -npm test # 65 passed (12 files) +npm test # 69 passed (13 files) npm run electron:build # compiles the Electron main process npm run build # rebuilds the React bundle (dist/) ``` -Plus live checks: a throwaway-profile `upload_auth` + `curl` upload to nostr.build returned -`status: success`; the link-preview parser (same code as Electron) fetched Wikipedia's og:title + -og:image and example.com's title correctly. Temp files cleaned up. +CLI smoke checks run earlier in the session: `signer status` printed the active profile + relays; +a `bunker://` link and a non-`nostrconnect` URI were both rejected with clear errors. ## How to resume 1. Open the repo: `cd /home/avi/Projects/skills/nost-feed-manager` -2. State is committed: `git status` should be clean; `git log --oneline -5` shows the three commits. -3. To try it: - - GUI: `cd frontend && npm start` — Compose → "Attach image" or paste an image URL → Preview tab. - (Note: `npm start` alone does NOT rebuild the React bundle — run `npm run build` first, or use - `npm run dev` + `NOSTR_GUI_DEV_URL=http://localhost:5173 npm start` for live reload.) - - CLI: `cargo build --release`, then `./target/release/nostr-manager-backend list` and - `./target/release/nostr-manager-backend show-secret ` (no angle brackets). +2. State is committed: `git status` should be clean; `git log --oneline -5` shows + `73cb08d` at the top. +3. To try the signer: + - GUI: `cd frontend && npm start`, then the **Signer** screen in the sidebar. Paste a + `nostrconnect://` link from a Nostr app to sign for it. (Note: `npm start` alone does NOT + rebuild the React bundle — run `npm run build` first, or use `npm run dev` + + `NOSTR_GUI_DEV_URL=http://localhost:5173 npm start` for live reload.) + - CLI: `cargo build --release`, then `./target/release/nostr-manager-backend signer status`. 4. Re-run verification with the commands above. ## Outstanding / next steps (if you continue) - Nothing uncommitted. Possible follow-ups: a real feed view (currently Home only shows the last - publication); the upload uses the active profile's key for NIP-98 (so an encrypted, locked vault - needs unlocking before attaching an image — the attach error explains this); relay defaults in code - still point at `relay.damus.io` (503 upstream) and `relay.nostr.band` (timeout) — user's local - settings already use `nos.lol` + `relay.primal.net` instead. + publication); a NIP-46 **client** role (initiating `bunker://` from this app is intentionally + not implemented — only the signer role is). Live cross-client verification against an actual + Nostr app (e.g. a NIP-46-compatible client that produces a `nostrconnect://` link) has not yet + been done end-to-end, only the unit and CLI smoke tests. \ No newline at end of file