INVITE ONLY
OBSERVATORY

10.2 QSFS

File storage that has only ever seen ciphertext.

QSFS

Sharded, encrypted file storage: every blob arrives sealed, splits across shards, and is erased on schedule.

Send a file through it. The keys never follow.

The encryption boundary

Your file is padded to a size class and sealed on your device — one ChaCha20-Poly1305 box over the padded plaintext, under a Q-Ratchet-derived key — before a single byte leaves it. QSFS stores opaque blobs addressed by identifiers, sharded across storage paths. The keys stay with you and your recipients; the store has only ever seen ciphertext.

Seal point
on-device, before upload — server never holds a key
Blob key
Q-Ratchet-derived per file, labeled HKDF
Traffic shape
pad-ladder size classes — length buckets, not exact lengths

Binding beats swapping

Sealing is not enough; a captured blob must not be re-servable somewhere else. Each media blob's AAD binds its conversation, epoch, chain position, sender, file identifier, and declared media type — substitution, replay onto a sibling group, or re-serving an image as a video manifest fails authentication.

Erased with its message

When a conversation is destroyed, the destruction signal carries the file paths of every blob it referenced, and the purge listener issues a DELETE per blob in the same window. Media does not outlive its conversation, and retired FSOR epochs lock any residue.

Uploads that survive bad networks

The sealed ciphertext uploads in 256 KB chunks, each idempotency-keyed by its digest, so a transient failure on chunk N never wipes the bytes for chunks 0…N−1. Resume is a bookkeeping operation on ciphertext — the crypto never re-runs.

Every blob arrives sealed. QSFS has never held a key.

The carrier model

Trace one file from your device to the shard

FILE padded to class SEALED BLOB AEAD + bound AAD CHUNK 0 · 256 KB CHUNK 1 · 256 KB CHUNK N SHARDED STORE opaque paths, ciphertext only u/<shard>/<file_id>/….enc deleted on the purge signal
Pad → seal → chunk → shard. Keys never leave the clients.

Read the binding in the source that ships

crypto-core/src/group_sender.rs
/// Build the AAD for a media-blob AEAD payload. Wire layout:
/// `GROUP_MEDIA_AAD_LABEL || 0x00 || conversation_id || 0x00 || epoch(u32) ||
///  chain_index(u32) || sender_profile_id || 0x00 || file_id || 0x00 ||
///  mime_type`
///
/// `mime_type` binding stops a blob host from re-serving an image
/// ciphertext as a video manifest (the iOS decoder picks behavior off the
/// declared mime). `file_id` binding stops a captured blob from being
/// served back under a different file_id slot. Conversation/epoch/chain
/// binding stops cross-channel replay onto a sibling group.
pub fn build_media_aad(
    conversation_id: &[u8],
    epoch: u32,
    chain_index: u32,
    sender_profile_id: &[u8],
    file_id: &[u8],
    mime_type: &[u8],
) -> Vec<u8> {
    let mut aad = Vec::with_capacity(/* exact-size preallocation elided */);
    aad.extend_from_slice(GROUP_MEDIA_AAD_LABEL);
    aad.push(0x00);
    aad.extend_from_slice(conversation_id);
    aad.push(0x00);
    aad.extend_from_slice(&epoch.to_be_bytes());
    aad.extend_from_slice(&chain_index.to_be_bytes());
    aad.extend_from_slice(sender_profile_id);
    aad.push(0x00);
    aad.extend_from_slice(file_id);
    aad.push(0x00);
    aad.extend_from_slice(mime_type);
    aad
}

Check every parameter against the file it came from

Storage parameters — values from source
ParameterValueSource
Blob seal ChaCha20-Poly1305, one box over padded plaintext FileUploadService.swift
Blob key Q-Ratchet-derived per file (labeled HKDF) crypto-core/src/group_sender.rs
AAD binding conversation, epoch, chain index, sender, file_id, mime type group_sender.rs::build_media_aad
Traffic shape pad-ladder size classes crypto-core/src/q_pad_ladder.rs
Upload chunk 256 KB, digest-idempotent, resumable FileUploadService.swift
Concurrency 4 chunks in flight FileUploadService.swift
Max file size 2 GB FileUploadService.swift
Deletion purge listener issues DELETE per blob path backend/src/services/lifecycle.rs

61

fields bound into every media blob's AAD

256 KB2

resumable upload chunk — ciphertext only

03

keys ever held by the storage layer

Measure where your files actually stand.

Category classes only — never named products
QERYX QSFS Classical cloud storage Email attachments
Real workloads
Provider read Ciphertext only Provider-readable or provider-keyed Plaintext at provider
Storage seizure Sealed chunks + identifiers Plaintext or provider-decryptable Plaintext
Deletion Erased with the message + epoch lock Soft-delete; versions persist Forever, in mailboxes
Integrity AAD-bound authentication Checksums at best Not included
Quantum recording today Hybrid-derived keys Classical TLS + at-rest Plaintext

Ask what a seizure of the store reaches.

Can QERYX open my files?

No. Files are sealed on your device before upload; QSFS holds ciphertext and identifiers, not keys.

Sealed blobs and their identifiers. Assembling plaintext requires the conversation keys — breaking both X25519 and ML-KEM-1024.

The destruction signal lists every blob path and deletes them in the same window; retired FSOR epochs lock any residue.

No — each blob's AAD binds its conversation, epoch, chain position, sender, file identifier, and declared media type. Substitution fails authentication.

Sizes land on the shared pad-ladder — length classes, not exact lengths.

Get QERYX Read the docs