From 79e62d8239573449d9166301026899d7e1df4543 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Sat, 5 Sep 2026 19:05:49 +0200 Subject: [PATCH] fix: attribute a group message to the member who sent it isUserMessage was computed by comparing the active key against groupEvent.pubKey -- the kind:445's signer. That is a fresh random ephemeral key on every send (`NostrSignerInternal(KeyPair())` in encryptAndSendMarmotInnerEvent), so it could never equal anybody's identity. The comparison was false for every Marmot message in every room, which means every message a member sent themselves rendered as somebody else's: wrong side of the transcript, wrong colour, and no delivery status, which is drawn only for our own lines. All fourteen arms now read the MLS sender identity, which is authenticated to a leaf and is the only thing this can honestly be computed from. For the four results that carry no leaf index -- the commit and proposal statuses -- it is null and yields false, exactly what they got before. Separated from the direct message work that exposed it because it changes bubble alignment in every existing MLS room, and that is worth being able to revert on its own. Co-Authored-By: Claude Opus 5 --- .../compose/database/model/ChatMessage.kt | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/model/ChatMessage.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/model/ChatMessage.kt index 1f54f41f..5d040b11 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/model/ChatMessage.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/model/ChatMessage.kt @@ -354,6 +354,16 @@ data class ChatMessage( * payload: a direct message's wrap is keyed to a throwaway key and names nobody, * and even for kinds that do carry a pubkey the MLS frame is the authenticated * source while the field is merely asserted. + * + * It is also the only thing [ChatMessage.isUserMessage] can honestly be computed + * from. Every arm below used to compare the active key against `groupEvent.pubKey` + * -- the kind:445's signer, which is a fresh random ephemeral key on every send + * (`NostrSignerInternal(KeyPair())` in `encryptAndSendMarmotInnerEvent`). That can + * never equal anybody's identity, so it was false for every Marmot message in + * every room, and every one of them rendered as somebody else's. + * + * Null for results that carry no leaf index -- the commit and proposal statuses -- + * which yields false, exactly as before. */ suspend fun fromGroupEventResult( database: MantraDatabase, @@ -387,7 +397,7 @@ data class ChatMessage( marmotGroupEventId = groupEvent.id, marmotInnerEventId = event.id, senderPublicKey = event.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(event.createdAt), content = event.content, // TODO: Figure out what to do here... @@ -417,7 +427,7 @@ data class ChatMessage( marmotGroupEventId = groupEvent.id, marmotInnerEventId = event.id, senderPublicKey = event.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(event.createdAt), content = "Added ${mantraArtifact.name} to artifacts" @@ -448,7 +458,7 @@ data class ChatMessage( marmotGroupEventId = groupEvent.id, marmotInnerEventId = event.id, senderPublicKey = event.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(event.createdAt), content = "Added ${mantraArtifactVersion.versionLabel} to artifact versions" // TODO: Use artifact name... @@ -479,7 +489,7 @@ data class ChatMessage( marmotGroupEventId = groupEvent.id, marmotInnerEventId = event.id, senderPublicKey = event.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(event.createdAt), content = "Added ${mantraChapter.name} to chapters" // TODO: Use artifact name... @@ -534,7 +544,7 @@ data class ChatMessage( marmotGroupEventId = groupEvent.id, marmotInnerEventId = event.id, senderPublicKey = event.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(event.createdAt), content = "Added ${mantraDialect.name} to dialects" // TODO: Use artifact name... @@ -565,7 +575,7 @@ data class ChatMessage( marmotGroupEventId = groupEvent.id, marmotInnerEventId = event.id, senderPublicKey = event.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(event.createdAt), content = "Added ${mantraTranslationArtifactVersion.name} to translation artifact versions" // TODO: Use artifact name... @@ -646,7 +656,7 @@ data class ChatMessage( marmotGroupEventId = groupEvent.id, marmotInnerEventId = event.id, senderPublicKey = event.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(event.createdAt), content = "Added ${mantraTranslation.text} to translations" // TODO: Reference original text @@ -670,7 +680,7 @@ data class ChatMessage( marmotGroupEventId = groupEvent.id, marmotInnerEventId = event.id, senderPublicKey = groupEvent.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), content = groupEventResult.innerEventJson, // TODO: Figure out what to do here... @@ -685,7 +695,7 @@ data class ChatMessage( marmotInnerEventId = null, messageType = "pendingCommit", senderPublicKey = groupEvent.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), content = "Pending Commit in epoch ${groupEventResult.epoch}", // TODO: Figure out what to do here... @@ -698,7 +708,7 @@ data class ChatMessage( marmotInnerEventId = null, messageType = "processedCommit", senderPublicKey = groupEvent.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), content = "Processed Commit in epoch ${groupEventResult.newEpoch}", // TODO: Figure out what to do here... @@ -711,7 +721,7 @@ data class ChatMessage( // groupEventId = groupEvent.id, // messageType = "duplicate", // senderPublicKey = groupEvent.pubKey, -// isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, +// isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), // chatRoomId = groupEventResult.groupId, // createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), // content = groupEventResult.innerEventJson, // TODO: Figure out what to do here... @@ -724,7 +734,7 @@ data class ChatMessage( // groupEventId = groupEvent.id, // messageType = "error", // senderPublicKey = groupEvent.pubKey, -// isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, +// isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), // chatRoomId = groupEventResult.groupId, // createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), // content = groupEventResult.innerEventJson, // TODO: Figure out what to do here... @@ -738,7 +748,7 @@ data class ChatMessage( marmotInnerEventId = null, messageType = "proposalStaged", senderPublicKey = groupEvent.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), content = "Proposal staged: ${groupEventResult.senderLeafIndex}", // TODO: Figure out what to do here... @@ -751,7 +761,7 @@ data class ChatMessage( marmotInnerEventId = null, messageType = "undecryptableOuterLayer", senderPublicKey = groupEvent.pubKey, - isUserMessage = activeKeyPair.pubKey.toHex() == groupEvent.pubKey, + isUserMessage = senderIdentity == activeKeyPair.pubKey.toHex(), chatRoomId = groupEventResult.groupId, createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), content = "Undecryptable Message", // TODO: Figure out what to do here...