Multi-Device Linking and Rotation
Overview
VeliKey Messenger supports secure multi-device synchronization with independent per-device mailboxes and coordinated rotation cadence. This architecture ensures message delivery across all linked devices while maintaining privacy and security.
Architecture
Mailbox Design
Each user account maintains multiple mailboxes:
- Per-Device Mailboxes: Each linked device has its own ERAC mailbox for receiving messages
- Sync Mailbox: A shared mailbox for sent messages and read states across devices
- Rotation Coordination: Receiver-initiated CAS (Compare-And-Swap) rotation with jitter
graph LR
A[Sender] --> B[Device 1 Mailbox]
A --> C[Device 2 Mailbox]
D[Device 1] --> E[Sync Mailbox]
F[Device 2] --> E
Recovery Codes
Recovery codes enable secure device linking without exposing cryptographic material.
Format
- 6 groups of 4 alphanumeric characters
- Example:
XXXX-XXXX-XXXX-XXXX-XXXX-XXXX - 24-hour expiration
- Single-use only
Security Properties
- Cryptographically random generation
- Time-limited validity
- Automatic invalidation after use
- No key material transmission
Device Linking Flow
Primary Device (Initiator)
// Generate recovery code
const manager = new MultiDeviceManager();
const code = manager.generateRecoveryCode();
// Display code to user: "ABCD-1234-WXYZ-5678-MNOP-9012"
Secondary Device (Joiner)
// User enters recovery code
const manager = new MultiDeviceManager();
const device = manager.linkDevice(code, {
deviceId: generateDeviceId(),
deviceName: 'iPhone 15 Pro',
publicKey: devicePublicKey,
attestation: attestationData
});
// Set up mailboxes
manager.setDeviceMailbox(device.deviceId, newMailboxId);
manager.setSyncMailbox(sharedSyncMailboxId);
Rotation Management
CAS Rotation Protocol
The Compare-And-Swap rotation ensures only one device successfully rotates at a time:
type CASRotateRequest struct {
MboxIDOld string // Current mailbox ID
FromSeq uint64 // Expected sequence number
RCap string // New redirect capsule
}
Rotation Cadence
Devices coordinate rotation using jittered intervals:
const rotationManager = new RotationManager({
baseIntervalMs: 6 * 60 * 60 * 1000, // 6 hours
jitterRatio: 0.2, // ±20% randomization
messageThreshold: 100, // Rotate after 100 messages
minIntervalMs: 60 * 60 * 1000 // Minimum 1 hour between rotations
});
// Automatic rotation on app open
rotationManager.onAppOpen();
// Track message fetches
rotationManager.onMessagesFetched(messageCount);
Epoch Overlap
During rotation, both old and new epochs remain valid for a grace period:
- Overlap Duration: 5 minutes default
- Purpose: Handle in-flight messages during rotation
- Cleanup: Old epoch expires after grace period
Message Routing
Incoming Messages
- Sender encrypts message for recipient
- Sender deposits to all recipient's device mailboxes
- Each device fetches from its own mailbox
- No cross-device mailbox access
Sent Messages
- Sending device encrypts message
- Copy deposited to sync mailbox
- All devices fetch from sync mailbox
- Sent messages visible across devices
Read States
- Device marks message as read
- Read state deposited to sync mailbox
- Other devices fetch and update UI
Security Considerations
Mailbox Independence
- Each device mailbox uses unique keys
- Compromise of one device doesn't affect others
- Device revocation removes only its mailbox
Rotation Race Conditions
- CAS ensures single winner in concurrent rotations
- Sequence numbers prevent replay attacks
- Exponential backoff on conflicts
Forward Secrecy
- Regular rotation limits exposure window
- Old keys deleted after epoch expiry
- No long-term key material retention
Implementation
JavaScript/TypeScript SDK
import { MultiDeviceManager } from 'velikey-js-sdk';
const manager = new MultiDeviceManager();
// Generate recovery code for new device
const code = manager.generateRecoveryCode();
// Link device
const device = manager.linkDevice(code, request);
// Manage mailboxes
manager.setDeviceMailbox(deviceId, mailboxId);
manager.setSyncMailbox(syncMailboxId);
// Export/import state
const state = manager.exportState();
manager.importState(state);
iOS SDK (Swift)
import Aegis
let manager = MultiDeviceManager()
// Generate recovery code
let code = manager.generateRecoveryCode()
// Link device
if let device = manager.linkDevice(code: code, request: request) {
// Success
}
// Secure persistence
try manager.saveToKeychain()
try manager.loadFromKeychain()
Android SDK (Kotlin)
import io.velikey.aegis.multidevice.MultiDeviceManager
val manager = MultiDeviceManager(context)
// Generate recovery code
val code = manager.generateRecoveryCode()
// Link device
val device = manager.linkDevice(code, request)
// Secure persistence
manager.saveToPreferences()
manager.loadFromPreferences()
Testing
Unit Tests
- Recovery code generation and validation
- Device linking and unlinking
- Mailbox management
- State persistence
Integration Tests
- Cross-device message delivery
- Rotation coordination
- Conflict resolution
- Network failure handling
E2E Tests
- Full multi-device flow
- Rotation race conditions
- Message synchronization
- Device revocation
Monitoring
Metrics
Monitor rotation health via spool metrics endpoint:
curl http://spool-service/v1/metrics
# HELP spool_rotate_success_total Successful CAS rotations
# TYPE spool_rotate_success_total counter
spool_rotate_success_total 1523
# HELP spool_rotate_conflict_total CAS rotation conflicts
# TYPE spool_rotate_conflict_total counter
spool_rotate_conflict_total 12
Console Dashboard
The Aegis Console provides real-time rotation metrics:
- Success/conflict counts
- Time-series visualization
- Per-mailbox status inspector (dev mode)
Troubleshooting
Common Issues
- Recovery Code Invalid
- Check expiration (24 hours)
- Verify not already used
-
Ensure correct entry
-
Rotation Conflicts
- Normal during simultaneous device use
- Automatic retry with backoff
-
Check rotation metrics
-
Message Not Syncing
- Verify sync mailbox configuration
- Check network connectivity
- Review device linking status
Debug Tools
# Check mailbox status
curl "http://spool/v1/rotate-status?mbox_id=<id>"
# View rotation metrics
curl http://spool/v1/metrics | grep rotate
# Inspect device state (iOS)
po manager.getLinkedDevices()
# Inspect device state (Android)
Log.d("Devices", manager.getLinkedDevices().toString())
Best Practices
- Recovery Code Handling
- Display clearly with proper formatting
- Provide copy functionality
-
Clear from memory after use
-
Rotation Timing
- Avoid rotation during active messaging
- Implement grace periods for in-flight messages
-
Use jitter to prevent thundering herd
-
Device Management
- Limit number of linked devices (e.g., 5)
- Implement device naming for user clarity
-
Provide easy revocation interface
-
Error Handling
- Graceful fallback on rotation failure
- Retry with exponential backoff
- Clear user feedback on issues