Security and protocol policy
Spec revision pins, post-quantum policy defaults, the deliberate divergence from Signal Messenger, the identity trust model, audit status, and the responsibility split.
Applies to @open-e2ee/signal-protocol-sdk 0.1.x. Public APIs and persisted formats may change before 1.0.
This page states what the package pins, what it defaults to, and what it does not claim. The SDK's own docs/SECURITY.md, docs/PROTOCOL_POLICY.md, and docs/ASSURANCE.md support everything here. The page reproduces quoted material verbatim.
Specification revisions
The implemented profile is independently versioned and based on selected public Signal Protocol specifications.
| Specification | Pinned 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) | — |
Internal format versions: message format v2, session record format v4, sender key format v1.
The Signal Protocol is a family of public specifications. Signal Messenger is a separate product, and this package is not affiliated with it. See E2EE architecture.
Protocol policy
Defaults are { postQuantum: 'required', braid: 'required' }. The composition options place both values under protocol.
| Setting | Value | Behaviour |
|---|---|---|
postQuantum | 'required' (default) | Post-quantum session establishment is mandatory. A peer that cannot do PQXDH cannot establish a session; you get PQXDHRequiredError |
'compatible' | Explicit opt-in. Permits a classical session only when a peer advertises no post-quantum material at all | |
braid | 'required' (default) | The specification-defined ML-KEM Braid SPQR profile |
'disabled' | Post-quantum establishment stays required; SCKA uses direct ML-KEM-768 instead of Braid |
Compatible mode does not allow downgrade recovery. Specifically:
- malformed post-quantum metadata fails closed
- cryptographic failure after post-quantum selection fails closed
- missing referenced local post-quantum prekey material fails closed
- successful post-quantum establishment keeps the post-quantum message ratchet mandatory
'compatible' is a narrow interoperability allowance for peers with no PQ material, not a fallback path when PQ goes wrong. And braid: 'disabled' "remains an explicit SDK mode for product-reviewed constraints; it is not the default": it is not downgrade recovery either.
There is no public postQuantum: 'disabled' mode. The root exports constants as PostQuantumPolicy and BraidPolicy.
PQXDHRequiredError carries reason ('no_kyber_prekey' or 'pqxdh_failed'), retryable, and suggestedRetryDelay: default 30000 ms. 'no_kyber_prekey' with retryable: true usually means the peer has not synced yet. Full shape in Errors.
Post-quantum profile
PQXDH and ML-KEM-1024
Verbatim from the security model:
- "Required post-quantum session establishment is the default."
- "PQXDH uses standardized FIPS 203 ML-KEM-1024 behavior."
- "Public keys and ciphertexts require exactly
0x0A || raw ML-KEM-1024 bytes. Untagged values, the deployed Kyber10240x08tag, unknown tags, and wrong lengths fail before secret derivation or state commit." - "Prekey signatures cover a domain-separated context containing the locally derived composite identity commitment, algorithm tag, key ID, and complete tagged public key."
Deliberate divergence from Signal Messenger
The
0x0Aencoding is distinct from Signal Messenger deployments that use round-3 Kyber1024 tagged0x08.
This is a design decision, not an oversight. The Ed25519 identity component creates ordinary RFC 8032 signatures and "does not implement XEdDSA and cannot consume or produce XEdDSA identity signatures."
The SDK is not wire-compatible with Signal Messenger or libsignal. A Signal Messenger client cannot decrypt messages from this SDK. The systems cannot exchange identities or migrate sessions in either direction. Their safety numbers never match because the systems use different inputs. Do not plan an integration that requires communication with Signal Messenger.
The cryptographic core follows the Signal Protocol specifications. It uses the same key agreement, ratchet, KDF labels, and chain-key constants. Many parts are byte-identical to libsignal. The SDK deliberately uses its own encoding. DEVIATIONS documents each departure and its cost. It identifies deviations that weaken an assurance from the specifications.
SPQR and ML-KEM Braid
- "PQXDH sessions keep SPQR mandatory."
- "The default SCKA mode is the specification-defined ML-KEM Braid profile using ML-KEM-768 and bounded Reed-Solomon decoding."
- "HEK follows the ML-KEM Braid specification:
SHA3-256(ek_seed || ek_vector)." - "
KDF_OKis applied once when raw ML-KEM output enters the epoch boundary. Braid output is already an epoch secret and is not derived a second time." - "Direct ML-KEM-768 SCKA remains an explicit SDK mode for product-reviewed constraints; it is not the default."
Note the two key sizes are not a contradiction: ML-KEM-1024 for session establishment (PQXDH), ML-KEM-768 inside the continuous-key-agreement profile (SPQR/Braid), matching the respective specifications.
Identity trust model
| State | Meaning |
|---|---|
UNVERIFIED_TOFU | First contact. Pinned, but not authenticated identity |
VERIFIED | Promoted by authenticated safety-number comparison |
IdentityTrustState is exactly these two values. The trust object is a composite tuple, not a single key:
- "One canonical X25519 + Ed25519 composite tuple is the trust object for each
(userId, identityType)." - "First contact is
UNVERIFIED_TOFU, not authenticated identity." - "Replacing either component of a pinned tuple fails closed until rotation is explicitly accepted. A retired tuple cannot silently regain trust."
- "Authenticated safety-number comparison covers both peers' complete composite tuples and only promotes the exact current tuple to
VERIFIED."
The provisioning flow copies the tuple unchanged across linked devices. Registration IDs, prekeys, and sessions stay device-specific.
TOFU means the first key you see is the key you trust. It detects later substitution. It does not authenticate first contact. Recovery of authenticity on unauthenticated first contact, without an authenticated safety-number or configured trust mechanism, is explicitly out of scope. Application flow: identity change and safety numbers.
Boundaries the package states about itself
JavaScript timing
"Browser, Node, and Expo/React Native JavaScript engines provide no machine-level constant-time contract." The code uses best-effort source patterns: full-scan MAC comparison and equal-work rejection padding on selected paths. It uses fixed work to derive both decapsulation candidates before masked selection.
"These patterns do not prove timing equivalence." Secret-influenced arithmetic, JIT compilation, allocation, garbage collection, cache effects, and host scheduling remain observable. "The current threat model does not qualify this JavaScript profile for hostile same-process code or a high-assurance co-resident timing adversary."
Memory
secureZeroBytes() overwrites the exact mutable typed array that the caller owns. It cannot erase earlier or implicit copies. It also cannot erase immutable strings, engine temporaries, JIT artifacts, or copies held by an adapter or operating system. "Treat a compromised JavaScript runtime as a key compromise."
Storage
Adapter encryption "does not compensate for XSS, malicious dependencies, compromised application code, a stolen unlocked device, or an attacker who also obtains the adapter encryption key." Browser IndexedDB and caller-provided React Native storage inherit their host application's security boundary.
Relay and object store
"Relay and remote object-store adapters receive public key material, routing metadata, and opaque ciphertext required by their contracts. TLS, authorization, abuse controls, atomic prekey consumption, and access logging remain deployment responsibilities. The relay is not an identity trust anchor."
The relay never needs message plaintext or device private keys. It does see routing metadata: who talks to whom, when, and how much. Limits and metadata and threat model map that boundary.
Assurance
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 SDK's docs/ASSURANCE.md. No independent firm has audited the SDK, and none is engaged. Do not read these docs as a third-party assurance claim.
Not FIPS 140-validated. The cryptography is pure JavaScript running in your app's process. It is not a validated cryptographic module and cannot be one in that form. The security model's line "PQXDH uses standardized FIPS 203 ML-KEM-1024 behavior" describes how the algorithm follows the FIPS 203 algorithm specification. It describes how the algorithm behaves but does not validate code. If procurement rules require FIPS 140, do not use this package.
Automated checks, most recent full run 2026-08-10:
| Metric | Value |
|---|---|
| Modules | 384 |
| Assertions | 6,893 |
| Passed | 6,891 |
| Skipped | 2 |
| Failed | 0 |
| Wall time | 330 s |
Those run in a private engineering repository. The published repository is a mechanized allowlist-filtered export, and the checks must pass before an export is cut. Public CI on the published repository runs npm ci, npm run build, npm run typecheck, and npm audit --omit=dev at moderate severity on every change.
Six direct production dependencies, resolving to six packages in total: @noble/ciphers, @noble/curves, @noble/hashes, @noble/post-quantum, async-lock, unique-names-generator. A small dependency surface is a smaller supply-chain surface, not an absence of one: "compromised endpoints, JavaScript engines, dependencies, or build pipeline" is explicitly out of scope.
Responsibility split
The client owns protocol coordination; the host application owns persistence, authentication, authorization, and product policy.
| SDK | Your application |
|---|---|
| Session establishment, ratcheting, encryption and decryption | Authentication and authorization |
| Trust pinning and fail-closed rotation behaviour | Whether and how to surface a trust event to the user |
| Bounds checks, replay defence, format validation | Rate limiting, abuse controls, request size limits, TLS |
| Prekey generation and rotation scheduling | Atomic prekey consumption on the relay |
| Local encryption of attachment bytes | Object access control, upload size limits, presigned URL handling |
| Session record semantics | Durable, atomic persistence of those records |
Remote object-store upload requests use a retry identifier, not a storage key. Applications must scope that identifier to the authenticated principal and own the durable mapping to a canonical object identifier and a private provider key. "Presigned URLs are short-lived bearer credentials and must not be logged or exposed beyond the authorized operation."
Threat model summary
In scope: passive network observation. Active tampering, replay, reordering, duplication, truncation. Malicious or stale relay responses within the adapter contract. Identity component substitution after a user pins trust.
Malformed protocol inputs include Braid traces and Reed-Solomon chunks. The scope includes loss and out-of-order delivery within configured bounds. It also includes later compromise followed by an uncompromised ratchet recovery step.
Out of scope or only partially mitigated: compromised endpoints, JavaScript engines, dependencies, or build pipeline. Hostile same-process code and high-assurance local timing attackers. Guaranteed secret erasure from managed memory. Hardware, physical, speculative-execution, and microarchitectural attacks.
The scope excludes traffic-analysis resistance beyond the implemented controls. It excludes Signal Messenger interoperability. It also excludes authenticity recovery for unauthenticated first contact.
Full text: threat model.
Reporting vulnerabilities
Follow the private reporting instructions in the SDK repository's root SECURITY.md. Do not open a public issue for a suspected vulnerability.
License
AGPL-3.0-or-later. For proprietary products that cannot meet AGPL obligations, commercial licensing is available. See licensing and buying a commercial license.
Next
- Threat model: what the design assumes about attackers
- Limits and metadata: what the relay can still see
- Security review: questions to ask before shipping
- Production checklist: the pre-launch pass
Errors
The complete error taxonomy — base classes, every EncryptionErrorCode, specialised classes with their type guards, and the retry rules.
Licensing
Compare AGPL use with Startup, Growth, and Enterprise commercial rights, and the steps to associate a commercial license with one legal entity and named product.