SHA256
125 lines
5.2 KiB
Markdown
125 lines
5.2 KiB
Markdown
# Файлы и голосовые 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 `<S:file;v=2...>` блоков;
|
||
- `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=<milliseconds>
|
||
```
|
||
|
||
Получатель видит player с Play/Pause, прогрессом и длительностью. Аудио расшифровывается только на клиенте.
|