Signal Protocol SDK

The Signal Protocol, where your app actually runs.

Expo, React Native, browsers, and Node from one package. Post-quantum PQXDH and the Double Ratchet, multi-device, groups, sealed sender, encrypted attachments. The protocol code is pure TypeScript with no native crypto module to link — the encrypted Expo store uses SQLCipher, which needs a development build rather than Expo Go.

0.1.x — public APIs and persisted formats may change before 1.0.

Two clients, one relay, no account

The quickstart runs entirely on your machine against the development adapters. This is the whole program.

quickstart.ts

import { createSignalProtocolClient } from "@open-e2ee/signal-protocol-sdk";
import { inMemoryStore } from "@open-e2ee/signal-protocol-sdk/local/store/memory";
import { inMemoryRelay } from "@open-e2ee/signal-protocol-sdk/remote/relay/memory";

const relay = inMemoryRelay();
await relay.registerDevice("alice", { encryptedDeviceName: new ArrayBuffer(0) });
await relay.registerDevice("bob", { encryptedDeviceName: new ArrayBuffer(0) });

const alice = await createSignalProtocolClient({
  identity: { userId: "alice" },
  adapters: { storage: inMemoryStore(), relay },
});
const bob = await createSignalProtocolClient({
  identity: { userId: "bob" },
  adapters: { storage: inMemoryStore(), relay },
});

await alice.syncToServer();
await bob.syncToServer();

await alice.send("bob", "Ship it Thursday. The staging key rotates at 09:00 UTC.");

// This is all the relay ever holds:
const [envelope] = relay.getPendingMessages("bob", 1);

bob.registerHook("onMessageDecrypted", async (message) => {
  console.log(message.content); // plaintext, only on Bob's device
});
bob.startRelaySubscription();

inMemoryStore() and inMemoryRelay() run the real protocol and the real cryptography against simulated infrastructure: no auth, no durability, nothing that outlives the process. Swap them for a real store and relay when the shape of your integration is settled — the client code above does not change.

What the package covers

One coherent API surface, imported by subpath so your bundle only carries what you use.

  • Identity and trust

    One versioned X25519 and Ed25519 composite tuple is the trust object. Safety numbers, rotation, and fail-closed trust mutation once an identity is pinned.

    ./keys./safety./profile./username

  • Sessions and messaging

    PQXDH establishment and the Double Ratchet, with post-quantum ratcheting, replay boundaries, skipped-key limits, and bounded state recovery.

    ../client./client/compose./client/headless

  • Devices and groups

    Multi-device fan-out, provisioning and transfer flows, group state, and sealed sender.

    ./device./device/provisioning./groups./sealed-sender

  • Attachments and files

    Encrypted attachments and files with digest verification, retry decisions, and pointer metadata handled by the SDK.

    ./media./files

  • Application integration

    Lifecycle hooks, blocking, server clock skew handling, and typed errors your application can act on.

    ./hooks./blocking./server-clock./types

  • Anonymous group credentials

    ZK credential primitives, for products whose group membership should not be legible to the server.

    ./zk/groups./zk/credentials

Composition, not configuration

Four boundaries are yours. You pass adapters into createSignalProtocolClient; the protocol logic above them never changes, and no credentials ever reach us.

Local store
Where protocol state lives on the device../local/store/expo./local/store/web./local/store/node./local/store/react-native./local/store/memory
Secret vault
Where bootstrap secrets live, backed by a platform keystore../local/vault./local/vault/expo-secure-store
Relay
How sealed envelopes reach the other device. Your backend, your contract../remote/relay/convex./remote/relay/memory./remote/relay/types
Object store
Where encrypted attachment bytes go../remote/object-store/convex-r2./remote/object-store/s3

The Expo, Node, browser, and bare React Native stores implement ISignalProtocolLocalStore in full, so groups and multi-device work on any of them. They differ in where the bytes land: the Expo store is expo-sqlite with SQLCipher, the Node store is an encrypted filesystem under node:crypto with 0600 permissions, the browser store is IndexedDB scoped to the page’s origin, and the bare React Native store writes through a key-value backend you supply and verify with the SDK’s exported backend-conformance kit. You can also implement ISignalProtocolLocalStore or ISignalProtocolRelayServer yourself — they are documented interfaces, not internals.

One caveat worth knowing before you plan a sprint: the encrypted local store uses expo-sqlite with SQLCipher, and SQLCipher requires a development build — it is not available in Expo Go. There is no native module of ours to link and npx expo prebuild is not involved, but you do need a dev build rather than the Expo Go sandbox.

Where your data actually sits

Plaintext and private keys never leave a device outline. Your relay carries sealed envelopes, and those envelopes still have addresses on them.

One message travelling from one device to another through a relay Two outlined devices with readable contents and a private key inside each. Between them, sealed envelopes drawn as solid slabs leaning in the direction of travel, carrying metadata ticks on their outside edge, and the OpenE2EE mark standing in for the relay. Dotted brass rules mark where the message is sealed and where it is opened.Device A · readableDevice B · readablesealopenmetadata visiblesealedsealedrelay · never needs plaintext One message travelling from one device to another through a relay Two outlined devices with readable contents and a private key inside each, one above the other. Between them, sealed envelopes drawn as solid slabs leaning in the direction of travel, carrying metadata ticks on their outside edge, and the OpenE2EE mark standing in for the relay. Dotted brass rules mark where the message is sealed and where it is opened.Device A · readablesealsealedmetadata visiblerelay · never needs plaintextsealedopenDevice B · readable

How it compares

libsignal is excellent, actively maintained, and the most scrutinised Signal Protocol code there is. It is also a native Node addon that does not run in a browser, in Expo, or in React Native, and its maintainers say plainly that use outside Signal is not something they support. That is the gap.

This repository is used by the Signal client apps (Android, iOS, and Desktop) as well as server-side. Use outside of Signal is unsupported. All APIs and implementations are subject to change without notice, as are the JNI, C, and Node add-on "bridge" layers.
signalapp/libsignal, README.md (opens in a new tab)

Read from the GitHub API, the npm registry API, and the published package tarballs on 2026-08-03. Dates are the ones those APIs returned. The axes are the ones this SDK was built to change, so the table is not a quality ranking — and the column for our own package is the one column nobody independent measured.

 @open-e2ee/signal-protocol-sdk@signalapp/libsignal-client (opens in a new tab)libsignal-protocol-javascript (opens in a new tab)@privacyresearch/libsignal-protocol-typescript (opens in a new tab)ts-mls (opens in a new tab)
Expo / React NativeYesNo — Node native addon; the 0.99.3 tarball ships binaries for macOS, Linux, and Windows onlyNoNo documented React Native pathNot stated — browsers, Node, and serverless are the documented targets
BrowserYesNoYesYesYes
MaintainedYes — 0.1.x, activeYes — very active; repo push 2026-07-31No — archived, last push 2021-08-04No — last npm publish 2023-05-06, last repo push 2023-07-18Yes — very active; repo push 2026-08-03
Post-quantum key agreementYes — PQXDH with ML-KEM, default, fails closedYesNoNoYes — ML-KEM ciphersuites
Post-quantum signaturesNo — identities are classical Ed25519NoNoNoYes — ML-DSA-87 ciphersuites
TypeScript-nativeYesNo — Rust core with TypeScript bindingsNo — JavaScriptYesYes
ProtocolSignal ProtocolSignal ProtocolSignal ProtocolSignal ProtocolMLS (RFC 9420) — a different protocol
Commercial license offeredYesNo — AGPL-3.0 onlyNo — GPL-3.0No — GPL-3.0Not needed — MIT

A licence row is not a scorecard either. ts-mls is MIT, which already permits proprietary use, so “no commercial licence offered” costs its users nothing. Ours is AGPL-3.0-or-later with a commercial licence available, which is a cost to some readers and the reason the row is there.

What each one is actually for

A table flattens the thing a table is worst at: what a project is good at. Two of the five below end with a recommendation that is not this SDK.

Take none of it on our word. Every project links to its repository, every date came from a public API, and the commands that read them are in the comment at the top of src/lib/comparison.mjs.

This SDK — @open-e2ee/signal-protocol-sdk

An independent TypeScript implementation of the published Signal Protocol specifications, running in Expo, React Native, modern browsers, and Node from one package, with post-quantum PQXDH on by default and failing closed. Storage and transport are yours to compose. It is 0.1.x, it is reviewed by adversarial AI agents but not audited by any independent firm, and it is not wire-compatible with Signal Messenger — each of those is stated at length elsewhere on this site rather than left for you to discover.

The case for it is the row above: the Signal Protocol, in the runtimes the other Signal Protocol libraries do not reach.

The official implementation — @signalapp/libsignal-client (opens in a new tab)

The implementation Signal Messenger itself uses, and by a distance the most scrutinised Signal Protocol code that exists. It is very actively maintained, post-quantum, and AGPL-3.0 — 0.99.3 published 2026-07-31, with the repository pushed the same day. It is also a Rust core reached through a Node native addon: the published 0.99.3 tarball carries six prebuilt binaries, for macOS, Linux, and Windows on arm64 and x64. There is no browser build, no Expo build, and no React Native build, and the README does not describe use outside Signal as a supported case.

If you are shipping a desktop or server application on Node, this is the one to reach for first.

The original JavaScript port — libsignal-protocol-javascript (opens in a new tab)

Signal’s own JavaScript port, and the ancestor of most of the browser Signal Protocol code in the wild. The repository is archived: the last push was 2021-08-04 and it is read-only on GitHub. It predates PQXDH, so it is classical X3DH with no post-quantum key agreement, and it is GPL-3.0.

Archived means archived. Nothing new should start here.

The TypeScript rewrite of it — @privacyresearch/libsignal-protocol-typescript (opens in a new tab)

An independent TypeScript rewrite of the archived port, and a genuinely useful one when it was current. Its last npm publish was 0.0.16 on 2023-05-06 and its last repository push was 2023-07-18. It is classical X3DH rather than post-quantum and it is GPL-3.0, and its README documents installation and API use without naming a target runtime — there is no React Native or Expo path in it to follow.

Still runs, still unmaintained. Check the dates against your support horizon.

A different protocol, done well — ts-mls (opens in a new tab)

ts-mls implements MLS (RFC 9420) in TypeScript for browsers, Node, and serverless runtimes, and is MIT-licensed. It is very actively maintained — the repository was pushed 2026-08-03, with 1.6.2 the current stable release from 2026-03-07 and a 2.0.0 release candidate line publishing through 2026-07-18. Its post-quantum coverage goes further than this SDK’s: alongside ML-KEM ciphersuites it offers ML-DSA-87 signatures, where identities here are still classical Ed25519. MLS is not the Signal Protocol — different group semantics, a different key schedule, a different ecosystem.

If MLS suits your product, use ts-mls. That is a real choice and this page is not an argument against it.

Licensing

The complete SDK is available under AGPL-3.0. Proprietary products purchase annual commercial production rights at a published price, starting at $5,000 per year.