Add signature on MarmotGroupEvent
This commit is contained in:
1 parent
ba555d6b14
commit
44f2e7c814
5 files changed
+40
-76
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"identityHash": "305e0b745bddf4352f8e34b9cbf11242",
|
"identityHash": "ec367eb293e2be375d50f8d1b8133e00",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "BroadcastNostrEventReceipt",
|
"tableName": "BroadcastNostrEventReceipt",
|
||||||
@@ -1252,7 +1252,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "MarmotGroupEvent",
|
"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": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
@@ -1278,6 +1278,12 @@
|
|||||||
"affinity": "TEXT",
|
"affinity": "TEXT",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "signature",
|
||||||
|
"columnName": "signature",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "encryptedContent",
|
"fieldPath": "encryptedContent",
|
||||||
"columnName": "encryptedContent",
|
"columnName": "encryptedContent",
|
||||||
@@ -1329,7 +1335,20 @@
|
|||||||
"columnNames": [
|
"columnNames": [
|
||||||
"id"
|
"id"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "NostrEvent",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"nostrEventId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tableName": "MarmotInnerEvent",
|
"tableName": "MarmotInnerEvent",
|
||||||
@@ -3439,7 +3458,7 @@
|
|||||||
],
|
],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"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')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,11 +391,13 @@ abstract class MarmotOutboundDao(
|
|||||||
MarmotGroupEvent(
|
MarmotGroupEvent(
|
||||||
id = groupEvent.id,
|
id = groupEvent.id,
|
||||||
createdAt = Instant.fromEpochSeconds(groupEvent.createdAt),
|
createdAt = Instant.fromEpochSeconds(groupEvent.createdAt),
|
||||||
|
expiresAt = groupEvent.expiration()?.let { Instant.fromEpochSeconds(it) },
|
||||||
encryptedContent = groupEvent.encryptedContent(),
|
encryptedContent = groupEvent.encryptedContent(),
|
||||||
chatRoomId = localChatRoom.chatRoom.id,
|
chatRoomId = localChatRoom.chatRoom.id,
|
||||||
nostrEventId = groupEvent.id,
|
nostrEventId = groupEvent.id,
|
||||||
userPublicKey = groupEvent.pubKey,
|
userPublicKey = groupEvent.pubKey,
|
||||||
publicKey = groupEvent.pubKey
|
publicKey = groupEvent.pubKey,
|
||||||
|
signature = groupEvent.sig,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
database.chatMessageDao().upsert(
|
database.chatMessageDao().upsert(
|
||||||
|
|||||||
@@ -386,7 +386,8 @@ abstract class NostrDao(
|
|||||||
chatRoomId = chatRoomId,
|
chatRoomId = chatRoomId,
|
||||||
nostrEventId = nostrEvent.id,
|
nostrEventId = nostrEvent.id,
|
||||||
createdAt = Instant.fromEpochSeconds(groupEvent.createdAt),
|
createdAt = Instant.fromEpochSeconds(groupEvent.createdAt),
|
||||||
expiresAt = groupEvent.expiration()?.let { Instant.fromEpochSeconds(it) }
|
expiresAt = groupEvent.expiration()?.let { Instant.fromEpochSeconds(it) },
|
||||||
|
signature = groupEvent.sig
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
MarmotInboundManager.processGroupEvent(
|
MarmotInboundManager.processGroupEvent(
|
||||||
|
|||||||
+8
-66
@@ -22,7 +22,12 @@ import kotlin.time.Instant
|
|||||||
|
|
||||||
@Entity(
|
@Entity(
|
||||||
foreignKeys = [
|
foreignKeys = [
|
||||||
|
ForeignKey(
|
||||||
|
entity = NostrEvent::class,
|
||||||
|
parentColumns = ["id"],
|
||||||
|
childColumns = ["nostrEventId"],
|
||||||
|
onDelete = ForeignKey.CASCADE,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
data class MarmotGroupEvent(
|
data class MarmotGroupEvent(
|
||||||
@@ -38,10 +43,10 @@ data class MarmotGroupEvent(
|
|||||||
|
|
||||||
val chatRoomId: String,
|
val chatRoomId: String,
|
||||||
|
|
||||||
// val signature: String, // TODO: Add signature...
|
val signature: String, // TODO: Add signature...
|
||||||
|
|
||||||
val encryptedContent: String,
|
val encryptedContent: String,
|
||||||
val expiresAt: Instant? = null,
|
val expiresAt: Instant?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What gets broadcasts?
|
* What gets broadcasts?
|
||||||
@@ -57,69 +62,6 @@ data class MarmotGroupEvent(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KIND = GroupEvent.KIND
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+4
-4
@@ -338,10 +338,10 @@ class SynchronizationViewModel(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
logger.d("broadcastNostrEventRequests: $broadcastNostrEventRequests")
|
logger.d("broadcastNostrEventRequests: $broadcastNostrEventRequests")
|
||||||
// Schedule broadcastNostrEventRequests
|
// TODO: Schedule broadcastNostrEventRequests
|
||||||
nostrRepository.scheduleBroadcastNostrEventRequests(
|
// nostrRepository.scheduleBroadcastNostrEventRequests(
|
||||||
broadcastNostrEventRequests
|
// broadcastNostrEventRequests
|
||||||
)
|
// )
|
||||||
|
|
||||||
relaysSocketManager.closeNegentropySync(
|
relaysSocketManager.closeNegentropySync(
|
||||||
negCloseCmd,
|
negCloseCmd,
|
||||||
|
|||||||
Reference in new issue
Block a user