fix: critical Marmot/MLS bugs - crashes, crypto, protocol compliance

- C1: Fix leaveGroup() crash (used group state after deletion)
- C2: Fix X25519.bigIntegerToBytes ArrayIndexOutOfBoundsException
- C3: Add all-zeros DH check to prevent small-subgroup attacks
- C4: Fix path secret derivation off-by-one (RFC 9420 Section 7.4)
- C5: Use constant-time comparison for confirmation/membership tags
- C6: Stage signing keys in proposeSigningKeyRotation, promote on commit
- C7: Make commit conflict tracker per-(group,epoch) not just per-epoch
- C8: Fix tree deserialization leafCount for trimmed trees
- H8: Add Mutex-based thread safety to MlsGroupManager
- H9: Fix extractPrivateKeyBytes little-endian padding
- H12: Blank direct path on addLeaf (RFC 9420 Section 7.7)
- M14: Track actual leaf index from addLeaf for Welcome generation

https://claude.ai/code/session_018gVkmmYgMFtBH7G31pCk9N
This commit is contained in:
Claude
2026-04-07 23:02:09 +00:00
parent fc07090121
commit 29d1610d1a
7 changed files with 284 additions and 141 deletions
@@ -182,9 +182,17 @@ class MarmotManager(
* Returns proposal bytes to publish (as a GroupEvent).
*/
suspend fun leaveGroup(nostrGroupId: HexKey): OutboundGroupEvent {
val proposalBytes = groupManager.leaveGroup(nostrGroupId)
// Build the outbound event BEFORE deleting group state (needs exporter secret)
val group =
groupManager.getGroup(nostrGroupId)
?: throw IllegalStateException("Not a member of group $nostrGroupId")
val proposalBytes = group.selfRemove()
val outboundEvent = outboundProcessor.buildCommitEvent(nostrGroupId, proposalBytes)
// Now clean up group state
groupManager.removeGroupState(nostrGroupId)
subscriptionManager.unsubscribeGroup(nostrGroupId)
return outboundProcessor.buildCommitEvent(nostrGroupId, proposalBytes)
return outboundEvent
}
// --- KeyPackage Management ---