The end-to-end encryption protocol powering OpenBook Chat. A formal specification of its cryptographic design, threat model, and security properties.
OpenBook Zero is the end-to-end encryption protocol designed and implemented by Moneta Eight FZE for OpenBook Chat. It provides per-message ephemeral key agreement (the ECIES pattern) with sender-side forward secrecy, authenticated encryption, and a Shamir-sharded (2-of-3) identity key system designed for recoverable custody (§07, L5) — without requiring users to manage cryptographic material directly.
The protocol is built entirely from standard, well-vetted cryptographic primitives: X25519 for key agreement, HKDF-SHA256 for key derivation, and AES-256-GCM for authenticated encryption. The identity key backup system uses Shamir's Secret Sharing over GF(2⁸), implemented from scratch in Dart.
All primitives are standard, publicly specified algorithms. No proprietary or novel cryptography is used. X25519, HKDF-SHA256, and AES-256-GCM are provided by the cryptography Dart package (v2.7) — a vetted, widely-deployed implementation. Key material at rest is held via flutter_secure_storage (iOS Keychain, hardware-backed). Only the Shamir Secret Sharing layer is first-party code.
| Algorithm | Role | Parameters / Standard |
|---|---|---|
| X25519 | Elliptic-curve Diffie-Hellman key agreement | RFC 7748 · 32-byte keypairs · Identity and ephemeral key agreement |
| HKDF-SHA256 | Key derivation from shared secret | RFC 5869 · 32-byte output · Salt = ephemeral pubkey · Info = openbook_zero_v3_message |
| AES-256-GCM | Authenticated symmetric encryption | NIST SP 800-38D · 256-bit key · 96-bit random nonce · 128-bit auth tag |
| SHA-256 | Key fingerprint generation | FIPS 180-4 · Verifiable identity fingerprints for out-of-band verification |
| Shamir SS / GF(2⁸) | Identity key backup | AES polynomial 0x11b · 2-of-3 threshold · Implemented in Dart |
| BIP-39 | Recovery phrase encoding | 2048-word wordlist · 12 words · 128 bits entropy |
Every OpenBook user has a long-term X25519 identity keypair. The public key is published to the server so contacts can encrypt messages to them. The private key never leaves the device unencrypted and is never transmitted to the server in any form.
On first setup, the app generates a 32-byte X25519 keypair using a cryptographically secure random source. The public key is stored locally and published to Firestore under the user's UID. The private key is immediately split into Shamir shards and never stored whole after that point.
Before sending a message, the sender fetches the recipient's X25519 public key from their Firestore user document (obZeroPublicKey field, base64-encoded). This fetch is cached in memory with a 10-minute TTL to minimise Firestore reads without staleness risk.
Every message is encrypted independently with no shared session state (the ECIES pattern). The sender’s ephemeral private key is destroyed immediately after use — nothing on the sender’s side can re-derive a message key. The recipient decrypts using their long-term private key together with the ephemeral public key stored alongside the ciphertext. The properties this does and does not provide are stated precisely in §02 and §10 (L1).
Fresh X25519 keypair unique to this message, used nowhere else.
ephemeralPrivKey × recipientPubKey → 32-byte shared secret
HKDF(sharedSecret, salt=ephemeralPubKey, info="openbook_zero_v3_message") → 32-byte AES key
messageKey + random 96-bit nonce → ciphertext + 128-bit authentication tag
ciphertext + ephemeralPubKey stored to Firestore · ephemeral private key immediately discarded
Because OpenBook Zero uses asymmetric key agreement, a message encrypted to the recipient's public key cannot be decrypted by the sender — the sender does not hold the recipient's private key. Every outgoing message is encrypted twice using independent ephemeral keypairs:
Stored in text + ephemeralPubKey
Stored in textForSender + ephemeralPubKeyForSender
The two encryptions are cryptographically independent — each uses its own ephemeral keypair. A compromise of one does not affect the other. The app selects which ciphertext to decrypt based on whether the current user is sender or recipient.
The long-term X25519 private key is the root of a user's cryptographic identity. Losing it means losing the ability to decrypt past messages and prove identity on a new device. OpenBook Zero protects against this using Shamir's Secret Sharing.
At setup, the 32-byte private key is split into three shards using a 2-of-3 Shamir scheme over GF(2⁸). Any two shards reconstruct the key. One shard alone reveals nothing — this is information-theoretically proven, not assumed.
| Shard | Location | Protection |
|---|---|---|
| Shard 1 | Device — iOS Keychain | flutter_secure_storage · encrypted at rest with hardware-protected keys |
| Shard 2 | Firebase Firestore | A single shard reveals nothing about the key — Shamir’s information-theoretic guarantee · at-rest encryption under a recovery-phrase-derived key ships in v1.1 |
| Shard 3 | Device — secure storage (transitional) | Moves to sole user custody as a 12-word BIP-39 phrase in v1.1 · never stored on the server |
The app silently reconstructs the private key from Shard 1 (device) and Shard 2 (server) each session. The reconstructed key exists in memory only, is never written to disk, and is discarded when the session ends. Shard 3 is used only as an offline fallback when the server shard is unreachable.
Once user-custody recovery ships, the user will enter their 12-word recovery phrase on a new device. Combined with Shard 2 retrieved from the server, the app reconstructs the private key and restores full access to message history; Shard 1 is then regenerated on the new device.
In v1.0, all shards are managed by OpenBook systems (two on-device, one on the server) and cross-device recovery is not yet available — see L6.
In chat settings, users can toggle to view raw base64 ciphertext as stored on Firebase — confirming that servers only ever see opaque encrypted data, never plaintext. The toggle is a UI mode switch; it does not modify any stored data.
SHA-256 hash of both parties' X25519 public keys, ordered canonically by user ID so both sides produce the same value. Users can compare fingerprints out-of-band — over a call or in person — to detect any MITM substitution of public keys.
All encryption (X25519 + AES-256-GCM) happens on-device before any data leaves the user's phone. Servers never see plaintext or keys.
Only ciphertext and minimal metadata are stored on Firebase. No message content, no keys, and no unnecessary personal data ever reaches our infrastructure.
Users can independently verify encryption state (Server View) and confirm conversation integrity (key fingerprints). Full recovery control via 12-word phrase.
Future roadmap items include enhanced data residency options and formal PDPL-aligned privacy documentation.
We publish known limitations because transparency about what a protocol does not do is as important as describing what it does.
This document will be updated as the protocol evolves. The version number in the header is the authoritative reference.