# Файлы и голосовые DM v2: chunked AES-GCM + BitTorrent v2 hashes ## Цели DM file v2 убирает ограничение размера исходного файла, не требует держать файл целиком в RAM и сохраняет серверную модель «сервер видит только ciphertext». Основные свойства: - plaintext режется на куски по `1 MiB`; - каждый кусок независимо шифруется `AES-256-GCM` одним случайным ключом файла, но с уникальным 96-bit IV; - каждый ciphertext-кусок хранится как immutable объект `Base58(SHA-256(ciphertext))`; - список кусков хранится в зашифрованных страницах манифеста по 256 записей; - корневой манифест тоже зашифрован и content-addressed; - E2EE DM содержит только ссылку на корневой манифест, AES-key, IV-prefix и пользовательские метаданные; - один DM может содержать до 10 `` блоков; - `kind=voice` использует тот же формат хранения и отдельный UI проигрывателя. ## IV и domain separation На один файл создаётся случайный 4-byte `ivPrefix`. 12-byte IV строится как: ```text ivPrefix[4] || uint64_be(token) ``` Диапазоны `token` разделены: - chunks: `0 .. 2^63-1`; - manifest pages: `2^63 + pageIndex`; - root manifest: `0xffffffffffffffff`. AAD также содержит домен (`chunk`, `page`, `root`), prefix и индекс. Поэтому перестановка ciphertext-кусков не проходит AES-GCM authentication. ## Manifest pages Каждая страница после расшифровки содержит до 256 записей: ```json { "v": 2, "page": 0, "chunks": [ { "i": 0, "id": "Base58(SHA-256(ciphertext))", "ps": 1048576, "es": 1048592, "ph": "BitTorrent-v2-piece-hash-base64url" } ] } ``` Страницы сами AES-GCM зашифрованы и загружаются в `/dm-files/{id}`. ## Root manifest После расшифровки: ```json { "v": 2, "scheme": "SHINE-DM-CHUNKED-AES-256-GCM", "chunkSize": 1048576, "fileSize": 123456789, "chunkCount": 118, "pages": [{"id":"...","count":118,"encryptedSize":12345}], "torrent": { "metaVersion": 2, "blockLength": 16384, "pieceLength": 1048576, "piecesRoot": "...", "infoHash": "..." } } ``` Имя файла и MIME в manifest не пишутся. Они остаются внутри E2EE DM. ## BitTorrent v2 совместимость Хеширование соответствует BEP 52: - базовый hash block: `16 KiB`; - SHA-256; - piece length: `1 MiB`; - Merkle padding leaf = 32 zero bytes; - `pieces root` вычисляется по правилам BitTorrent v2; - `infoHash` = `SHA-256(bencode(info dictionary))`; - piece-layer hash каждого 1-MiB куска хранится в зашифрованной manifest page. Из `name`, `size`, `piecesRoot` и списка `ph` можно построить tracker-less `.torrent` v2 без повторного хеширования исходного файла. Это совместимость метаданных и проверки контента. HTTP `/dm-files` пока не является BitTorrent peer transport: для настоящего P2P потребуется отдельный seeding/peer слой. ## Скачивание Получатель: 1. получает и проверяет ciphertext root manifest по Base58(SHA-256); 2. расшифровывает root manifest; 3. по очереди получает manifest pages; 4. получает каждый chunk с сервера отправителя; 5. проверяет content address ciphertext; 6. расшифровывает chunk локально; 7. проверяет BitTorrent-v2 piece hash; 8. пишет plaintext на диск; 9. в конце проверяет итоговый `pieces root`. В браузерах с File System Access API plaintext пишется на диск по частям и целиком в RAM не собирается. В остальных браузерах остаётся Blob fallback без искусственного лимита размера, но фактический предел зависит от памяти браузера. ## Голосовые `MediaRecorder` пишет `Opus/WebM`, `Opus/Ogg` или поддерживаемый браузером audio MIME. После завершения запись проходит тот же DM file v2 pipeline. Технический блок отличается полями: ```text kind=voice;dur= ``` Получатель видит player с Play/Pause, прогрессом и длительностью. Аудио расшифровывается только на клиенте.