Add signature on MarmotGroupEvent

This commit is contained in:
Kgothatso Ngako
2026-07-08 20:20:04 +02:00
parent ba555d6b14
commit 44f2e7c814
5 changed files with 40 additions and 76 deletions

View File

@@ -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')"
]
}
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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
}
}

View File

@@ -338,10 +338,10 @@ class SynchronizationViewModel(
)
}
logger.d("broadcastNostrEventRequests: $broadcastNostrEventRequests")
// Schedule broadcastNostrEventRequests
nostrRepository.scheduleBroadcastNostrEventRequests(
broadcastNostrEventRequests
)
// TODO: Schedule broadcastNostrEventRequests
// nostrRepository.scheduleBroadcastNostrEventRequests(
// broadcastNostrEventRequests
// )
relaysSocketManager.closeNegentropySync(
negCloseCmd,