📝 Passing Notes in Class
Sending Messages in MLS
In 15 minutes: Learn to send and receive encrypted group messages
Prerequisite: Adding members
🎯 The Simple Story
Alice and Bob are in a secret meeting room (MLS group).
Alice wants to tell Bob: "Meeting at 2pm"
How does she do it?
She whispers in the room (encrypts with group secret K₁). Bob hears (decrypts with K₁) because he's in the room
Eve is outside the room (can't hear anything).
🧠 Mental Model
Hold this picture in your head:
Passing Notes (MLS Encrypted Messages):
Inside the room:
┌─────────────────────────────┐
│ Alice, Bob, Charlie inside │
│ Shared group secret: K₁ │
└─────────────────────────────┘
Alice wants to send: Hello:
1. Alice whispers: Hello
2. Encrypt with K₁ (group secret)
3. Send to: Bob, Charlie
Bob receives:
1. Decrypts with K₁ (he's inside)
2. Reads: Hello
Charlie receives:
1. Decrypts with K₁ (he's inside)
2. Reads: Hello
Eve (outside):
1. Can't decrypt K₁ (not in room)
2. Can't read anything
📊 See It Happen
🎭 Code: Sending a Message
Alice Encrypts
// Alice's code
// Alice wants to send message to group
const message = 'Hello everyone Meeting at 2pm';
// Encrypt for the group
const envelope = await alice.encryptMessage('team-chat', message);
// envelope contains:
// - groupId
// - ciphertext
// - timestamp
// Send envelope to transport (e.g., server)
// await sendToServer(envelope);
Bob Decrypts
// Bob's code
// Bob receives envelope from server
// const envelope = await receiveFromServer();
// Decrypt
const plaintext = await bob.decryptMessage(envelope);
console.log('From Alice:', plaintext);
// Output: Hello everyone Meeting at 2pm
🔄 What Happens Behind the Scenes?
encryptMessage() Flow
// What encryptMessage() does
await alice.encryptMessage('team-chat', 'Hello'):
1. Alice gets current group state
├─ group_id
├─ epoch
└─ K₁ (current group secret)
2. Alice derives message key
├─ Use MLS message derivation
├─ Based on K₁ + epoch
└─ Get: message_key
3. Alice encrypts message
├─ plaintext: Hello
├─ key: message_key
├─ algorithm: AES-128-GCM
└─ ciphertext: C
4. Alice creates message envelope
├─ ciphertext: C
├─ timestamp: now
├─ authentication: Ed25519 signature
└─ group_id
5. Alice updates state
├─ Update leaf secret (ratcheting)
└─ Generate new leaf secret
6. Return envelope
Note: Bob does the same steps in reverse
decryptMessage() Flow
// What decryptMessage() does
await bob.decryptMessage(envelope):
1. Bob gets envelope
├─ ciphertext
├─ timestamp
└─ group_id
2. Bob verifies group
├─ Check group_id
└─ Check he's a member
3. Bob derives message key
├─ Use MLS message derivation
│ (same as Alice used)
├─ Based on K₁ + epoch
└─ Get: message_key
4. Bob decrypts message
├─ ciphertext: C
├─ key: message_key
├─ algorithm: AES-128-GCM
└─ plaintext: Hello
5. Bob verifies signature
├─ Check who sent it
└─ Prevent forgery
6. Bob updates state
├─ Update leaf secret (ratcheting)
└─ Generate new leaf secret
7. Return plaintext
🎮 Try It Yourself
Question 1: Alice encrypts "Hello" with K₁ and sends to Bob using transport. What does Bob do to read it?
Show Answer
Alice's steps:
- Get group secret K₁
- Derive message key from K₁
- Encrypt "Hello" = ciphertext C
- Send C to Bob
Bob's steps:
- Receive C
- Get group secret K₁
- Derive message key from K₁ (same derivation)
- Decrypt C = "Hello"
Answer: Bob decrypts using K₁ (same key Alice used)
Question 2: Eve intercepts an encrypted message. She doesn't have K₁. Can she read it?
Show Answer This
Eve has:
- Ciphertext C
- Wants to read "Hello"
Eve tries:
- Decryption needs message_key
- message_key derived from K₁
- Eve doesn't have K₁ (not in group)
- Can't derive message_key
- Can't decrypt C
Answer: No, Eve can't decrypt (missing K₁)
Question 3: How does MLS prevent message replay?
Show Answer
Message envelopes include:
- timestamp
- epoch
Each message is unique:
- Same message in same epoch can't be replayed
- Signatures authenticate sender
- Timestamps prevent timing attacks
Answer: Timestamp + signatures prevent replay
💡 Complete Example
// ==========================================
// ALICE SENDS MESSAGE
// ==========================================
// Alice encrypts
const envelope = await alice.encryptMessage('team-chat', 'Meeting at 2pm');
// envelope structure:
// - groupId: Uint8Array
// - ciphertext: Uint8Array
// - timestamp: number
console.log('Encrypted message:', envelope.ciphertext);
// Send to transport (WebSocket, REST API, P2P, etc.)
// const result = await sendToServer(envelope);
// ==========================================
// BOB RECEIVES MESSAGE
// ==========================================
// Bob receives from transport
// const envelope = await receiveFromServer();
// Decrypt
const plaintext = await bob.decryptMessage(envelope);
console.log('Decrypted:', plaintext);
// Meeting at 2pm
✅ Quick Check
Can you explain MLS messaging to a 5-year-old?
Try saying this out loud:
"Sending messages in MLS is like whispering in a soundproof room. Everyone inside the room can hear you, but people outside can't hear anything. The secret is that everyone inside knows the same secret password to understand what's being said"
🎓 Key Takeaways
✅ encryptMessage() = Encrypt with group secret
✅ decryptMessage() = Decrypt with group secret
✅ Message_key = Derived from K₁ + epoch
✅ Envelope = ciphertext + metadata
✅ Everyone in group = Can decrypt (has K₁)
✅ People outside = Can't decrypt (no K₁)
✅ Signatures = Authenticate sender
🎉 What You'll Learn Next
Now messaging works Let's handle members leaving:
👋 Continue: Kicking Someone Out
We'll learn how to remove members from an MLS group
Now you know how to send messages. Next: Let's remove members