ADNL TCP - Liteserver
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
Это низкоуровневый протокол, на котором построено все взаимодействие в сети TON, он может работать поверх любого протокола, но чаще всего используется поверх TCP и UDP. UDP используется для связи между узлами, а TCP - для связи с lite-серверами.
Теперь мы проанализируем ADNL, работающий поверх TCP, и узнаем, как напрямую взаимодействовать с lite-серверами.
В TCP-версии ADNL сетевые узлы используют открытые ключи ed25519 в качестве адресов и устанавливают соединение с помощью общего ключа, полученного с помощью процедуры Диффи-Хеллмана на эллиптических кривых - ECDH.
Структура пакета
Каждый пакет ADNL TCP, за исключением пакета рукопожатия, имеет следующую структуру:
- 4 байта размера пакета в порядке от младшего к старшему (N)
- 32 байта nonce (случайные байты для защиты от атак с контрольной суммой)
- (N - 64) байта полезной нагрузки
- 32 байта контрольной суммы SHA256 из nonce и полезной нагрузки
Весь пакет, включая размер, зашифрован AES-CTR. После расшифровки необходимо проверить, соответствует ли контрольная сумма данным, для проверки нужно просто самостоятельно посчитать контрольную сумму и сравнить результат с тем, что у нас в пакете.
Пакет подтверждения связи является исключением, он передается в частично незашифрованном виде и описан в следующей главе.
Установка соединения
Чтоб ы установить соединение, нам нужно знать IP, порт и открытый ключ сервера, а также сгенерировать собственный закрытый и открытый ключ ed25519.
Данные публичного сервера, такие как IP, порт и ключ, можно получить из глобальной конфигурации. IP в конфигурации в числовой форме, его можно привести к нормальной форме, используя, например, этот инструмент. Открытый ключ в конфигурации в формате base64.
Клиент генерирует 160 случайных байтов, некоторые из которых будут использоваться сторонами в качестве основы для шифрования AES.
Из них создаются 2 постоянных шифра AES-CTR, которые будут использоваться сторонами для шифрования/расшифровки сообщений после подтверждения связи.
- Шифр A - ключ 0 - 31 байт, iv 64 - 79 байт
- Шифр B - ключ 32 - 63 байта, iv 80 - 95 байт
Шифры применяются в следующем порядке:
- Шифр A используется сервером для шифрования отправляемых им сообщений.
- Шифр A используется клиентом для расшифровки полученных сообщений.
- Шифр B используется клиентом для шифрования отправляемых им сообщений.
- Шифр B используется сервером для расшифровки полученных сообщений.
Чтобы установить соединение, клиент должен отправить пакет подтверждения, содержащий:
- [32 байта] ID ключа сервера [Подробнее]
- [32 байта] Наш открытый ключ ed25519
- [32 байта] Хеш SHA256 из наших 160 байт
- [160 байт] Наши 160 байт зашифрованы [Подробнее]
При получении пакета подтверждения связи сервер выполнит те же действия, получит ключ ECDH, расшифрует 160 байт и создаст 2 постоянных ключа. Если все получится, сервер ответит пустым пакетом ADNL, без полезной нагрузки, для расшифровки которого (а также последующих) нам нужно использовать один из постоянных шифров.
С этого момента соединение можно считать установленным.
После установления соединения мы можем начать получать информацию; язык TL используется для сериализации данных.
Ping&Pong
Оптимально отправлять пакет ping каждые 5 секунд. Это необходимо для поддержания соединения во время отсутствия передачи данных, в противном случае сервер может разорвать соединение.
Пакет ping, как и все остальные, строится по стандартной схеме, описанной выше, и несет в себе идентификатор запроса и идентификатор ping в качестве полезных данных.
Давайте найдем нужную схему для запроса ping здесь и вычислим идентификатор схемы как
crc32_IEEE("tcp.ping random_id:long = tcp.Pong")
. При преобразовании в байты с прямым порядком байтов получаем 9a2b084d.
Таким образом, наш пакет ping ADNL будет выглядеть так:
- 4 байта размера пакета в прямом порядке -> 64 + (4+8) = 76
- 32 байта nonce -> случайные 32 байта
- 4 байта схемы ID TL -> 9a2b084d
- 8 байт идентификатора запроса -> случайное число uint64
- 32 байта контрольной суммы SHA256 из nonce и полезной нагрузки
Мы отправляем наш пакет и ждем tcp.pong, random_id
будет равен тому, который мы отправили в пакете ping.
Получение информации от Liteserver
Все запросы, направленные на получение информации из блокчейна, упакованы в схему Liteserver запроса, которая в свою очередь упакована в схему ADNL запроса.
Lite запрос:
liteServer.query data:bytes = Object
, id df068c79
ADNL запрос:
adnl.message.query query_id:int256 query:bytes = adnl.Message
, id 7af98bb4
Lite запрос передается внутри ADNL запроса как query:bytes
, а окончательный запрос передается внутри Lite запроса как data:bytes
.