feat(chats): per-message encryption badge — lock for NIP-17, lock-open for NIP-04

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) <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-03-19 09:23:06 +02:00
parent f3e55f9958
commit 5b2f2ca42b
3 changed files with 70 additions and 0 deletions
@@ -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(
+14
View File
@@ -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.
@@ -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.