🔗 Initial Secret Setup
Complete X3DH Handshake Walkthrough
In 20 minutes: Trace the complete X3DH handshake from Alice to Bob
Prerequisite: Four Types of Keys
🎯 The Simple Story
Alice wants to send Bob a message, but Bob is offline.
X3DH handshake steps:
- Bob uploads keys (while offline)
- Alice downloads keys (from server)
- Alice verifies keys (check signature)
- Alice generates ephemeral (per handshake)
- Alice computes 4 DH (shared secret derivation)
- Alice sends message (with ephemeral included)
- Bob receives message (when he comes online)
- Bob computes 4 DH (same as Alice!)
- Both have shared secret (can start Double Ratchet!)
🧠 Mental Model
Hold this picture in your head:
X3DH Handshake Flow:
Bob (Offline):
┌────────────────────────────────┐
│ Step 1: Create & Upload Keys │
├────────────────────────────────┤
│ • IK_B (Identity Key) │
│ • SPK_B (Signed Pre-Key) │
│ • OPK_B[] (100 One-Time Keys) │
│ • SIG_B (Signature of SPK_B) │
└────────────────────────────────┘
↓
Upload
↓
Bob goes offline
6 months later...
Alice (Online):
┌────────────────────────────────┐
│ Step 2: Download Bob's Keys │
├────────────────────────────────┤
│ • pk(IK_B), pk(SPK_B) │
│ • pk(OPK_B[0]), SIG_B │
└────────────────────────────────┘
↓
─────────────────────────────────────────
┌────────────────────────────────┐
│ Step 3: Verify SIG_B │
├────────────────────────────────┤
│ verify(pk(SPK_B), IK_B) │
│ ✅ Valid! Bob's keys │
└────────────────────────────────┘
↓
─────────────────────────────────────────
┌────────────────────────────────┐
│ Step 4: Generate EK_A │
├────────────────────────────────┤
│ • EK_A (Ephemeral Key) │
└────────────────────────────────┘
↓
─────────────────────────────────────────
┌────────────────────────────────┐
│ Step 5: Compute 4 DH │
├────────────────────────────────┤
│ DH1 = IK_A × SPK_B │
│ DH2 = EK_A × IK_B │
│ DH3 = EK_A × SPK_B │
│ DH4 = EK_A × OPK_B[0] │
└────────────────────────────────┘
↓
─────────────────────────────────────────
┌────────────────────────────────┐
│ Step 6: Derive Shared Secret │
├────────────────────────────────┤
│ SK = KDF(DH1 || DH2 || DH3) │
│ || || || DH4) │
└────────────────────────────────┘
↓
Send encrypted message
+ pk(EK_A) + pk(OPK_B[0]) to Bob
↓
↓
Bob comes online!
↓
─────────────────────────────────────────
┌────────────────────────────────┐
│ Step 7: Bob receives message │
├────────────────────────────────┤
│ • Extract pk(EK_A) │
│ • Retrieve sk(OPK_B[0]) │
│ • Delete OPK_B[0] │
└────────────────────────────────┘
↓
─────────────────────────────────────────
┌────────────────────────────────┐
│ Step 8: Bob Computes 4 DH │
├────────────────────────────────┤
│ DH1 = SPK_B × IK_A │
│ DH2 = IK_B × EK_A │
│ DH3 = SPK_B × EK_A │
│ DH4 = OPK_B[0] × EK_A │
└────────────────────────────────┘
↓
─────────────────────────────────────────
┌────────────────────────────────┐
│ Step 9: Derive Same Secret │
├────────────────────────────────┤
│ SK = KDF(DH1 || DH2 || DH3) │
│ || || || DH4) │
└────────────────────────────────┘
↓
Alice has SK
Bob has SK
Eve doesn't!
↓
Start Double Ratchet!
📊 See It Happen
Complete X3DH sequence:
🎭 The Story: Alice's First Message
Bob's Setup (6 months ago):
Bob installed Signal. He created:
- Identity key (IK_B = permanent)
- Signed pre-key (SPK_B = this week)
- 100 one-time pre-keys (OPK_B[0] to OPK_B[99])
Bob signed SPK_B with IK_B and uploaded all to server. Then he powered down his computer (offline).
Alice's First Message (today):
Alice needs to tell Bob: "I love you"
Alice opens Signal, enters Bob's phone number.
Signal server sends Alice:
- pk(IK_B) - Bob's identity key
- pk(SPK_B) - Bob's signed pre-key
- pk(OPK_B[0]) - First one-time pre-key
- SIG_B - Signature of SPK_B
Alice verifies: Is SIG_B valid for pk(SPK_B)? ✅ Yes!
Alice creates: EK_A (ephemeral key just for this message)
Alice computes:
- DH1 = Alice's identity × Bob's SPK
- DH2 = Alice's ephemeral × Bob's identity
- DH3 = Alice's ephemeral × Bob's SPK
- DH4 = Alice's ephemeral × Bob's OPK[0]
Alice derives: SK (shared secret) = KDF(DH1, DH2, DH3, DH4)
Alice encrypts: "I love you" with SK → "Kj7$mP9q..."
Alice sends: "Kj7$mP9q..." + pk(EK_A) + pk(OPK_B[0])
Bob's Receives (tomorrow):
Bob powers on his computer.
Bob sees Alice's message. Extracts pk(EK_A) and pk(OPK_B[0]).
Bob retrieves sk(OPK_B[0]) from his device, then deletes OPK_B[0].
Bob computes:
- DH1 = Bob's SPK × Alice's identity (same as Alice's DH1!)
- DH2 = Bob's identity × Alice's ephemeral (same as Alice's DH2!)
- DH3 = Bob's SPK × Alice's ephemeral (same as Alice's DH3!)
- DH4 = Bob's OPK[0] × Alice's ephemeral (same as Alice's DH4!)
Bob derives: SK (shared secret) = KDF(DH1, DH2, DH3, DH4) (same SK!)
Bob decrypts: "Kj7$mP9q..." with SK → "I love you"
Bob reads: "I love you"
Both Alice and Bob now have SK. They can start the Double Ratchet (forward secrecy per message)!
🔢 The Math
Alice's X3DH Steps
# Step 1: Download Bob's keys
pk(IK_B), pk(SPK_B), pk(OPK_B[0]), SIG_B ← Download(BobID)
# Step 2: Verify signature
Verify(pk(SPK_B), SIG_B, pk(IK_B)) → ✅ Valid
# Step 3: Generate ephemeral
sk(EK_A), pk(EK_A) ← GenerateKeyPair()
# Step 4: Compute 4 Diffie-Hellman operations
DH1 = DH(sk(IK_A), pk(SPK_B))
DH2 = DH(sk(EK_A), pk(IK_B))
DH3 = DH(sk(EK_A), pk(SPK_B))
DH4 = DH(sk(EK_A), pk(OPK_B[0]))
# Step 5: Derive shared secret
SK = KDF(DH1 || DH2 || DH3 || DH4)
# Step 6: Encrypt message
Ciphertext = Encrypt(message, SK)
# Step 7: Send
Send(Ciphertext, pk(EK_A), pk(OPK_B[0]))
Bob's X3DH Steps
# Step 1: Receive message
Ciphertext, pk(EK_A), pk(OPK_B[0]) ← Receive()
# Step 2: Retrieve and delete OPK
sk(OPK_B[0]) ← Retrieve(PRIVATE_KEY_STORE)
Delete(sk(OPK_B[0]), pk(OPK_B[0]))
# Step 3: Compute 4 Diffie-Hellman operations
DH1 = DH(sk(SPK_B), pk(IK_A))
DH2 = DH(IK_B, pk(EK_A))
DH3 = DH(sk(SPK_B), pk(EK_A))
DH4 = DH(sk(OPK_B[0]), pk(EK_A))
# Step 4: Derive shared secret
SK = KDF(DH1 || DH2 || DH3 || DH4)
# Step 5: Decrypt message
Message = Decrypt(Ciphertext, SK)
Key Point: DH operations are commutative. Alice's and Bob's computations match!
💡 Why We Care
Why X3DH Works for Offline Users
Traditional DH (online both parties):
- Alice: "Bob, are you online? Let's do DH!"
- Bob: "Yes!"
- Alice and Bob agree: g^(ab) → shared secret
- Problem: Both must be online simultaneously
X3DH (Bob can be offline):
- Bob uploads keys to server (while offline)
- Alice downloads keys, does her part
- Bob comes online, does his part
- Alice and Bob still agree: shared secret S
Security Properties
| Property | How X3DH Provides It |
|---|---|
| Confidentiality | Eve sees public keys only, can't compute S (missing EK_A) |
| Authentication | SIG_B verifies Bob's keys (SOPA-resistant) |
| Forward secrecy | OPK deleted after use, EK_A deleted after handshake |
| Deniability | OPK provides deniability (DH4) |
| Message integrity | MAC tag on ciphertext (derived from SK) |
✅ Quick Check
Can you trace X3DH?
Step-by-step:
- Bob uploads IK_B, SPK_B, OPK_B[], SIG_B to server
- Alice downloads Bob's keys
- Alice verifies SIG_B (is it really Bob's SPK?)
- Alice creates EK_A
- Alice computes DH1, DH2, DH3, DH4
- Alice derives SK = KDF(DH1 || DH2 || DH3 || DH4)
- Alice sends message with pk(EK_A) + pk(OPK_B[0])
- Bob receives message
- Bob computes same DH1-DH4, derives same SK
- Bob decrypts
- Both have SK!
Why 4 DH operations?
Security benefits:
DH1: Identity × signed pre-key (long-term security) DH2: Ephemeral × identity (forward security) DH3: Ephemeral × signed pre-key (extra entropy) DH4: Ephemeral × one-time pre-key (deniability + non-repudiation)
Combined: All 4 needed to break. Hard!
📋 Key Takeaways
✅ Bob uploads: IK, SPK, OPK[], SIG (when offline)
✅ Alice downloads: Bob's keys and verifies signature
✅ Alice generates: Ephemeral key (EK_A)
✅ 4 DH operations: Each provides different security
✅ Shared secret: KDF(DH1 || DH2 || DH3 || DH4)
✅ Alice sends: Encrypted message + pk(EK_A) + pk(OPK_B)
✅ Bob retrieves: sk(OPK) and computes same DH operations
✅ Both derive: Same shared secret SK
✅ Result: Can start Double Ratchet for forward secrecy
🎉 What You'll Learn Next
Now you understand the complete X3DH handshake! Next, we'll learn why verification steps are important.
✓ Continue: Verifying Bob's Keys
We'll learn why signatures matter and how Eve might try to impersonate Bob!
Complete X3DH handshake explained! Next: Why verify keys?