fix(marmot): frame outbound commits as MlsMessage(PublicMessage(...))

addMember/removeMember/updateGroupMetadata were publishing the raw TLS
Commit struct inside the kind:445 outer ChaCha20 layer, so receivers
that started with MlsMessage.decodeTls read the first two bytes of the
Commit as the MLS protocol version and aborted with "Unsupported MLS
version: <garbage>" (e.g. 16704 = 0x4140).

Wrap commits produced by MlsGroup.commit in a PublicMessage carrying
the sender leaf index and confirmation_tag, then in an MlsMessage
envelope, and expose those bytes via CommitResult.framedCommitBytes so
MarmotManager uses them instead of the raw commit bytes. The internal
CommitResult.commitBytes field stays raw so MlsGroup.processCommit and
existing tests that exercise it directly keep working.

Also mark the committer's own published kind:445 as already processed
in MarmotInboundProcessor so that the relay echo of our own commit is
treated as a duplicate rather than re-applied on top of the already-
advanced local epoch.

https://claude.ai/code/session_01NR6JgYRimVb142T2nkChuh
This commit is contained in:
Claude
2026-04-20 20:45:32 +00:00
parent 6e61653bca
commit f9214daba2
5 changed files with 180 additions and 5 deletions
@@ -179,7 +179,10 @@ class MarmotManager(
}
val commitResult = groupManager.addMember(nostrGroupId, keyPackageBytes)
val commitEvent = outboundProcessor.buildCommitEvent(nostrGroupId, commitResult.commitBytes)
val commitEvent = outboundProcessor.buildCommitEvent(nostrGroupId, commitResult.framedCommitBytes)
// We've already applied this commit locally; prevent the relay-echoed
// copy from being re-applied by the inbound pipeline.
inboundProcessor.markEventProcessed(commitEvent.signedEvent.id)
val welcomeDelivery =
welcomeSender.wrapWelcome(
@@ -266,7 +269,9 @@ class MarmotManager(
targetLeafIndex: Int,
): OutboundGroupEvent {
val commitResult = groupManager.removeMember(nostrGroupId, targetLeafIndex)
return outboundProcessor.buildCommitEvent(nostrGroupId, commitResult.commitBytes)
val commitEvent = outboundProcessor.buildCommitEvent(nostrGroupId, commitResult.framedCommitBytes)
inboundProcessor.markEventProcessed(commitEvent.signedEvent.id)
return commitEvent
}
/**
@@ -278,7 +283,9 @@ class MarmotManager(
metadata: MarmotGroupData,
): OutboundGroupEvent {
val commitResult = groupManager.updateGroupExtensions(nostrGroupId, listOf(metadata.toExtension()))
return outboundProcessor.buildCommitEvent(nostrGroupId, commitResult.commitBytes)
val commitEvent = outboundProcessor.buildCommitEvent(nostrGroupId, commitResult.framedCommitBytes)
inboundProcessor.markEventProcessed(commitEvent.signedEvent.id)
return commitEvent
}
// --- KeyPackage Management ---