From 9a0e4c77fc61584187506e5c302e6a5490758670 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Tue, 2 Jun 2026 21:55:35 +0200 Subject: [PATCH] Fix opening chat rooms with users that don't have a valid public key. --- .../compose/database/dao/NostrDao.kt | 7 ++- .../compose/database/dao/NostrNip17Dao.kt | 30 ++---------- .../compose/database/model/ChatRoom.kt | 47 +++++++++++++++++++ .../compose/database/model/GiftWrapPayload.kt | 25 ++-------- 4 files changed, 58 insertions(+), 51 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt index bb42341b..8e5cd1c3 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt @@ -352,11 +352,10 @@ abstract class NostrDao( giftWrapPayload ) - val musig2AggregatedPublicKey = giftWrapPayload.aggregateParticipantsPublicKeys() - if (musig2AggregatedPublicKey != null) { - val chatRoomId = musig2AggregatedPublicKey.publicKey.toHex() + val chatRoomId = giftWrapPayload.aggregatedParticipantsPublicKey() + logger.d("ChatRoomId: $chatRoomId") - logger.d("ChatRoomId: $chatRoomId") + if (chatRoomId != null) { // Find or create chatRoom val localChatRoom = database.chatRoomDao().findChatRoomById( chatRoomId diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrNip17Dao.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrNip17Dao.kt index 5232dec1..b2d804af 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrNip17Dao.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrNip17Dao.kt @@ -8,11 +8,8 @@ import ac.cord.auxiliary.compose.managers.SeedManager import androidx.room3.Dao import androidx.room3.Transaction import co.touchlab.kermit.Logger -import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip01Core.core.toHexKey -import fr.acinq.bitcoin.PublicKey -import fr.acinq.bitcoin.crypto.musig2.Musig2 -import kotlin.math.log + @Dao abstract class NostrNip17Dao( @@ -26,38 +23,19 @@ abstract class NostrNip17Dao( val activePublicKey = SeedManager.activePublicKey().toHexKey() - val hexKeys = listOf( + val hexKeys = setOf( activePublicKey, publicKey - ).sorted() - - logger.d("hexKeys: $hexKeys") - - val publicKeys = hexKeys.map { - val publicKey = PublicKey( - PublicKey.compress(it.hexToByteArray()) - ) - if (publicKey.isValid().not()) { - logger.w("$it is an invalid pubkey: $publicKey") - } - - publicKey - } - logger.d("PublicKeys: $publicKeys") - - val chatRoomXOnlyPublicKey = Musig2.aggregateKeys( - publicKeys ) - logger.d("ChatRoomXOnly: $chatRoomXOnlyPublicKey") - val chatRoomId = chatRoomXOnlyPublicKey.publicKey.value.toHex() + val chatRoomId = ChatRoom.deriveChatRoomId(hexKeys) logger.d("chatRoomId: $chatRoomId") val localChatRoom = database.chatRoomDao().findChatRoomById(chatRoomId) if (localChatRoom != null) { return localChatRoom } else { - val profiles = database.profileDao().getProfileByPublicKeys(hexKeys) + val profiles = database.profileDao().getProfileByPublicKeys(hexKeys.toList()) if (profiles.isNotEmpty()) { // Create new chatRoom diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/ChatRoom.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/ChatRoom.kt index 622c7112..1631e8e2 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/ChatRoom.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/ChatRoom.kt @@ -6,8 +6,14 @@ import ac.cord.auxiliary.compose.database.model.traits.TimestampedEntity import ac.cord.auxiliary.compose.database.model.traits.UserViewableEntity import androidx.room3.Entity import androidx.room3.ForeignKey +import androidx.room3.Ignore import androidx.room3.PrimaryKey +import co.touchlab.kermit.Logger import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray +import fr.acinq.bitcoin.PrivateKey +import fr.acinq.bitcoin.PublicKey +import fr.acinq.bitcoin.crypto.musig2.Musig2 import kotlin.time.Clock import kotlin.time.Instant @@ -72,4 +78,45 @@ data class ChatRoom( override val deletedAt: Instant? = null, ): TimestampedEntity, LocalStoreEntity, UserViewableEntity, SoftDeletableEntity { + + companion object { + const val TAG = "ChatRoom" + + @Ignore + val logger = Logger.withTag(TAG) + + fun deriveChatRoomId(hexKeySet: Set): String { + val hexKeys = hexKeySet.sorted() + + logger.d("hexKeys: $hexKeys") + + val publicKeys = hexKeys.map { + + val publicKey = PublicKey( + PublicKey.compress(it.hexToByteArray()) + ) + if (publicKey.isValid().not()) { + logger.w("$it is an invalid pubkey (not representing a point on the secp256k1 curve): $publicKey") + // Using it to derive a publicKey + val derivedPublicKey = PrivateKey( + publicKey.xOnly().value + ).publicKey() + + logger.w("Derived $derivedPublicKey: ${derivedPublicKey.isValid()}") + + derivedPublicKey + } else { + publicKey + } + } + logger.d("PublicKeys: $publicKeys") + + val chatRoomXOnlyPublicKey = Musig2.aggregateKeys( + publicKeys + ) + logger.d("ChatRoomXOnly: $chatRoomXOnlyPublicKey") + + return chatRoomXOnlyPublicKey.publicKey.toHex() + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/GiftWrapPayload.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/GiftWrapPayload.kt index da199d99..92d0ad5e 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/GiftWrapPayload.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/GiftWrapPayload.kt @@ -15,7 +15,6 @@ import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers import com.vitorpamplona.quartz.nip14Subject.subject import fr.acinq.bitcoin.PublicKey -import fr.acinq.bitcoin.XonlyPublicKey import fr.acinq.bitcoin.crypto.musig2.Musig2 import kotlin.time.Clock import kotlin.time.Instant @@ -82,29 +81,12 @@ data class GiftWrapPayload( // TODO: Rename this to GiftWrapPayload... @Ignore private val logger = Logger.withTag(TAG) - fun aggregateParticipantsPublicKeys(): XonlyPublicKey? = try { + fun aggregatedParticipantsPublicKey(): String? = try { logger.d("aggregateParticipantsPublicKeys") - val hexKeys = participantPubKeys() + val hexKeySet = participantPubKeys() - logger.d("HexKeys: $hexKeys") - val publicKeys = hexKeys.map { - val publicKey = PublicKey( - PublicKey.compress(it.hexToByteArray()) - ) - - logger.d("$publicKey: ${publicKey.isValid()}") - publicKey - } - - logger.d("PublicKeys: $publicKeys") - - val chatRoomXOnlyPublicKey = Musig2.aggregateKeys( - publicKeys - ) - logger.d("ChatRoomXOnly: $chatRoomXOnlyPublicKey") - - return chatRoomXOnlyPublicKey + return ChatRoom.deriveChatRoomId(hexKeySet) } catch (e: Throwable) { logger.e("Failed to aggregate participants publicKeys", e) return null @@ -122,6 +104,7 @@ data class GiftWrapPayload( // TODO: Rename this to GiftWrapPayload... return hexKeys } catch (e: Throwable) { + logger.e("Failed to get participant public keys", e) return emptySet() }