The Mathematics of End-to-End Encryption
RAILGUN uses the Signal Protocol — the gold standard in encrypted messaging. This page explains the actual mathematics that protect every message you send.
1. Protocol Overview
The Signal Protocol combines three cryptographic primitives to achieve end-to-end encryption with perfect forward secrecy and post-compromise security:
X3DH
Establishes a shared secret between two parties who have never communicated, even if one is offline.
Double Ratchet
Derives unique encryption keys for every single message, providing forward secrecy and break-in recovery.
AES-256-GCM
Encrypts the actual message content using the keys produced by the ratchet, with authenticated encryption.
These three layers work together. X3DH bootstraps the first shared secret. The Double Ratchet continuously evolves keys so that compromising one key reveals nothing about past or future messages. AES-256-GCM provides the actual symmetric encryption and authentication for each message.
2. Curve25519 — The Elliptic Curve
All key generation and key agreement in RAILGUN is built on Curve25519, an elliptic curve designed by Daniel J. Bernstein for high-speed Diffie-Hellman key exchange.
The Curve Equation
y² = x³ + 486662x² + x
over the prime field 𝔽ₚ where p = 2²⁵⁵ − 19
The prime 2²⁵⁵ − 19 was chosen because it enables extremely fast modular arithmetic. The coefficient 486662 was chosen to be the smallest value that produces a safe curve with the required security properties.
Key Generation
1. Private key: Generate 32 random bytes using a CSPRNG (cryptographically secure pseudorandom number generator). Clamp the key by clearing the lowest 3 bits and the highest bit, and setting the second-highest bit. This ensures the key is a multiple of 8 and in the valid range.
a = random(32 bytes)
a[0] &= 248 // Clear low 3 bits
a[31] &= 127 // Clear high bit
a[31] |= 64 // Set second-highest bit
2. Public key: Compute the scalar multiplication of the private key with the base point G = 9.
A = a · G // Scalar multiplication on Curve25519
The security relies on the Elliptic Curve Discrete Logarithm Problem (ECDLP): given A and G, it is computationally infeasible to recover a. Curve25519 provides approximately 128 bits of security.
Diffie-Hellman Key Exchange
Two parties (Alice and Bob) can compute a shared secret without ever transmitting it:
Alice: a (private), A = a·G (public)
Bob: b (private), B = b·G (public)
Alice computes: S = a · B = a · (b·G)
Bob computes: S = b · A = b · (a·G)
Both arrive at S = ab·G — the same shared secret
An eavesdropper who knows A and B (both public) cannot compute S without solving the ECDLP. This is the Computational Diffie-Hellman (CDH) assumption.
3. X3DH — Extended Triple Diffie-Hellman
X3DH solves a hard problem: how do two people establish an encrypted session when one of them is offline? Traditional Diffie-Hellman requires both parties to be online. X3DH uses pre-uploaded key bundles to enable asynchronous key agreement.
Key Types
Identity Key (IK)
Long-term Curve25519 key pair. Generated once, identifies the user cryptographically. Never changes unless the user re-registers.
Signed Pre-Key (SPK)
Medium-term Curve25519 key pair, signed by the Identity Key. Rotated periodically (e.g., weekly). The signature proves it belongs to the identity key holder.
One-Time Pre-Key (OPK)
Ephemeral Curve25519 key pairs uploaded in batches. Each is used once then deleted. Provides an extra layer of forward secrecy for the initial message.
Ephemeral Key (EK)
A fresh Curve25519 key pair generated by the sender for each new session. Never stored server-side.
The X3DH Handshake
When Alice wants to message Bob (who may be offline), she fetches Bob's pre-key bundle from the server and performs four Diffie-Hellman computations:
// Alice has: IKₐ (her identity key), EKₐ (fresh ephemeral)
// Bob published: IKᵦ, SPKᵦ, OPKᵦ
DH1 = DH(IKₐ, SPKᵦ) // Alice's identity ↔ Bob's signed pre-key
DH2 = DH(EKₐ, IKᵦ) // Alice's ephemeral ↔ Bob's identity
DH3 = DH(EKₐ, SPKᵦ) // Alice's ephemeral ↔ Bob's signed pre-key
DH4 = DH(EKₐ, OPKᵦ) // Alice's ephemeral ↔ Bob's one-time pre-key
SK = KDF(DH1 || DH2 || DH3 || DH4)
// SK is the shared secret, derived via HKDF-SHA-256
Why four DH operations?
- DH1 provides mutual authentication (both identity keys involved)
- DH2 ensures the ephemeral key is tied to Bob's identity
- DH3 provides forward secrecy via the ephemeral key
- DH4 provides additional forward secrecy. If no OPK is available, X3DH still works with just DH1–DH3
KDF — Key Derivation Function
The raw DH outputs are combined using HKDF-SHA-256 (HMAC-based Key Derivation Function). HKDF extracts entropy from the concatenated DH outputs and expands it into a uniformly random shared secret:
PRK = HKDF-Extract(salt="", input=DH1||DH2||DH3||DH4)
SK = HKDF-Expand(PRK, info="RailGunX3DH", length=32)
4. The Double Ratchet Algorithm
After X3DH establishes the initial shared secret, the Double Ratchet takes over. It uses two interlocking "ratchets" to derive a new unique key for every message, ensuring that even if one key is compromised, past and future messages remain secure.
DH Ratchet (Asymmetric)
Each time the conversation direction changes (Alice → Bob, then Bob → Alice), a new Diffie-Hellman key exchange occurs using fresh ephemeral keys. This "ratchets" the root key forward:
dh_out = DH(my_ratchet_key, their_ratchet_key)
root_key, chain_key = KDF(root_key, dh_out)
This provides post-compromise security: even if an attacker steals the current state, they lose access once a new DH ratchet step occurs.
Symmetric Ratchet (Hash Chain)
Between DH ratchet steps, a hash-based ratchet derives a new message key from the chain key for each message:
message_key = HMAC-SHA256(chain_key, 0x01)
chain_key = HMAC-SHA256(chain_key, 0x02)
The old chain key is deleted after each step. The message key is used to encrypt exactly one message, then deleted. This is the forward secrecy mechanism.
Ratchet Progression
Visualized, the Double Ratchet looks like this. Each arrow is a one-way function — you can go forward but never backward:
Root Key Chain:
RK₀ ──DH──→ RK₁ ──DH──→ RK₂ ──DH──→ RK₃ ...
Each RK step produces a Chain Key:
↓ ↓ ↓
CK₀ CK₁ CK₂
↓ ↓ ↓
Each CK step produces Message Keys:
CK₀→MK₀ CK₁→MK₃ CK₂→MK₅
CK₀→MK₁ CK₁→MK₄ CK₂→MK₆
CK₀→MK₂ CK₂→MK₇
Each MKₙ encrypts exactly one message. After use, it is deleted. Even if an attacker obtains MK₄, they cannot derive MK₃ or MK₅.
5. AES-256-GCM — Message Encryption
Each message key from the Double Ratchet is used with AES-256-GCM (Advanced Encryption Standard in Galois/Counter Mode) to encrypt the actual message content.
How AES-256-GCM Works
// Inputs
key = message_key // 256 bits from ratchet
nonce = random(12 bytes) // 96-bit unique nonce
aad = header_data // Authenticated but not encrypted
plain = message_content // The actual plaintext
// Output
(ciphertext, tag) = AES-256-GCM(key, nonce, aad, plain)
Confidentiality: AES-256 encrypts the plaintext in counter mode. 256-bit keys mean 2²⁵⁶ possible keys — far beyond brute force even with quantum computers using Grover's algorithm (which reduces effective security to 128 bits).
Integrity: GCM produces a 128-bit authentication tag using GHASH (a polynomial hash in GF(2¹²⁸)). This tag covers both the ciphertext and the additional authenticated data (AAD), detecting any tampering.
AAD: The message header (sender info, timestamps, ratchet public keys) is authenticated but not encrypted, so the protocol can route messages without decrypting them.
6. Perfect Forward Secrecy
Perfect Forward Secrecy (PFS) means that compromising long-term keys does not compromise past session keys. RAILGUN achieves PFS through two mechanisms:
Ephemeral Keys in X3DH
The sender generates a fresh ephemeral key pair for every new session. After the X3DH handshake, the private ephemeral key is deleted. Even if an attacker later steals both parties' identity keys, they cannot reconstruct the session secret because the ephemeral private key no longer exists.
Ratchet Key Deletion
The Double Ratchet deletes old chain keys and message keys after use. The KDF chain is a one-way function — given CKₙ, you can compute CKₙ₊₁ but not CKₙ₋₁. This means a key compromise at time T reveals nothing sent before T.
Post-Compromise Security
The DH ratchet also provides future secrecy: even if an attacker compromises the current session state, the next DH ratchet step introduces new randomness that the attacker doesn't have, locking them out of future messages. No other major messaging protocol offers this property.
7. Relay-Only Architecture
RAILGUN uses a relay-only server model, similar to Signal. The server never stores message content — not even encrypted ciphertexts.
What the Server Sees
✓ Server stores:
- • Public key bundles (IK, SPK, OPK)
- • User registration info
- • Community/channel metadata
✗ Server never stores:
- • Message content (encrypted or plain)
- • Private keys
- • Session state or ratchet keys
- • Message history
Messages are relayed to online recipients in real-time via WebSocket. For offline devices, encrypted envelopes are queued in Redis with a 30-day TTL, then permanently deleted. Message history is stored only on your device using IndexedDB (Dexie.js).
8. Security Properties Summary
| Property | Mechanism | Status |
|---|---|---|
| End-to-End Encryption | Signal Protocol (X3DH + Double Ratchet + AES-256-GCM) | ✓ |
| Perfect Forward Secrecy | Ephemeral keys + ratchet key deletion | ✓ |
| Post-Compromise Security | DH ratchet introduces new entropy on each turn | ✓ |
| Deniable Authentication | X3DH — no cryptographic proof of who sent a message | ✓ |
| Asynchronous Key Agreement | X3DH pre-key bundles enable offline key exchange | ✓ |
| No Server-Side Storage | Relay-only architecture, IndexedDB client-side storage | ✓ |
| Quantum Resistance | AES-256 provides 128-bit post-quantum security (Grover) | Partial |
| Open Source | Full source code on GitHub for public audit | ✓ |
Want to see the code?
RAILGUN is open source. Audit the cryptographic implementation yourself.