Skip to main content

💻 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:

  1. Using TypeScript/WASM implementation
  2. Complete example: Alice and Bob messaging
  3. Best practices and implementation details
  4. 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

  1. Use.CryptographyProvider wrapper
  2. Wait for cryptoReady before operations
  3. Store keys encrypted
  4. Generate new ephemeral keys per handshake
  5. Verify signatures (Eve's keys detection)
  6. Delete message keys after use

❌ Don't

  1. ❌ Use key material directly (use derived keys)
  2. ❌ Store plaintext private keys
  3. ❌ Reuse ephemeral keys
  4. ❌ Skip signature verification
  5. ❌ Initialize multiple times (wait for cryptoReady)
  6. ❌ 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.