Sending Messages: The Package Delivery
Mental Model: The Mailing System
Imagine you want to send a package across the country:
- You pack your item in a box
- The courier picks it up
- It passes through various transit hubs (sorting centers)
- At each hub, it gets sorted and labeled
- Finally, the courier delivers it to the recipient
CascadingCipher is like that mailing system:
- Your message = the item
- Each cipher layer = a transit hub
- Encryption = sorting and labeling
- Decryption = opening the package
The CascadingCipherManager is the mail carrier who knows the route!
The Encryption Flow
Step 1: Manager Receives Your Message
Step 2: Bottom-Up Encryption (Cascading)
Each layer processes the message in order:
Important: Encryption flows downstream—from the first layer to the last!
How It Works: The Encryption Process
The Manager's Job
The CascadingCipherManager orchestrates the encryption:
async encrypt(plaintext: string): Promise<EncryptedPayload> {
// 1. Convert plaintext to bytes
const plaintextBytes = new TextEncoder().encode(plaintext)
// 2. Start with the first layer
let currentPayload: Uint8Array = plaintextBytes
// 3. Pass through each layer in order
for (const layer of this.layers) {
currentPayload = await layer.encrypt(currentPayload)
}
// 4. Wrap in an EncryptedPayload
const encryptedPayload: EncryptedPayload = {
ciphertext: currentPayload,
metadata: {
layerOrder: this.layers.map(l => l.name),
timestamps: Date.now()
}
}
return encryptedPayload
}
Each layer knows how to encrypt its data:
// Example: AES Layer
async encrypt(data: Uint8Array): Promise<Uint8Array> {
// 1. Generate a random IV (initialization vector)
const iv = crypto.getRandomValues(new Uint8Array(12))
// 2. Derive encryption key from password or DH secret
const key = await this.deriveKey()
// 3. Encrypt the data
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
data
)
// 4. Return IV + ciphertext
return new Uint8Array([...iv, ...new Uint8Array(encrypted)])
}
Full Example: Encrypting a Message
const manager = new CascadingCipherManager()
// Add layers in order: bottom (AES) → top (ML-KEM)
await manager.addLayer(new AESCipherLayer({ password: 'secret' }))
await manager.addLayer(new DHCipherLayer({
myPrivateKey: myPrivateKey,
theirPublicKey: theirPublicKey
}))
await manager.addLayer(new SignalCipherLayer({
myKeyPair,
theirPublicKey,
rootKey
}))
await manager.addLayer(new MLKEMCipherLayer({
myKeyPair,
theirPublicKey
}))
// Encrypt a message
const plaintext = 'Hello secure world!'
const encrypted = await manager.encrypt(plaintext)
console.log('Encrypted:', encrypted)
// Output:
// {
// ciphertext: Uint8Array(1024),
// metadata: {
// layerOrder: ['AES', 'DH', 'Signal', 'MLKEM'],
// timestamps: 1238472938472
// }
// }
What Each Layer Does
| Layer | What It Does | What It Returns |
|---|---|---|
| AES Layer | Encrypt with password/DH key | IV + ciphertext |
| DH Layer | Derive key, encrypt | Key-encrypted ciphertext |
| Signal Layer | Ratchet message key | Message-encrypted ciphertext |
| ML-KEM Layer | Post-quantum key encapsulation | Post-quantum ciphertext |
Each layer takes the previous layer's output and encrypts it again!
Visualizing the Encryption Flow
Why Layers Must Be in Order
The Foundation Rule
Layers must be applied in a specific order:
LAYER 1 (Foundation) → LAYER 2 (Context) → LAYER 3 (Post-Quantum)
If you flip the order:
ML-KEM (top) → Signal → DH (bottom)
Then ML-KEM encrypts plaintext (bad!) instead of the post-quantum protected message!