Skip to main content

Enkrypted Chat — Complete Protocol Specification

Work in progress — not audited

This document is an initial draft by Positive Intentions. Enkrypted Chat has not been independently audited. Do not rely on it for sensitive communications or production use.

Document status: Initial draft (informal v0.1) · Product: Enkrypted Chat · Organization: Positive Intentions

This page combines every specification chapter for one-shot printing. Use Print → Save as PDF and enable Background graphics so Mermaid diagrams render.

Document overview

Normative protocol specification for Enkrypted Chat: gap analysis, design rationales (P0), profile overview (P1–P8), and appendices. For the navigable multi-page version, see the Protocol spec section in the sidebar.

Protocol specification gap analysis

Work in progress — not audited

This analysis does not claim equivalence to Noise or the WireGuard paper.

Purpose

The Enkrypted Chat specification is a hybrid product + protocol document. This page tracks readiness for reviewers who need behavior without reading TypeScript.

Reference bar (updated)

Spec elementNoise / WireGuard barEnkrypted Chat status
ScopeSingle protocolMet (split) — P0–P8 + appendices vs ch. 1–12; composition documented
Wire formatsByte-level packetsPartial — JSON normative; 16 MiB cap; P4.13 full field reference; not binary
Crypto primitivesNamed profileMetP1
Processing rulesPseudocodeMetP6
State machinesFSM + diagramsMetP7, P3 sequences
Security propertiesFormal claimsMet — P1.4 Provides/Partial/Does-not-provide + ch. 9
Limits / errorsCaps + codesMetP8; protocol-error implemented in p2p
Test vectorsGolden hexPartial — V1–V7, V9 in Appendix A; live cascade V8 deferred
RationalesDesign whyMetP0
Signal deltasN/AMetAppendix B
VersioningFrozen idPartialEnkryptedChat-Profile-v0; v1 criteria in P1.7
Change logRev historyMetCHANGELOG-SPEC

Structural difference (honest)

Enkrypted Chat is a composition, not one monolithic protocol:

Auditor checklist

#QuestionWhere answered
1Session message order?P3, P7
2Algorithms for a text message?P1, P5
3JSON on the wire?P4, P5
4Decrypt failure / oversize?P6, P8
5What is not guaranteed?P1.5, ch. 11
6Reproduce a round-trip?Appendix A + repo tests
7P2P vs standard Signal?Appendix B

Deferred (documented, not hidden)

  • Live MLS+Signal+ML-KEM+AES hex (Appendix A V8, Profile-v1 gate).
  • Mandatory specVersion on all PDUs (Profile-v1 gate).
  • Binary wire profile (EnkryptedChat-Profile-v2-binary informative only).

Protocol document map

ChapterTitle
P0Design rationales
P1Protocol overview and profile
P2Signaling (broker path)
P3Session establishment
P4PDU catalog and schemas
P5Cascade wire encoding
P6Processing rules
P7State machines
P8Limits, errors, edge cases
Appendix ATest vectors
Appendix BP2P Signal deltas
Appendix CGlossary
Appendix DSFrame (informative WIP)

P0. Design rationales

Work in progress — not audited

Informative design background for EnkryptedChat-Profile-v0. Normative behavior is in P1–P8.

P0.1 Purpose

This chapter explains why the protocol is shaped as it is. It does not replace normative rules in P1–P8.

P0.2 JSON application PDUs on WebRTC data channels

Choice: UTF-8 JSON objects with a string type discriminator on SCTP data channels.

Rationale:

  • Browser-first stack: JSON.stringify / parse integrate cleanly with React state and debugging tools.
  • Inspectability during development: handshake and cascade envelopes can be logged (with secrets redacted) without a custom binary decoder.
  • Single implementation: no interoperability requirement for a compact binary codec.

Trade-off: Larger on-wire size than a typed binary protocol (e.g. WireGuard packets). Mitigated by chunking and fast-file paths for bulk data.

Future: An informative EnkryptedChat-Profile-v2-binary MAY be defined later; v0/v1 documentation remains JSON-normative.

P0.3 Cascade order MLS → Signal → ML-KEM → AES

Choice: Four application layers applied in registration order inside CascadingCipherManager.

Rationale:

  • MLS (RFC 9420): Group epoch semantics and future multi-device alignment; outermost for group-aware deployments.
  • Signal: Battle-tested 1:1 forward secrecy and break-in recovery via Double Ratchet (Appendix B).
  • ML-KEM-768: Post-quantum confidentiality layer on message content (not full-PQ transport).
  • AES-GCM-256: Fast symmetric envelope keyed from peer-derived material; supports rotation suffix.

Trade-off: Defense-in-depth adds latency and complexity. Cascade is not a substitute for auditing Signal, MLS, or the browser delivery chain.

P0.4 No third-party client interoperability

Choice: One product (Enkrypted Chat) and one codebase family; no version negotiation with external clients.

Rationale: Rapid iteration, module federation, and tight coupling between p2p and glitr-chat. A public interop surface would require frozen schemas and conformance certification before it adds value.

P0.5 Broker-first signaling (PeerJS / WSS)

Choice: Default path uses an online PeerJS-compatible broker for SDP/ICE exchange.

Rationale:

  • NAT traversal for browser peers without manual copy-paste in the common case.
  • Clear trust boundary: broker is semi-trusted for connection setup, not for message plaintext (P2).

Trade-off: Broker compromise enables signaling MITM until channel binding exists. QR/manual SDP remains a product option (ch. 3) but is out of scope for the v0 protocol profile.

P0.6 Cleartext handshake material (known limitation)

Handshake PDUs (P3) carry key packages and Signal exchange payloads inside JSON on the data channel before encryptionReady. Confidentiality during this phase is partial (P1.4). Encrypting the handshake wrapper is future work.

P0.7 TOFU and optional verification

Peers are addressed by Peer ID on the broker. Cryptographic binding comes from in-band Signal/MLS handshakes, not a global PKI. High-risk deployments SHOULD use safety-number or QR verification (product UX) in addition to TOFU.

TopicDocument
Normative profileP1
Signal deltasAppendix B
Supply chainch. 8
Change historyCHANGELOG-SPEC

P1. Protocol overview and profile

Work in progress — not audited

Normative protocol documentation for the current implementation. Not independently audited.

P1.0 Notation

Symbol / termMeaning
Byte stringUint8Array; on the wire as JSON array of integers 0–255
ConcatenationA || B
HexLowercase hex digits; test vectors omit 0x prefix
TextUTF-8 unless noted
IK, EK, …Signal/MLS symbols per upstream specs when cited

Keywords MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are interpreted per RFC 2119 in all P-chapters and appendices.

P1.1 Protocol identifier

This document defines profile:

EnkryptedChat-Profile-v0

Implementations claiming this profile MUST:

  1. Use WebRTC data channels as the transport (see P2, ch. 3).
  2. Use JSON UTF-8 objects as application PDUs on the data channel (see P4).
  3. When application E2EE is enabled, apply cascade layers in order MLS → Signal → ML-KEM → AES on plaintext (see P5).
  4. Follow processing rules in P6.

Alternate profiles (non-normative for v0):

ProfileDescription
EnkryptedChat-Profile-v0-MLS-fallbackCascade disabled or failed; MLS-only envelope
EnkryptedChat-Profile-v0-fast-fileFast file transfer; cascade MAY be bypassed; DTLS only for bulk data

P1.2 Layer stack

LayerComponentNormative reference
L0Signaling (PeerJS / WSS)P2
L1WebRTC (DTLS, SCTP data channel, SRTP for media)RFC 8825–8829; ch. 3
L2Session handshake PDUsP3
L3Cascade layer 1 — MLSRFC 9420
L4Cascade layer 2 — Signal (X3DH + Double Ratchet)Signal spec; Appendix B
L5Cascade layer 3 — ML-KEM-768FIPS 203 / project quantum doc
L6Cascade layer 4 — AES-GCM-256Web Crypto API; password-derived in implementation
L7Application PDU (type field)P4

Encrypt direction (plaintext to wire): L7 payload → L3…L6 cascade → wrapped in encryptedMessage → L2 PDU → L1 DTLS.

P1.3 Product configuration (Enkrypted Chat)

Authoritative product settings in glitr-chat:

enableCascadingCipher = true
cipherLayers = ["MLS", "Signal", "ML-KEM", "AES"]
enableSignalProtocol = true

The p2p library defaults enableCascadingCipher = false; the product shell overrides to true.

P1.4 Security properties (normative summary)

Legend: Provides · Partial · Does not provide

PropertySignalingHandshakeTransport (DTLS/SRTP)Application message
Confidentiality (content)N/APartial — key material in JSON PDUsProvidesProvides — cascade + DTLS
IntegrityProvides (TLS)PartialProvidesProvides — AEAD per layer
Forward secrecy (message)Does not providePartialProvides (ephemeral)Provides — Signal + MLS epochs
Post-quantum confidentialityDoes not provideDoes not provideDoes not providePartial — ML-KEM layer only
Authentication (peer identity)Partial — Peer IDPartial — in-band keysDoes not providePartial — no global PKI
Metadata minimizationDoes not provideDoes not provideDoes not providePartial
Offline / async deliveryDoes not provideDoes not provideDoes not provideDoes not provide

PQ wording: ML-KEM-768 adds post-quantum protection for message content at the cascade layer; transport and handshake are not post-quantum.

At-rest protection for local keys is product-dependent and not claimed by this profile.

P1.5 Explicit non-properties

Implementations of EnkryptedChat-Profile-v0 do not provide:

  • Offline / asynchronous message delivery without both peers online
  • Global identity or account system
  • Guaranteed anonymity (IPs visible to TURN/peers)
  • Interoperability with Signal, WhatsApp, or Matrix clients
  • Independently audited security (see disclaimers)

P1.6 Formal verification scope

In-house formal work applies to the Rust Signal Protocol core only. It does not cover the browser stack, cascade composition, WebRTC, or JSON PDU layer. See Signal Protocol formal verification.

P1.7 EnkryptedChat-Profile-v1 (future freeze)

EnkryptedChat-Profile-v0 remains unfrozen. Before publishing v1, implementations MUST meet:

  1. Frozen PDU schemas (no new required fields without version bump).
  2. Mandatory specVersion: "1.0" on every application PDU.
  3. Implemented protocol-error PDUs (P8.8).
  4. Golden hex vectors in Appendix A.
  5. Normative Appendix B.
  6. Independent security audit completed or documented waiver.

Until then, receivers SHOULD ignore unknown JSON fields. See CHANGELOG-SPEC.

Optional field in current builds: specVersion (string) MAY appear on PDUs for forward compatibility; MUST NOT be required until v1.

P1.8 Vulnerability disclosure

Report security issues via GitHub Security Advisories on positive-intentions/chat. See SECURITY.md in that repository.

P1.9 Document boundary

In protocol spec (P0, P1–P8, appendices)In product spec (ch. 1–12)
Data channel PDUs, handshake, cascadeModule federation, UI, OPFS, calendar
WebRTC/signaling profileCompetitive analysis, roadmap
Limits, errors, test vectorsDeployment, CSP/SRI MUSTs
Design rationales (P0)SFrame UX (informative Appendix D)

P2. Signaling protocol

Work in progress — not audited

Normative protocol documentation for the current implementation. Not independently audited.

P2.1 Scope

This chapter specifies connection establishment signaling for EnkryptedChat-Profile-v0 when using an online PeerJS-compatible broker over WSS.

Out of scope for v0: QR-based or manual SDP exchange (ch. 3).

P2.2 Roles

RoleResponsibility
InitiatorPeer that opens connection to remote peerId
ResponderPeer that accepts incoming connection
BrokerRelays signaling messages; semi-trusted (ch. 2)

P2.3 Signaling sequence

P2.4 Normative requirements

  1. Signaling MUST use TLS (WSS) in production.
  2. The broker MUST NOT be assumed to protect message application plaintext (it does not receive cascade ciphertext except as opaque SDP/ICE side channels).
  3. Peers MUST treat broker compromise as signaling MITM risk until channel binding exists (future work).
  4. After data channel open, peers MUST run the crypto handshake in P3 before sending type: "message" with encryptedMessage unless encryption is disabled for debugging.

P2.5 Peer ID

  • Generated client-side; opaque string used as broker address.
  • Not a cryptographic identity key.
  • Collision resistance is probabilistic; no global registry.

P2.6 STUN/TURN (transport adjunct)

ICE candidates may traverse STUN/TURN as defined by WebRTC. TURN policy is product-configurable (ch. 3). TURN is not part of the JSON PDU catalog.

P2.7 Signaling vs data channel

TrafficChannelFormat
SDP, ICEBroker (PeerJS)PeerJS protocol
Application PDUsWebRTC data channelJSON UTF-8 (P4)

No application type from P4 is sent over the signaling broker in v0.

P2.8 Compromised broker

A malicious or compromised broker can:

  • Observe Peer IDs and connection timing (social graph metadata).
  • Perform signaling MITM on SDP/ICE unless peers verify out-of-band or channel binding is added (future work).

A broker cannot (in the v0 architecture):

  • Read cascade ciphertext (not carried on signaling).
  • Inject application handshake PDUs (those use the data channel only).

Peers SHOULD use WSS with certificate validation in production. See ch. 9 threat model and ch. 8 deployment.

P2.9 Future signaling authentication (informative)

Roadmap options (not normative in v0/v1): broker API token, mutual TLS on signaling. No implementation deadline is specified in this specification.

P3. Session establishment

Work in progress — not audited

Normative protocol documentation for the current implementation. Not independently audited.

P3.1 Preconditions

  1. Both peers registered on signaling broker.
  2. WebRTC data channel state is open.
  3. enableEncryption is true (production default).

P3.2 Handshake order (normative)

When EnkryptedChat-Profile-v0 cascade is enabled, peers SHOULD exchange PDUs in this order (either peer may send first where symmetric):

StepPDU typePurpose
1mls-key-package-requestRequest remote MLS key package
2mls-key-packageDeliver MLS key package
3mls-encryption-sync-requestSynchronize encryption state
4mls-encryption-sync-responseRespond to sync
5signal-key-exchange-requestStart P2P Signal material exchange
6signal-key-exchange-responseRespond to Signal exchange
7signal-session-completeSignal session ready
8mlkem-key-exchangeML-KEM public key material
9mlkem-key-exchange-responseML-KEM response
10mls-commit / mls-welcomeMLS group commits (as needed)
11mls-welcome-ackAck welcome (when applicable)

Exact ordering may include additional mls-commit rounds for groups. Implementations MUST tolerate interleaved handshake PDUs until encryptionReady is true.

P3.3 Completion criterion

Session crypto is ready when:

  1. encryptionReadyRef is true in MLSProvider, and
  2. Remote peer ID is in encryptedConnections set.

Only then MUST encrypted message PDUs be sent (see P6).

P3.4 Sequence diagram (1:1)

P3.5 P2P Signal deltas

Without a central pre-key server, X3DH material is exchanged in live PDUs over the data channel. Normative deltas: Appendix B. Tutorial: P2P Signal Protocol.

P3.5.1 Trust on first use

Peers MUST treat broker Peer IDs as routing handles only. Cryptographic trust comes from in-band Signal/MLS handshakes (TOFU). Deployments SHOULD offer safety-number or QR verification for high-risk users (product UX; not required for encryptionReady).

P3.6 Reconnection

State persisted locallyOn reconnect
Signal ratchet stateMAY resume if both peers retain state and peer IDs unchanged
MLS epoch / group stateMAY resume for same groupId
WebRTC sessionMUST re-establish ICE/signaling

If crypto state is lost, peers MUST repeat handshake from step 1.

P3.7 Reconnection sequence

P3.8 Key rotation sequence

P3.9 Out-of-order chunks

P3.10 Group sessions

Group chat uses additional PDUs (group-member-joined, mls-welcome, etc.). Status: experimental — normative PDU shapes in P4; security claims in ch. 11.

P4. PDU catalog and schemas

Work in progress — not audited

Normative protocol documentation for the current implementation. Not independently audited.

P4.1 Encoding

  • All PDUs described here are sent over WebRTC data channels unless noted.
  • Serialization: JSON encoded as UTF-8 text unless binary/Uint8Array fields are noted.
  • Every PDU MUST include a string field type as the discriminator.
  • Schema version: EnkryptedChat-Profile-v0 — field names may change before v1.0 freeze.

P4.2 PDU categories

CategoryPurpose
HandshakeEstablish encryptionReady
Encrypted transportUser content with encryptedMessage wrapper
ChunkingSplit large ciphertext
MessagingCleartext routing metadata (payload may be encrypted inside wrapper)
FilesFile transfer (standard and fast paths)
CallsGroup/voice/video signaling
PresenceTyping, peer list

P4.3 Handshake PDUs

typeDirectionRequired fields (minimum)Notes
mls-key-package-requestEitherTriggers key package send
mls-key-packageEitherkeyPackage (implementation-defined)MLS RFC 9420 structure
mls-encryption-sync-requestEitherState sync
mls-encryption-sync-responseEitherState sync response
signal-key-exchange-requestEitherSignal payload fields (WASM)P2P X3DH start
signal-key-exchange-responseEitherSignal payload fields
signal-session-completeEitherSignal ready
mlkem-key-exchangeEitherML-KEM public key material
mlkem-key-exchange-responseEitherML-KEM response
mls-commitEitherMLS commit bytes / structureGroup ops
mls-welcomeEitherMLS welcomeNew member
mls-welcome-ackEitherAck
rotation-timestamp-broadcastEitherrotation timestampAES key rotation suffix
handshakeEitherLegacy/generic
protocol-errorEithercode number MUST; messageId string MAY; detail string MAYSee P8.8

P4.4 Encrypted message wrapper

User-visible chat uses outer PDU:

FieldTypeRequiredDescription
typestringMUST"message" for chat
encryptedMessageobjectMUST when E2EE onSee P4.5
callbackanyMAYImplementation callback

Inner cleartext fields (before encryption) for message action:

FieldTypeDescription
messageIdstringUnique id
messagestringText body
timestampnumberUnix ms
groupIdstringMAY — group route
attachmentobjectMAY — media metadata

P4.5 encryptedMessage object

P4.5.1 Cascade mode (cascaded: true)

FieldTypeRequiredDescription
encryptedbooleanMUSTtrue
cascadedbooleanMUSTtrue
cascadedPayloadobjectMUSTSee P5
timestampnumberMUSTEncryption time
chunkedbooleanMAYtrue if split
messageIdstringMAYRequired if chunked
totalChunksnumberMAYChunk count
chunksnumber[]MAYByte arrays as JSON numbers

P4.5.2 MLS-only fallback (cascaded absent)

FieldTypeRequiredDescription
encryptedbooleanMUSTtrue
envelopeobjectMUSTgroupId, epoch, ciphertext (byte array)
chunkedbooleanMAYMLS chunking

MLS envelope fields:

FieldTypeDescription
groupIdnumber[]Byte array as JSON numbers
epochnumberMLS epoch
ciphertextnumber[]MLS ciphertext bytes

P4.6 Chunk transport PDU

typeFieldsDescription
__mls_chunk__messageType, messageId, chunkIndex, totalChunks, chunk, envelope?, timestampReassembles to full encryptedMessage

Reassembly MUST complete before decrypt. Out-of-order chunks SHOULD be buffered by chunkIndex.

P4.7 Messaging and presence PDUs

typePurpose
messageChat (see P4.4)
deleteMessageDelete by messageId
typing-indicatorTyping state
peer-listKnown peers
peer-joinedPeer presence
group-member-joinedGroup membership
group-inviteGroup invite

P4.8 File transfer PDUs (standard path)

typePurpose
file-metadataStart transfer
request-chunkRequest chunk index
file-chunkChunk payload
chunk-receivedAck
file-transfer-completeDone
chunk-ackAck variant

P4.9 File transfer PDUs (fast path)

Used when fast file transfer is enabled; cascade MAY be bypassed.

typePurpose
chunk-requestRequest chunk
file-chunkData
byte-range-requestRange request
byte-range-responseRange response
transfer-verify-requestVerify
transfer-verify-responseVerify response
transfer-chunk-status-requestStatus
transfer-chunk-status-responseStatus response
transfer-complete-notifyComplete
transfer-complete-verifyVerify complete
missing-data-responseMissing data
fast-file-metadataFast path metadata (same role as file-metadata)

Chunk sizes: 1 MB–16 MB adaptive (useFastFileTransfer); per-message cap 256 KB metadata overhead in some paths.

P4.10 Call PDUs

typePurpose
groupCallRequestIncoming group call
groupCallAcceptedAccepted
groupCallEndedEnded
call-ended1:1 call end

Media uses WebRTC SRTP; optional SFrame layer — experimental (ch. 4).

P4.11 Unknown types

Receivers MUST drop unknown type values without crashing; MAY log at debug only. Senders MUST NOT send handshake PDUs after encryptionReady except for rotation or group maintenance.

P4.12 Global PDU fields

FieldTypeRequiredMax / notes
typestringMUSTDiscriminator; see tables below
specVersionstringMAYMUST in Profile-v1; value "1.0"

Total serialized PDU size MUST NOT exceed 16 MiB (P8).

P4.13 Complete PDU field reference

Extracted from p2p routing (MLSProvider.tsx) and p2p/src/features/. WASM/MLS binary blobs are opaque string or number[] on the wire.

Handshake and crypto control

typeFields (name · type · required)
mls-key-package-request— (no additional fields)
mls-key-packagekeyPackage · string/object · MUST
mls-encryption-sync-requestimplementation-defined · MAY
mls-encryption-sync-responseimplementation-defined · MAY
signal-key-exchange-requestSignal WASM payload · MUST · Appendix B
signal-key-exchange-responseSignal WASM payload · MUST
signal-session-complete— · MAY
mlkem-key-exchangeML-KEM public key material · MUST
mlkem-key-exchange-responseML-KEM response · MUST
mls-commitMLS commit bytes/structure · MUST
mls-welcomeMLS welcome · MUST
mls-welcome-ack— · MAY
rotation-timestamp-broadcastrotationTimestamp · number · SHOULD
handshakelegacy · MAY

Messaging and presence

typeFields
messagemessageId string SHOULD · message string MAY · timestamp number MAY · groupId string MAY · attachment object MAY · encryptedMessage object MUST when E2EE · originalSender string MAY (relay) · _senderId string MAY
deleteMessagemessageId string MUST · groupId string MAY
typing-indicatorindicatorType string SHOULD (typing, recording-audio, sharing-location, stopped) · chatId string MAY
peer-listpeer list payload · implementation-defined
peer-joinedpeerId string SHOULD
group-member-joinedgroupId string MUST · member fields · implementation-defined
group-invitegroupId string MUST · invite payload · implementation-defined

Chunk transport

typeFields
__mls_chunk__messageId MUST · chunkIndex MUST · totalChunks MUST · chunk number[] MUST · messageType MAY · timestamp MAY · envelope object MAY (MLS chunk path)

Standard file transfer

typeFields
file-metadatafileId MUST · fileName MAY · fileSize number MAY · mimeType string MAY · totalChunks number MAY · prefix string MAY · thumbnail string/object MAY · groupId string MAY
fast-file-metadatasame as file-metadata
request-chunkfileId MUST · chunkIndex number MUST · requestedSize number MAY · groupId string MAY · originalRequester string MAY
file-chunkfileId MUST · chunkIndex number MUST · chunk number[]/binary MUST · groupId string MAY
chunk-receivedfileId MUST · chunkIndex number MUST
chunk-ackack fields · implementation-defined
file-transfer-completefileId MUST · completion metadata MAY

Fast file transfer

typeFields
chunk-requestfileId MUST · chunkIndex number MUST · requestedSize number MAY
byte-range-requestrange fields · implementation-defined
byte-range-responserange + data · implementation-defined
transfer-verify-requestfileId MUST · verify metadata MAY
transfer-verify-responseverify result · implementation-defined
transfer-chunk-status-requeststatus query · implementation-defined
transfer-chunk-status-responsestatus result · implementation-defined
transfer-complete-notifyfileId MUST
transfer-complete-verifyverify complete · implementation-defined
missing-data-responsemissing chunk indices · implementation-defined

Calls

typeFields
groupCallRequestcallType string MUST · groupId string MAY · callerName string MAY · originalCaller string MAY · relayedBy string MAY
groupCallAcceptedgroup/call ids · implementation-defined
groupCallEndedgroup/call ids · implementation-defined
call-endedfrom string MAY (peer id)

| protocol-error | code MUST · messageId MAY · detail MAY (no secrets) |

protocol-error (Profile-v0 implemented)

FieldTypeRequiredNotes
typestringMUST"protocol-error"
codenumberMUST1–5 per P8.8
messageIdstringMAYRelated message
detailstringMAYDebug only; no secrets

Relay metadata (group PDUs)

Group relay PDUs MAY include: originalSender, _senderId, relayedBy (string). Receivers SHOULD use originalSender for routing when present.

P4.14 Source of truth

PDU handlers are centralized in MLSProvider.tsx (routing) and feature modules under p2p/src/features/. This catalog reflects that implementation as of profile v0. See ch. 10 for repository map.

P5. Cascade wire encoding

Work in progress — not audited

Normative protocol documentation for the current implementation. Not independently audited.

P5.1 Scope

Defines the cascadedPayload object inside encryptedMessage for EnkryptedChat-Profile-v0. Cryptographic primitives of each layer are defined by Signal, MLS, ML-KEM, and Web Crypto — this chapter defines composition and serialization only.

P5.2 Layer processing order

CascadingCipherManager applies layers in registration order:

plaintext: Uint8Array
→ layer[0] MLS encrypt
→ layer[1] Signal encrypt (input: base64 decode of prior output)
→ layer[2] ML-KEM encrypt
→ layer[3] AES-GCM encrypt
→ finalCiphertext: Uint8Array

Between layers, intermediate ciphertext is represented as base64 strings internally; finalCiphertext is raw bytes.

Decrypt MUST reverse layer order.

P5.3 cascadedPayload schema

FieldJSON typeRequiredDescription
finalCiphertextarray of numbersMUST (unless chunked)Each element 0–255
layersarray of objectsMUSTPer-layer metadata
layerParametersarray of objectsMUSTPer-layer decrypt params
totalProcessingTimenumberMUSTMilliseconds
originalSizenumberMUSTPlaintext byte length
finalSizenumberMUSTCiphertext byte length
timestampnumberSHOULDUnix ms; informative for interop debugging

Decrypt MUST use finalCiphertext and layerParameters. layers[] statistics are informative but SHOULD be included for diagnostics.

P5.3.1 layers[] entry (LayerMetadata)

FieldTypeDescription
algorithmstringe.g. MLS, Signal, ML-KEM, AES-GCM-256
versionstringLayer implementation version
timestampnumberLayer processing time
inputSizenumberBytes in
outputSizenumberBytes out
processingTimenumberms
metadataobjectMAY — algorithm-specific

P5.3.2 layerParameters[]

Opaque per-layer decryption parameters (keys, nonces, Signal state handles). Structure is layer-specific; auditors SHOULD inspect cryptography module layer implementations.

P5.4 Key material (implementation notes)

For each encrypted message, MLSProvider builds CipherKeys:

Layer key nameSource
MLSmlsManager + groupId
SignalWASM session after handshake
ML-KEM-768peerMLKEMKeyPairsRef for target peer
AES-GCM-256Password derived from sorted peer IDs + optional rotation suffix

AES password format (normative for v0 implementation):

1:1 chat: "{sortedPeerA}-{sortedPeerB}-aes-key" or with "-{rotationTimestamp}" suffix
Group: "{localPeerId}-group-aes-key" (+ suffix)

P5.5 Chunking cascaded ciphertext

ConstantValue
CHUNK_SIZE8192 bytes (8 KiB)

If finalCiphertext.lengthCHUNK_SIZE:

  • Send single PDU with full cascadedPayload.

If larger:

  1. Set chunked: true, messageId, totalChunks.
  2. Omit finalCiphertext from inline payload; send chunks[] arrays.
  3. Optionally use __mls_chunk__ transport (P4).

Receivers MUST reassemble chunks in order before decrypt.

P5.6 Failure behavior

FailureBehavior
Missing layer keysThrow CascadingCipherError; encrypt path falls back to MLS-only
Layer decrypt/auth failMUST NOT emit plaintext; abort message (P6)
Zero layers configuredMUST NOT call encrypt

P5.7 Reference implementation

P6. Processing rules

Work in progress — not audited

Normative protocol documentation for the current implementation. Not independently audited.

P6.1 Terminology

Keywords MUST, MUST NOT, SHOULD, and MAY are used per RFC 2119.

P6.2 Outbound encrypt path

Given application PDU data with type and payload fields:

  1. If enableEncryption is false → send data unchanged; STOP.
  2. If remote peer ∉ encryptedConnections → send cleartext or queue handshake; implementation-defined; MUST NOT claim E2EE.
  3. Serialize inner payload to bytes (typically UTF-8 JSON of message fields).
  4. If enableCascadingCipher and cascade ready:
    1. Build CipherKeys per P5.
    2. cascadingCipher.encrypt(plaintextBytes, keys).
    3. Wrap result in encryptedMessage per P5.
    4. If size > 8192 bytes → chunk per P5.5.
  5. Else → MLS-only envelope per P4.
  6. Set data.encryptedMessage = result; preserve data.type.
  7. JSON.stringify → send on data channel via PeerJS.

On any encrypt failure at step 4, implementation MAY fall back to step 5 and SHOULD log.

P6.3 Inbound decrypt path

On data channel data event:

  1. Parse JSON; if parse fails → MUST drop.
  2. If type === "__mls_chunk__" → buffer; if complete → reconstruct encryptedMessage; goto 4.
  3. If encryptedMessage absent → dispatch cleartext PDU to action router; STOP.
  4. If encryptedMessage.chunked → reassemble chunks → single payload.
  5. If encryptedMessage.cascaded:
    1. Rebuild Uint8Array from finalCiphertext or chunks.
    2. cascadingCipher.decrypt(cascadedPayload, keys) reverse layer order.
    3. On error → MUST NOT deliver plaintext; MUST drop or surface error.
  6. Else MLS-only → mlsManager.decryptMessage(envelope).
  7. Parse decrypted bytes to inner fields (message, etc.).
  8. Dispatch to handler for original type (e.g. message action).

P6.4 Handshake dispatch

PDUs listed in P3 MUST be processed even when encryptionReady is false. Handlers MUST update local crypto state before replying.

P6.5 Ordering

  • JSON PDUs have no global sequence number at application layer.
  • Signal ratchet MUST handle out-of-order ciphertext within a session.
  • Chunk PDUs MUST be ordered by chunkIndex before decrypt.

P6.6 Fast file transfer profile

When fast transfer active:

  • file-chunk / byte-range-* PDUs MAY carry binary without encryptedMessage.
  • Protection is DTLS + optional path security only.
  • UI MUST indicate reduced protection (product requirement).

P6.7 Debug / disabled encryption

When encryption disabled for development:

  • Senders MUST NOT set encryptedMessage.
  • Receivers MUST accept cleartext message fields.
  • Deployments MUST NOT disable encryption in production.

P7. State machines

Work in progress — not audited

Normative protocol documentation for the current implementation. Not independently audited.

P7.1 Connection FSM

StateAllows message E2EE
IdleNo
SignalingRegisteredNo
WebRTCConnectingNo
DataChannelOpenNo
CryptoHandshakingNo (handshake PDUs only)
ReadyYes
DisconnectedNo

P7.2 Crypto handshake FSM

Implementations MAY parallelize Signal and MLS substates; they MUST reach Ready only when all enabled layers in cipherLayers are initialized.

P7.3 Per-peer encryption set

  • Global set encryptedConnections contains remote peerId strings.
  • Send path checks membership before encrypt (P6).
  • On disconnect, peer SHOULD be removed from set.

P7.4 Reconnection transitions

FromEventToCrypto action
ReadyICE lostDisconnectedKeep local state
DisconnectedICE restoredDataChannelOpenIf state intact → Ready; else → CryptoHandshaking
ReadySession corruptCryptoHandshakingFull handshake

P7.5 Group state (experimental)

Additional states for groupId, MLS epochs, and groupMembers map. Not normative for production security claims until audited.

P8. Limits, errors, and edge cases

Work in progress — not audited

Normative protocol documentation for the current implementation. Not independently audited.

P8.1 Size limits

LimitValueApplies to
Max JSON PDU (data channel)16 MiBAny single JSON object; MUST reject larger
Cascade chunk size8192 bytesfinalCiphertext over data channel
Fast transfer chunk (initial)8 MiBFast file path
Fast transfer chunk range1 MiB – 16 MiBAdaptive
Fast per-message metadata cap256 KiBSome fast paths
WebRTC buffer threshold8 MiBbufferedAmount backpressure

Oversized cascade payloads MUST use chunking (P5); senders MUST NOT truncate without chunking.

P8.2 Error handling — Profile-v0 (current)

ConditionReceiver behavior
JSON parse errorMUST drop PDU
PDU size > 16 MiBMUST drop before full parse if possible
Unknown typeMUST drop; MAY log at debug only
Decrypt/auth failureMUST NOT deliver plaintext
Missing chunkMUST wait or timeout; MUST NOT decrypt partial
Handshake PDU after fatal errorSHOULD ignore
Encrypt failureMAY fallback MLS-only; SHOULD log

Profile-v0 has no application-level error codes on the wire.

Implementations MAY use other values until Profile-v1. Recommended SHOULD values:

TimerRecommendedApplies to
Chunk reassembly120 sPer messageId
Handshake stall300 sFrom data channel open to encryptionReady

P8.4 Denial of service

Receivers SHOULD cap:

  • 4096 chunks per messageId
  • 64 concurrent messageId reassemblies per remote peer

Additional mitigations:

  • Reject excessive JSON array lengths before allocation.
  • Signaling rate limits: broker policy (out of band).

P8.5 Security considerations

This profile addresses mitigations for:

  • Chunk flooding / reassembly DoS (P8.4 caps).
  • Oversize JSON / memory exhaustion (16 MiB cap).
  • Signaling MITM via broker compromise (P2.8).
  • Malicious peer (invalid PDUs, spam) — drop on failure.
  • Application-layer replay — dedup by messageId (product layer).
  • Supply-chain / subverted JS — see ch. 8.

P8.6 Edge cases

CaseRule
Cascade disabled mid-sessionMUST re-handshake or send cleartext only if encryption off
Peer ID changeMUST full handshake
MLS fallback after cascade failencryptedMessage uses envelope not cascadedPayload
Empty plaintextAllowed; still encrypted
Duplicate messageIdApplication-layer dedup; not protocol-forbidden

P8.7 Versioning (v0)

Until EnkryptedChat-Profile-v1 is published, implementations MAY add JSON fields without breaking single-codebase deployments. Third parties MUST NOT assume stability.

Profile-v0 implements protocol-error PDUs in p2p (MLSProvider, PeerProvider). Receivers MUST still drop failing PDUs if no error PDU is received.

P8.8 protocol-error PDU (implemented)

FieldTypeRequiredDescription
typestringMUST"protocol-error"
codenumberMUSTSee table below
messageIdstringMAYRelated message
detailstringMAYDebug text; MUST NOT contain secrets
CodeNameWhen
1DECRYPT_FAILEDAEAD/auth failure
2UNKNOWN_PDUUnknown type
3CHUNK_TIMEOUTReassembly timeout
4OVERSIZEPDU over limit
5HANDSHAKE_REQUIREDE2EE before encryptionReady

Senders SHOULD emit protocol-error on the paths implemented in Profile-v0 (MLSProvider, PeerProvider); receivers MUST still drop failing PDUs if no error PDU is received.

Appendix A. Test vectors

Work in progress — not audited

Golden hex fixtures are committed in cryptography and p2p (see below). Live MLS+Signal+ML-KEM+AES vectors with production keys remain a Profile-v1 gate.

A.1 Conformance statement

An implementation conforms to this specification when it:

  1. Implements all MUST rules in P6.
  2. Recognizes all PDU types in P4.
  3. Passes all Published (hex) vectors in this appendix (V1–V7).
  4. SHOULD pass automated golden tests (commands in A.9).

Profile id for mock cascade vectors: EnkryptedChat-Profile-v0-golden-mock.
Profile id for wire PDU vectors: EnkryptedChat-Profile-v0-golden-wire.

A.2 Minimum vector set

IDPathStatus
V1Mock cascade "Hi" (3 layers)Published (hex)
V2Profile-order mock cascade (4 layers)Published (hex)
V3cascadedPayload JSON shapePublished (structure)
V4Handshake PDU pair UTF-8Published (hex)
V5MLS-only fallback envelopePublished (hex)
V6__mls_chunk__ PDUPublished (hex)
V7Chunk count for 9000 B payloadPublished (math)
V8Live MLS+Signal+ML-KEM+AES round-tripDeferred — Profile-v1
V9protocol-error PDUPublished (hex)

A.3 Vector V1 — Three-layer mock cascade

Source: golden-vectors.fixtures.js

StepHex
Plaintext ("Hi")4869
finalCiphertext (MockCipher1→3)4869010203
cd cryptography && npm test -- golden-vectors.test.js

A.4 Vector V2 — Four-layer profile-order mock cascade

Simulates layer depth MLS → Signal → ML-KEM → AES using deterministic mock layers (append 0x010x04).

StepHex
Plaintext0a141e28
finalCiphertext0a141e2801020304

Same test file as V1.

A.5 Vector V3 — cascadedPayload JSON shape

Structure normative when wrapping V1/V2 bytes in encryptedMessage:

{
"encrypted": true,
"cascaded": true,
"cascadedPayload": {
"finalCiphertext": [72, 105, 1, 2, 3],
"layerParameters": [{}],
"layers": [{ "algorithm": "MLS", "version": "1.0.0-golden" }],
"originalSize": 2,
"finalSize": 5,
"timestamp": 1717000000000
}
}

A.6 Vector V4 — Handshake PDU pair (UTF-8 hex)

Source: protocol-golden-vectors.fixtures.js

PDUJSONUTF-8 hex
Request{"type":"mls-key-package-request"}7b2274797065223a226d6c732d6b65792d7061636b6167652d72657175657374227d
Response{"type":"mls-key-package","keyPackage":"golden-fixture-key-package-bytes"}7b2274797065223a226d6c732d6b65792d7061636b616765222c226b65795061636b616765223a22676f6c64656e2d666978747572652d6b65792d7061636b6167652d6279746573227d

Full handshake order: P3.

A.7 Vector V5 — MLS-only fallback envelope (UTF-8 hex)

{"encrypted":true,"envelope":{"groupId":[1,2,3],"epoch":0,"ciphertext":[10,20,30]}}

UTF-8 hex:

7b22656e63727970746564223a747275652c22656e76656c6f7065223a7b2267726f75704964223a5b312c322c335d2c2265706f6368223a302c2263697068657274657874223a5b31302c32302c33305d7d7d

A.8 Vector V6 — __mls_chunk__ PDU (UTF-8 hex)

JSON:

{"type":"__mls_chunk__","messageType":"message","messageId":"golden-msg-0001","chunkIndex":0,"totalChunks":2,"chunk":[1,2,3],"timestamp":1717000000000}

UTF-8 hex:

7b2274797065223a225f5f6d6c735f6368756e6b5f5f222c226d65737361676554797065223a226d657373616765222c226d6573736167654964223a22676f6c64656e2d6d73672d30303031222c226368756e6b496e646578223a302c22746f74616c4368756e6b73223a322c226368756e6b223a5b312c322c335d2c2274696d657374616d70223a313731373030303030303030307d

A.9 Vector V7 — Chunk count (9000 B finalCiphertext)

Per P8 cascade chunk size 8192:

InputExpected totalChunks
9000 bytes2

Verified in protocol-golden-vectors.test.js.

A.11 Vector V9 — protocol-error PDU (UTF-8 hex)

{"type":"protocol-error","code":1,"messageId":"golden-msg-0001"}

UTF-8 hex:

7b2274797065223a2270726f746f636f6c2d6572726f72222c22636f6465223a312c226d6573736167654964223a22676f6c64656e2d6d73672d30303031227d

Codes 1–5 defined in P8.8. Implementation: p2p/src/utils/protocolError.ts.

A.12 Authoritative automated tests

RepositoryCommandVectors
cryptographynpm test -- golden-vectors.test.jsV1, V2
p2pnpm test -- protocol-golden-vectors.test.jsV4, V5, V6, V7, V9
p2pnpm test -- protocolError.test.tsprotocol-error helper
cryptographynpm test -- cascading-cipher-manager.test.jsLayer order (legacy)
signal-protocolnpm testX3DH, ratchet, WASM
p2pnpm test -- MLSProviderHandshake routing

Formal models: ProVerif.

A.13 Profile-v1 requirement (V8)

Before EnkryptedChat-Profile-v1, add V8: fixed hex for a full encrypt/decrypt round-trip using live MLS, Signal, ML-KEM, and AES layers with frozen keys in a committed fixture.

Appendix B. P2P Signal protocol deltas

Work in progress — not audited

Normative for auditors reviewing Signal usage in Enkrypted Chat. Upstream definitions: Signal Protocol documentation. Tutorial background: P2P Signal Protocol.

B.1 Scope

This appendix lists only deltas from the standard Signal deployment model. Where this document is silent, implementations SHOULD follow the Signal specification for primitive choice (Curve25519, AES-256, HMAC-SHA256, etc.) unless noted here.

B.2 No central pre-key server

Standard SignalEnkrypted Chat P2P
Bob uploads pre-key bundle to server before Alice messagesPeers exchange material live over WebRTC data channel after ICE
Alice fetches bundle asynchronouslySynchronous handshake PDUs: signal-key-exchange-request, signal-key-exchange-response, signal-session-complete (P3)
One-time prekeys consumed from server poolNo one-time prekeys3-DH X3DH variant instead of 4-DH

Implication: No offline initial message to a peer who has never been online in the same session establishment window.

B.3 X3DH variant (3-DH)

The P2P handshake performs three DH contributions (identity, signed prekey, ephemeral) without a one-time prekey DH4 term. Forward secrecy for the first application message relies on ephemeral keys plus WebRTC DTLS and subsequent Double Ratchet steps.

B.4 WASM implementation bridge

  • Browser builds use WebAssembly compiled from the signal-protocol Rust core.
  • JavaScript in p2p marshals keys and ciphertext across the WASM boundary; wire PDUs remain JSON (P4).
  • Formal verification (overview) applies to the Rust core, not the JS/WASM glue or cascade wrapper.

B.5 Local storage

AssetTypical location
Identity and session stateBrowser storage (IndexedDB / OPFS per product build)
Ratchet statePersisted per peer session in MLSProvider / Signal store

Storage encryption at rest is product-dependent and not guaranteed by EnkryptedChat-Profile-v0 (P1.5).

B.6 Identity key vs Peer ID

IdentifierRole
Peer IDBroker routing address; opaque string; not a Signal identity key
Signal identity keyLong-term key inside WASM store; used in X3DH and ratchet

Verifying that a Peer ID maps to an expected identity key is out of band (TOFU or future safety-number UX).

B.7 Session lifecycle

EventBehavior
First connectFull Signal exchange via P3 handshake
Reconnect, state retainedMAY resume ratchet if peer IDs unchanged (P3.6)
Lost state / peer ID changeMUST repeat Signal handshake
TeardownLocal session state SHOULD be cleared on explicit disconnect or unrecoverable decrypt failure

B.8 Out-of-order and skipped messages

Double Ratchet MUST accept out-of-order ciphertext within a session per Signal semantics. Maximum skip windows follow the WASM library configuration; auditors SHOULD inspect signal-protocol tests for bounds.

B.9 Composition with cascade

Signal encrypts the output of the MLS layer (or plaintext entry) inside the cascade (P5). Signal security properties apply to that layer’s input/output; they do not extend to broker metadata, MLS group administration plaintext in handshake, or fast-file DTLS-only paths.

B.10 Security properties (Signal layer only)

PropertyP2P Signal layer
Forward secrecy (messages)Provides (after handshake complete)
Break-in recoveryProvides (Double Ratchet)
Asynchronous deliveryDoes not provide (no relay server)
Server-assisted key directoryDoes not provide

B.11 References

Appendix C. Glossary

Work in progress — not audited

Definitions are normative for interpreting P1–P8 unless a chapter defines a term more specifically.

TermDefinition
Application PDUJSON object on a WebRTC data channel with required type field (P4).
BrokerPeerJS-compatible signaling server over WSS; relays SDP/ICE only in v0 profile.
CascadeOrdered stack MLS → Signal → ML-KEM → AES applied by CascadingCipherManager (P5).
cascadedPayloadObject inside encryptedMessage holding finalCiphertext and layer metadata.
encryptionReadyImplementation flag: local crypto handshake complete for a peer; required before E2EE message PDUs (P3).
encryptedConnectionsSet of remote Peer IDs allowed on encrypt path (P7).
encryptedMessageWrapper object carrying ciphertext (cascade or MLS-only).
Fast file profileBulk transfer that MAY bypass cascade; DTLS-only protection (P6.6).
Handshake PDUControl type values in P3 establishing crypto state.
Initiator / ResponderWebRTC roles for outbound connect vs inbound accept (P2).
MLSMessaging Layer Security (RFC 9420).
Peer IDOpaque broker address string; not a global cryptographic identity (P2.5).
PDUProtocol data unit; here, one JSON object on the data channel.
ProfileNamed configuration EnkryptedChat-Profile-v0 (P1).
SignalingWSS broker traffic for WebRTC setup, distinct from application PDUs.
TOFUTrust on first use — accept Peer ID and in-band keys without prior out-of-band verification.
TURNRelay for ICE when direct UDP fails; sees traffic metadata (ch. 3).

Appendix D. SFrame (informative, WIP)

Work in progress — not audited

Not normative for EnkryptedChat-Profile-v0. Voice and video use WebRTC SRTP by default (ch. 3).

D.1 Status

SFrame integration in the cryptography module is experimental. This appendix documents intent only; wire formats and key schedules are not frozen.

D.2 Relationship to protocol spec

LayerNormative in P1–P8?
SRTP (WebRTC media)Referenced via WebRTC
SFrame end-to-end mediaNo — product/ch. 4 only

Implementers MUST NOT claim SFrame protection in conformance statements until a future profile adds normative rules.

D.3 See also