Skip to main content

➕ 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 Kfor 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:

📝 Continue: Passing Notes

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