From 44f2e7c814ae13d4ad19b1b74c0783cb7ddb8a0f Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Wed, 8 Jul 2026 20:20:04 +0200 Subject: [PATCH] Add signature on MarmotGroupEvent --- .../1.json | 27 ++++++- .../compose/database/dao/MarmotOutboundDao.kt | 4 +- .../at/torch/compose/database/dao/NostrDao.kt | 3 +- .../database/model/MarmotGroupEvent.kt | 74 ++----------------- .../ui/view/model/SynchronizationViewModel.kt | 8 +- 5 files changed, 40 insertions(+), 76 deletions(-) diff --git a/composeApp/schemas/at.torch.compose.database.TorchDatabase/1.json b/composeApp/schemas/at.torch.compose.database.TorchDatabase/1.json index 00406868..437c7fc8 100644 --- a/composeApp/schemas/at.torch.compose.database.TorchDatabase/1.json +++ b/composeApp/schemas/at.torch.compose.database.TorchDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "305e0b745bddf4352f8e34b9cbf11242", + "identityHash": "ec367eb293e2be375d50f8d1b8133e00", "entities": [ { "tableName": "BroadcastNostrEventReceipt", @@ -1252,7 +1252,7 @@ }, { "tableName": "MarmotGroupEvent", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `publicKey` TEXT NOT NULL, `chatRoomId` TEXT NOT NULL, `encryptedContent` TEXT NOT NULL, `expiresAt` INTEGER, `nostrEventId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `publicKey` TEXT NOT NULL, `chatRoomId` TEXT NOT NULL, `signature` TEXT NOT NULL, `encryptedContent` TEXT NOT NULL, `expiresAt` INTEGER, `nostrEventId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `deletedAt` INTEGER, `broadcastedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`nostrEventId`) REFERENCES `NostrEvent`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", @@ -1278,6 +1278,12 @@ "affinity": "TEXT", "notNull": true }, + { + "fieldPath": "signature", + "columnName": "signature", + "affinity": "TEXT", + "notNull": true + }, { "fieldPath": "encryptedContent", "columnName": "encryptedContent", @@ -1329,7 +1335,20 @@ "columnNames": [ "id" ] - } + }, + "foreignKeys": [ + { + "table": "NostrEvent", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "nostrEventId" + ], + "referencedColumns": [ + "id" + ] + } + ] }, { "tableName": "MarmotInnerEvent", @@ -3439,7 +3458,7 @@ ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '305e0b745bddf4352f8e34b9cbf11242')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ec367eb293e2be375d50f8d1b8133e00')" ] } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/MarmotOutboundDao.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/MarmotOutboundDao.kt index f9625cc1..e3a9f13f 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/MarmotOutboundDao.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/MarmotOutboundDao.kt @@ -391,11 +391,13 @@ abstract class MarmotOutboundDao( MarmotGroupEvent( id = groupEvent.id, createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), + expiresAt = groupEvent.expiration()?.let { Instant.fromEpochSeconds(it) }, encryptedContent = groupEvent.encryptedContent(), chatRoomId = localChatRoom.chatRoom.id, nostrEventId = groupEvent.id, userPublicKey = groupEvent.pubKey, - publicKey = groupEvent.pubKey + publicKey = groupEvent.pubKey, + signature = groupEvent.sig, ) ) database.chatMessageDao().upsert( diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrDao.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrDao.kt index 1ed1be78..73c4341a 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrDao.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/dao/NostrDao.kt @@ -386,7 +386,8 @@ abstract class NostrDao( chatRoomId = chatRoomId, nostrEventId = nostrEvent.id, createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), - expiresAt = groupEvent.expiration()?.let { Instant.fromEpochSeconds(it) } + expiresAt = groupEvent.expiration()?.let { Instant.fromEpochSeconds(it) }, + signature = groupEvent.sig ) ) MarmotInboundManager.processGroupEvent( diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/MarmotGroupEvent.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/MarmotGroupEvent.kt index 17e3565a..91748356 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/MarmotGroupEvent.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/model/MarmotGroupEvent.kt @@ -22,7 +22,12 @@ import kotlin.time.Instant @Entity( foreignKeys = [ - + ForeignKey( + entity = NostrEvent::class, + parentColumns = ["id"], + childColumns = ["nostrEventId"], + onDelete = ForeignKey.CASCADE, + ), ], ) data class MarmotGroupEvent( @@ -38,10 +43,10 @@ data class MarmotGroupEvent( val chatRoomId: String, -// val signature: String, // TODO: Add signature... + val signature: String, // TODO: Add signature... val encryptedContent: String, - val expiresAt: Instant? = null, + val expiresAt: Instant?, /** * What gets broadcasts? @@ -57,69 +62,6 @@ data class MarmotGroupEvent( companion object { const val KIND = GroupEvent.KIND - - fun fromGroupEvent( - groupEvent: GroupEvent, - userPublicKey: String - ): MarmotGroupEvent? { - return groupEvent.groupId()?.let { chatRoomId -> - MarmotGroupEvent( - id = groupEvent.id, - encryptedContent = groupEvent.encryptedContent(), - nostrEventId = groupEvent.id, - createdAt = Instant.fromEpochSeconds(groupEvent.createdAt), - chatRoomId = chatRoomId, - userPublicKey = userPublicKey, - publicKey = groupEvent.pubKey, - expiresAt = groupEvent.expiration()?.let { Instant.fromEpochSeconds(it) }, - ) - } - } } - fun toMarmotInnerEvent(group: MlsGroup): MarmotInnerEvent? { - return null; - } - - /** - * Decrypt the outer ChaCha20-Poly1305 layer, trying the current epoch's - * exporter key first and falling back to retained epoch exporter keys. - * - * After a commit advances the epoch, late-arriving messages encrypted - * with the previous epoch's exporter key would fail without this fallback. - * - * Returns null when neither the current epoch key nor any retained key - * decrypts. This happens normally for commits/application messages from - * epochs that predate our join (we never held those keys), so callers - * should treat null as an expected "nothing to do here" outcome and log - * at DEBUG, not as an error. - */ - private fun tryDecryptOuterLayer( - group: MlsGroup, - encryptedContent: String, - ): ByteArray? { - // Try current epoch key first - try { - val exporterKey = group.exporterSecret() - return GroupEventEncryption.decrypt(encryptedContent, exporterKey) - } catch (e: Exception) { - // Current epoch key failed — try retained epoch keys - Log.e("MarmotGroupEvent", "Current Epoch Key Failed", e) - } - - // Try retained epoch exporter keys (most recent first) - throw MarmotMissingRetainedExporterSecretsException( - "Torch currently doesn't support retainedExporterSecrets" - ) -// val retainedKeys = groupManager.retainedExporterSecrets(groupId) -// for (retainedKey in retainedKeys) { -// try { -// return GroupEventEncryption.decrypt(encryptedContent, retainedKey) -// } catch (_: Exception) { -// // This retained key didn't work — try the next one -// } -// } -// -// return null - } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/SynchronizationViewModel.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/SynchronizationViewModel.kt index 7899088e..3173ca1d 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/SynchronizationViewModel.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/SynchronizationViewModel.kt @@ -338,10 +338,10 @@ class SynchronizationViewModel( ) } logger.d("broadcastNostrEventRequests: $broadcastNostrEventRequests") - // Schedule broadcastNostrEventRequests - nostrRepository.scheduleBroadcastNostrEventRequests( - broadcastNostrEventRequests - ) + // TODO: Schedule broadcastNostrEventRequests +// nostrRepository.scheduleBroadcastNostrEventRequests( +// broadcastNostrEventRequests +// ) relaysSocketManager.closeNegentropySync( negCloseCmd,