a.s.d.f Back to home

Version 1.0  |  February 2026

The Fourier Protocol

A modern end-to-end encryption protocol providing forward secrecy, post-compromise security, and scalable group encryption. Built on the Signal Protocol implementation with enhancements for metadata protection and large-scale groups.

Contents

  1. Protocol Overview
  2. Architecture & Layers
  3. Cryptographic Primitives
  4. X3DH Key Agreement
  5. Double Ratchet
  6. Sender Keys (Group E2EE)
  7. Epoch Keys (Public Groups)
  8. Security Properties
  9. Protocol Enhancements

1. Protocol Overview

The Fourier Protocol provides end-to-end encryption for all messaging in a.s.d.f. It ensures that the server operates as a "dumb relay" — storing and forwarding ciphertext without the ability to decrypt message content.

Z
Zero-Knowledge Server The server never possesses plaintext or the keys to derive it. All encryption and decryption happens on client devices.
F
Forward Secrecy Compromise of long-term keys does not retroactively compromise past messages. Every message uses a unique, ephemeral key that is deleted after use.
H
Self-Healing (Post-Compromise Security) After a key compromise, the protocol automatically recovers security through DH ratchet steps. An attacker can only read messages until the next ratchet advance.
A
Asynchronous Users can start encrypted conversations with offline recipients via prekey bundles, without requiring both parties to be online.

The protocol supports three chat types, each with a tailored encryption scheme:

Chat TypeEncryption SchemeE2EEMax Size
Direct 1:1X3DH + Double RatchetFull E2EE2 users
Private GroupSender KeysFull E2EE1,000 members
Public GroupEpoch KeysServer-managed200,000 members

2. Architecture & Layers

Fourier is structured as three independent layers, each with a distinct responsibility:

Application Layer

Messages, Groups, Media, Presence, Reactions, Receipts

Encryption Layer

X3DH, Double Ratchet, Sender Keys, Epoch Keys

Transport Layer

HTTPS REST + WebSocket (TLS 1.3)

The encryption layer is implemented entirely on the client. The server only sees opaque ciphertext and routing metadata (user IDs, chat IDs, timestamps).

3. Cryptographic Primitives

Fourier uses well-studied, constant-time cryptographic primitives:

PrimitiveAlgorithmKey SizePurpose
Digital SignatureEd25519256-bitIdentity authentication, prekey signing
Key AgreementX25519 (ECDH)256-bitDiffie-Hellman in X3DH and Double Ratchet
Symmetric EncryptionChaCha20-Poly1305256-bitAEAD message encryption
Key DerivationHKDF-SHA256VariableDeriving root, chain, and message keys
HashingSHA-256256-bitSafety numbers, fingerprints

All secret key material is zeroized from memory upon drop, preventing key recovery from process memory.

4. X3DH Key Agreement

X3DH (Extended Triple Diffie-Hellman) establishes a shared secret between two parties, even when one is offline. Fourier uses the 4-DH variant when a one-time prekey is available, falling back to 3-DH otherwise.

Key Types

KeyLifetimePurpose
Identity Key (IK)Device lifetimeLong-term Ed25519 identity
Signed Prekey (SPK)7–30 daysMedium-term X25519, signed by IK
One-Time Prekey (OPK)Single useConsumed during handshake
Ephemeral Key (EK)Per sessionFresh randomness per handshake

Handshake Flow

   Alice                          Server                          Bob
     |                               |                              |
     |  1. Fetch Bob's PreKeyBundle  |                              |
     |------------------------------>|                              |
     |  { IK_B, SPK_B, OPK_B }      |                              |
     |<------------------------------|                              |
     |                               |                              |
     |  2. Verify SPK_B signature    |                              |
     |     with IK_B                 |                              |
     |                               |                              |
     |  3. Generate Ephemeral Key    |                              |
     |     EK_A = X25519_GENERATE()  |                              |
     |                               |                              |
     |  4. Compute 4 DH secrets:     |                              |
     |     DH1 = IK_A  x SPK_B      |                              |
     |     DH2 = EK_A  x IK_B       |                              |
     |     DH3 = EK_A  x SPK_B      |                              |
     |     DH4 = EK_A  x OPK_B      |                              |
     |                               |                              |
     |  5. Derive shared secret:     |                              |
     |     HKDF(DH1||DH2||DH3||DH4) |                              |
     |     info: "Fourier X3DH v1"   |                              |
     |                               |                              |
     |  6. Send initial message      |                              |
     |  + EK_A public key            |                              |
     |------------------------------>|----------------------------->|
     |                               |  7. Server deletes used OPK  |
     |                               |                              |
     |                               |        8. Bob computes same  |
     |                               |           4 DH operations    |
     |                               |           + derives secret   |

X3DH handshake: Alice initiates an encrypted session with offline Bob

What Each DH Provides

DHProvidesWithout It
DH1Mutual authenticationServer could impersonate Bob
DH2Alice proves identityMessages could be forged
DH3Forward secrecy (medium-term)Identity key compromise breaks all sessions
DH4One-time forward secrecyReplay attacks on initial messages

5. Double Ratchet

After X3DH establishes the initial shared secret, the Double Ratchet provides continuous forward secrecy and post-compromise security. It combines two ratchets:

     Root Key (from X3DH)
         |
         |  DH Ratchet Step (new key material)
         v
   +-----------+          +-----------+
   | Root Key' |----+     | Root Key''|----+
   +-----------+    |     +-----------+    |
                    v                      v
              +-----------+          +-----------+
              | Chain Key |          | Chain Key  |
              | (Send)    |          | (Receive)  |
              +-----------+          +-----------+
                    |                      |
          Symmetric Ratchet       Symmetric Ratchet
          (per message)           (per message)
                    |                      |
              +-----+-----+         +-----+-----+
              |     |     |         |     |     |
              v     v     v         v     v     v
             MK0   MK1   MK2      MK0   MK1   MK2
          (msg keys, single use, deleted after decrypt)

Double Ratchet: DH ratchet advances on turn changes; symmetric ratchet advances per message

DH Ratchet

When the conversation turn changes (Alice receives a reply from Bob with a new ratchet public key), a DH ratchet step introduces fresh randomness:

  1. Compute DH: dh_output = X25519(our_secret, their_new_public)
  2. Derive new keys: (new_root_key, new_chain_key) = HKDF(root_key, dh_output, "Fourier Double Ratchet v1")
  3. Generate a new DH key pair for the next turn

Symmetric Ratchet

For each message within a chain:

  1. Derive: (new_chain_key, message_key) = HKDF(chain_key, "Fourier Chain+Message Keys v1")
  2. Encrypt with ChaCha20-Poly1305 using the message key
  3. Advance chain key, delete message key immediately

Out-of-order messages: Up to 1,000 skipped message keys are cached. Each cached key is single-use and deleted after decryption, preventing replay attacks.

Message Wire Format

  +-------------------+-------------------------------------+
  | Field             | Size                                |
  +-------------------+-------------------------------------+
  | version           | u32 (currently 1)                   |
  | ratchet_key       | 32 bytes (sender's DH public key)   |
  | message_number    | u64 (position in chain)             |
  | prev_chain_length | u64 (previous chain length)         |
  | ciphertext        | variable (ChaCha20-Poly1305 + tag)  |
  | associated_data   | variable (authenticated metadata)   |
  +-------------------+-------------------------------------+

Encrypted message format for 1:1 conversations

6. Sender Keys (Group E2EE)

Private groups (up to 1,000 members) use Sender Keys for efficient E2EE. Each member maintains their own sending key and encrypts once for the entire group — O(1) encryption instead of O(N) pairwise.

  Alice (sender)                                       Group Members
       |                                                    |
       |  1. Generate SenderKey:                            |
       |     { chain_key, signing_key, seed }               |
       |                                                    |
       |  2. Distribute via 1:1 encrypted channels          |
       |     (Double Ratchet to each member)                |
       |                                                    |
       |  Alice ---[DR]---> Bob: SenderKey                  |
       |  Alice ---[DR]---> Charlie: SenderKey              |
       |  Alice ---[DR]---> Diana: SenderKey                |
       |                                                    |
       |  3. Encrypt group message (O(1)):                  |
       |     mk = HKDF(chain_key, seed)                     |
       |     ct = ChaCha20(mk, plaintext)                   |
       |     sig = Ed25519.Sign(ct)                         |
       |     chain_key = HKDF(chain_key)  // ratchet        |
       |                                                    |
       |  4. Send single ciphertext                         |
       |------------------>[ Server ]--------------------->|
       |                   (opaque)                         |
       |                                                    |
       |                          5. Each member decrypts:  |
       |                             Verify Ed25519 sig     |
       |                             Derive mk from chain   |
       |                             Decrypt with mk        |

Sender Keys: O(1) encryption for private groups with per-sender chains

Key Rotation

Sender keys are regenerated and redistributed when:

7. Epoch Keys (Public Groups)

For large public groups (up to 200,000 members), full E2EE with Sender Keys is impractical due to O(N²) key distribution. Fourier uses server-managed epoch keys for scalability.

  K_master  (per room, created once)
      |
      |  HKDF("epoch||room_id||epoch_number")
      v
  K_epoch_0 ---> K_epoch_1 ---> K_epoch_2 ---> ...
      |               |               |
      |  HKDF         |  HKDF         |  HKDF
      v               v               v
  msg_key_0      msg_key_0       msg_key_0
  msg_key_1      msg_key_1       msg_key_1
  msg_key_2      msg_key_2       msg_key_2
     ...            ...             ...

  Epoch rotates on:
  +-------------------+------------------------+
  | Trigger           | Security Property      |
  +-------------------+------------------------+
  | Member joins      | Backward secrecy       |
  | Member leaves     | Forward secrecy        |
  | Hourly timer      | Limits exposure window |
  +-------------------+------------------------+

Epoch key hierarchy: per-epoch keys derived from a master key

Important: In public group mode, the server manages key material and can theoretically decrypt messages. Users are clearly informed that public groups do not provide the same E2EE guarantees as direct messages or private groups.

8. Security Properties

Threat Model

ThreatMitigation
Passive network observerTLS 1.3 + E2EE double-layer encryption
Compromised serverServer only sees ciphertext; cannot derive plaintext
Man-in-the-middleSigned prekeys + safety number verification
Past key compromiseForward secrecy via ephemeral keys and chain ratcheting
Current key compromisePost-compromise security via DH ratchet self-healing
Message replayMonotonic counters, one-time skipped keys
Traffic analysisMessage padding (fixed-size tiers), auto-purge

What the Server Knows vs. Doesn't Know

  +-----------------------------+-------------+
  | Data                        | Server Sees |
  +-----------------------------+-------------+
  | User IDs, device IDs        |     Yes     |
  | Who messages whom (chat ID) |     Yes     |
  | Message timestamps          |     Yes     |
  | Message content (plaintext) |     NO      |  <-- 1:1 & private groups
  | Encryption keys             |     NO      |
  | Group membership            |     Yes     |
  | Encrypted ciphertext        |  Yes(opaque)|
  +-----------------------------+-------------+

Safety Numbers

Users can verify each other's identity out-of-band using Safety Numbers — a 60-digit fingerprint derived from both parties' identity keys. If a user's identity key changes (new device, re-registration), the safety number changes and the app alerts the user.

9. Protocol Enhancements

Fourier extends the base Signal Protocol implementation with additional protections:

EnhancementStatusDescription
Message PaddingImplementedFixed-size tiers (256B to 1MB) prevent length-based traffic analysis
Header EncryptionImplementedEncrypts ratchet keys and counters to hide metadata
Message CompressionImplementedzstd compression before encryption reduces bandwidth
Auto-PurgeImplementedMessages deleted from server after all recipients confirm delivery
PQXDHPlannedPost-quantum X3DH using Kyber-1024 hybrid key exchange
Sealed SenderPlannedHide sender identity from the server

The full technical specification is available in the opacE2EE audit framework and the Noel Protocol Standard document. For security audit results, see our Security Audit page.