Files
amethyst/quartz
Claude c88923a3d1 fix(mls): decrypt retained-epoch messages correctly (Test 12 offline catchup)
The `tryDecryptWithRetainedEpoch` fallback in `MlsGroupManager.decrypt`
exists precisely to cover the interop harness's Test 12 scenario —
application messages encrypted under epoch N arriving at a receiver
that has already processed a commit advancing to N+1. The primary
path throws "Message epoch X doesn't match current epoch Y", then the
manager iterates the retained epoch window and tries each.

Two bugs kept the fallback from ever succeeding:

1. sender-data ciphertext sample length. The primary decrypt path uses
   `MlsCryptoProvider.HASH_OUTPUT_LENGTH` (32 bytes, = KDF.Nh for
   HKDF-SHA256, per RFC 9420 §6.3.2); the retained path was using
   `AEAD_KEY_LENGTH` (16). The comment on the primary path — "Using
   AEAD.Nk here made sender-data decryption fail against every
   spec-compliant sender" — was the same fix just never propagated
   to this branch. sender-data AEAD always failed, the try/catch
   swallowed it, and the fallback returned null.

2. content plaintext was returned raw. AEAD output is a
   PrivateMessageContent struct (§6.3.1):
     `applicationData<V> || signature<V> || padding`.
   The primary decrypt path parses this with `pmcReader.readOpaqueVarInt()`
   to extract applicationData; the retained path returned the whole
   struct. Callers saw a length-prefixed blob with signature + zero
   padding glued on — obvious in a raw diff, invisible in a pass/fail
   test that only checked "decrypted is not null".

Fix both in `tryDecryptWithRetainedEpoch`. Existing quartz↔quartz
tests still pass because they don't exercise out-of-order decrypt —
this branch was effectively dead code before.

Add testDecryptRetainedEpoch_ApplicationMessageAfterCommit as a
regression: Bob encrypts at epoch N, Alice advances to N+1 via
add-member, Bob's epoch-N message then arrives at Alice — must round-
trip through the retained-epoch fallback and match the original
plaintext exactly.
2026-04-23 00:30:57 +00:00
..