Back to Home
Cryptography

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

PropertyMechanismStatus
End-to-End EncryptionSignal Protocol (X3DH + Double Ratchet + AES-256-GCM)
Perfect Forward SecrecyEphemeral keys + ratchet key deletion
Post-Compromise SecurityDH ratchet introduces new entropy on each turn
Deniable AuthenticationX3DH — no cryptographic proof of who sent a message
Asynchronous Key AgreementX3DH pre-key bundles enable offline key exchange
No Server-Side StorageRelay-only architecture, IndexedDB client-side storage
Quantum ResistanceAES-256 provides 128-bit post-quantum security (Grover)Partial
Open SourceFull source code on GitHub for public audit

Want to see the code?

RAILGUN is open source. Audit the cryptographic implementation yourself.