diff --git a/CHECKPOINT-encryption.md b/CHECKPOINT-encryption.md index da13251..86dda29 100644 --- a/CHECKPOINT-encryption.md +++ b/CHECKPOINT-encryption.md @@ -1,27 +1,1447 @@ -# Checkpoint — fix profile delete/undo + themed auto-dismiss bar (2026-08-27) +# Checkpoint — nostr stack 0.45 security migration (2026-08-25) + +A stopping point you can return to if this session is closed. Everything below was +verified green at the moment this file was written. ## Where things are + - Project: `/home/avi/Projects/Nostr_Keynctr` -- Git repo: `master` @ `9e635e7` ("fix: restore profile delete/undo and themed auto-dismiss undo bar"). Before it: `56c2a6d` (checkpoint: click profile row), `8130c3e` (click profile row on Home), `a5838ea`, `98f4520` etc. -- Working tree: clean except `KeynectrAppIconPossibility02.jpeg` untracked (long-standing, not part of commits). +- Git repo: `master` @ `8130c3e` ("frontend: click a profile row on Home to + select it"). Before it: `98f4520` (active-profile picture on Compose + + sidebar), `f534165` (remove Profiles sidebar tab), `835b0a0` (list all + profiles on Home, pin dev host, widen card), `5635f79` (checkpoint), + `71e580f` (Trim white margins), `f29d4f9` (Checkpoint 0.45 migration), + `fa5ba08` (Security: upgrade nostr stack 0.40->0.45), `bf10ae3`, `e210f17`, + `7098e75`, `e6a1efc`, `6d5063d`, `1c428a9`. +- Working tree: still DIRTY with the long-standing user changes — `concept3.svg` + deleted, `KeynectrAppIconPossibility02.jpeg` untracked. These are NOT part + of the frontend commit above and remain uncommitted. ## What was completed -1. **Fixed broken profile delete/undo.** `src/ipc.rs` was missing `DeleteProfile`/`UndoDelete` request variants and handlers, so `delete_profile` from renderer returned `Unexpected signer request`. Added `DeleteProfile {npub}`/`UndoDelete` to `Request`, handlers that call `profiles::delete_profile`/`app.undo_delete()`, save vault, push/pop `undo_history`, return `state_view()`. Exposed `profiles::delete_profile` outside `#[cfg(test)]` (was only inside tests). Updated `src/profiles.rs` accordingly. CLI `delete_profile_direct` kept for `main.rs`. -2. **Wired frontend to backend state.** `frontend/src/lib/api.ts`: `deleteProfile`/`undoDelete` now `call` (was `ProfileSummary`). `frontend/src/state/AppProvider.tsx`: both now use `applyState(...)` and type `Promise`; added `clearLastDeleted` that locally slices `undo_history` for UI dismissal. -3. **Themed auto-dismiss undo bar.** `frontend/src/screens/ProfilesScreen.tsx`: replaced permanent white bar (`var(--token-item-bg, #f0f0f0)` + "Profile deleted. You can restore it until you delete another or quit.") with themed bar (`var(--primary-soft)` bg, `var(--border)` border) showing `Profile "X" deleted. — Undo and restore profile` where the link uses `var(--primary)` (adapts to light/dark/neon/glass). Bar appears in both empty and populated states, auto-dismisses after 5s via `useEffect` watching `lastDeleted` (previous version fired unconditionally on mount). Fake backend updated to simulate delete/undo for tests. -## Commits added in this session (newest first) -- `9e635e7` fix: restore profile delete/undo and themed auto-dismiss undo bar +1. **Security migration of the Nostr stack (`fa5ba08`).** `cargo audit` was + installed on this machine (`cargo install cargo-audit`, v0.22.2) and its + first scan found **11 RustSec vulnerabilities** in nostr 0.40 / + relay-pool 0.40.1 — including forged-event signature-bypass (0224), + NIP-46 credential Debug leak (0225), and auth-challenge memory + exhaustion (0231). Upgraded to `nostr 0.45.3` + `nostr-sdk 0.45.2` + (secp256k1 0.30), clearing every vulnerability and all 4 warnings. + Code adaptations: `Client::builder().authenticator(SignerAuthenticator)` + replaces implicit signer (NIP-42 auto-auth stays working); signing moved + from `EventBuilder::sign` to `finalize_async`; nip44 encrypt now takes an + explicit random nonce (getrandom); feed + signer receive loops moved to + `stream_events` (signer's notification stream still opens BEFORE the + announce, preserving the ordering fix); contact p-tags parsed via + `single_letter_tag()`; `relay()` returns Option; `try_connect().timeout()`. + Dropped unused `nip46` feature (signer is hand-rolled). Dry-run first: + bump-only build proved zero dependency-crate failures / no MSRV issues; + all 24 errors were exactly the planned API edits. +2. **New regression tests (+2, suite now 115).** Malformed NIP-44 payloads + (not-base64, empty, truncated, bad version byte) reject cleanly with no + panic; error strings never contain secret-key material (0225 class). +3. Earlier today: R3 modal-save dedup (`7098e75`) completing the DRY audit, + NIP-42 clarification docs (`bf10ae3`). + +## Commits added most recently + +- `8130c3e` frontend: click a profile row on Home to select it +- `98f4520` frontend: show active-profile picture on Compose and sidebar +- `f534165` frontend: remove redundant Profiles sidebar tab +- `835b0a0` frontend: list all profiles on Home, pin dev host, widen profiles card +- `fa5ba08` Security: upgrade nostr stack 0.40->0.45 clearing 11 RustSec advisories ## Verification commands run (all green) -- Rust: `cargo fmt --check` clean; `cargo clippy --all-targets` 2 pre-existing warnings; `cargo test` 115 passed; `cargo build --release` success -- Frontend (`frontend/`): `npm run typecheck` clean; `npm run lint` clean; `npm run format:check` clean (after `npm run format`); `npm test` 15 files / 99 tests passed; `npm run build` success; `npm run electron:build` success -- The `--ozone-platform=wayland` Vulkan and `has no handler with id` messages seen on `electron:build` are harmless Electron/Wayland warnings, not profile errors. -## How to resume / reproduce -GUI: `cargo build --release && cd frontend && npm run build && npm run electron:build && npm start` (or `npm run dev`). Create 2 profiles, delete one via Profiles → Delete → confirm, see themed undo bar for 5s with "Undo and restore profile" in primary color, click it to restore, or wait for auto-dismiss. -CLI: `cargo run -- delete-profile ` then `cargo run -- undo-delete` (in-memory undo only within same `serve` session; CLI undo across processes is not persisted). +Rust (repo root): cargo fmt --check clean; cargo clippy --all-targets only +the two pre-existing profiles.rs warnings; cargo test 115 passed (release +mode re-run too); cargo build --release success. +Frontend (`frontend/`, untouched): typecheck clean; format:check clean; +npm test 15 files / 99 tests; vite build success; electron:build success. +Live: probe built on 0.45 stack published kind-1 notes accepted by +wss://soloco.nl and wss://relay.primal.net within the 6 s watchdog. +Pre-commit audit per user protocol: staged diff = Cargo.toml/Cargo.lock/src +only; git diff --check clean. -## Outstanding / next-step items -- Undo restores profile with empty `secret_key` (`StoredProfile { secret_key: "" }`) — existing design stores `ProfileSummary` in `undo_history` so secret cannot be recovered. Needs full `StoredProfile` in undo stack if secret preservation is required. -- `KeynectrAppIconPossibility02.jpeg` remains untracked; `concept3.svg` deletion already committed earlier. +## How to use / reproduce + +No user-facing change. Rebuild + restart to pick up the new backend: + +```bash +cd ~/Projects/Nostr_Keynctr && cargo build --release +cd frontend && npm run build && npm start +``` + +Re-check Updates card: Rust advisory section should now list no +vulnerabilities (cargo-audit is installed machine-wide). + +## Notes & next steps + +- wss://nostr.l484.com had a DNS-resolution blip at the very end of the + session (gaierror for ~a minute); it worked earlier today including a + stored test event. If it stays down, check the relay host — not app code. +- Probe harnesses live in /tmp/opencode (nip42-probe on 0.45; python raw + readers). Copy into scripts/ if they should survive reboots. +- Pre-existing clippy warnings in src/profiles.rs remain untouched by request. +- Relay health today: nos.lol 502 intermittent, nostr.wine 403, + l484.com transient DNS failure at session end, nostr.band unreachable; + primal/mom/soloco healthy. + +--- + +# Older checkpoint — DRY modal save lifecycle / R3 complete (2026-08-25) + +A stopping point you can return to if this session is closed. Everything below was +verified green at the moment this file was written. + +## Where things are + +- Project: `/home/avi/Projects/Nostr_Keynctr` +- Git repo: `master` @ `7098e75` ("refactor: share modal save lifecycle"). + Before it: `e6a1efc` (refactor: share hex-id shortening in lib/format), + `ec237bf` (Checkpoint: relay pool bootstrap), `6d5063d` + (refactor: share relay pool bootstrap), `1c428a9` (DRY fan-out R1), + `b21678f` (signer-fix checkpoint), `64ad94d` (NIP-46 handshake fix). +- Working tree after this checkpoint commit: only the long-standing user + changes remain — `concept3.svg` deleted and + `KeynectrAppIconPossibility02.jpeg` untracked, exactly as the user had them. + +## What was completed + +1. **R3 modal save-lifecycle dedup (`7098e75`).** The three profile modals in + `frontend/src/screens/ProfilesScreen.tsx` (Picture, Rename, NIP-05) each + carried an identical ~20-line async save lifecycle. Now a single + module-local `runModalSave` helper owns it: clear error → set saving → + claim publishing slot (`onSavingChange(npub)`) → run API → build success + message from the publish report → report via `onSaved`, or on failure + surface the error via `onError` → always release saving state and the slot + in `finally`. Each modal keeps only what is genuinely its own: the API call, + its success-message logic inline (including NIP-05's set/removed branch), + and RenameModal's `canSave` guard outside the helper. No user-facing change: + callback order, messages, buttons, props, and tests untouched. + With this, all four DRY-audit items (R1–R4) are complete. +2. Earlier: R4 hex-id shortening (`e6a1efc`), R2 relay-pool bootstrap + (`6d5063d`), R1 fan-out dedup (`1c428a9`) — details in the older checkpoints + below. + +## Commits added most recently + +- `7098e75` refactor: share modal save lifecycle + +## Verification commands run (all green) + +Rust (repo root): cargo test 113 passed; cargo clippy --all-targets finished +(pre-existing profiles.rs warnings only); cargo fmt --check clean; +cargo build --release success. +Frontend (`frontend/`): npm test 15 files / 99 tests passed; typecheck clean; +lint clean; format:check clean; npm run build success; electron:build success. +Pre-commit audit per user protocol: staged diff contained only +ProfilesScreen.tsx; git diff --check clean; nothing else staged. + +## How to use / reproduce + +No user-facing change. Profiles → Picture / Edit name / NIP-05 modals behave +exactly as before; any future change to the shared save policy happens in one +place: `runModalSave` in `frontend/src/screens/ProfilesScreen.tsx` +(just above `Nip05Modal`). + +## Notes & next steps + +- **NIP-42 status clarified (2026-08-25 addendum).** Earlier notes saying + "l484.com needs NIP-42 auth" described *raw probes* that don't answer + challenges — not a Keynectr gap. Verified: nostr-sdk 0.40 attaches the + signer in `Client::new` and enables NIP-42 auto-authentication by default + (`nip42_auto_authentication: true`), so AUTH challenges on read AND write + are answered automatically on every Keynectr path. Live test against + `wss://nostr.l484.com` replicating Keynectr's exact client setup: event + accepted in 0.3 s (inside the 6 s `RELAY_SEND_TIMEOUT`) and retrievable + afterwards. Known edge, left as-is by choice: the SDK's worst-case auth + dance (~7 s wait + resend) can exceed the 6 s watchdog on a very slow + authed relay → "did not respond in time"; healthy relays finish in <1 s. + Probe harness: `/tmp/opencode/nip42-probe` (throwaway key, same crates). +- DRY audit items R1–R4 are all complete; nothing left on hold from that list. +- Pre-existing clippy warnings in src/profiles.rs remain untouched by request. +- Relay health today: nos.lol 502 (intermittent), nostr.wine 403, + l484.com sends NIP-42 AUTH challenges but works fine with Keynectr + (auto-answered — see addendum above), nostr.band unreachable; + primal/mom/soloco healthy. +- Client visibility rule of thumb confirmed today: a note appears only on + clients whose read relays overlap the relays it was published to; if a + note "is missing" somewhere, first compare relay lists (Primal indexes + broadly; Iris/Yakihonne read narrower sets). + +--- + +# Older checkpoint — DRY hex-id shortening (2026-08-24) + +A stopping point you can return to if this session is closed. Everything below was +verified green at the moment this file was written. + +## Where things are + +- Project: `/home/avi/Projects/Nostr_Keynctr` +- Git repo: `master` @ `e6a1efc` ("refactor: share hex-id shortening in lib/format"). + Before it: `ec237bf` (Checkpoint: relay pool bootstrap), `6d5063d` + (refactor: share relay pool bootstrap), `1c428a9` (DRY fan-out R1), + `b21678f` (signer-fix checkpoint), `64ad94d` (NIP-46 handshake fix), + `dd50b24` (Aurora rename). +- Working tree: clean apart from this checkpoint update. `concept3.svg` deleted + and `KeynectrAppIconPossibility02.jpeg` untracked remain as the user had them. + +## What was completed + +1. **R4 hex-id shortening (`e6a1efc`).** Exported `shortHexId` from + `frontend/src/lib/format.ts` with verbatim logic from the former local + `shortHex` in `SignerScreen.tsx`; SignerScreen now imports and calls + `shortHexId`; added `format.test.ts` with 7 focused tests (empty, len 16, + len 17, 64-hex, shortenNpub disabled, enabled truncation). Output behavior + is byte-identical: >16 uses first 8 chars + Unicode ellipsis U+2026 + last 8, + ≤16 passes through unchanged. Does not reuse `shortenNpub`; preserves + existing visual output exactly per audit constraint. +2. **R2 DRY refactor (`6d5063d`).** New `relays::open_pool(keys, urls, + wait: Option) -> Result` is the single pool-construction + path (add each relay with strict error propagation, connect, optional + wait). Adopted by feed's two fetchers (`Keys::generate()` + 10 s wait) and + note publishing (`keys.clone()`, no wait). `profiles.rs` intentionally NOT + adopted — its unique ignore-add-errors policy stays local; it regained an + explicit `connect()` when `send_to_all_relays` dropped its internal one + (avoids double-connect on the note path). `signer.rs` deliberately + untouched: ordering-critical notification setup + fail-to-status error model. + Net −20 lines; behavior, error strings, timing unchanged. +3. Earlier this session: R1 fan-out dedup (`1c428a9`), NIP-46 handshake fix + (`64ad94d`), Aurora rename + dropdown fix (`dd50b24`), frosted-glass look + (`ecb666b`), glass build fix (`bfe14f0`). + +## Commits added most recently + +- `e6a1efc` refactor: share hex-id shortening in lib/format +- `6d5063d` refactor: share relay pool bootstrap + +## Verification commands run (all green) + +Rust: cargo test 113 passed; clippy only pre-existing profiles.rs warnings; +fmt clean; release binary rebuilt. +Frontend (untouched): npm test 99; typecheck, lint, format, electron:build, +vite build all clean. +Pre-commit audit per user protocol: git diff --check clean; signer.rs empty +diff; 3 test files added (format.test.ts with 7 new tests). + +## How to use / reproduce + +No user-facing change. Future connection-policy work (e.g., NIP-42 auth for +nostr.l484.com, retries) now starts in `relays::open_pool`. + +## Notes & next steps + +- Remaining DRY items by user request on hold: R3 (modal save-lifecycle hook). +- R4 (shortHex vs shortenNpub) is complete: shared `shortHexId` in lib/format, + output preserved exactly, tests green. +- Relay health today: nos.lol 502, nostr.wine 403, l484.com needs NIP-42 auth, + nostr.band unreachable; primal/mom/soloco healthy. +--- + +# Older checkpoint — DRY relay fan-out (2026-08-24) + +A stopping point you can return to if this session is closed. Everything below was +verified green at the moment this file was written. + +## Where things are + +- Project: `/home/avi/Projects/Nostr_Keynctr` +- Git repo: `master` @ `1c428a9` ("DRY: single parallel relay fan-out for notes + and metadata"). Before it: `64ad94d` (NIP-46 handshake fix), `b21678f`/ + `ae84526` (checkpoints), `dd50b24` (Aurora rename). +- Working tree: clean apart from this checkpoint update. `concept3.svg` deleted + and `KeynectrAppIconPossibility02.jpeg` untracked remain as the user had them. + +## What was completed + +1. **R1 DRY refactor (`1c428a9`).** The parallel relay send block (~55 lines: + JoinSet per-relay sends, 6 s timeout, indexed outcome collection, + disconnect, succeeded/failed split) existed twice — in + `publish.rs::publish_with_keys` and `profiles.rs::publish_metadata_async`. + Now one `pub(crate) publish::send_to_all_relays(client, urls, event, noun)` + serves both; callers keep their distinct add-relay policies (note path + aborts on add errors, metadata ignores them) so behavior, error strings, + and public APIs are unchanged. Removed duplicate constant + `METADATA_SEND_TIMEOUT`, dead helper `failure_for`. Net −49 lines. +2. Deliberately NOT done (pending approval): R2 shared client-bootstrap + helper, R3 modal save-lifecycle hook (frontend), R4 shortHex/shortenNpub. + +## Commits added most recently + +- `1c428a9` DRY: single parallel relay fan-out for notes and metadata + +## Verification commands run (all green) + +Rust: cargo fmt --check clean; clippy only pre-existing profiles.rs warnings; +cargo test 113 passed; release binary rebuilt. +Frontend (untouched, suite rerun): npm test 92 passed; typecheck, lint, +format:check, electron:build, vite build all clean. + +## How to use / reproduce + +No user-facing change; note and metadata publishing behave exactly as before. +Any future change to relay-send policy now happens in one place: +`send_to_all_relays` in `src/publish.rs`. + +## Notes & next steps + +- R2–R4 from the DRY audit remain unimplemented by request. +- Relay health today: nos.lol 502, nostr.wine 403, nostr.l484.com needs + NIP-42 auth, nostr.band unreachable; primal/mom/soloco healthy. + +--- + +# Older checkpoint — NIP-46 signer handshake fix (2026-08-24) + +A stopping point you can return to if this session is closed. Everything below was +verified green at the moment this file was written. + +## Where things are + +- Project: `/home/avi/Projects/Nostr_Keynctr` +- Git repo: `master` @ `64ad94d` ("Signer: listen before announcing so the + handshake reply is not lost"). Before it: `dd50b24` (Aurora rename + + dropdown fix), `ecb666b` (frosted-glass look), `bfe14f0` (glass build fix). +- Working tree: clean apart from this checkpoint update. `concept3.svg` deleted + and `KeynectrAppIconPossibility02.jpeg` untracked remain as the user had them. + +## What was completed + +1. **Root-caused the "Yakihonne never leaves the QR screen" bug (`64ad94d`).** + Reproduced with a reference NIP-46 client harness over live relays + (`/tmp/opencode/nc-client`): the app's announcement arrived, the client's + `ack` + `get_public_key` were answered to nobody — the signer task created + its notification receiver *after* publishing the announce, so the client's + millisecond-fast handshake reply landed in the broadcast channel before any + receiver existed and was dropped. Client waits forever → stuck QR. + Fix: open `client.notifications()` immediately after adding relays, before + connect/subscribe/announce. Post-fix round-trip PASSES end-to-end + (announce → ack → get_public_key → pubkey returned). +2. Relay observations from testing: nos.lol 502 today; nostr.wine 403 + (auth/paid); nostr.l484.com sends NIP-42 AUTH challenges; healthy: + relay.primal.net, nostr.mom, soloco.nl. + +## Commits added most recently + +- `64ad94d` Signer: listen before announcing so the handshake reply is not lost + +## Verification commands run (all green) + +Rust (repo root): cargo test 113 passed; clippy (pre-existing profiles.rs +warnings only); fmt clean; release binary rebuilt with the fix. +Frontend (`frontend/`): npm test 92 passed; typecheck, lint, format clean. +End-to-end: reference NIP-46 client + `keynectr signer connect ` over +wss://nostr.mom, relay.primal.net, soloco.nl — full handshake PASS. + +## How to use / reproduce + +GUI: `cd ~/Projects/Nostr_Keynctr/frontend && npm start` → Signer → paste a +client's nostrconnect:// link → approve requests. With Yakihonne: choose its +remote-signer/connect option, copy its link into Keynectr within its timeout; +Yakihonne should now switch from the QR screen to the logged-in state. + +Harness (if ever needed again): `/tmp/opencode/nc-client` — `nc-client` +prints a nostrconnect URI to /tmp/opencode/uri.txt and PASSes on round-trip; +run `target/release/keynectr signer connect $(cat /tmp/opencode/uri.txt)`. + +## Notes & next steps + +- If a client is still slow, remaining edge: relays that reject kind 24133 or + need auth can starve the handshake — consider surfacing per-relay connect + status in the Signer UI later. +- Pre-existing clippy warnings in src/profiles.rs untouched by request. + +--- + +# Older checkpoint — Aurora rename + dropdown fix (2026-08-24) + +A stopping point you can return to if this session is closed. Everything below was +verified green at the moment this file was written. + +## Where things are + +- Project: `/home/avi/Projects/Nostr_Keynctr` +- Git repo: `master` @ `dd50b24` ("Rename glass theme display to Aurora; fix + unreadable select options"). Before it: `ecb666b` (frosted-glass look), + `0cab883`/`1aae81c` (checkpoints), `bfe14f0` (glass fix). +- Working tree: clean apart from this checkpoint update. `concept3.svg` deleted + and `KeynectrAppIconPossibility02.jpeg` untracked remain as the user had them. + +## What was completed + +1. **Theme renamed to Aurora (`dd50b24`).** User picked "Aurora" from options + (Aurora/Frost/Glacier/Ice/keep). Display label only — internal id stays + `glass`, so existing settings.json values need no migration. +2. **Fixed unreadable theme dropdown (`dd50b24`).** Under the glass theme the + native `