Skip to content

Commit 1d81c65

Browse files
committed
fix: pad kind:445 and Welcome plaintexts to fixed-size buckets
Pad the MLS plaintext to a power-of-two bucket before encryption so a relay observer cannot fingerprint message classes by on-the-wire size. - kind:445 group messages floor at 512 bytes (chosen so an empty SelfRemove PublicMessage and a typical short text-note rumor land in the same bucket — a 256-byte floor leaves them distinguishable because the inner rumor JSON already pushes a "hi" message to ~280 bytes of MLS PrivateMessage plaintext). - Welcome rumor payloads floor at 1024 bytes so small-group invitations share a bucket regardless of exact member count. Receivers switch from MlsMessageIn::tls_deserialize_exact to non-strict tls_deserialize so trailing zero-padding bytes are ignored; legacy unpadded messages continue to decode unchanged. The Welcome receiver path already used non-strict deserialization. Closes marmot-protocol/marmot-security#33 (Welcome size leaks group size) Closes marmot-protocol/marmot-security#37 (SelfRemove size leaks departure intent)
1 parent 592a582 commit 1d81c65

5 files changed

Lines changed: 525 additions & 3 deletions

File tree

crates/mdk-core/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
### Breaking changes
2727

28+
- **kind:445 group message and Welcome rumor wire formats now pad the encrypted plaintext to power-of-two buckets.** Senders pad the serialized MLS payload to a minimum of 512 bytes for kind:445 events and 1024 bytes for Welcome rumors, then round up to the next power of two for larger payloads. Receivers in this release accept both padded and legacy unpadded inputs, but older receivers using `MlsMessageIn::tls_deserialize_exact` will reject padded messages — all clients must upgrade. Closes [marmot-protocol/marmot-security#33](https://github.com/marmot-protocol/marmot-security/issues/33) (Welcome size leaks group member count) and [marmot-protocol/marmot-security#37](https://github.com/marmot-protocol/marmot-security/issues/37) (SelfRemove size leaks departure intent). ([#308](https://github.com/marmot-protocol/mdk/pull/308))
2829
- **MIP-04 media upload/reference structs now include optional audio display metadata.** `EncryptedMediaUpload` and `MediaReference` both add `duration_ms: Option<u64>` and `waveform: Option<Vec<u8>>`; callers constructing these structs directly must provide the new fields. The IMETA parser reads optional NIP-A0 `duration` and `waveform` entries on a best-effort basis, and the tag builder emits them when provided. ([#300](https://github.com/marmot-protocol/mdk/pull/300))
2930
- **Extension version bumped from 2 to 3**: `NostrGroupDataExtension::CURRENT_VERSION` is now `3`. The new version adds a `disappearing_message_secs` field to the TLS-serialized group data extension. Existing v1/v2 groups are forward-compatible (the field deserializes as `None`). `NostrGroupConfigData::new` now takes an additional `disappearing_message_secs: Option<u64>` parameter. ([#253](https://github.com/marmot-protocol/mdk/pull/253))
3031
- **`NostrGroupDataExtension::migrate_to_v2` removed from public API.** The 0.8.0 method was used only to construct test fixtures; production code never migrates extensions in-place (deserialization upgrades v1/v2 → v3 through `into_v3`, and new groups author v3 directly). Mirrors the existing convention for `migrate_group_image_v1_to_v2`, which was already documented as internal-only. ([#253](https://github.com/marmot-protocol/mdk/pull/253))
@@ -39,6 +40,8 @@
3940

4041
### Fixed
4142

43+
- Padded kind:445 message plaintexts and Welcome rumor payloads to fixed power-of-two buckets so a relay observer cannot fingerprint SelfRemove proposals by size or estimate group member count from gift-wrap event length. Empty SelfRemove `PublicMessage`s now share a bucket with short text messages, and small-group Welcomes share a bucket with each other regardless of exact member count. Closes [marmot-protocol/marmot-security#33](https://github.com/marmot-protocol/marmot-security/issues/33) and [marmot-protocol/marmot-security#37](https://github.com/marmot-protocol/marmot-security/issues/37). ([#308](https://github.com/marmot-protocol/mdk/pull/308))
44+
- Relaxed `process_mls_message` from `MlsMessageIn::tls_deserialize_exact` to non-strict `tls_deserialize` so the trailing zero padding bytes added by the new sender path are ignored. The receiver also rejects non-zero trailing bytes, pinning the padding region to "zero only" so it cannot be used as a covert channel. Legacy unpadded messages continue to deserialize successfully. ([#308](https://github.com/marmot-protocol/mdk/pull/308))
4245
- Fixed admin auto-commit of legacy `Remove(self)` leaves in mixed/legacy groups so departing members are actually removed instead of silently remaining in the MLS group. ([#288](https://github.com/marmot-protocol/mdk/pull/288))
4346
- Accepted `NostrGroupDataExtension` payloads from future versions with unknown trailing fields, per MIP-01's forward-compatibility requirement. Previously, any v(N+1) extension on the wire was rejected by `deserialize_bytes`, which would have bricked every group operation (commit/proposal/welcome/admin checks) the moment any peer authored a newer-version extension. ([#88](https://github.com/marmot-protocol/marmot-security/issues/88))
4447
- Recorded rollback snapshots for locally merged admin-list updates so clients can converge on the MIP-03 winner when competing admins concurrently mutate `admin_pubkeys`. ([#289](https://github.com/marmot-protocol/mdk/pull/289))

0 commit comments

Comments
 (0)