Device Security & Backups
PIN Protection
The app is protected by a user-chosen PIN, verified using a deliberately slow hashing algorithm to resist brute-force attacks.
How PIN Hashing Works
| Parameter | Value |
|---|---|
| Algorithm | PBKDF2-SHA256 |
| Salt | 16 bytes, cryptographically random |
| Iterations | 10,000 (v2 format) |
| Output | 32-byte derived key |
| Storage | v2:<iterations>:<salt_hex>:<hash_hex> in SecureStore |
The PIN is never stored in plaintext. On verification, the entered PIN is hashed with the stored salt and compared using a constant-time comparison to prevent timing side-channel attacks:
function timingSafeEqual(a: string, b: string): boolean {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) {
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return diff === 0;
}Legacy Migration
Earlier versions used 600,000 PBKDF2 iterations (v1 format). On successful verification of a v1 PIN, the hash is automatically migrated to v2 format. This is transparent to the user.
In plain English: Your PIN is never stored as-is. Instead, it goes through a slow mathematical process that produces a fingerprint. When you enter your PIN, the app runs the same process and checks if the fingerprints match. The process is designed to be slow enough that trying millions of PINs would take an impractical amount of time.
Biometric Unlock
Biometric authentication (Face ID / Touch ID / fingerprint) can be enabled as an alternative to PIN entry. When enabled:
- The biometric preference is stored as a flag in SecureStore.
- The OS handles the actual biometric matching — Relay never accesses biometric data directly.
- Biometric unlock can be skipped during setup; PIN remains the fallback.
Auto-Lock
The app automatically locks when backgrounded, requiring PIN or biometric authentication to resume. The lock timing is configurable:
| Setting | Behavior |
|---|---|
| Every time | Locks immediately when app goes to background |
| After 1 minute | Locks if backgrounded for more than 60 seconds |
| After 5 minutes | Locks if backgrounded for more than 5 minutes |
| After 15 minutes | Locks if backgrounded for more than 15 minutes |
| After 1 hour | Locks if backgrounded for more than 1 hour |
| Never | App does not auto-lock |
The lock state is tracked using AppState listeners. When the app returns to the foreground, the elapsed background time is compared against the configured threshold.
Encrypted Backups
Relay provides a local encrypted backup system for your seed phrase and message history. Backups are encrypted on-device before being saved to local storage.
Backup Pipeline
Encryption Details
| Parameter | Value |
|---|---|
| Key derivation | Argon2id |
| Argon2 iterations | 3 |
| Argon2 memory | 64 MB |
| Argon2 parallelism | 1 |
| Output key length | 256 bits |
| Encryption | AES-256-GCM |
| IV length | 12 bytes (random) |
| Salt length | 16 bytes (random) |
Binary Format
The backup file uses a custom binary format:
RELAY_BACKUP_V1 file layout:
Offset Length Contents
0 16 Magic bytes: "RELAY_BACKUP_V1\0"
16 16 Argon2id salt (random)
32 12 AES-GCM IV (random)
44 variable Ciphertext + GCM auth tagFile extension: .relaybackup
Round-Trip Verification
Before saving, the backup is immediately decrypted and compared byte-for-byte against the original data. If they do not match, the backup is rejected. This prevents saving corrupt backups that would fail on restore.
Backup Scope
Users control what gets included in the backup:
| Toggle | Contents |
|---|---|
| Seed phrase | The 12-word BIP39 mnemonic |
| Text messages | All conversation message history |
| Voice messages | Voice recordings (planned) |
| Media | Photos and attachments (planned) |
Backup Password
The backup password is used only at encryption and decryption time. It is:
- Never stored in plaintext — only a "has been set" marker is saved.
- Held in memory only for the duration of the backup/restore operation.
- Separate from the PIN — it can (and should) be different.
In plain English: When you make a backup, you choose a password. That password is run through a very hard-to-crack process (Argon2id, which deliberately uses a lot of memory and CPU) to produce an encryption key. Your data is then encrypted with that key and saved as a file. The password is not saved anywhere — if you forget it, the backup cannot be opened. Before saving, the app double-checks by decrypting the backup to make sure it is not corrupted.
Server Trust Model
The server is designed to hold the absolute minimum data required to deliver encrypted messages. Everything else stays on your device.
What the Server Never Sees
This is the important list — the data that never leaves your device under any circumstances:
| Data | Why it stays on-device |
|---|---|
| Wallet private key | Self-custodial — funds cannot be moved without it |
| Auth private key | Only the signature is sent, never the key itself |
| Wallet address | Never exposed to the server, other users, or any API |
| Seed phrase | The root of all key material — exists only in device secure hardware |
| Plaintext messages | Encrypted before they leave the device; decrypted only on the recipient's device |
| PIN or biometric data | Verified entirely on-device by the OS |
| Signal session state | Ratchet keys, chain keys, skipped message keys |
| Backup passwords | Used transiently during backup/restore, then discarded from memory |
| Message history | Messages are automatically purged from the server on delivery |
What the Server Does Hold
Only what is strictly necessary for a messaging relay to function:
- Username and auth public key — needed to route messages and verify login signatures.
- Signal pre-key bundles (public parts only) — needed so a new contact can establish an encrypted session with you.
- Encrypted ciphertext in transit — held temporarily until the recipient's device acknowledges delivery, then automatically purged.
That is all. The server does not retain message history, does not store wallet addresses, and does not maintain a social graph.
What a Full Server Compromise Would Reveal
If an attacker gained complete access to the Relay server and database, they would find:
- A list of usernames and auth public keys (public information by design).
- Signal pre-key bundles (public keys only — useless without the corresponding private keys).
- Any messages currently in transit (encrypted ciphertext, unreadable without the recipient's on-device private keys).
They would not be able to read any message, access any wallet, impersonate any user, or recover any seed phrase. Forward secrecy means that even recording all traffic and later compromising a key would not decrypt past messages.
In plain English: If someone hacked the Relay server and stole everything, they would get a list of usernames and a small number of encrypted messages waiting to be delivered. They could not read those messages, log in as you, or touch your money. There is no message archive to steal because messages are deleted as soon as they are delivered.