В уже  далеком 2019 Telegram объявил конкурс на создание веб-версии своего мессенджера, в котором мне удалось поучаствовать. По итогу у меня осталась библиотека, которая может работать с API Telegram по протоколу MTProto. Полученный опыт вдохновил меня реализовать протокол MTProto для бэкенда. Разработку вел на python, так как я этот язык хорошо знаю и был уверен, что смогу на нем реализовать свою идею. Для удобства использовал библиотеку aiohttp для соединения по web-socket’у, а для описания структур использовал typings и dataclass.  Протокол MTProto можно поделить на три независимые части: Верхнеуровневая часть (API и запросы к нему) - описывает, как вызовы и ответы API могут быть переведены в двоичный код. Криптографическая (Авторизационная) часть  - описывает, как сообщения будут зашифрованы/расшифрованы. Транспортная часть - описывает, как зашифрованное сообщение будет передано по одному из протоколов: (HTTP, HTTPS, WS, WSS, TCP, UDP) Схема телеграма описывает какие есть доступные типы и функции для API. Благодаря схеме клиент знает как сериализовать/десериализовать входящие и исходящие сообщения. Схема описывается на языке TL или с помощью JSON. При описании конструкторов, типов, и функций доступных в схеме используются базовые типы int, long, doubles, bytes, string. Для них описано как они будут  сериализованы и десериализованы: int - 32 битное знаковое целое, для дампа которого используется little-endian порядок байтов long - 64 битное знаковое целое, для дампа которого используется little-endian порядок байтов double - 64 битное число с плавающей запятой bytes - последовательность байтов определенной длины: Если длина последовательности меньше либо равна 253, то в первый байт мы записываем длину последовательности, а в конце добавляем нулевые байты так, что общая длина последовательности делилась на 4.  Если длина последовательности  больше 253, то в первый байт мы записываем число 254, дальше идут 3 байта в порядке little-endian,  описывающие длину последовательности, сама последовательность, ну и в конце добавляем нулевые байты, чтобы общая длина делилась на 4. string - строка состоящая из UTF-8 символов, для сериализации используется тот же алгоритм, что и для bytes.  Также в MTProto поддерживаются типы int128/int256, но они используются только для шифрования.  С помощью базовых типов можно описать комплексные структуры. Самая простая комплексная структура - конструктор. Атрибутами конструктора могут быть базовые или пользовательские типы данных. Для описания конструктора на языке python мы воспользуемся модулем dataclass. Объявим класс, в котором будут описаны все атрибуты конструктора(их может и не быть вовсе). Также объявим классMeta,у которого есть два свойства name и order. В свойстве name храним название  конструктора, а в order - порядок сериализации/десериализации атрибутов конструктора.  У конструктора могут быть опциональные поля, описанные с помощью типа Optional и функции dataclasses.field, определяющую метаданные для этого поля. Параметр flag определяет какой бит атрибута flag будет индикатором того, определён или нет описанный атрибут. Атрибутом структуры может быть список. Для определения списка используется тип typing.List с указанием базового или пользовательского типа для элементов списка Пользовательский тип данных может быть представлен в виде объединения нескольких конструкторов. Для этого используется типы языка Union и Annotated: Для сериализации/десериализации пользовательских типов данных нужно знать какой конструктор был закодирован.Для этого мы будем использовать 32-битное знаковое число, вычисленное из строки описания конструктора алгоритмом crc32. Описание конструктора - строка вида: Зная конструктор или число конструктора, можно сериализовать/десериализовать его содержимое в нужном порядке.  К примеру для конструктора RegisteredUser, который относится к типу User,  будет сформирована строка: А число будет -366049798 Все функции для протокола будут асинхронными. Первым аргументом является request - объект типа aiohttp.web.Request, который мы используем для доступа к общим ресурсам. Следующими аргументами могут быть либо базовые, либо пользовательские типы. Все аргументы функции являются обязательными.Результат функции должен иметь пользовательский тип.  Для идентификации функции используется номер, который считается по алгоритму crc32 из строки вида: Для каждого WebSocket соединения определены несколько контекстных переменных: Создав типы данных и функции для бэкенда, можем объединить их в схему, которую будет использовать клиент. Для создания схемы используется класс mtpylon.Schema Создав схему, мы можем ее сериализовать либо в TL-программу, либо в JSON, используя для этого сериализации mtpylon.serializers.to_tl_program() или mtpylon.serializers.to_json(). Зная как сериализовать и десериализовать сообщения, мы можем начать общение между клиентом и сервером. Данная часть хорошо описана в официальной документации телеграмма.  Есть два варианта сообщений: незашифрованные и зашифрованные сообщения.     Незашифрованные используются для создания ключа с использованием алгоритма Деффи-Хельмана. Подробное описание для клиента можно почитать здесь Структура незашифрованного сообщения:  Со стороны сервера нам необходимо: Cгенерировать и хранить публичный/приватный RSA-ключ. Хранить авторизационный ключ(2048 bit). У авторизационного ключа имеется 64х битный идентификатор(auth_id), поэтому достаточно просто организовать key-value хранилище. В своей реализации я храню их в памяти, используя класс  mtproto.crypto.auth_key_manager.AuthKeyManager, но реализуя протокол mtproto.crypto.auth_key_manager.AuthKeyManagerProtocol,можно работать с любой бд. К примеру, я использовал postgresql, где хранил id как BigInteger, а сам ключ как BLOB. Получив ключ авторизации,  клиент может посылать сервисные и пользовательские сообщения в зашифрованном виде. Структура зашифрованного сообщения: Схема шифрования данных: Исходя из структуры и схемы, нам надо хранить идентификатор сессии клиента и соль. Идентификатор сессии создается пользователем, поэтому пара auth_key_id и session_id будет уникальна, тогда как session_id у разных пользователей может повторяться.  session_id - 64 битное значение, поэтому его легко хранить. mtproto.sessions.sessions_storage_protocol.SessionSсtorageProtocol должен быть реализован для хранения в различных бд, или можно использовать mtproto.sessions.in_memory_session_storage.InMemorySessionStorage для хранения информации о сессии в памяти. Для информирования о создании или удалении сессий используется паттерн observer. Соль - 64 битное случайное число, меняющееся в определенный период времени. Используется для предотвращения атак повторного воспроизведения. Сервер должен генерировать числа на текущее время и на некоторое будущее. Сгенерированная соль и авторизационный ключ создают уникальную пару.  Для генерации и работы с солью был создан mtpylon.salts.server_salt_manager.ServerSaltManager, который хранит все данные в памяти. Для реализации кастомного протокола нужно реализовать mtpylon.salts.server_satl_manager_protocol.ServerSaltManagerProtocol.  Каждое сообщение имеет свой id. И пара Авторизационный ключ + id сообщения будет уникальна. Сервер хранит сообщение и пересылает пользователю до тех пор, пока не придет подтверждение, что оно было обработано или сессия пользователя не была уничтожена. Для хранения сообщений в оперативной памяти реализован класс mtpylon.acknowledgment_store.inmemory_acknowledgment_store.InmemoryAcknowledgmentStore . Зашифрованное сообщение передается по протоколу websocket. Согласно протоколу MTProto, каждое передаваемое сообщение должно быть обфусцировано. Каждое сообщение перед отправкой “оборачивается” в intermediate блок  и проходит обязательную обфускацию Реализовав все части протокола MTProto, получилось создать библиотеку mtpylon, с помощью которой можем создать собственное API.  1. создадим функцию, генерирующую rsa-ключи: rsa_keys.py 2. Определим возвращаемый тип, функцию и саму схему: schema.py 3. Настроим aiohttp для работы с протоколом: web.py 4. Запустим приложение: 5. Со стороны клиента используем библиотеку zagram Спасибо Telegram, что разнообразили мои будни, заставили меня поглубже разобраться в криптографии и решать нетривиальные задачи. Буду рад, если кому-нибудь эти библиотеки будут полезны или вдохновят на собственную реализацию протокола MTProto. Mtpylon: исходный код: https://github.com/Zapix/mtpylon документация: https://mtpylon.readthedocs.io/en/latest/ Zagram: исходный код: https://github.com/Zapix/zagram Примеры приложений: echo-server https://github.com/Zapix/echo-server ToDo-list: https://github.com/Zapix/mtpylon-todo-list