diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 831057941..10ec32d29 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -3018,7 +3018,12 @@ class Account( val innerEvent = com.vitorpamplona.quartz.nip01Core.core.Event .fromJson(json) - val isNew = cache.justConsume(innerEvent, null, false) + // wasVerified=true: these events were already verified + // via MLS credential identity when first decrypted (see + // GroupEventHandler). Persisted inner events are + // unsigned per MIP-03, so justVerify would fail and + // reactions/deletions would silently drop here. + val isNew = cache.justConsume(innerEvent, null, true) val innerNote = cache.getOrCreateNote(innerEvent.id) if (isNew) { innerNote.event = innerEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt index 100760c5e..6226c6fd8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt @@ -592,7 +592,15 @@ class GroupEventHandler( "GroupEventHandler.add: ApplicationMessage decrypted innerKind=${innerEvent.kind} " + "innerId=${innerEvent.id.take(8)}… author=${innerEvent.pubKey.take(8)}…" } - if (cache.justConsume(innerEvent, null, false)) { + // wasVerified=true: MIP-03 inner events are unsigned (sig is empty + // or placeholder), so justVerify() would fail. Authenticity is + // already guaranteed by the MLS layer — MarmotInboundProcessor + // enforces innerEvent.pubKey == MLS sender credential identity + // before returning ApplicationMessage. Without this, side-channel + // inner events from other Marmot clients (kind:7 reactions, + // kind:5 deletions) are silently dropped by LocalCache, so a + // WhiteNoise reaction never attaches to its target chat bubble. + if (cache.justConsume(innerEvent, null, true)) { val innerNote = cache.getOrCreateNote(innerEvent.id) innerNote.event = innerEvent