refactor: route every MLS group restore through ChatRoom.toMlsGroup()

Four places restored an MlsGroup from a chat room's persisted state, each
spelling out the same MlsGroup.restore(MlsGroupState.decodeTls(hex))
chain by hand. Three of them could not have called the shared helper
even if they wanted to: until ea8b2b2, ChatRoom.toMlsGroup() demanded a
pastGenerations count so it could replay the sender ratchet, which is a
question none of these callers had any business answering. Dropping that
parameter left the helper callable everywhere, so call it everywhere.

database/repository/DatabaseChatRepository.kt
* sendChatMessage() restores via toMlsGroup(). It only wants to know
  whether the room is MLS-backed or gift-wrapped, which is exactly what
  a null return says.

ui/view/model/AddArtifactViewModel.kt
* Same collapse: the ?.let { restore(...) }?.let { mlsGroup -> ... }
  double-let becomes toMlsGroup()?.let { mlsGroup -> ... }.

database/dao/NostrDao.kt
* The fourth copy, and the one easiest to miss: it decoded the hex into
  a mlsGroupStateByteArray temp and then restored under an explicit null
  check on the bytes, rather than the ?.let the others used. Same
  operation wearing different clothes. Now toMlsGroup() with a null
  check on the group itself, which is what the branch actually meant.

None of these three encrypt, so none of them ever needed the generation
replay the old signature forced on them -- NostrDao is the inbound path,
AddArtifactViewModel only gates on the group existing, and
sendChatMessage writes a rumor for the outbound pipeline to encrypt
later. The only caller that does encrypt, encryptAndSendMarmotInnerEvent,
persists mlsGroup.saveState() immediately afterwards, so the ratchet
position now round-trips through storage on its own.

Unused imports go with them: MlsGroup and MlsGroupState from the first
two files, MlsGroupState from NostrDao (which still needs MlsGroup for
processWelcome).

MlsGroup.restore now appears exactly once in the codebase, inside
toMlsGroup() itself.

Verified:
  ./gradlew :composeApp:compileCommonMainKotlinMetadata
  ./gradlew :composeApp:compileDebugKotlinAndroid --rerun-tasks

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kgothatso NgakoandClaude Opus 5 committed 2026-08-29 18:38:39 +02:00
1 parent ea8b2b276a
commit 65182ac892
3 files changed
+4 -27

No files matched your search

@@ -36,7 +36,6 @@ import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
import com.vitorpamplona.quartz.marmot.mls.codec.TlsReader import com.vitorpamplona.quartz.marmot.mls.codec.TlsReader
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroup import com.vitorpamplona.quartz.marmot.mls.group.MlsGroup
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroupState
import com.vitorpamplona.quartz.marmot.mls.messages.KeyPackageBundle import com.vitorpamplona.quartz.marmot.mls.messages.KeyPackageBundle
import com.vitorpamplona.quartz.marmot.mls.messages.MlsKeyPackage import com.vitorpamplona.quartz.marmot.mls.messages.MlsKeyPackage
import com.vitorpamplona.quartz.marmot.mls.tree.Credential import com.vitorpamplona.quartz.marmot.mls.tree.Credential
@@ -382,15 +381,9 @@ abstract class NostrDao(
val localChatRoom = database.chatRoomDao().findChatRoomById(chatRoomId) val localChatRoom = database.chatRoomDao().findChatRoomById(chatRoomId)
if (localChatRoom != null) { if (localChatRoom != null) {
val mlsGroupStateByteArray = localChatRoom.chatRoom.mlsGroupState?.hexToByteArray() val mlsGroup = localChatRoom.chatRoom.toMlsGroup()
if (mlsGroupStateByteArray != null) {
val mlsGroup = MlsGroup.restore(
MlsGroupState.decodeTls(
mlsGroupStateByteArray
)
)
if (mlsGroup != null) {
val memberPubkeys = mlsGroup.members().mapNotNull { (leafIndex, leafNode) -> val memberPubkeys = mlsGroup.members().mapNotNull { (leafIndex, leafNode) ->
val pubkey = when (val cred = leafNode.credential) { val pubkey = when (val cred = leafNode.credential) {
@@ -12,8 +12,6 @@ import press.mantra.compose.database.model.intermdiate.LocalChatMessage
import press.mantra.compose.database.model.intermdiate.LocalChatRoom import press.mantra.compose.database.model.intermdiate.LocalChatRoom
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroup
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroupState
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Kind import com.vitorpamplona.quartz.nip01Core.core.Kind
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
@@ -153,13 +151,7 @@ class DatabaseChatRepository(
messageType: Kind messageType: Kind
) { ) {
logger.i("sendChatMessage($text): $localChatRoom") logger.i("sendChatMessage($text): $localChatRoom")
val mlsGroup = localChatRoom.chatRoom.mlsGroupState?.let { mlsGroupState -> val mlsGroup = localChatRoom.chatRoom.toMlsGroup()
MlsGroup.restore(
MlsGroupState.decodeTls(
mlsGroupState.hexToByteArray()
)
)
}
if (mlsGroup != null) { if (mlsGroup != null) {
// Create ChatRumor... // Create ChatRumor...
@@ -14,8 +14,6 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import press.mantra.compose.repository.ChatRepository import press.mantra.compose.repository.ChatRepository
import press.mantra.compose.repository.NostrRepository import press.mantra.compose.repository.NostrRepository
import co.touchlab.kermit.Logger import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroup
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroupState
import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.HexKey
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO import kotlinx.coroutines.IO
@@ -73,13 +71,7 @@ class AddArtifactViewModel(
onSuccess: (artifactId: String) -> Unit, onSuccess: (artifactId: String) -> Unit,
onFailure: () -> Unit onFailure: () -> Unit
) { ) {
localChatRoom.chatRoom.mlsGroupState?.let { mlsGroupState -> localChatRoom.chatRoom.toMlsGroup()?.let { mlsGroup ->
MlsGroup.restore(
MlsGroupState.decodeTls(
mlsGroupState.hexToByteArray()
)
)
}?.let { mlsGroup ->
val name = nameField.text.toString() val name = nameField.text.toString()
val url = urlField.text.toString() val url = urlField.text.toString()
val versionLabel = versionLabelField.text.toString() val versionLabel = versionLabelField.text.toString()