@open-e2ee/signal-protocol-sdk
v0.1.0
Published
Signal Protocol E2EE SDK for TypeScript — Expo, React Native, browser, and Node. Post-quantum PQXDH and Double Ratchet.
Maintainers
Readme
OpenE2EE Signal Protocol SDK
End-to-end encrypted messaging for TypeScript apps. Signal Protocol, post-quantum by default, runs in Expo.
Website · Docs · Quick Start · Architecture · Security Model · Protocol Policy · Deviations
- Pure TypeScript. No native modules, no prebuild step, no platform binaries to ship.
- Runs where your app runs. Expo, React Native, modern browsers, and Node from one package.
- Post-quantum by default. PQXDH and ML-KEM session establishment are on without configuration, and fail closed.
- Real messaging features. Multi-device, groups, sealed sender, encrypted attachments, safety numbers.
- Pluggable storage and relay. Device-local storage is required and yours; the relay is an interface, not a hosted service.
- AGPL-3.0-or-later, or a commercial license. Building something closed-source? See COMMERCIAL.
Not affiliated with Signal Messenger. This is an independent implementation of the public Signal Protocol specifications — full notice in NOTICE. It is not wire-compatible with Signal Messenger or libsignal: messages, identities, and safety numbers do not interoperate, and every deliberate difference is documented in DEVIATIONS.
0.1.x — public APIs and persisted formats may change before 1.0.
See it run
A second device is linked to an account over the real QR handshake. The message sent before that device existed is not on it — not withheld by a policy, but unreadable because the device's keys did not exist when that message was encrypted.
Recorded from open-e2ee.dev/demo, which runs this package in the browser against the in-memory store and the in-memory relay: real protocol and cryptography, simulated in-memory infrastructure. Every scenario there prints the receiving device's own log, unedited.
Install
npm install @open-e2ee/signal-protocol-sdkInstalling straight from the repository also works — the package compiles itself during install, so TypeScript is the only build requirement:
npm install github:open-e2ee/signal-protocol-jsAdapters declare their runtime requirements as optional peer dependencies, so install the ones your chosen adapters need (for example expo-sqlite and expo-secure-store, or convex).
Quick Start
Two clients, one process, no account and no server. The relay holds the envelope; only Bob's device turns it back into text.
// Real protocol and cryptography; simulated in-memory infrastructure.
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();
// Decrypted content reaches your app here, and nowhere else.
bob.registerHook("onMessageDecrypted", async (message) => {
console.log(`${message.senderId}: ${message.content}`); // alice: hello
});
await alice.send("bob", "hello"); // the relay now holds ciphertext and metadata
bob.startRelaySubscription(); // delivery and local decryption start hereEvery identifier above is a real export. This exact block is extracted from this README and executed against the packed package by this repository's CI on every change, and the same sequence runs in the engineering repository's automated checks.
Next: inspect what the relay actually stored, then compose the Expo + Convex production client.
How it compares
Measured from the GitHub API, the npm registry API, and the published package tarballs on 2026-08-03. Every alternative is a real project doing a real job; the axes below are the ones this SDK was built to change, not a general quality ranking.
| | Expo / React Native | Browser | Maintained | Post-quantum | TypeScript-native | Commercial license |
|---|---|---|---|---|---|---|
| @open-e2ee/signal-protocol-sdk | Yes | Yes | Yes — 0.1.x, active | Key agreement yes — PQXDH + ML-KEM, default and fails closed. Signatures no — identities are classical Ed25519 | Yes | Yes |
| @signalapp/libsignal-client | No — Node native addon; the 0.99.3 tarball ships binaries for macOS, Linux, and Windows only | No | Yes — very active; repo push 2026-07-31 | Key agreement yes | No — Rust core with TypeScript bindings | No (AGPL-3.0 only) |
| libsignal-protocol-javascript | No | Yes | No — archived, last push 2021-08-04 | No | No — JavaScript | No (GPL-3.0) |
| @privacyresearch/libsignal-protocol-typescript | No documented React Native path | Yes | No — last npm publish 2023-05-06, last repo push 2023-07-18 | No | Yes | No (GPL-3.0) |
| ts-mls | Not stated — browsers, Node, and serverless are the documented targets | Yes | Yes — very active; repo push 2026-08-03 | Key agreement and signatures — ML-KEM and ML-DSA-87 ciphersuites | Yes | Not needed (MIT) |
ts-mls implements MLS (RFC 9420), a different protocol with different properties — if MLS suits your product, it is a good library and this table is not an argument against it, and its post-quantum coverage reaches further than this SDK's. @signalapp/libsignal-client is the implementation Signal Messenger itself uses; its README states that use outside Signal Messenger is unsupported. If you are shipping a desktop or server application on Node, reach for it first.
The longer version of this table, with a paragraph on each project, is at open-e2ee.dev/compare.
Trust and verification
Cryptography deserves evidence rather than adjectives, so here is what there is and what there is not.
Audit status. Reviewed continuously by adversarial AI agents; not audited by any independent firm. Every change passes an adversarial AI review before it merges, and recurring whole-codebase AI audit passes run against the engineering repository. What that covers — and what it does not — is stated in the assurance summary. No independent firm has audited the SDK, and none is engaged. Nothing here should be read as a third-party assurance claim.
Security model and protocol policy. The security model states the threat model, what is in and out of scope, the storage boundary, and the resource limits. The protocol policy states which protocol modes are supported and which fail closed.
Specifications, pinned by revision. The implementation follows its own versioned profile based on these published specifications:
| Specification | Revision | |---|---| | X3DH | Revision 1, 2016-11-04 | | PQXDH | Revision 3, 2023-05-24 (last updated 2024-01-23) | | Double Ratchet | Revision 4, 2025-11-04 | | Sesame | Revision 2, 2017-04-14 | | ML-KEM Braid | Revision 1, 2025-02-21 (last updated 2025-09-26) | | FIPS 203 (ML-KEM) | Final, 2024-08-13 | | RFC 8032 (Ed25519) | — |
Not wire-compatible, and specific about why. This SDK cannot exchange messages with Signal Messenger or libsignal, cannot exchange identities with them, and its safety numbers will never match the ones a Signal Messenger client displays for the same two users. The cryptographic core is the same; the wire profile is deliberately its own. Identities are a versioned composite of separate X25519 and Ed25519 keys rather than one Curve25519 key, so signatures are ordinary RFC 8032 Ed25519 rather than XEdDSA; ML-KEM-1024 keys and ciphertexts are tagged 0x0A, distinct from the round-3 Kyber1024 0x08 tag. DEVIATIONS is the complete account — every difference from the specifications and from libsignal, with the reason, the cost, and the file that implements it. The threat model boundary is in the security model.
Dependencies: 6. Six direct production dependencies — @noble/ciphers, @noble/curves, @noble/hashes, @noble/post-quantum, async-lock, unique-names-generator — resolving to 6 packages in total: the only transitive edges are the @noble packages depending on one another, so the resolved tree adds nothing the list above does not already name. Everything else in the tree is a development or optional peer dependency.
Automated checks. The published repository is a mechanized export of a private engineering repository, filtered by an allowlist; the automated checks live there and have to pass before an export is cut. The assurance summary carries the current run figures — regenerated from a real run as part of every release, never hand-edited — and explains what the checks cover, what is not published, and why. The published repository runs its own build, typecheck, and production dependency audit in CI on every change.
Constant-time posture, honestly. JavaScript engines offer no machine-level constant-time contract, and this SDK cannot invent one. What exists is best-effort source-level work on selected paths: full-scan comparison for equal-length MACs and identity bytes, fixed-work derivation of both decapsulation candidates before masked selection, and equal-work rejection padding on selected replay and authentication paths. These are not timing-equivalence proofs. Secret-influenced remainder and compression arithmetic remains, and JIT compilation, allocation, garbage collection, and cache effects stay observable. secureZeroBytes() overwrites the exact typed array it is handed and nothing more — not copies, not strings, not engine temporaries. The threat model does not cover hostile same-process code or a high-assurance co-resident timing adversary, and it says so.
Reporting a vulnerability. Email [email protected] rather than opening an issue. Acknowledgment within 72 hours, initial assessment within 7 days; the full policy is in SECURITY.md.
Documentation
Hosted guides, quickstarts per runtime, and concept docs live at docs.open-e2ee.dev. The in-repo references below are versioned with the code:
- Getting Started — installation, mental model, first working client.
- Package Surface — root exports, every subpath, adapter implementations, core concepts.
- Recipes — message flow, protocol policy, multi-device, attachments, usernames.
- Client Composition — production composition with Expo and Convex.
- Architecture and Adapters — layer model and composition boundaries.
- Integration Interfaces — the interfaces to implement for custom storage, vaults, relays, and object stores.
- Error Handling and Troubleshooting.
- E2EE concepts — what changes about your architecture when the relay cannot read.
- Deviations — every difference from the Signal Protocol specifications and from
libsignal, with reasons and costs. - API Reference — generated from the exported declarations.
License and warranty
Licensed under AGPL-3.0-or-later; see LICENSE. For proprietary products that cannot meet AGPL obligations, a commercial license is available — see COMMERCIAL or email [email protected].
The software is provided as is, without warranties or conditions of any kind. To the extent permitted by applicable law, copyright holders and contributors are not liable for damages arising from its use. Applications remain responsible for evaluating this SDK against their own requirements and for securing their deployment, storage, authentication, authorization, and operations. This summary does not modify the license; the complete warranty disclaimer and limitation of liability are in sections 15 and 16 of the GNU Affero General Public License.

