Files, photos and IPFS
Encrypt, sign, upload. Why the content hash is public and the content is not.
Files, photos and IPFS
Media does not get a separate encryption scheme. It reuses the same envelope as text, and the only genuinely new component is where the ciphertext ends up afterward.
A file's bytes are base64-encoded and encrypted with the identical ECDH-agreed, HKDF-derived, ChaCha20-Poly1305-sealed envelope used for a text message. The resulting envelope JSON is then signed with the sender's identity key in the form "{sig}:{envelopeJson}" and the signed, encrypted package is uploaded to IPFS through a middleware HTTP API - /api/upload /api/file/{hash} /api/pin - with the request itself carrying ADR-036-signed headers. The SDK never talks to a raw IPFS gateway directly; every upload and fetch goes through that authenticated middleware. A SHA-256 hash of the media is computed for integrity, and that same hash travels as signed custom data on the upload request.
Two practical caps exist. Thumbnails are capped at 150 KB and travel inline as base64 data:image/jpeg - they never touch IPFS at all, which is why a thumbnail appears before a full image finishes loading. Full images are capped client-side at 50 MB. Avatars are the one exception to the ChaCha20-Poly1305 rule across the entire system: they are encrypted with AES-256-GCM instead, the only place that cipher appears. This covers every media type the app sends the same way - photos, videos, voice messages, video notes, stickers and GIFs all go through the identical encrypt-sign-upload path described above, with no separate handling per file type.
The ADR-036-signed request headers on every upload and fetch matter for a reason beyond convention: they let the middleware verify that the account making the request actually controls the wallet it claims to, without a separate login system existing anywhere. Separately, the gRPC transport that carries ordinary messages defaults its frame size to 4 MB - a limit on message envelopes in flight over that channel, not on how large a file can be, since media never travels over the message gRPC stream at all; it goes to IPFS through the middleware API instead.
"The hash is public, the content is not" is accurate, but it has a real limit worth naming rather than glossing over. IPFS is content-addressed: the hash you can see is derived from the exact encrypted bytes stored, and it is genuinely public - anyone can see that a particular hash exists and was pinned. What it does not reveal is the plaintext behind that hash; without the decryption key, the ciphertext is opaque. The limit is this: an observer who already possesses the same plaintext file - the exact photo, the exact document - can encrypt or hash their own copy and compare it against a hash they observe, confirming a match without ever decrypting anything. That is a property of content-addressed storage generally, not a DChat-specific flaw, but "public hash" should not be read as "no information is ever leaked" - it means the content itself stays opaque, not that matching is impossible for someone who already holds the reference file.
Related: FAQ - limits on drafts and file sizes · Help - albums, files and the image size cap