π‘ layer-mtproto
MTProto 2.0 session management, DH key exchange, and message framing for Rust.
A complete, from-scratch implementation of Telegram's MTProto 2.0 session layer.
π¦ Installation
[]
= "0.4.5"
= { = "0.4.5", = ["tl-mtproto"] }
β¨ What It Does
layer-mtproto implements the full MTProto 2.0 session layer β the encrypted tunnel through which all Telegram API calls travel. This is the core protocol machinery that sits between your application code and the TCP socket.
It handles:
- π€ 3-step DH key exchange β deriving a shared auth key from scratch
- π Encrypted sessions β packing/unpacking MTProto 2.0 messages with AES-IGE
- π¦ Message framing β salt, session_id, message_id, sequence numbers
- ποΈ Containers & compression β
msg_container,gzip_packedresponses - π§ Salt management β server salt tracking and auto-correction
- π Session state β time offset, sequence number, salt rotation
- π Error recovery β
bad_msg_notification,bad_server_salt,msg_resend_req - β
Acknowledgements β
MsgsAckwith a pending-ack queue
ποΈ Architecture
Application (layer-client)
β
βΌ
EncryptedSession β encrypt/decrypt, pack/unpack
β
βΌ
Authentication β 3-step DH handshake (step1 β step2 β step3 β finish)
β
βΌ
layer-crypto β AES-IGE, RSA, SHA, Diffie-Hellman
β
βΌ
TCP Socket
π Core Types
EncryptedSession
Manages the live MTProto session after a key has been established.
use EncryptedSession;
// Create from a completed DH handshake
let session = new;
// Pack a RemoteCall into encrypted wire bytes
let wire_bytes = session.pack?;
// Pack any Serializable directly (bypasses RemoteCall bound)
let wire_bytes = session.pack_serializable?;
// Unpack an encrypted response from the server
let msg = session.unpack?;
println!;
authentication β 3-Step DH Key Exchange
The full MTProto DH handshake as specified by Telegram:
use authentication as auth;
// Step 1 β req_pq_multi: get the server's PQ
let = step1?;
// ... send req1 over the wire, receive res_pq ...
// Step 2 β req_DH_params: send our DH parameters
let = step2?;
// ... send req2, receive server_DH_params ...
// Step 3 β set_client_DH_params: send our client DH
let = step3?;
// ... send req3, receive dh_answer ...
// Finish β derive the auth key from the completed handshake
let done = finish?;
// done.auth_key β [u8; 256] β the shared secret
// done.first_salt β i64 β first server salt to use
// done.time_offset β i32 β clock skew relative to server
Message
A decoded MTProto message as returned by session.unpack():
The body bytes are deserialized by layer-client using layer-tl-types's Deserializable trait.
Session (plain / unencrypted)
Used only for sending the initial DH handshake messages before an auth key exists:
use Session;
let mut plain = new;
// Pack a plaintext MTProto message
let framed = plain.pack_plain?;
π What's Inside
Encryption Details
- AES-IGE encryption/decryption via
layer-crypto msg_key= SHA-256 of(auth_key[88..120] || plaintext)for clientβservermsg_key= SHA-256 of(auth_key[96..128] || plaintext)for serverβclient- The 256-byte auth key is split into sub-keys for encryption, MAC, and padding
Message Framing
Every outgoing MTProto message has this structure:
server_salt (8 bytes) β current server salt
session_id (8 bytes) β random, stable for this session's lifetime
message_id (8 bytes) β Unix time * 2^32, monotonically increasing
seq_no (4 bytes) β content-related message counter
message_length (4 bytes) β length of payload in bytes
payload (N bytes) β serialized TL object
padding (M bytes) β 12β1024 random bytes so total % 16 == 0
Wrapped in a 32-byte msg_key prefix after encryption.
Containers & Compression
msg_container(TL ID0x73f1f8dc) β wraps multiple logical messages in one TCP frame; both packing and unpacking are supportedgzip_packed(TL ID0x3072cfa1) β response bodies are decompressed automatically; outgoing large requests are optionally compressed
Salt & Time Management
bad_server_saltβ session records the corrected salt and automatically resetsfuture_saltsprefetch β salts are requested in advance; session rotates before expirytime_offsetcorrection β all outgoingmsg_idvalues are adjusted for clock skew
Error Recovery
bad_msg_notificationβ messages with wrongmsg_id,seq_no, orsession_idare resent with corrected framingseq_noauto-correction for error codes 32 (seq_no too low) and 33 (seq_no too high)msg_resend_reqβ fulfils resend requests by replaying from a sent-body cache
Acknowledgements
- Received content-related messages accumulate in a
pending_acklist - The
flush_acks()call bundles them into a singleMsgsAckmessage layer-clientflushes pending ACKs on a timer and before each outgoing call
π Part of the layer stack
layer-client
βββ layer-mtproto β you are here
βββ layer-tl-types (tl-mtproto feature)
βββ layer-crypto
π License
Licensed under either of, at your option:
- MIT License β see LICENSE-MIT
- Apache License, Version 2.0 β see LICENSE-APACHE
π€ Author
Ankit Chaubey
github.com/ankit-chaubey Β· ankitchaubey.in Β· ankitchaubey.dev@gmail.com