From 5b2f2ca42b25a42166a6b98154ce935235c51bc4 Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Thu, 19 Mar 2026 09:23:06 +0200 Subject: [PATCH] =?UTF-8?q?feat(chats):=20per-message=20encryption=20badge?= =?UTF-8?q?=20=E2=80=94=20lock=20for=20NIP-17,=20lock-open=20for=20NIP-04?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Show a small lock icon next to the timestamp on each DM message: - NIP-17 (ChatMessageEvent, ChatMessageEncryptedFileHeaderEvent): filled lock in primary color — relay can't see sender/recipient - NIP-04 (PrivateDmEvent): open lock in muted gray — legacy encryption, metadata visible to relays Matches Android's IncognitoBadge pattern. Both NIP-04 and NIP-17 messages in the same 1-on-1 conversation produce identical ChatroomKeys, so they merge into one conversation — the badge is the only way to tell them apart. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../amethyst/desktop/ui/chats/ChatPane.kt | 23 +++++++++++++ docs/TODO.md | 14 ++++++++ ...26-03-19-dm-encryption-badge-brainstorm.md | 33 +++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 docs/TODO.md create mode 100644 docs/brainstorms/2026-03-19-dm-encryption-badge-brainstorm.md diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/chats/ChatPane.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/chats/ChatPane.kt index 660745d3e0..a5e360c1bd 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/chats/ChatPane.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/chats/ChatPane.kt @@ -523,6 +523,29 @@ private fun MessageWithReactions( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp), ) { + // Encryption badge + when (event) { + is PrivateDmEvent -> { + Icon( + Icons.Default.LockOpen, + contentDescription = "NIP-04 (legacy)", + modifier = Modifier.size(12.dp), + tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f), + ) + } + + is com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent, + is ChatMessageEncryptedFileHeaderEvent, + -> { + Icon( + Icons.Default.Lock, + contentDescription = "NIP-17 (encrypted)", + modifier = Modifier.size(12.dp), + tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f), + ) + } + } + // Timestamp note.createdAt()?.let { timestamp -> Text( diff --git a/docs/TODO.md b/docs/TODO.md new file mode 100644 index 0000000000..979b4c9996 --- /dev/null +++ b/docs/TODO.md @@ -0,0 +1,14 @@ +# TODO + +## DM: Mixed NIP-04 + NIP-17 conversations + +**Question:** What happens when a conversation with an npub has both NIP-04 (kind 4) and NIP-17 (kind 14/15 in GiftWrap) messages? + +**Current behavior to investigate:** +- NIP-04 messages use `PrivateDmEvent.chatroomKey(pubKey)` → creates a `ChatroomKey` based on recipient +- NIP-17 messages use `ChatMessageEvent.chatroomKey(pubKey)` → also creates a `ChatroomKey` based on group members +- Do these produce the **same** `ChatroomKey` for 1-on-1 chats? If yes, both message types merge into one conversation. If no, they appear as separate conversations. +- Android Amethyst handles this — check how `Account.kt` merges them +- Edge cases: timeline ordering, decryption display, NIP-04 messages showing as "legacy" vs NIP-17 + +**Action:** Test with a real conversation that has both types. If they split into two rooms, we need to merge them in `ChatroomListState` or unify the `ChatroomKey` derivation. diff --git a/docs/brainstorms/2026-03-19-dm-encryption-badge-brainstorm.md b/docs/brainstorms/2026-03-19-dm-encryption-badge-brainstorm.md new file mode 100644 index 0000000000..e106e509db --- /dev/null +++ b/docs/brainstorms/2026-03-19-dm-encryption-badge-brainstorm.md @@ -0,0 +1,33 @@ +# Brainstorm: Per-Message Encryption Badge in DMs + +**Date:** 2026-03-19 +**Status:** Ready for implementation + +## What We're Building + +Per-message encryption indicator in DM chat bubbles — lock icon (NIP-17) or lock-open icon (NIP-04) next to the timestamp. Matches Android's approach with incognito badges. + +## Why This Approach + +Users need to know which messages are truly private (NIP-17: relay can't see sender/recipient) vs legacy encrypted (NIP-04: relay sees metadata). Android already does this with incognito badges. Desktop uses lock/lock-open icons (already imported in ChatPane) for consistency with the existing NIP-17 toggle. + +## Key Decisions + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Indicator type | Lock icon per message | Matches existing lock icon pattern in desktop NIP-17 toggle | +| NIP-17 icon | Lock (filled) in primary color | Private, secure | +| NIP-04 icon | LockOpen in muted gray | Legacy, weaker privacy | +| Placement | Next to timestamp in detailRow | Matches Android's IncognitoBadge placement | +| Tooltip | None (match Android) | Keep it subtle, not alarming | + +## Implementation + +The badge goes in `MessageWithReactions` in ChatPane.kt, in the `detailRow` slot of `ChatMessageCompose`. Check `note.event` type: +- `is PrivateDmEvent` → NIP-04 → lock-open gray +- `is ChatMessageEvent` or `is ChatMessageEncryptedFileHeaderEvent` → NIP-17 → lock primary +- else → no badge + +## Key Finding: NIP-04 + NIP-17 Messages Merge + +For 1-on-1 chats, both protocols produce identical `ChatroomKey({otherPubkey})`. Messages from both protocols appear in the same conversation. The per-message badge is the only way to tell them apart.