SprinTelex End-to-End Encryption: Technical Whitepaper

Version 0.3 · 13 October 2026 · Download PDF

Status: Living document. This paper describes the SprinTelex Messenger encryption architecture as implemented in the shipped product. It is updated as the product changes. Where a feature is mid-migration, the paper says so.

1. Introduction

SprinTelex is a messaging application (mobile client, application server) in which person-to-person and group conversations are end-to-end encrypted (E2EE). This paper documents the cryptographic protocols, key management, and trust assumptions behind that encryption, and states plainly what the system does and does not protect against.

Scope: 1:1 messaging, group messaging, the two implementations of the protocol (a TypeScript engine on every platform and Sige, our native Swift engine on iOS), message decryption inside the iOS notification extension, key verification, encrypted backups, registration lock, contact discovery, and view-once media. Out of scope: payments, bots, embedded mini-apps (bot chats are explicitly non-E2EE and are labeled as such in the client), stories, calls signaling beyond what is noted.

Nothing in this paper is a marketing claim. Every property stated here is grounded in the shipped product, and Section 7 lists the honest limitations. Version 0.3 corrects several claims in earlier versions that had stopped matching the product, most importantly about group messaging; Section 10 lists every correction.

2. Cryptographic notation

The formulas in Section 4 use the following notation, following the conventions of the Signal protocol specifications:

3. Architecture overview

3.1 Components

3.2 What the server sees

The server stores and can observe:

3.3 What the server never sees

3.4 Transport security

All client-to-server traffic (API, key directory, storage, realtime channel) uses HTTPS endpoints. On iOS, the OS refuses cleartext HTTP for these connections. Certificate pinning is deployed on both platforms for our API endpoints through the platform's network security configuration: the pin-set carries the live leaf SPKI as primary with the issuing intermediate and root as backups, and a mismatch fails closed. A network attacker with a fraudulently issued certificate from a trusted CA can no longer intercept client-server traffic without also defeating the pin-set; E2EE still protects message content in any case (the server never holds plaintext or private keys). Pin rotation follows a documented runbook (leaf expiry 2026-11-24; pin-set expiry 2027-10-01).

3.5 Update authenticity (OTA signing)

The Android client can receive over-the-air JavaScript bundle updates. This channel previously verified only the SHA-256 of the advertised bundle: integrity without authenticity. A compromised server or deploy machine could have pushed arbitrary JS to every install, which in an E2EE messenger is silent remote code execution.

Every bundle is now signed offline with an Ed25519 key whose private half never leaves the founder's secure machine. The update service serves the signature alongside the update check; the client verifies it against the baked-in public key before applying the bundle, and the platform's existing SHA-256 check still runs after download. Both gates must pass.

The posture is fail-closed: an unconfigured key, a missing signature, or a bad signature refuses the update with a loud log, never silently. Key rotation ships as an app update (new baked-in public key).

Trust assumption, stated plainly: update authenticity reduces to the security of the single offline signing machine (see limitation 10). iOS installs do not use this channel (App Store distribution).

3.6 Push notifications and lock-screen previews

Push notifications (APNs/FCM) never carry message content in a form the server, Apple, or Google can read.

The visible banner is server-rendered from a closed set. The push service looks up the sender's display name (and, for groups, the group's name) itself and renders the banner text from a closed list of kinds ("New message", "Photo", "Missed voice call", and similar). Free-text title and body fields from a user caller are ignored by design, so no caller can put arbitrary words on another user's lock screen or exfiltrate message text through the push channel. This generic banner is what the platform push service sees, whatever the user's preview setting.

Readable previews are end-to-end encrypted. The real text can reach the lock screen in two ways, both decrypted only on the recipient's device:

  1. Sender-encrypted preview. Each account holds a random 32-byte push preview key, which it shares, inside the E2EE channel, with everyone it sends messages to (its 1:1 contacts and the members of groups it posts in). A sender who holds the recipient's key encrypts a small preview (text or caption, sender name, conversation) with AES-256-GCM and attaches it to the push; the server relays it without the key. The recipient's notification extension decrypts it with the key the app stored in a keychain group shared only with the app's own extensions.
  2. Decryption of the message itself (iOS). The notification extension can decrypt the device's own copy of the message with Sige (Section 4.8). Its text is used when no sender-encrypted preview arrived.

The user decides what the lock screen shows. The "Show previews" setting (name and message, name only, or nothing) is applied on the device by the extension. "Nothing" also replaces the sender's name with the app's name. Every step fails open to the generic banner: a missing key, a failed decryption, or the extension running out of time never suppresses a notification and never reveals more than the generic banner.

3.7 Web companion

The web client is a second client of the same protocols, not a separate product. It runs the TypeScript engine only. It enforces the phone's device-security contracts rather than bypassing them: on link it adopts the phone's auth-boundary lifecycle for the Signal store, and it presents the registration-lock PIN gate before publishing its key bundle (Section 5.3). In the browser, key material and message history are stored encrypted with AES-256-GCM under a non-extractable WebCrypto key, so the key's raw bits are never visible to page scripts. Server-side access controls remain the backstop: a web session that somehow skipped the gate still cannot read another device's keys. Backup restore on web verifies per-file MACs and content hashes exactly like the phone (Section 5.2).

4. Cryptographic protocols

4.1 Session establishment (X3DH)

New 1:1 sessions are established with X3DH, the Signal triple Diffie-Hellman handshake, extended to PQXDH (Section 4.4). Session establishment is performed by the TypeScript engine, a Signal-protocol library maintained as a fork of the upstream open-source code it derives from. A manual differential audit of the fork against the Signal specification confirmed it is faithful: DH ordering, the 0xFF × 32 prefix, and the HKDF info strings (WhisperText, WhisperMessageKeys, WhisperRatchet) match the spec. On iOS, Sige processes incoming session-establishing (PreKey) messages (Section 4.7).

Each device publishes:

On launch the client checks the bundle the directory advertises for this device against the keys it actually holds (identity key, signed prekey, PQ key, and the whole one-time pool). If they disagree it republishes the bundle under the same identity key, so safety numbers do not change.

Our fork of the TypeScript library carries four modifications from the upstream code:

  1. A normalization of the public signature-verification API, which in the upstream code returned inverted booleans (false for valid signatures). The modification makes true mean valid. Internal session establishment used the throwing verifier and was never affected.
  2. An async fix in the session-processing path, where a missing await on the identity-trust check could leave session state half-applied.
  3. Clone-then-commit decryption. A decryption attempt works on a copy of the session record and is written back only if it succeeds, so a failed or malicious message can never advance or corrupt a healthy session. The build refuses to compile without this modification.
  4. Threading of the post-quantum shared secret into the handshake (Section 4.4).

4.2 The Double Ratchet

Once a session is established, messages are encrypted under the Double Ratchet: a symmetric-key ratchet (per-message forward secrecy: each message key is derived and then deleted, so a later key compromise does not expose earlier messages) combined with a Diffie-Hellman ratchet (post-compromise security: each DH step heals the session after a compromise). Out-of-order messages are tolerated within a 2000-message window, matching the Signal specification. Both engines enforce the same window.

Message cipher. Each message key is derived as HKDF(chainKey, zeroSalt, 'WhisperMessageKeys'), which yields three 32-byte values: cipher key, MAC key, and IV material. The body is encrypted with AES-256-CBC under the 32-byte cipher key and a 16-byte IV (the first 16 bytes of the third value). Authenticity is HMAC-SHA256 under the 32-byte MAC key, computed over both parties' 33-byte identity keys, a version byte, and the encoded message, then truncated to 8 bytes and appended to the envelope. The MAC is verified in constant time before any decryption is attempted.

Decryption failures and recovery. Failures follow a 3-strike budget: the first two leave the ciphertext queued and retry silently. The third archives the session (it is kept, not deleted, so messages already in flight under it can still decrypt) and sends the sender's device a resend request for that message. To answer such requests, each device keeps the plaintext of the messages it sent, inside its encrypted local store, for 7 days (at most the 300 most recent). It re-encrypts a message only for a device that was one of its original recipients, and it rate-limits requests per requester (excess requests queue rather than drop). A notice that a message could not be delivered appears only if no resent copy arrives within an hour. Control messages (receipts, sync, repair) are marked by the sender so their loss produces no notice. A client change rolling out with the next release stops requests for messages older than the 7-day window, which no sender could answer.

4.3 Group messaging

Today every group message is encrypted pairwise. The sender encrypts one Signal-protocol copy for each device of each member (and for each of the sender's own other devices), and the server relays each copy to its device. Group messages therefore get the full guarantees of the pairwise Double Ratchet (Section 4.2), including per-message forward secrecy and post-compromise security. The cost is one encryption per recipient device; group membership itself is managed by the server.

Sender keys: implemented, not active. The clients contain a sender-key scheme of our own design (it is not Signal's SenderKey message format), and devices already generate and distribute sender keys inside pairwise sessions, but no message is encrypted with them: a sender switches a recipient device to sender keys only after that device acknowledges the key, and devices do not send that acknowledgement. Activating the scheme is a separate, planned rollout. Its design, for completeness:

Security note, applying once the scheme is active: the sender-key chain is a one-way hash ratchet with no per-message DH step, so post-compromise security is weaker than the pairwise double ratchet. A leaked chain key exposes future messages in that sender's stream until the next rotation. This is the accepted trade-off of every sender-key design, shared with the reference implementations.

4.4 Post-quantum session establishment (PQXDH)

Classical X3DH relies on elliptic-curve Diffie-Hellman, which a sufficiently powerful quantum computer would break retroactively: an adversary recording today's ciphertext could decrypt it years from now ("harvest now, decrypt later"). Signal shipped the fix as PQXDH in September 2023; to our knowledge WhatsApp has not deployed one.

SprinTelex implements the same hybrid construction:

4.5 Key transparency (phases 1 and 2)

A signed public-key directory defends against a server that hands different keys to different people (equivocation / man-in-the-middle via the key directory).

Phase 1: an hourly server job builds a Merkle tree over every device's published bundle (identity key + signed prekey) and publishes a signed tree head (Sig, Ed25519, under the directory key). When the client fetches a contact's prekey bundle, it also fetches the Merkle inclusion proof for that device (at most one check per epoch per contact) and verifies: the head signature, the recomputed leaf, and the path to the signed root. Signatures are checked as strict Ed25519 (RFC 8032, without the more lenient ZIP-215 rules). Epoch monotonicity is enforced in encrypted storage. Hard failures (bad signature, bad proof, leaf mismatch, epoch rollback) surface through the safety-number warning path; transient issues (network failure, missing snapshot, device published after the last snapshot) defer quietly, because staleness is not an attack.

Phase 2 closes the phase-1 trust hole (the signing-key holder) in three steps. First, chained heads: every epoch head commits to the previous epoch's head hash, forming a hash chain, and carries a second directory signature over the chained message; a history rewrite breaks the chain. Second, consistency proofs: any client or auditor can fetch the signed head chain between two epochs and verify the history only ever appended. Third, witness countersignatures: independent witnesses countersign each chained head, and clients require a quorum (currently 1) before trusting a new epoch. Publishing is atomic: the server refuses to write a head that breaks the chain or lacks witness signatures, failing loud instead of forking silently. Once a client has verified any phase-2 epoch, a v1-only head is rejected as a downgrade.

Trust assumption, stated plainly: phase 2 detects equivocation by anyone who does not hold both the directory signing key and a witness key (two separately stored keys; a single-key compromise no longer suffices for an undetected equivocation), and any fork is publicly visible through the head-chain endpoint. It does not stop the operator itself going rogue while the only enrolled witness is first-party (a second keypair held separately from the directory signing key, not decentralization). Real third-party witnesses are the operational next step; the directory's public endpoints and the client quorum logic are built for them.

4.6 Safety-number verification

Out-of-band identity verification uses 60-digit safety numbers (12 groups of 5 digits, the Signal shape), derived from both parties' identity keys, sorted so both sides compute the same digits. The client also supports QR verification: the QR payload is derived from the exact same identity-key bytes as the digit display, so the two can never diverge. Scanning verifies against the locally stored identity key for the current contact and records verification through the same persistence path as manual digit confirmation. Malformed, wrong-version, wrong-contact, and mismatched payloads fail closed.

Safety numbers are trust-on-first-use unless the user verifies them. Key transparency (Section 4.5) raises the cost of a first-use MITM but does not replace verification.

4.7 Sige: the native protocol implementation (iOS)

What it is. Sige is SprinTelex's own implementation of the Signal protocol in Swift, written clean-room from the published specifications and the wire format. It contains no libsignal code. We wrote it so that the iOS notification extension can decrypt messages (Section 4.8), which requires the ratchet in native code, and so that per-message cryptography runs natively in the app.

Why not Signal's own library. Signal's current native library cannot talk to SprinTelex's existing clients: recent releases no longer accept classical X3DH sessions and use a different message version. Adopting it would have cut off every older build and the web companion. Sige speaks exactly the wire format our clients already use.

What it does, and what stays in TypeScript.

OperationEngine on iOS
Encrypting on an established session (including the follow-up PreKey messages of a pending session)Sige
Decrypting regular messages and incoming PreKey messages (the responder side of X3DH/PQXDH)Sige
Decrypting sender-key group envelopes (when the scheme is active, Section 4.3)Sige
Starting a new session as initiator, generating and publishing keys, archiving sessionsTypeScript
Android and the web companionTypeScript only

One store, two engines. Both engines read and write the same serialized session records, so a session can pass from one engine to the other between any two messages. If Sige meets anything it does not handle, or any error, it writes nothing and the TypeScript engine processes that message, so the fallback is always the long-standing implementation.

Primitives. Sige uses Apple's CryptoKit for X25519, SHA-512, HMAC-SHA256, HKDF, ML-KEM-1024, and Ed25519 signature verification, and the system's CommonCrypto for AES-256-CBC. XEdDSA signing is implemented in Sige from Signal's specification; it is the only place Sige implements curve arithmetic itself, and its signatures are verified through CryptoKit's Ed25519 after the specification's key conversion.

Moving a device to Sige. When a device switches to Sige it generates a fresh, seed-based ML-KEM-1024 key and a new signed prekey and republishes its bundle. Its identity key does not change, so safety numbers stay the same.

Verification. Sige was built against the TypeScript engine as an oracle: the engine generates test vectors (complete session transcripts, with the stored record before and after every step), and Sige must reproduce every byte. A loopback harness runs sessions in both directions between the two engines, and each rollout stage passed a gate on two physical phones before the next began.

Status. Sige has been enabled for all iOS users since 2026-10-11, on app builds 1.1 (124) and later; a device switches over on its next launches after receiving the setting. The decision can be reversed per feature from the server, which returns devices to the TypeScript engine.

4.8 Decryption in the notification extension (iOS)

When a message arrives for a device whose app is closed or suspended, iOS runs the app's notification extension, a separate process with a short time budget. With this feature on, the extension decrypts the device's own copy of the message before the app opens, so the message is already on the device and the notification can show its text (subject to the preview setting, Section 3.6).

5. Data at rest

5.1 On-device key storage

Identity keys, session state, sender keys, PQ private keys, key transparency epochs, and the 7-day send log (Section 4.2) live in encrypted on-device storage, whose AES key is held in the OS-provided keystore (Keychain on iOS, Keystore on Android). Key material is never logged.

On iOS, the protocol stores live in a container shared between the app and its notification extension (an App Group), still encrypted, and their keys sit in a keychain access group shared only with the app's own extensions. These keychain items are readable only after the device's first unlock since boot and are bound to the device: they are not synced to other devices and do not move to a new phone through device backups.

Two integrity safeguards protect the stores. On every open, an encrypted store is checked against what its file holds: if the storage library reports fewer entries than the file contains, the app sets the file aside and recovers from it, and if nothing is readable it refuses to open the store rather than overwrite it. (This guards against a defect we observed in the storage library on 2026-10-13; it rolls out with the next release.) Separately, numeric key-store state (registration ID, key-ID counters) is repaired on open if an earlier storage migration damaged it.

5.2 Encrypted backups

Chat history backup encrypts everything on-device before upload:

Backup security reduces to the recovery PIN: a weak PIN permits offline brute force of the backup by anyone who obtains the ciphertext. New backups require a recovery PIN of 8+ ASCII alphanumeric characters; legacy backups keep their original (often 6-digit) PINs, which the restore path continues to accept unchanged.

The identity keypair itself is backed up separately at signup: a PIN-encrypted copy (scrypt N=32768, r=8, p=1, same envelope as above) is uploaded to a dedicated server-side record, so a new device can recover the long-term identity and peers' safety numbers stay valid. The envelope's MAC is verified before decryption, so a wrong PIN and a tampered record fail the same way. Current envelopes (v3) also bind the key-derivation recipe into the MAC, so an attacker cannot relabel a record to be read with weaker parameters; older envelopes remain readable. Signed/one-time prekey privates are never backed up; they are re-minted on restore. The backup record is readable only by the account owner through server-side access controls, but a server operator or database reader can read the ciphertext: its security likewise reduces to the recovery PIN, which is the honest reason Section 3.3 lists it as the one private-key exception.

5.3 Registration lock

Account re-registration is protected by a user-chosen secret: 6 to 20 ASCII alphanumeric characters. The server stores a verifier derived with PBKDF2-SHA256 at 210,000 iterations (per-user salt); the secret itself never leaves the device. Rate limiting (five free guesses, doubling lockouts) and a seven-day escape hatch apply unchanged. Existing 6-digit verifiers continue to work with no migration.

6. Contact discovery

Current state (as of this writing): private contact discovery via a bucket scheme. The client uploads only 20-bit truncated SHA-256 bucket keys of normalized numbers, no raw phone numbers and no full hashes, to the contact-discovery service, which returns candidate profiles without phone numbers (a match confirmation the client checks locally lets it confirm exact matches). The server can no longer reconstruct the address book or the social graph from this path; a per-user daily quota (100k bucket keys) blunts directory enumeration. Honest caveat: as with every contact-discovery scheme including Signal's, targeted probing ("is this one specific number registered?") remains possible; what the scheme kills is mass collection.

The legacy raw-upload endpoint is retained for old clients and marked deprecated; current builds never call it.

7. Threat model and honest limitations

Defended against:

Not defended against (limitations):

  1. Metadata. The server always knows who talks to whom, when, and roughly how much. E2EE hides content, not the social graph of communication. Sealed sender is not deployed. Each push also shows the platform push service the sender's or group's name and a message-kind label.
  2. Device compromise. If the endpoint is compromised (malware, unlocked device, forensic extraction past the OS keychain), encryption cannot help. This includes the last 7 days of sent messages kept for resends (Section 4.2), which sit in the same encrypted store as the chat history. On iOS the notification extension can read the same stores as the app, so it is part of the same trust boundary.
  3. Key-transparency trust. Phase 2 needs both the directory signing key and a separately stored witness key for an undetected equivocation, and forks are publicly visible. It still does not stop the operator itself while the only witness is first-party; third-party witnesses are the next step.
  4. Contact discovery (legacy path). Old clients still upload raw phone numbers via the deprecated endpoint; current builds use the bucket scheme (Section 6).
  5. Targeted number probing. Even after the rebuild, checking whether one specific number is registered remains possible, as in every deployed contact-discovery system.
  6. Unverified safety numbers. Without out-of-band verification, identity is trust-on-first-use.
  7. View-once media. Forwarding, copying, saving, and OS sharing are blocked in-app, and screenshot/screen-recording detection notifies the sender, but no software can prevent a recipient from photographing a screen with another device. View-once is a speed bump, not a guarantee.
  8. Lock-screen previews. With previews set to "name and message", decrypted text appears on the lock screen, where anyone holding the phone can read it; "name only" and "nothing" hide it. A user's push preview key is held by everyone that user has sent messages to (including members of groups they post in), and it is not rotated today: an operator who recorded push payloads and colluded with any one of those people could read the previews of messages sent to that user. Previews produced by the extension's own decryption (Section 4.8) do not depend on this key.
  9. Group cost. Group messages are pairwise today (Section 4.3), which keeps the full Double Ratchet guarantees but costs one encryption per recipient device. Once sender keys are activated, their weaker post-compromise security (Section 4.3) will apply to groups.
  10. OTA signing-key custody. Update authenticity reduces to the security of the single offline signing machine (Section 3.5). Compromise of that machine is compromise of the update channel for every Android install. Key rotation ships as an app update.
  11. Two implementations. Running two protocol engines on iOS adds the risk that they disagree on some input. It is mitigated by Sige's byte-exact test vectors from the TypeScript engine, the cross-engine loopback tests, and the rule that Sige writes nothing on any error and hands the message to the TypeScript engine (Section 4.7).

8. Comparison with WhatsApp and Signal

Stated only where we have public evidence; where evidence is thin, we say so.

PropertySprinTelexSignalWhatsApp
1:1 E2EE (X3DH + Double Ratchet)YesYesYes
Group messagesPairwise per recipient device; sender keys implemented, not yet activeSender keysSender keys (documented)
Post-quantum handshakeYes (ML-KEM-1024 hybrid)Yes (PQXDH, 2023)Not publicly deployed (to our knowledge)
Key transparencyPhase 2: chained heads, consistency proofs, witness quorum (one first-party witness so far)Yes (long-standing)Yes (deployed AKD; still more mature: their auditors are third-party, ours are not yet)
Private contact discoveryYes (bucket scheme; legacy raw-upload path retained for old clients)Yes (SGX-based)No (address-book upload)
Safety-number QR verificationYesYesYes
Decrypting messages in the iOS notification extensionYes, for established sessions (Sige)YesNot documented
Native protocol implementationSige (Swift, iOS); TypeScript elsewherelibsignal (Rust, open source)Not public

We do not claim superiority where we have not earned it: WhatsApp's key transparency deployment is more mature than ours, and Signal's contact discovery remains the reference design.

9. References

10. Changelog

Back to the security overview