P5. Cascade wire encoding
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
| Field | JSON type | Required | Description |
|---|---|---|---|
finalCiphertext | array of numbers | MUST (unless chunked) | Each element 0–255 |
layers | array of objects | MUST | Per-layer metadata |
layerParameters | array of objects | MUST | Per-layer decrypt params |
totalProcessingTime | number | MUST | Milliseconds |
originalSize | number | MUST | Plaintext byte length |
finalSize | number | MUST | Ciphertext byte length |
timestamp | number | SHOULD | Unix 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)
| Field | Type | Description |
|---|---|---|
algorithm | string | e.g. MLS, Signal, ML-KEM, AES-GCM-256 |
version | string | Layer implementation version |
timestamp | number | Layer processing time |
inputSize | number | Bytes in |
outputSize | number | Bytes out |
processingTime | number | ms |
metadata | object | MAY — 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 name | Source |
|---|---|
| MLS | mlsManager + groupId |
| Signal | WASM session after handshake |
ML-KEM-768 | peerMLKEMKeyPairsRef for target peer |
AES-GCM-256 | Password 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
| Constant | Value |
|---|---|
CHUNK_SIZE | 8192 bytes (8 KiB) |
If finalCiphertext.length ≤ CHUNK_SIZE:
- Send single PDU with full
cascadedPayload.
If larger:
- Set
chunked: true,messageId,totalChunks. - Omit
finalCiphertextfrom inline payload; sendchunks[]arrays. - Optionally use
__mls_chunk__transport (P4).
Receivers MUST reassemble chunks in order before decrypt.
P5.6 Failure behavior
| Failure | Behavior |
|---|---|
| Missing layer keys | Throw CascadingCipherError; encrypt path falls back to MLS-only |
| Layer decrypt/auth fail | MUST NOT emit plaintext; abort message (P6) |
| Zero layers configured | MUST NOT call encrypt |
P5.7 Reference implementation
CascadingCipherManager.ts— layer chaintypes.ts—CascadedPayloadinterfaceMLSProvider.tsx— serialization to JSON number arrays