fix(marmot): thread actual wire_format into ConfirmedTranscriptHashInput
RFC 9420 §8.2 says ConfirmedTranscriptHashInput carries the wire_format of the AuthenticatedContent being committed, not a hard-coded value. openmls/mdk pass mls_content.wire_format() through, so B's PrivateMessage commits use wire_format=2 in their transcript-hash input. Quartz always wrote wire_format=1 (PublicMessage), so amy's receiver-side new_confirmed_transcript_hash diverged from B's — and every tag derived from it (confirmation_key, confirmation_tag, epoch secrets) diverged too, surfacing as "Confirmation tag verification failed" once my error-surfacing fix stopped hiding it. Plumb the real wire format through processCommit and its decrypt() dispatch so PrivateMessage commits from B are hashed as PrivateMessage on amy's side. https://claude.ai/code/session_1469d7f4-bb66-4ffa-a44d-1dfa4b526484
This commit is contained in:
@@ -967,9 +967,11 @@ class MlsGroup private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Apply the commit inline — after this returns, the caller
|
// Apply the commit inline — after this returns, the caller
|
||||||
// only needs to know the epoch advanced. The signature rides
|
// only needs to know the epoch advanced. The signature + wire
|
||||||
// into the transcript-hash computation inside processCommit.
|
// format ride into the transcript-hash computation inside
|
||||||
processCommit(commitBytes, senderLeafIndex, confirmationTag, signature)
|
// processCommit (RFC 9420 §8.2 requires the real wire format
|
||||||
|
// for ConfirmedTranscriptHashInput).
|
||||||
|
processCommit(commitBytes, senderLeafIndex, confirmationTag, signature, WireFormat.PRIVATE_MESSAGE)
|
||||||
|
|
||||||
return DecryptedMessage(
|
return DecryptedMessage(
|
||||||
senderLeafIndex = senderLeafIndex,
|
senderLeafIndex = senderLeafIndex,
|
||||||
@@ -1039,6 +1041,7 @@ class MlsGroup private constructor(
|
|||||||
senderLeafIndex: Int,
|
senderLeafIndex: Int,
|
||||||
confirmationTag: ByteArray,
|
confirmationTag: ByteArray,
|
||||||
signature: ByteArray = ByteArray(0),
|
signature: ByteArray = ByteArray(0),
|
||||||
|
wireFormat: WireFormat = WireFormat.PUBLIC_MESSAGE,
|
||||||
) {
|
) {
|
||||||
val commit = Commit.decodeTls(TlsReader(commitBytes))
|
val commit = Commit.decodeTls(TlsReader(commitBytes))
|
||||||
|
|
||||||
@@ -1215,7 +1218,7 @@ class MlsGroup private constructor(
|
|||||||
|
|
||||||
// Update transcript hashes (RFC 9420 Section 8.2)
|
// Update transcript hashes (RFC 9420 Section 8.2)
|
||||||
// ConfirmedTranscriptHashInput = wire_format || FramedContent || signature
|
// ConfirmedTranscriptHashInput = wire_format || FramedContent || signature
|
||||||
val confirmedTranscriptHashInput = buildConfirmedTranscriptHashInput(commit, senderLeafIndex, signature)
|
val confirmedTranscriptHashInput = buildConfirmedTranscriptHashInput(commit, senderLeafIndex, signature, wireFormat)
|
||||||
val confirmedInput = TlsWriter()
|
val confirmedInput = TlsWriter()
|
||||||
confirmedInput.putBytes(interimTranscriptHash)
|
confirmedInput.putBytes(interimTranscriptHash)
|
||||||
confirmedInput.putBytes(confirmedTranscriptHashInput)
|
confirmedInput.putBytes(confirmedTranscriptHashInput)
|
||||||
@@ -1393,7 +1396,8 @@ class MlsGroup private constructor(
|
|||||||
commit: Commit,
|
commit: Commit,
|
||||||
senderLeafIndex: Int,
|
senderLeafIndex: Int,
|
||||||
signature: ByteArray = ByteArray(0),
|
signature: ByteArray = ByteArray(0),
|
||||||
): ByteArray = buildConfirmedTranscriptHashInput(commit, senderLeafIndex, groupId, epoch, signature)
|
wireFormat: WireFormat = WireFormat.PUBLIC_MESSAGE,
|
||||||
|
): ByteArray = buildConfirmedTranscriptHashInput(commit, senderLeafIndex, groupId, epoch, signature, wireFormat)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the RFC 9420 §7.9.2 parent_hash chain for a commit we are
|
* Compute the RFC 9420 §7.9.2 parent_hash chain for a commit we are
|
||||||
@@ -1992,9 +1996,17 @@ class MlsGroup private constructor(
|
|||||||
groupId: ByteArray,
|
groupId: ByteArray,
|
||||||
epoch: Long,
|
epoch: Long,
|
||||||
signature: ByteArray = ByteArray(0),
|
signature: ByteArray = ByteArray(0),
|
||||||
|
wireFormat: WireFormat = WireFormat.PUBLIC_MESSAGE,
|
||||||
): ByteArray {
|
): ByteArray {
|
||||||
|
// RFC 9420 §8.2: ConfirmedTranscriptHashInput carries the wire_format
|
||||||
|
// of the actual AuthenticatedContent — openmls passes
|
||||||
|
// `mls_content.wire_format()` through. When B sends a commit as a
|
||||||
|
// PrivateMessage (mdk default = AlwaysCiphertext outgoing), amy
|
||||||
|
// MUST recompute the transcript hash with wire_format=2, or the
|
||||||
|
// resulting confirmed_transcript_hash — and thus the
|
||||||
|
// confirmation_tag derived from it — silently diverges from B's.
|
||||||
val writer = TlsWriter()
|
val writer = TlsWriter()
|
||||||
writer.putUint16(WireFormat.PUBLIC_MESSAGE.value)
|
writer.putUint16(wireFormat.value)
|
||||||
writer.putOpaqueVarInt(groupId)
|
writer.putOpaqueVarInt(groupId)
|
||||||
writer.putUint64(epoch)
|
writer.putUint64(epoch)
|
||||||
writer.putUint8(1) // SenderType.MEMBER
|
writer.putUint8(1) // SenderType.MEMBER
|
||||||
|
|||||||
+2
-1
@@ -289,6 +289,7 @@ class MlsGroupManager(
|
|||||||
senderLeafIndex: Int,
|
senderLeafIndex: Int,
|
||||||
confirmationTag: ByteArray,
|
confirmationTag: ByteArray,
|
||||||
signature: ByteArray = ByteArray(0),
|
signature: ByteArray = ByteArray(0),
|
||||||
|
wireFormat: com.vitorpamplona.quartz.marmot.mls.framing.WireFormat = com.vitorpamplona.quartz.marmot.mls.framing.WireFormat.PUBLIC_MESSAGE,
|
||||||
) = mutex.withLock {
|
) = mutex.withLock {
|
||||||
val group = requireGroup(nostrGroupId)
|
val group = requireGroup(nostrGroupId)
|
||||||
|
|
||||||
@@ -299,7 +300,7 @@ class MlsGroupManager(
|
|||||||
// the current epoch key, wasting the finite retention slots.
|
// the current epoch key, wasting the finite retention slots.
|
||||||
val retainedBefore = group.retainedSecrets()
|
val retainedBefore = group.retainedSecrets()
|
||||||
|
|
||||||
group.processCommit(commitBytes, senderLeafIndex, confirmationTag, signature)
|
group.processCommit(commitBytes, senderLeafIndex, confirmationTag, signature, wireFormat)
|
||||||
|
|
||||||
pushRetainedEpoch(nostrGroupId, retainedBefore)
|
pushRetainedEpoch(nostrGroupId, retainedBefore)
|
||||||
persistGroup(nostrGroupId)
|
persistGroup(nostrGroupId)
|
||||||
|
|||||||
Reference in New Issue
Block a user