➕ Inviting Guests
Adding Members to an MLS Group
In 15 minutes: Learn to add Bob and Charlie to Alice's group
Prerequisite: Creating a group
🎯 The Simple Story
Alice has created a secret meeting room. Now she wants to invite Bob and Charlie
How? She sends them VIP tickets (welcome messages) that contain:
- The secret to enter the room
- Proof the ticket is real
- Instructions on how to use it
Let's see how this works
🧠 Mental Model
Hold this picture in your head:
Adding Members to MLS Group:
State before:
┌─────────────────────┐
│ Alice's Group │
│ Epoch: 0 │
│ Members: Alice │
│ Group secret: K₀ │
└─────────────────────┘
Step 1: Alice and Bob prepare
Alice has key package
Bob generates key package
Step 2: Alice adds Bob
Alice creates: add proposal for Bob
Alice commits: proposal → commit
MLS creates: welcome message for Bob
MLS generates: new group secret K₁
Step 3: Bob receives welcome
Bob decrypts welcome with private key
Bob gets group secret K₁
Bob joins group
State after:
┌─────────────────────┐
│ Alice's Group │
│ Epoch: 1 │
│ Members: Alice, Bob │
│ Group secret: K₁ │
└─────────────────────┘
📊 See It Happen
🎭 Code: Adding Bob
Step 1: Bob Gets Ready
// Bob's code
import { MLSManager } from 'ts-mls';
// Step 1: Initialize Bob
const bob = new MLSManager('bob@example.com');
await bob.initialize();
// Step 2: Generate key package
const bobKeyPackage = await bob.generateKeyPackage();
// Step 3: Bob sends his key package to Alice
// (over email, HTTPS, P2P signaling, etc.)
Step 2: Alice Adds Bob
// Alice's code
// Step 1: Alice receives Bob's key package
const bobKeyPackagePublic: KeyPackage = /* from Bob */;
const bobKeyPackageBundle: MLSKeyPackageBundle = {
publicPackage: bobKeyPackagePublic,
userId: 'bob@example.com',
};
// Step 2: Alice adds Bob to the group
const { welcome, commit } = await alice.addMembers('team-chat', [bobKeyPackageBundle]);
// Step 3: Alice processes the commit (she updates to K₁)
await alice.processCommit('team-chat', commit);
Step 3: Bob Processes Welcome
// Bob's code
// Step 1: Bob receives welcome message (from Alice)
const welcomeMessage: Welcome = /* from Alice over transport */;
// Step 2: Bob processes welcome to join the group
const groupInfo = await bob.processWelcome(welcomeMessage);
console.log('Joined group:', new TextDecoder().decode(groupInfo.groupId));
console.log('Epoch:', groupInfo.epoch);
console.log('Members:', groupInfo.members);
Step 4: Now Bob Can Chat
// Bob's code
// Bob is in the group, can send messages
const envelope = await bob.encryptMessage('team-chat', 'Hello Alice');
// Alice can decrypt
const plaintext = await alice.decryptMessage(envelope);
console.log('From Bob:', plaintext); // Hello Alice
🔄 What Happens Behind the Scenes?
addMembers() Flow
// What addMembers() does
await alice.addMembers('team-chat', [bobKeyPackage]):
1. Alice gets current group state: (epoch 0, K₀)
2. Create add proposal:
├─ Proposer: Alice
├─ Type: add
└─ Include: Bob's key package
3. Execute commit:
├─ MLS generates new secrets on path
├─ New group secret: K₁
├─ Alice's leaf updated
├─ Bob's leaf added
│ └─ Leaf index: 1
└─ Ratchet tree updated
4. Create welcome message:
├─ Encrypt K₁ for Bob
│ └─ Use Alice's key package
├─ Include ratchet tree state
├─ Include group info
└─ Include epoch (1)
5. Return: { welcome, commit }
processWelcome() Flow
// What processWelcome() does
await bob.processWelcome(welcomeMessage):
1. Bob decrypts welcome message
├─ Use key package
└─ Get group secret K₁
2. Bob obtains ratchet tree state
└─ Rebuilds exact same tree Alice has
3. Bob joins group
├─ Store group state
├─ epoch: 1
├─ members: [alice, bob]
└─ has K₁
4. Bob can now:
├─ Encrypt messages
├─ Decrypt messages
└ Participate normally
🎮 Try It Yourself
Question 1: Alice adds Bob to the group. What's the new epoch?
Show Answer
Before:
- Epoch: 0
- Group secret: K₀
- Members: Alice
After Alice adds Bob:
- New group secret: K₁
- New epoch: 1
- Members: Alice, Bob
Epoch increments when members change
Answer: Epoch 1
Question 2: Bob receives the welcome and joins. Can he see messages from epoch 0?
Show Answer This
No
Epoch 0 messages:
- Encrypted with K₀
Bob joins:
- Gets K₁ from welcome
- Does NOT have K₀
- K₀ deleted (forward secrecy)
Bob tries to decrypt epoch 0 messages:
- Has K₁
- Needs K₀
- Fails
Answer: No, Bob can't read old epoch 0 messages
Question 3: After Bob joins, the group has 2 members. What's the ratchet tree look like?
Show Answer
Ratchet tree after Bob joins:
Alice(0) Bob(1)
│ │
└─ AB ───┘
│
GroupSecret
Leaves:
- Index 0: Alice
- Index 1: Bob
Bob is at leaf index 1, can reach the group secret through the tree
Answer: Alice at index 0, Bob at index 1
💡 Complete Example
Let's see the full flow:
// ==========================================
// BOB GETS READY
// ==========================================
import { MLSManager } from 'ts-mls';
const bob = new MLSManager('bob@example.com');
await bob.initialize();
const bobKeyPackage = await bob.generateKeyPackage();
// Share: alice gets bobKeyPackage.publicPackage
// ==========================================
// ALICE ADDS BOB
// ==========================================
const bobKeyPackageBundle: MLSKeyPackageBundle = {
publicPackage: bobKeyPackage.publicPackage,
userId: 'bob@example.com',
};
const { welcome, commit } = await alice.addMembers('team-chat', [bobKeyPackageBundle]);
await alice.processCommit('team-chat', commit);
// Send welcome to Bob (via transport)
// ...
// ==========================================
// BOB JOINS
// ==========================================
// Bob receives welcome via transport
await bob.processWelcome(welcome);
// ==========================================
// NOW THEY CAN CHAT
// ==========================================
// Bob sends to group
const envelope = await bob.encryptMessage('team-chat', 'Hello Alice');
// Alice receives and decrypts
const plaintext = await alice.decryptMessage(envelope);
console.log('Bob said:', plaintext); // Hello Alice
✅ Quick Check
Can you explain adding members to a 5-year-old?
Try saying this out loud:
"Adding members to an MLS group is like inviting someone to your secret club. You give them a ticket that's written in a special code they can read with their secret decoder ring. Once they crack the code, they can enter the room and talk with everyone inside"
🎓 Key Takeaways
✅ Key package = Bob's ID card
✅ addMembers() = Create add proposal, execute, make welcome
✅ Commit = Updates ratchet tree, generates K₁
✅ Welcome = Encrypted group secret for Bob
✅ Epoch increments = New group version
✅ Bob decrypts welcome → Gets K₁
✅ Bob can now chat → Encrypts/decrypts messages
🎉 What You'll Learn Next
Now Alice and Bob are in the group Let's see messages:
We'll learn how to send and receive encrypted messages in an MLS group
Now you can add members to an MLS group. Next: Send and receive messages