Низкоуровневый ADNL
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
Сетевой уровень абстрактных датаграмм (Abstract Datagram Network Layer - ADNL) — это основной протокол TON, который помогает сетевым одноранговым узлам взаимодействовать друг с другом.
Идентификация одноранговых узлов
Каждый одноранговый узел должен иметь по крайней мере один идентификатор, возможно, но необязательно использовать несколько. Каждая идентификация — это пара ключей, которая используется для выполнения алгоритма Диффи-Хеллмана между одноранговыми узлами. Абстрактный сетевой адрес выводится из открытого ключа следующим образом: address = SHA-256(type_id || public_key)
. Обратите внимание, что type_id должен быть сериализован как little-endian uint32.
Список криптосистем с открытым ключом
type_id | cryptosystem |
---|---|
0x4813b4c6 | ed255191 |
1. Для выполнения x25519 пара ключей должна быть сгенерирована в формате x25519. Однако открытый ключ передается по сети в формате ed25519, поэтому вам придется преобразовать открытый ключ из x25519 в ed25519, примеры таких преобразований можно найти здесь для Kotlin.
Клиент-серверный протокол (ADNL через TCP)
Клиент подключается к серверу по протоколу TCP и отправляет пакет подтверждения ADNL, содержащий абстрактный адрес сервера, открытый ключ клиента и зашифрованные параметры сеанса AES-CTR, которые определяются клиентом.
Подтверждение
Сначала клиент должен выполнить протокол согласования ключей (например, x25519), используя свой закрытый ключ и открытый ключ сервера, принимая во внимание type_id
ключа сервера. В результате клиент получит secret
, который используется для шифрования ключей сеанса на последующих этапах.
Затем клиент должен сгенерировать параметры сеанса AES-CTR, 16-байтовый nonce и 32-байтовый ключ, как для направления TX (клиент->сервер), так и для направления RX (сервер->клиент) и сериали зовать их в 160-байтовый буфер следующим образом:
Параметр | Размер |
---|---|
rx_key | 32 байта |
tx_key | 32 байта |
rx_nonce | 16 байт |
tx_nonce | 16 байт |
padding | 64 байта |
Цель padding неизвестна, он не используется реализациями сервера. Рекомендуется заполнять весь 160-байтовый буфер случайными байтами, в противном случае злоумышленник может провести активную MitM-атаку, используя скомпрометированные параметры сеанса AES-CTR.
Следующий шаг — зашифровать параметры сеанса с помощью secret
через протокол согласования ключей выше. Для этого AES-256 должен быть инициализирован в режиме CTR с 128-битным счетчиком big-endian с использованием пары (key, nonce), которая вычисляется следующим образом (aes_params
— это 160-байтовый буфер, который был построен выше):
hash = SHA-256(aes_params)
key = secret[0..16] || hash[16..32]
nonce = hash[0..4] || secret[20..32]
После шифрования aes_params
, которое отмечено как E(aes_params)
, AES следует удалить, поскольку он больше не нужен.
Теперь мы готовы преобразовать всю эту информацию в 256-байтный пакет подтверждения связи и отправить его на сервер:
Параметр | Размер | Примечания |
---|---|---|
receiver_address | 32 байта | Идентификация однорангового сервера, как описано в соответствующем разделе |
sender_public | 32 байта | Открытый ключ клиента |
SHA-256(aes_params) | 32 байта | Подтверждение целостности параметров сеанса |
E(aes_params) | 160 байт | Зашифрованные параметры сеанса |
Сервер должен расшифровать параметры сеанса с по мощью секрета, полученного из протокола согласования ключей, тем же способом, что и клиент. Затем сервер должен выполнить следующие проверки для подтверждения свойств безопасности протокола:
- Сервер должен иметь соответствующий закрытый ключ для
receiver_address
, в противном случае невозможно выполнить протокол согласования ключей. SHA-256(aes_params) == SHA-256(D(E(aes_params)))
, в противном случае протокол согласования ключей не выполнен, иsecret
не одинаков на обеих сторонах.
Если любая из этих проверок не пройдена, сервер немедленно разорвет соединение, не отвечая клиенту. Если все проверки пройдены, сервер должен выдать пустую датаграмму (см. раздел Датаграммы) клиенту, чтобы доказать, что он владеет закрытым ключом для указанного receiver_address
.
Датаграмма
Как клиент, так и сервер должны инициализировать по два экземпляра AES-CTR для обоих направлений: TX и RX. AES-256 должен использоваться в режиме CTR с 128-битным счетчиком big-endian. Каждый экземпляр AES и нициализируется с использованием пары (key, nonce), принадлежащей ему, которую можно взять из aes_params
при обмене данными.
Чтобы отправить датаграмму, одноранговый узел (клиент или сервер) должен построить следующую структуру, зашифровать ее и отправить другому одноранговому узлу:
Параметр | Размер | Примечания |
---|---|---|
length | 4 байта (LE) | Длина всей датаграммы, исключая поле length |
nonce | 32 байта | Случайное значение |
buffer | length - 64 байта | Фактические данные для отправки на другую сторону |
hash | 32 байта | `SHA-256(nonce \ |
Вся структура должна быть зашифрована с использованием соответствующего экземпляра AES (TX для клиента -> сервера, RX для сервера -> клиента).
Принимающий узел должен извлечь первые 4 байта, расшифровать их в поле length
и прочитать ровно length
байтов, чтобы получить полную датаграмму. Принимающий узел может начать расшифровку и обработку buffer
раньше, но он должен учитывать, что он может быть поврежден, намеренно или случайно. Hash
датаграммы должен быть проверен, чтобы гар антировать целостность buffer
. В случае сбоя новые датаграммы не могут быть выданы, и соединение должно быть разорвано.
Первая датаграмма в сеансе всегда отправляется от сервера клиенту после того, как сервер успешно принял пакет подтверждения связи, и его фактический буфер пуст. Клиент должен расшифровать ее и отключиться от сервера в случае сбоя, поскольку это означает, что сервер не следовал протоколу должным образом, и фактические ключи сеанса различаются на стороне сервера и клиента.
Подробности связи
Если вы хотите углубиться в подробности связи, вы можете ознакомиться со статьей ADNL TCP - Liteserver, чтобы увидеть несколько примеров.