Skip to main content

✓ Do This, Not That

Signal Protocol Implementation Guide

In 15 minutes: Learn how to implement Signal Protocol correctly
Prerequisites: Practical Implementation


🎯 Simple Setup

Use the cryptography library wrapper with proper initialization.

import { CryptographyProvider, useCryptography } from '@positive-intentions/cryptography/ui'

function App() {
return (
<CryptographyProvider>
<SignalProtocolDemo />
</CryptographyProvider>
)
}

function SignalProtocolDemo() {
const crypto = useCryptography()

// Wait for crypto to be ready
if (!crypto.cryptoReady) {
return <div>Loading cryptography...</div>
}

// Now you can use Signal Protocol functions
}

🔒 Storage and Encryption

✅ DO: Use DeriveKey for password-based encryption

// Good: Use deriveKey from crypto
const deriveEncryptionKey = async (password: string) => {
const salt = crypto.getRandomBytes(16)
const key = await crypto.deriveKey(password, salt)
return { key, salt }
}

❌ DON'T: Store plaintext private keys

// ❌ Terrible practice
const savePrivateKey = (key: ArrayBuffer) => {
return localStorage.setItem('private_key', key) // Security risk!
}

🔄 Initialization

✅ DO: Wait for cryptoReady

const crypto = useCryptography()

if (!crypto.cryptoReady) {
await crypto.initializeCrypto()
}

// Now safe to use Signal Protocol
const alice = await crypto.initializeSignalUser("Alice")

❌ DON'T: Initialize multiple times

// ❌ Bad
const crypto = useCryptography()
crypto.initializeCrypto()
crypto.initializeCrypto() // Don't call twice!

🔑 Key Storage

✅ DO: Keys expire automatically

The Signal Protocol implementation handles key expiry automatically via WASM. The Go backend generates ephemeral keys that expire after use.

❌ DON'T: Store derived keys long-term

// ❌ Don't store message keys K[1], K[2], etc.
localStorage.setItem('message_key_k1', key1)

These should be used once then deleted (managed by WASM backend).


🎯 Common Implementation Patterns

Pattern 1: Secure Messaging

const SecureMessenger = () => {
const crypto = useCryptography()
const [currentUser, setCurrentUser] = useState(null)

const sendmessage = async (recipientId: string, message: string) => {
// Get recipient's public keys
const recipientKeys = await crypto.getSignalPublicKeyBundle(recipientId)

// Encrypt
const encrypted = await crypto.encryptSignalMessage(
currentUser,
recipientKeys,
new TextEncoder().encode(message)
)

return encrypted
}

return { sendMessage }
}

Pattern 2: User Initialization

const initializeNewUser = async (username: string) => {
const crypto = useCryptography()

// Wait for initialization
if (!crypto.cryptoReady) {
await crypto.initializeCrypto()
}

const user = await crypto.initializeSignalUser(username)

// Get public key bundle for sharing
const publicBundle = await crypto.getSignalPublicKeyBundle(user)

return { user, publicBundle }
}

Pattern 3: Key Exchange

const establishSharedSecret = async (
sender: any,
recipientKeys: any
) => {
const exchange = await crypto.performSignalX3DHKeyExchange(
sender,
recipientKeys
)

return {
sharedSecret: exchange.masterSecret,
encryptionKey: exchange.masterSecret.slice(0, 32)
}
}

✅ Testing Checklist

Before using Signal Protocol in production:

  • Wait for crypto.cryptoReady before any operation
  • Verify initializeSignalUser returns valid user
  • Check getSignalPublicKeyBundle returns public keys
  • Confirm performSignalX3DHKeyExchange returns consistent result
  • Test encryptSignalMessagedecryptSignalMessage round-trip
  • Verify shared secrets match on both sides
  • Keys expire and clean up automatically

🎓 Resources

  • Implementation: /cryptography/src/crypto/SignalProtocol/DoubleRatchet/DoubleRatchet.ts
  • Storybooks: /cryptography/src/stories/SignalProtocol/
  • Tests: /cryptography/src/tests/signal-protocol*.test.js

Best practices complete! Now you know how to use the Signal Protocol library correctly.