From 91eef089dc973e9e4fa443e3233f21cbdd5c916a Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 14:14:24 +0000 Subject: [PATCH] fix: handle external join in processCommit and relax KeyPackage ciphersuite check processCommit now detects ExternalInit proposals and allows the sender leaf index to equal tree.leafCount (the new joiner's slot), matching RFC 9420 Section 12.4.3.2 external commit semantics. MlsKeyPackage.decodeTls no longer rejects non-0x0001 ciphersuites so that interop test vectors with other suites round-trip correctly. https://claude.ai/code/session_01LpR6qsF6nep1RnXA9KbXGs --- .../quartz/marmot/mls/group/MlsGroup.kt | 27 ++++++++++++++----- .../marmot/mls/messages/MlsKeyPackage.kt | 2 +- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/group/MlsGroup.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/group/MlsGroup.kt index 69a56248a..592de12ac 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/group/MlsGroup.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/group/MlsGroup.kt @@ -637,15 +637,28 @@ class MlsGroup private constructor( senderLeafIndex: Int, confirmationTag: ByteArray? = null, ) { - require(senderLeafIndex >= 0 && senderLeafIndex < tree.leafCount) { - "Invalid sender leaf index: $senderLeafIndex" - } - require(tree.getLeaf(senderLeafIndex) != null) { - "Sender leaf is blank at index $senderLeafIndex" - } - val commit = Commit.decodeTls(TlsReader(commitBytes)) + // External commits (containing ExternalInit) have a sender that is not + // yet in the tree — their leaf will be added via the UpdatePath below. + val isExternalCommit = + commit.proposals.any { + it is ProposalOrRef.Inline && it.proposal is Proposal.ExternalInit + } + + if (isExternalCommit) { + require(senderLeafIndex >= 0 && senderLeafIndex <= tree.leafCount) { + "Invalid sender leaf index for external commit: $senderLeafIndex" + } + } else { + require(senderLeafIndex >= 0 && senderLeafIndex < tree.leafCount) { + "Invalid sender leaf index: $senderLeafIndex" + } + require(tree.getLeaf(senderLeafIndex) != null) { + "Sender leaf is blank at index $senderLeafIndex" + } + } + // Apply proposals (resolve references from pending pool) for (proposalOrRef in commit.proposals) { when (proposalOrRef) { diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/messages/MlsKeyPackage.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/messages/MlsKeyPackage.kt index 41600e659..84f1e0fa8 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/messages/MlsKeyPackage.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/marmot/mls/messages/MlsKeyPackage.kt @@ -122,7 +122,7 @@ data class MlsKeyPackage( val version = reader.readUint16() require(version == 1) { "Unsupported MLS version: $version" } val cipherSuite = reader.readUint16() - require(cipherSuite == 1) { "Unsupported ciphersuite: $cipherSuite" } + require(cipherSuite in 1..0xFFFF) { "Invalid ciphersuite: $cipherSuite" } return MlsKeyPackage( version = version, cipherSuite = cipherSuite,