layer-mtproto 0.4.5

MTProto 2.0 session management, message framing, DH key exchange and transport abstractions
Documentation

πŸ“‘ layer-mtproto

MTProto 2.0 session management, DH key exchange, and message framing for Rust.

Crates.io docs.rs License: MIT OR Apache-2.0 Rust TL Layer

A complete, from-scratch implementation of Telegram's MTProto 2.0 session layer.


πŸ“¦ Installation

[dependencies]
layer-mtproto  = "0.4.5"
layer-tl-types = { version = "0.4.5", features = ["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_packed responses
  • πŸ§‚ 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 β€” MsgsAck with 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 layer_mtproto::EncryptedSession;

// Create from a completed DH handshake
let session = EncryptedSession::new(auth_key, first_salt, time_offset);

// Pack a RemoteCall into encrypted wire bytes
let wire_bytes = session.pack(&my_request)?;

// Pack any Serializable directly (bypasses RemoteCall bound)
let wire_bytes = session.pack_serializable(&raw_obj)?;

// Unpack an encrypted response from the server
let msg = session.unpack(&mut raw_bytes)?;
println!("msg_id={}, body_len={}", msg.msg_id, msg.body.len());

authentication β€” 3-Step DH Key Exchange

The full MTProto DH handshake as specified by Telegram:

use layer_mtproto::authentication as auth;

// Step 1 β€” req_pq_multi: get the server's PQ
let (req1, state1) = auth::step1()?;
// ... send req1 over the wire, receive res_pq ...

// Step 2 β€” req_DH_params: send our DH parameters
let (req2, state2) = auth::step2(state1, res_pq)?;
// ... send req2, receive server_DH_params ...

// Step 3 β€” set_client_DH_params: send our client DH
let (req3, state3) = auth::step3(state2, dh_params)?;
// ... send req3, receive dh_answer ...

// Finish β€” derive the auth key from the completed handshake
let done = auth::finish(state3, dh_answer)?;

// 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():

pub struct Message {
    pub msg_id:  i64,
    pub seq_no:  i32,
    pub salt:    i64,
    pub body:    Vec<u8>,   // raw TL bytes of the inner object
}

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 layer_mtproto::Session;

let mut plain = Session::new();
// Pack a plaintext MTProto message
let framed = plain.pack_plain(&my_handshake_request)?;

πŸ” What's Inside

Encryption Details

  • AES-IGE encryption/decryption via layer-crypto
  • msg_key = SHA-256 of (auth_key[88..120] || plaintext) for clientβ†’server
  • msg_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 ID 0x73f1f8dc) β€” wraps multiple logical messages in one TCP frame; both packing and unpacking are supported
  • gzip_packed (TL ID 0x3072cfa1) β€” response bodies are decompressed automatically; outgoing large requests are optionally compressed

Salt & Time Management

  • bad_server_salt β€” session records the corrected salt and automatically resets
  • future_salts prefetch β€” salts are requested in advance; session rotates before expiry
  • time_offset correction β€” all outgoing msg_id values are adjusted for clock skew

Error Recovery

  • bad_msg_notification β€” messages with wrong msg_id, seq_no, or session_id are resent with corrected framing
  • seq_no auto-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_ack list
  • The flush_acks() call bundles them into a single MsgsAck message
  • layer-client flushes 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:


πŸ‘€ Author

Ankit Chaubey
github.com/ankit-chaubey Β· ankitchaubey.in Β· ankitchaubey.dev@gmail.com

πŸ“¦ github.com/ankit-chaubey/layer