💻 Real Code Examples
Signal Protocol Implementation
In 30 minutes: See the Signal Protocol in action with TypeScript/WASM
Prerequisites: X3DH + Double Ratchet concepts
🎯 The Simple Story
You understand the theory. Now let's see the real Signal Protocol in practice!
We'll cover:
- Using TypeScript/WASM implementation
- Complete example: Alice and Bob messaging
- Best practices and implementation details
- Testing and verification
🚀 Getting Started
Installation
The Signal Protocol is implemented in TypeScript with Go WASM backing for performance.
npm install --save @positive-intentions/cryptography
Basic Setup
import { useCryptography } from '@positive-intentions/cryptography/ui'
import { CryptographyProvider } from '@positive-intentions/cryptography/components/Cryptography'
function SignalProtocolDemo() {
const crypto = useCryptography()
return (
<CryptographyProvider>
<SignalProtocolExample />
</CryptographyProvider>
)
}
📓 Complete Example: Alice Messages Bob
Step 1: Initialize Users
import { useCryptography } from '@positive-intentions/cryptography/ui'
function SignalProtocolExample() {
const crypto = useCryptography()
const initializeUsers = async () => {
// Create Alice and Bob users
const alice = await crypto.initializeSignalUser("Alice")
const bob = await crypto.initializeSignalUser("Bob")
console.log("Alice's Identity Key:", await crypto.exportSignalPublicKey(alice.identityKeyPair.publicKey))
console.log("Bob's Identity Key:", await crypto.exportSignalPublicKey(bob.identityKeyPair.publicKey))
return { alice, bob }
}
initializeUsers()
}
Step 2: Bob Creates Key Bundle
const getBobsKeys = async (bob) => {
// Get Bob's public key bundle
const bobBundle = await crypto.getSignalPublicKeyBundle(bob)
return {
identityKey: await crypto.exportSignalPublicKey(bobBundle.identityKey),
signedPreKey: crypto.bufferToSignalHex(bobBundle.signedPrekey),
oneTimePreKey: bobBundle.oneTimePrekey
? crypto.bufferToSignalHex(bobBundle.oneTimePrekey)
: null
}
}
Step 3: Alice Initiates X3DH Handshake
const performX3DH = async (alice, bobBundle) => {
// Create ephemeral key for this session
const aliceEphemeralPair = await crypto.generateSignalKeyPair()
// Perform X3DH key exchange
const keyExchangeResult = await crypto.performSignalX3DHKeyExchange(
alice,
bobBundle
)
console.log("Shared Secret (first 16 bytes):",
crypto.bufferToSignalHex(keyExchangeResult.masterSecret.slice(0, 16)))
return {
sharedSecret: keyExchangeResult.masterSecret,
aliceEphemeralPair,
keyExchangeResult
}
}
Step 4: Complete Messaging Flow
import React, { useState } from 'react'
import { useCryptography } from '@positive-intentions/cryptography/ui'
function SecureMessaging() {
const crypto = useCryptography()
const [messages, setMessages] = useState([])
const [alice, setAlice] = useState(null)
const [bob, setBob] = useState(null)
const sendMessage = async (sender, receiver, message: string) => {
if (!crypto.cryptoReady) {
await crypto.initializeCrypto()
}
// Encrypt message from sender to receiver
const encrypted = await crypto.encryptSignalMessage(
sender,
receiver,
new TextEncoder().encode(message)
)
// Add to message history
setMessages(prev => [...prev, {
sender,
receiver,
encrypted,
timestamp: Date.now()
}])
return encrypted
}
const receiveMessage = async (encrypted, receiver, sender) => {
// Decrypt message
const decrypted = await crypto.decryptSignalMessage(
receiver,
sender,
encrypted
)
return new TextDecoder().decode(decrypted)
}
return { sendMessage, receiveMessage }
}
🔑 Key Management
Storing Keys securely
const storeKeys = async (userKeys, password: string) => {
// Derive encryption key from password
const encryptionKey = await crypto.deriveKey(password)
// Encrypt private keys (implementation-specific)
// Store encrypted keys in secure storage (localStorage, IndexedDB, keychain)
}
const loadKeys = async (userId, password: string) => {
const encryptionKey = await crypto.deriveKey(password)
// Load encrypted keys and decrypt
// Return user keys
}
💡 Implementation Pattern
Encryption Flow
const secureMessageFlow = async (plaintext: string): Promise<void> => {
// 1. Initialize users (first time only)
const alice = await crypto.initializeSignalUser("Alice")
const bob = await crypto.initializeSignalUser("Bob")
// 2. Bob's public keys are on server (for Alice to download)
const bobPublicKeys = await crypto.getSignalPublicKeyBundle(bob)
// 3. Alice performs X3DH to establish shared secret
const exchangeResult = await crypto.performSignalX3DHKeyExchange(
alice,
bobPublicKeys
)
// 4. Alice encrypts her message
const messageBytes = new TextEncoder().encode(plaintext)
const encrypted = await crypto.encryptSignalMessage(
alice,
bob,
messageBytes
)
// 5. Bob receives and decrypts
const decrypted = await crypto.decryptSignalMessage(
bob,
alice,
encrypted.ciphertext
)
const messageText = new TextDecoder().decode(decrypted)
console.log("Original:", plaintext)
console.log("Decrypted:", messageText)
}
✅ Testing
Verify X3DH
// Test: Shared secrets match
async function testX3DH() {
const alice = await crypto.initializeSignalUser("Alice")
const bob = await crypto.initializeSignalUser("Bob")
const bobKeys = await crypto.getSignalPublicKeyBundle(bob)
const exchange = await crypto.performSignalX3DHKeyExchange(alice, bobKeys)
console.log("Alice's secret:", crypto.bufferToSignalHex(exchange.masterSecret.slice(0, 16)))
console.log("Bob should match:", crypto.bufferToSignalHex(bobKeys.signedPrekey))
}
Verify Encryption/Decryption
const testEncryption = async () => {
const original = "Hello Bob!"
const alice = await crypto.initializeSignalUser("Alice")
const bob = await crypto.initializeSignalUser("Bob")
const encrypted = await crypto.encryptSignalMessage(
alice,
bob,
new TextEncoder().encode(original)
)
const decrypted = await crypto.decryptSignalMessage(
bob,
alice,
encrypted.ciphertext
)
const result = new TextDecoder().decode(decrypted)
console.assert(
original === result,
"Encryption/decryption test failed"
)
}
📋 Best Practices
✅ Do
- Use.CryptographyProvider wrapper
- Wait for cryptoReady before operations
- Store keys encrypted
- Generate new ephemeral keys per handshake
- Verify signatures (Eve's keys detection)
- Delete message keys after use
❌ Don't
- ❌ Use key material directly (use derived keys)
- ❌ Store plaintext private keys
- ❌ Reuse ephemeral keys
- ❌ Skip signature verification
- ❌ Initialize multiple times (wait for cryptoReady)
- ❌ Expose cryptostate to client-side JS
🎯 Summary
✅ Initialize users with initializeSignalUser()
✅ Get key bundles with getSignalPublicKeyBundle()
✅ Perform X3DH with performSignalX3DHKeyExchange()
✅ Encrypt/decrypt with encryptSignalMessage() / decryptSignalMessage()
✅ Use WASM backend for performance
✅ Wrapper provides clean API for browser use
🔗 Resources
- Source Code:
/cryptography/src/crypto/SignalProtocol/DoubleRatchet/DoubleRatchet.ts - Storybooks:
/cryptography/src/stories/SignalProtocol/ - Tests:
/cryptography/src/tests/signal-*.test.js
Real implementation complete! Now you know how to use the Signal Protocol in TypeScript/WASM.