layer 0.1.1

Ergonomic Telegram MTProto library — auto-generated TL types, raw API access, session management
Documentation

⚡ layer

A modular, from-scratch Rust implementation of the Telegram MTProto protocol.

License: MIT OR Apache-2.0 Rust TL Layer Status: Experimental


⚠️ Use at your own risk! 😁

This is an experimental, educational project built from scratch to understand Telegram's MTProto protocol at the lowest level. It is not production-ready. For serious projects, use grammers.


✨ About

layer is a hand-written, bottom-up Rust implementation of the Telegram MTProto protocol. Every component — from the TL schema parser, to the AES-IGE cipher, to the Diffie-Hellman key exchange — is written and owned by this project.

Built purely for learning and experimentation: to understand what happens inside a Telegram client at the raw byte level, all the way from TCP to a high-level API call.


💡 Inspiration & Credits

This project is heavily inspired by and based on the architecture of grammers by Lonami.

🙏 A huge Thank You to Lonami for building grammers — an incredibly well-structured, readable library that made MTProto's internals approachable. Without grammers, this project simply would not exist. Thank you for the awesome library! 🎉

The flow, naming conventions, SRP 2FA math, DC migration logic, session persistence, and overall architecture mirror grammers closely — intentionally, as a learning exercise.

Written by: Ankit Chaubey
Inspired by: grammers by Lonami


🏗️ Crate Structure

layer/
├── layer-tl-parser/   ── Parses .tl schema text → AST
├── layer-tl-gen/      ── AST → Rust source code (runs at build time)
├── layer-tl-types/    ── Auto-generated types, functions & enums (Layer 222)
├── layer-crypto/      ── AES-IGE, RSA, SHA, auth key derivation
├── layer-mtproto/     ── MTProto session, DH exchange, message framing, transport
├── layer-client/      ── High-level Client: auth, 2FA, send messages
├── layer-connect/     ── Demo binary: raw DH + getConfig
├── layer-app/         ── Binary: interactive login + send a message
└── layer/             ── Convenience facade re-exporting everything

The code generation pipeline runs automatically at build time:

api.tl / mtproto.tl
      │
      ▼
layer-tl-parser  ── .tl text → Definition AST
      │
      ▼
layer-tl-gen     ── AST → Rust source (inside build.rs)
      │
      ▼
layer-tl-types   ── compiled structs, enums, RemoteCall impls
      │
      ▼
layer-mtproto    ── Session + EncryptedSession + Transport
      │
      ▼
layer-client     ── Client::connect / sign_in / send_message

🚀 Quick Start

Add to your Cargo.toml:

[dependencies]
layer-client = { git = "https://github.com/ankit-chaubey/layer" }

Basic usage:

use layer_client::{Client, SignInError};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connects, runs DH key exchange, calls initConnection(GetConfig)
    let mut client = Client::load_or_connect("session.bin", API_ID, API_HASH)?;

    if !client.is_authorized()? {
        let token = client.request_login_code("+1234567890")?;
        let code  = "12345"; // the code Telegram sent you

        match client.sign_in(&token, code) {
            Ok(name) => println!("Welcome, {name}!"),

            // 2FA cloud password
            Err(SignInError::PasswordRequired(pw_token)) => {
                let hint = pw_token.hint().unwrap_or("no hint");
                println!("2FA hint: {hint}");
                client.check_password(pw_token, "my_2fa_password")?;
            }

            Err(e) => return Err(e.into()),
        }

        client.save_session("session.bin")?;
    }

    client.send_message("me", "Hello from layer! 😁")?;
    Ok(())
}

Or just run the included app — fill in your credentials at the top of layer-app/src/main.rs:

cargo run -p layer-app

✅ What's Implemented

🔐 Cryptography (layer-crypto)

  • AES-IGE encryption / decryption (MTProto 2.0)
  • RSA encryption with Telegram's public keys
  • SHA-1 and SHA-256 hashing utilities
  • Auth key derivation from nonce material
  • PQ factorization (Pollard's rho algorithm)
  • Diffie-Hellman shared secret computation

📡 MTProto Layer (layer-mtproto)

  • Full 3-step Diffie-Hellman key exchange handshake
  • MTProto 2.0 encrypted sessions (AES-IGE + auth key)
  • Proper message framing (salt, session_id, msg_id, seq_no)
  • Abridged TCP transport
  • Server salt tracking and correction
  • msg_container (multi-message) unpacking
  • gzip-packed response decompression
  • Pong, bad_server_salt, new_session_created handling

📦 TL Type System

  • Full .tl schema parser (layer-tl-parser)
  • Build-time Rust code generation (layer-tl-gen)
  • All Layer 222 constructors — 2,295 definitions (layer-tl-types)
  • Serializable / Deserializable traits for all types
  • RemoteCall trait for all RPC functions
  • From<types::T> for enums::E conversion impls
  • Optional: Debug, serde, name_for_id(u32)

👤 High-Level Client (layer-client)

  • Client::connect() — TCP + DH + initConnection(GetConfig)
  • Client::load_or_connect() — reuses saved session if available
  • Client::save_session() — persists auth key + DC table to disk
  • Client::is_authorized() — probes with updates.getState
  • Client::request_login_code() — sends code via SMS or Telegram app
  • Client::sign_in() — phone code login
  • Client::check_password() — full SRP 2FA (same math as grammers)
  • Client::send_message() — sends text to any peer ("me", username, phone)
  • Client::invoke() — raw RemoteCall escape hatch for any API method
  • DC migration (PHONE_MIGRATE_X, USER_MIGRATE_X)
  • RPC error propagation (code + message string)

❌ What's NOT Implemented

  • Async / Tokio — fully synchronous, blocking I/O
  • Update handling — no event loop, no iter_messages(), no update callbacks
  • Media — no file upload, no file download, no thumbnails
  • Dialogs / contacts — no get_dialogs(), get_entity()
  • Automatic flood waitFLOOD_WAIT_X errors surface as Err, not retried
  • MTProxy / obfuscation — no proxy support
  • Multiple accounts — single session only
  • Channels / groups — only basic peer resolution for send_message
  • Bots — no bot-specific auth flow
  • WebSocket transport — TCP only

🆚 layer vs grammers

Feature layer grammers
Async / non-blocking ❌ sync ✅ Tokio
DH key exchange
2FA / SRP
Session persistence
DC migration
Send message
TL code generation ✅ custom ✅ grammers-tl-gen
Update / event handling
Media upload/download
Dialogs / contacts
Flood wait handling
MTProxy support
Production ready
Purpose Learning & experiment Real projects

Use grammers for anything real. Use layer to understand how MTProto actually works.


🔧 Feature Flags

layer-tl-types exposes optional compile-time features:

Feature Default Description
tl-api High-level Telegram API schema (api.tl)
tl-mtproto Low-level MTProto schema (mtproto.tl)
impl-debug #[derive(Debug)] on all generated types
impl-from-type From<types::T> for enums::E
impl-from-enum TryFrom<enums::E> for types::T
name-for-id name_for_id(u32) -> Option<&'static str>
impl-serde serde::Serialize / Deserialize on all types
deserializable-functions Deserializable on RPC function types (server use)
[dependencies]
layer-tl-types = { git = "...", features = ["impl-serde", "name-for-id"] }

📐 Updating to a New TL Layer

  1. Replace layer-tl-types/tl/api.tl with the new schema file.
  2. Update the // LAYER N comment on the first line.
  3. Run cargo build — all types regenerate automatically. That's it.

🧪 Tests

cargo test --workspace

📄 License

Licensed under either of, at your option:


👤 Author

Ankit Chaubey
GitHub: github.com/ankit-chaubey


🙏 Acknowledgements

  • Lonami — for grammers. The architecture, design decisions, SRP math, and session handling in this project are all directly inspired by grammers. It's a fantastic library and an even better learning resource. Thank you for making it open source! 🎉

  • Telegram — for the detailed MTProto specification.


📦 Publishing to crates.io

The publishable crates (in dependency order) are:

layer-tl-parser  →  layer-tl-gen  →  layer-tl-types  →  layer-crypto
                                                              ↓
                                                        layer-mtproto
                                                              ↓
                                                        layer-client
                                                              ↓
                                                           layer

One-time setup:

# 1. Init git (crates.io requires a clean git repo)
git init
git add .
git commit -m "initial release v0.1.0"

# 2. Login with your crates.io API token
cargo login

Publish in this exact order — each crate must finish uploading before the next:

# 1. No layer deps
cargo publish -p layer-tl-parser

# 2. Depends on layer-tl-parser
cargo publish -p layer-tl-gen

# 3. Depends on layer-tl-parser + layer-tl-gen (build-deps)
cargo publish -p layer-tl-types

# 4. No layer deps
cargo publish -p layer-crypto

# 5. Depends on layer-tl-types + layer-crypto
cargo publish -p layer-mtproto

# 6. Depends on layer-tl-types + layer-mtproto
cargo publish -p layer-client

# 7. Facade — depends on everything above
cargo publish -p layer

layer-app and layer-connect are marked publish = false — they are skipped automatically.

Dry run first to catch issues without uploading:

cargo publish -p layer-tl-parser --dry-run
# repeat for each crate

Bump the version for future releases (all crates share the workspace version):

# 1. Edit `version = "0.1.0"` → `"0.2.0"` in the root Cargo.toml
# 2. Commit the change
git add Cargo.toml && git commit -m "bump to v0.2.0"
# 3. Publish in the same order above

layer — because sometimes you have to build it yourself to truly understand it.