Commit graph

7 commits

Author SHA1 Message Date
SkateSpots
1c361b8df5 fix(amber): stop decrypt loops after navigation 2026-10-30 10:08:19 +07:00
SkateSpots
707dcaf644 refactor(crew-chat): Chat tab is always NIP-17; widen group read window
Per the crew-chat audit recommendations:
- The crew Chat tab no longer silently switches transport on a coordinator tag.
  It is always NIP-17; coordinator routing lives only in the Private chat (MLS).
  useCordnChat / useCrewChatUnread / lib/cordn client+crypto are kept but unwired
  (headers marked). Drops the tab's Cordn-only unread dot.
- Group reads (Groups list + crew thread) widened 200 -> 500 wraps; the shared
  decrypt cache makes the extra reads affordable. 1:1 list stays 200.
2026-10-13 18:35:03 +07:00
SkateSpots
8e7c5bfa09 fix(crew-chat): show sent messages, stop re-decrypting the inbox
Audit of the crew Chat tab (see CREW_CHAT_AUDIT.md):
- A sent crew message never appeared: useSendDirectMessage invalidated the list
  keys but not ['group-messages', gid, ...] (the thread key). Now optimistically
  inserts the sender's own plaintext (no refetch -> no Amber pop-ups).
- Every reader decrypted every wrap under its own query key, so each view
  (Messages, then each crew thread) re-decrypted the whole inbox. Added a shared,
  coalesced, bounded session decrypt cache (decryptGiftWrap), successes only.
- Threads dedup by rumor id; CrewChat guards double-send with a ref; the Cordn
  path optimistically inserts and invalidates only for silent signers.
2026-10-13 00:21:42 +07:00
SkateSpots
3ae05a0c38 fix(amber): stop DM/read hooks re-decrypting on every navigation
For an interactive signer (Amber) each nip44 decrypt opens the app. The DM/NIP-17
read hooks decrypt 2x per gift wrap and DirectMessagesPage mounts two of them, so
navigating to Messages stormed Amber. Strengthened amberSafeQueryOptions for
interactive signers (staleTime/gcTime Infinity, refetchOnMount/refetchOnReconnect
false) so decryption happens once per session, mirroring useStash; same guard on
usePrivateContacts + useCordnChat. Added a manual Refresh to the Messages header
(Amber gets no polling). Gated useSyncPendingPosts' legacy re-sign loop for
interactive signers.
2026-10-12 23:28:03 +07:00
shakespeare.diy
b910141699 fix(amber): stop passive decryption from storming Amber on navigation (1.2.16)
With Amber as signer, every nip44 decrypt is a foreground intent (visible
popup). Several hooks decrypt inside auto-running queries, so Amber flashed on
navigation/focus and (worst case) storms — one popup per encrypted event, and
polling queries re-fired every few seconds.

New helper isInteractiveSigner() (signerCapabilities.ts) detects the Amber
(x-amber) signer, where each crypto op prompts the user. Hooks now gate passive
decryption for interactive signers only; extension/nsec/bunker keep their
silent, live-polling behaviour.

Fixes:
- useCrewDismissals (GLOBAL via header NotificationBell — the main offender):
  Amber users now use the instant localStorage mirror only, never passively
  decrypt the synced list; disabled refetchOnWindowFocus + Infinity staleTime.
  Dismissals still sync OUT on the write path.
- useDirectMessages (all 4 DM query hooks): added amberSafeQueryOptions() —
  no background polling and no focus refetch for Amber (these decrypt up to
  ~200 gift wraps × 2 each); silent signers keep the 60s poll.
- useStash / usePrivateContacts: disabled focus refetch + Infinity staleTime
  for Amber so the single decrypt runs once, not on every focus.
- useCordnChat: dropped the 5s live poll for Amber (was a popup every 5s);
  silent signers keep it.

Scanned every signEvent call site: all are inside mutations/user callbacks
(zaps, invites, DMs, chat, follows, purchases, NIP-98 auth) — no passive
auto-signing anywhere. Deliberate user actions still prompt Amber as intended.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-07 14:57:35 +07:00
shakespeare.diy
e843bd526a Cordn crew chat: ephemeral envelopes (coordinator can't see the sender)
Close the metadata gap: the outer kind-4497 envelope was signed with the
sender's real key, so a coordinator/relay could see exactly who was talking.

- buildEnvelope now signs each envelope with a fresh ephemeral key (NIP-59
  style) — the coordinator never learns the sender.
- Authorship moves inside the ciphertext: a new v2 sealed payload
  (`nip44-group-v2`) carries a fully-signed inner author-proof event (kind 4498)
  bound to the crew via an `a` tag. openGroupMessage verifies the signature,
  author, content match, and group binding — so authorship stays unforgeable and
  a proof can't be replayed into another crew.
- Transport trusts the verified v2 proof and surfaces the real sender; legacy
  v1 envelopes remain readable (outer-key binding) for back-compat.
- NIP.md updated with the v2 format + an honest note on the residual
  verbatim-replay caveat (full replay/forward-secrecy hardening lands with MLS).

Scope decision: kept to crew chat only. Regular 1:1/group DMs already use NIP-17
gift wrap (ephemeral keys, sealed per recipient) and have no coordinator, so
they need no change and stay idiot-proof.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 18:14:58 +07:00
shakespeare.diy
50d8d9e614 Wire live Cordn encrypted crew chat over the coordinator (ContextVM)
The crew coordinator is no longer just a pointer — when a crew configures one,
the Chat tab runs through it for real.

- src/lib/cordn: clean transport/crypto split so an MLS (ts-mls) upgrade is a
  drop-in swap of the crypto layer only.
  - types.ts: CordnMessage/CordnEndpoint + envelope kind 4497 + app tag.
  - crypto.ts: sealGroupMessage/openGroupMessage — real NIP-44 group encryption
    (per-recipient sealing), the exact seam MLS encrypt-to-epoch slots into.
  - client.ts: buildEnvelope/publishEnvelope/fetchGroupMessages with per-group
    cursor ordering and ciphertext→author sender binding (anti-spoof).
  - endpoint.ts: resolve a crew's `coordinator` to a usable relay endpoint
    (wss/ws direct; https falls back to the ContextVM relay).
- useCordnChat: fetch-then-poll hook mirroring the NIP-17 chat API.
- CrewChat: uses Cordn when the crew has a coordinator, NIP-17 fallback
  otherwise; header badge shows the active transport (Cordn vs Private).
- CrewCoordinatorCard: reflects that private chat is live.
- NIP.md: document envelope kind 4497, delivery (fetch-then-poll + cursors),
  the nip44-group sealed content, and the honest crypto→MLS seam.

Real encryption, real coordinator routing, non-breaking (fallback intact),
with the MLS ratchet as a clearly-marked next step.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-03 00:15:22 +07:00