✓ 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.cryptoReadybefore any operation - Verify
initializeSignalUserreturns valid user - Check
getSignalPublicKeyBundlereturns public keys - Confirm
performSignalX3DHKeyExchangereturns consistent result - Test
encryptSignalMessage→decryptSignalMessageround-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.