Криптография (общая)
Одинакова для wire 1.0–1.4 (на 1.3+ AEAD использует AAD). Меняется формат кадра после рукопожатия. Кадр транспортно-агностичен: сегодня WebSocket, в 2.0 — также UDP/TCP.
- ECDH — X25519, свежая пара ключей на сессию
- KDF — HKDF-SHA256, salt пустой; info
DVeProto-v1/c2sиDVeProto-v1/s2c - AEAD — AES-256-GCM, nonce 12 байт на каждое сообщение
- Клиент → сервер: ключ c2s. Сервер → клиент: ключ s2c.
Рукопожатие
Всегда UTF-8 JSON, одинаково для обеих версий. Поле v рукопожатия = 1.
- Сервер:
dve_helloсserver_pk, опциональноoffer: ["1.0"…"1.5"]иcaps. - Клиент считает shared secret X25519 и ключи HKDF (или resume из ticket).
- Клиент:
dve_client_ackсclient_pkиselectдо"1.5", либоresume. Безselect→ 1.0.select "1.5"≡ wire 1.4. - Дальше только выбранный wire-формат.
{
"type": "dve_hello",
"proto": "DVeProto",
"v": 1,
"server_pk": "<base64 32 bytes>",
"offer": ["1.0", "1.1", "1.2", "1.3", "1.4", "1.5"],
"caps": 2111
}
{
"type": "dve_client_ack",
"proto": "DVeProto",
"v": 1,
"client_pk": "<base64 32 bytes>",
"select": "1.5",
"caps": 2111
}
Версии
1.0–1.5 LTS — поколение 1.x. 2.0 — отдельная линия (Handshake 2.0 / Wire 2.0).
Wire 1.0 — JSON text frames
reference-1.0.py · reference-1.0.js
После handshake каждое сообщение — один WebSocket text frame с JSON. Ciphertext и nonce в Base64.
- Транспорт: только text frames
- Обёртка:
type: "dve",proto,v: 1,n,c - Plaintext внутри AEAD — UTF-8 JSON объект
- Режим по умолчанию, если клиент не указал select
{
"type": "dve",
"proto": "DVeProto",
"v": 1,
"n": "<base64 12-byte nonce>",
"c": "<base64 AES-GCM ciphertext||tag>"
}
Подходит для каналов без binary frames и для максимальной совместимости со старыми клиентами.
Wire 1.1 — binary frames
reference-1.1.py · reference-1.1.js
После handshake данные идут только binary WebSocket frames. Без JSON и Base64 на wire.
- Транспорт: только binary frames (opcode 0x2)
- Длина сообщения = граница кадра
verвсегда0x11- Чанковая передача файлов (FILE_*)
[1] ver = 0x11 [1] ptype [12] nonce […] ciphertext || GCM-tag(16)
ptype
| ptype | Имя | Назначение |
|---|---|---|
0x01 | APP_JSON | UTF-8 JSON / RPC |
0x02 | APP_BIN | Произвольные байты |
0x03 | TEXT | UTF-8 текст |
0x10 | FILE_BEGIN | Старт файла |
0x11 | FILE_CHUNK | Чанк |
0x12 | FILE_END | Конец |
0x13 | FILE_ABORT | Отмена |
0x14 | FILE_ACK | ACK / resume |
0x15 | FILE_RESUME | Продолжить с чанка |
Файлы
FILE_BEGIN → FILE_CHUNK* → FILE_END. Каждый chunk — отдельный binary frame со своим nonce/tag. Resume: FILE_RESUME / FILE_ACK. Чанк по умолчанию 256 KiB.
FILE_BEGIN: transfer_id[16] | file_size u64be | chunk_size u32be | total_chunks u32be | flags u8 | name_len u16be | name | mime_len u16be | mime | [sha256 32 if flags&1] FILE_CHUNK: transfer_id[16] | chunk_index u32be | data FILE_END: transfer_id[16] | sha256[32] FILE_ABORT: transfer_id[16] | reason_code u8 FILE_ACK: transfer_id[16] | status u8 | last_ok u32be FILE_RESUME: transfer_id[16] | from_chunk u32be
Wire 1.2 — binary + mux
reference-1.2.py · reference-1.2.js
Как 1.1, плюс stream_id в заголовке, control-кадры и зарезервированные ptype под DVeNet/DVeVPN (2.0).
- Транспорт: только binary frames
ver=0x12, затемptype,stream_idu16be, nonce, ciphertext||tag- Mux: несколько логических потоков в одной DVe-сессии
- PING / PONG / CLOSE; NET_* и VPN_PKT зарезервированы до 2.0
[1] ver = 0x12 [1] ptype [2] stream_id u16be [12] nonce […] ciphertext || GCM-tag(16)
ptype (дополнительно к 1.1)
| ptype | Имя | Назначение |
|---|---|---|
0x20 | NET_CTRL | DVeNet control (2.0) |
0x21 | NET_PKT | DVeNet packet (2.0) |
0x22 | VPN_PKT | DVeVPN IP/Eth (2.0) |
0x30 | PING | Keepalive; ответ PONG |
0x31 | PONG | Ответ на PING |
0x32 | CLOSE | code u16be + reason |
DVeNet и DVeVPN — слой поверх 1.2; спецификация их кадров появится в протоколе 2.0.
Wire 1.3 — AAD + counter + flow control
reference-1.3.py · reference-1.3.js
Макет как у 1.2 (ver=0x13), плюс AAD в AES-GCM, счётчик nonce и WINDOW_UPDATE.
- AAD =
ver|ptype|stream_id(4 байта) - Nonce = prefix(4) || counter u64be (на направление)
- WINDOW_UPDATE 0x33: credit u32be на stream
- Начальное окно 256 KiB; PING/PONG/CLOSE/WINDOW не тратят credit
[1] ver = 0x13 [1] ptype [2] stream_id u16be [12] nonce = prefix4 || counter u64be […] ciphertext || GCM-tag(16) AAD: ver | ptype | stream_id
Wire 1.4 — final 1.x (Resume + Priority + Caps)
reference-1.4.py · reference-1.4.js
Последняя binary-версия поколения 1.x. Кадр WebSocket, ver=0x14. Handshake 2.0 / Wire 2.0 — на отдельной вкладке 2.0.
ver=0x14+pkt_sequ32be; AAD = ver|ptype|stream_id|pkt_seq- Session Resume: SESSION_TICKET → reconnect без полного ECDH
- STREAM_PRIORITY / STREAM_CANCEL / DATA_ACK; PING с timestamp → RTT
- Capability flags; Statistics API; NET/VPN ptypes по-прежнему reserved до 2.0
[1] ver = 0x14 [1] ptype [2] stream_id u16be [4] pkt_seq u32be [12] nonce = prefix4 || counter u64be […] ciphertext || GCM-tag(16) AAD: ver | ptype | stream_id | pkt_seq
ptype (дополнительно к 1.3)
| ptype | Имя | Назначение |
|---|---|---|
0x34 | STREAM_PRIORITY | priority u8 (0…3) |
0x35 | STREAM_CANCEL | reason + note |
0x36 | DATA_ACK | last_seq u32be |
0x37 | SESSION_TICKET | session_id + token |
0x38 | CAPS | caps u32be |
Документация Handshake 2.0 / Wire 2.0 — на вкладке 2.0. 1.4 остаётся зрелым транспортом для FlowChat.
Package 1.5 LTS — Wire Freeze
reference-1.5.py · reference-1.5.js · reference.py
Финальный релиз поколения 1.x. Binary wire = 1.4 (ver=0x14). select "1.5" — LTS-метка того же кадра.
- Wire Format Freeze: макет кадра и карта ptype заморожены; изменения — только в 2.x
- Session Ticket Store + auto Resume; Production API: send / receive / stats / close / rekey
- Security limits: max frame/lifetime, soft idle, replay window, ticket TTL clamp
- Векторы: test_vectors.json · smoke: interop_smoke.py
select "1.5" ≡ wire 1.4 / ver=0x14 package = 1.5 LTS next major = 2.0 (DVeNet / DVeVPN / UDP+TCP) DVeProto 1.5 LTS is the final release of the first-generation transport protocol. Future network-layer features are developed in DVeProto 2.x.
Эталонный код
Референс-реализации в этой же папке — без привязки к конкретному продукту.
Python
DVeClientSession.from_server_hello_text→ session + ack JSONpack_outgoing/unpack_incoming—str(1.0) илиbytes(1.1/1.2)- Хелперы
pack_file_*·pip install cryptography
session, ack = DVeClientSession.from_server_hello_text(hello_text, prefer="1.5")
frame = session.pack_outgoing({"dve_op": "ping"})
print(session.stats())
Браузер (ESM)
dveDeveloperHandshake→{ session, clientAckJson, wire }- Для 1.1–1.3
encryptOutgoing→Uint8Array
import { dveDeveloperHandshake } from './reference-client.js';
const { session, clientAckJson, wire } = await dveDeveloperHandshake(hello);
const out = await session.encryptOutgoing({ dve_op: 'ping' });
Конвенция APP_JSON
dve_op— имя операции / RPCdve_seq— опциональный монотонный счётчик (DVeSeq)
DVeProto 1.5 LTS is the final release of the first-generation transport protocol. Future network-layer features are developed in DVeProto 2.x.
Совместимость
Клиенты без select работают на 1.0. Выбирается высший общий wire (до 1.4). 1.5 LTS — тот же binary, что 1.4; после LTS wire 1.x замораживается.
Security Considerations
- Nonce 1.3+: prefix(4)||counter u64 — без повторного (key, nonce); переполнение → закрытие сессии.
- pkt_seq в AAD (1.4); дубликаты/окно — на усмотрение реализации, без reuse nonce.
- SESSION_TICKET TTL по умолчанию 24ч; привязка к identity; expired/bad → dve_resume_reject.
- AEAD + counter nonce ограничивают replay; после Resume ключи новые (resume HKDF).
- Клиенты, требующие 1.4+, обязаны прервать сессию при более низком select.
Тестовые векторы: test_vectors.json · gen_test_vectors.py.
DVeProto 2.0 DVeProto 2.0 Alpha
COMPATIBILITY.md · CHANGELOG.md · v2/test_vectors.json · DVeVPN
Overview
DVeProto 2.0 — следующее поколение протокола: бинарный Wire 2.0, Handshake 2.0 и носители UDP, TCP и WebSocket. Над ними строятся overlay DVeNet и IP-туннель DVeVPN. Линия 1.5 LTS остаётся отдельным замороженным транспортом.
Статус этой линии протокола — Alpha. Документ описывает реализованные слои и подтверждённые лабораторные тесты. Это не формальный security audit, не криптографическое доказательство и не заявление о production-scale reliability.
- NodeID — стабильная идентичность узла (Ed25519 public key).
- ConnectionID — идентификатор пути / криптосессии (8 байт). Не заменяет NodeID.
- Handshake 2.0 — ClientHello → ServerHello → Finished; версия кадра рукопожатия
0x20. - Wire 2.0 — запечатанные кадры после рукопожатия, magic
DV,ver=0x20.
Architecture
Два рабочих стека Server.System. FlowChat использует замороженный DVeProto 1.5 LTS над WebSocket. DVeNet и DVeVPN используют Handshake 2.0 и Wire 2.0 над UDP, TCP или WebSocket.
Security
Ниже — фактически используемые примитивы Handshake 2.0 и Wire 2.0. Раздел не заменяет внешний аудит и не утверждает абсолютную безопасность или анонимность.
- X25519 — эфемерный обмен ключами на сессию.
- HKDF-SHA256 — расписание ключей; info
DVeProto-v2/c2s,DVeProto-v2/s2c,DVeProto-v2/finished. - AES-256-GCM — AEAD по умолчанию (suite
0x0001), nonce 12 байт. - ChaCha20-Poly1305 — альтернативный AEAD (suite
0x0002). - Ed25519 — Node Identity; NodeID = public key (32 байта). ServerHello подписывается.
- Finished — HMAC-SHA256 по транскрипту рукопожатия.
- Несовместимая версия рукопожатия (не
0x20) и отсутствие общей cipher suite завершают handshake ошибкой. Неподдерживаемая версия не получает ServerHello.
Канонические векторы 2.0: v2/test_vectors.json.
Transports
Один и тот же запечатанный кадр Wire 2.0 переносится разными носителями (carriers).
- UDP — один datagram = один кадр; события CONNECTED, DISCONNECTED, RTT_CHANGED, LOSS_CHANGED, MTU_CHANGED, NETWORK_CHANGED.
- TCP — кадр с префиксом длины u32be.
- WebSocket — тот же кадр в binary message (браузер / прокси).
Состояния носителя: idle, connecting, connected, closing, closed. DVeVPN не переключает UDP на TCP молча: preferred carrier фиксирован.
SmartTransport
Выбор активного носителя по предпочтению UDP → TCP → WebSocket среди зарегистрированных здоровых путей. Переключение инициируется приложением (уведомление, что текущий носитель недоступен, или явный prefer). Это слой multipath/DVeNet, а не скрытый fallback внутри DVeVPN.
Session Migration
Перенос уже установленной криптосессии на другой носитель без повторного handshake: NodeID и ключи сохраняются, ConnectionID обновляется. Вызов явный — SessionMigration / DVeNet.migrate_session.
Multipath
Carrier Manager держит несколько носителей одного узла. MultiRoute хранит несколько путей в таблице маршрутов DVeNet и выбирает лучший по метрике. SmartTransport и Session Migration входят в этот слой.
DVeNet
Overlay: объявление узла, база пиров, статический bootstrap, прямое announce, обмен маршрутами и пересылка. Состояния пира: UNKNOWN, DISCOVERED, VERIFIED, CONNECTED, ACTIVE. Долгосрочное доверие строится на NodeID, не на IP и не на ConnectionID.
DVeVPN
IP-туннель над DVeNet: handshake, выдача VIP, adapter (в т.ч. loopback / TUN / Wintun), keepalive и reconnect. Продуктовый сайт и установщик: dveproto.serversys.ru/dvevpn. DVeVPN не подменяет UDP на TCP без явной настройки preferred carrier.
Compatibility
Политика: COMPATIBILITY.md. 1.x LTS — только security/bugfix, wire остаётся ver=0x14. Внутри 2.x — взаимосовместимость и аддитивные изменения. Нет общей версии или suite — handshake прерывается. NodeID ≠ ConnectionID.
Testing & Reliability
Матрица ниже — фактически пройденные тесты stability harness (loopback). Это не production-scale reliability и не полевой прогон в интернете.
| Scenario | Result | Notes |
|---|---|---|
| loss 10% | PASS | echo under impairment; carrier connected |
| latency 200ms | PASS | measured RTT includes both directions |
| jitter 50ms | PASS | echo with jitter profile |
| combined impairment | PASS | loss + latency + jitter |
| duplication | PASS | duplicate PacketNumber dropped |
| reordering | PASS | frames decrypt independently |
| corruption | PASS | bad AEAD rejected; later clean echo |
| blackhole | PASS | keepalive → reconnect → CONNECTED |
| server restart | PASS | reconnect; sticky virtual IP |
| UDP → TCP | PASS | SmartTransport after UDP socket down |
| TCP → WS | PASS | SmartTransport after TCP socket down |
| version rejection | PASS | ClientHello version 0x21 rejected; no ServerHello |
| simultaneous 3 clients | PASS | three VPN clients; unique VIPs |
Дополнительно линия 2.0 проходит alpha-регрессию: test vectors (Python / Node.js), live Py↔Py и Py↔Node, negative tests, fuzz Wire/handshake.
Changelog
Полная лента: CHANGELOG.md. Кратко для публичной линии 2.0:
- 2.0 Alpha — Handshake 2.0, Wire 2.0, UDP/TCP carriers, interop vectors, compatibility policy.
- DVeNet / Multipath — discovery, routing, Carrier Manager, SmartTransport, Session Migration, WebSocket carrier.
- DVeVPN — IP-туннель, reconnect, installer и операционные документы на /dvevpn/.
DVeProto in FlowChat
FlowChat использует DVeProto как слой транспорта и защиты полезной нагрузки на WebSocket: handshake 1.x, затем только зашифрованные кадры. Текущая линия в FlowChat — DVeProto 1.5 LTS (binary wire 1.4, ver=0x14): чат, RPC и передача файлов FILE_*.
FlowChat не использует Handshake 2.0 / DVeVPN как транспорт чата. Поколение 2.0 — отдельный стек (DVeNet, DVeVPN, UDP/TCP/WS). 1.5 LTS остаётся поддерживаемым каналом для существующих клиентов.