mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 17:23:21 +00:00
Two bugs found by a device sweep of the Messages list.
**Raw NIP-04 ciphertext rendered as the message preview.** Rows in New
Requests showed base64 blobs like `0tyoSVovKSK9uDKLUVMs137TD0b+vz…`.
Cause: every decryption branch in `Account.cachedDecryptContent` and
`decryptContent` is gated on `isWriteable()`, and the non-writeable path
fell through to `event.content` verbatim — which for a kind:4 IS the
NIP-04 blob. A read-only (npub-only) login therefore hit this on every
legacy DM room.
Both functions now return null instead of the ciphertext, which closes
the leak on every surface reading them — including the open chatroom
body, which had the identical fallthrough. A new pure classifier backs
the preview and never reads `event.content` for an encrypted kind, so a
future raw fallback cannot resurface there.
Pending and undecryptable are now distinguished on facts the UI actually
has, rather than collapsed into one message: no key at all, or a kind:4
between two other people, is "could not decrypt"; encrypted with our key
a party but plaintext not yet back is "Decrypting…", which resolves
itself when the signer answers. The old code showed the not-found string
for the pending case.
**Group DM titles stuck on npubs while the facepile beside them showed
real names** — and this one is not a display bug at all. Both already
observe metadata through the same flow; the fault is in `User`:
fun metadata() = metadata ?: UserMetadataCache().also { metadata = it }
Non-atomic lazy init on a plain field, called from BOTH the Compose main
thread (every `observeUserInfo` composition) and the relay/IO threads
(`updateUserInfo`). Two threads can each read null, each allocate, and
one instance is orphaned. A composable collecting the ORPHANED cache
never receives the metadata, so it sits on its pubkey fallback forever
while a sibling that got the surviving instance renders the name — which
is exactly "npubs in the title, names in the facepile, same row", and
why it never recovers.
All six per-user lazy caches are now `@Volatile` with double-checked
locking under one process-wide lock, held only for the allocation. The
store holds tens of thousands of users, so a lock per user would be
worse than the bug.
This likely explains a broader class of "some names resolve and others
never do" symptoms, not just the row that surfaced it.
Not fixed: for a read-only account the open chatroom body now renders
nothing for a kind:4 rather than ciphertext — better, but it deserves the
same "could not decrypt" placeholder the preview row got.
Unverified: that the account which showed the ciphertext was in fact
read-only. Every other route to ciphertext was traced and returns null,
so the non-writeable fallthrough is the only reachable source, but the
device state itself was not captured.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>