Skill: zhihang9978/mtproto-2-0 (version 1.0.1)

--- name: mtproto-2.0 description: MTProto 2.0 protocol implementation guide for Telegram backend development. Use when implementing MTProto encryption, handshake, message serialization, or building Telegram-compatible servers. Covers DH key exchange, AES-256-IGE encryption, TL language, and security best practices. version: 1.0.1 --- # MTProto 2.0 Protocol Complete implementation guide for Telegram's MTProto 2.0 encryption protocol. ⚠️ **IMPORTANT SECURITY NOTICE** This skill provides educational guidance on MTProto 2.0 protocol. Before using in production: 1. **Verify Auth Key ID size**: Ensure 8-byte alignment throughout your implementation 2. **Use crypto/rand**: Never use math/rand for cryptographic operations 3. **Test against official vectors**: Validate against Telegram's reference implementations 4. **Audit by experts**: Have cryptographers review before handling real keys 5. **Check off-by-one errors**: Carefully validate all buffer sizes and offsets See [references/security-notice.md](references/security-notice.md) for detailed corrections. ## Quick Reference ### Core Components - **Auth Key**: 256-bit symmetric key from DH exchange - **Server Salt**: 64-bit anti-replay nonce - **Message Key**: 128-bit message identifier - **AES-256-IGE**: Encryption mode with error propagation ### When to Use Use this skill when: 1. Implementing MTProto handshake (req_pq → req_DH_params → set_client_DH_params) 2. Encrypting/decrypting MTProto messages 3. Serializing TL objects 4. Building Telegram-compatible gateways 5. Debugging connection issues ## Handshake Flow ``` Client Server | | | 1. req_pq | |----------------------------------->| | | | 2. server_public_key_fingerprints| |<-----------------------------------| | | | 3. req_DH_params | |----------------------------------->| | | | 4. server_DH_params | |<-----------------------------------| | | | 5. set_client_DH_params | |----------------------------------->| | | | 6. dh_gen_ok | |<-----------------------------------| ``` See [references/handshake.md](references/handshake.md) for detailed implementation. ## Encryption ### AES-256-IGE Mode ```go // Key derivation from Auth Key + Message Key func deriveKeys(authKey, msgKey []byte) (aesKey, aesIV []byte) { x := sha256.Sum256(append(msgKey, authKey[:36]...)) y := sha256.Sum256(append(authKey[40:76], msgKey...)) aesKey = append(x[:8], y[8:24]...) aesIV = append(y[:8], x[24:32]...) return } ``` See [references/encryption.md](references/encryption.md) for complete algorithm. ## Message Format ### Encrypted Message Structure ``` ┌─────────────────────────────────────────────────────────┐ │ Auth Key ID │ 8 bytes │ SHA1(authKey)[96:128] │ ├─────────────────────────────────────────────────────────┤ │ Message Key │ 16 bytes │ SHA256(plaintext)[8:24] │ ├─────────────────────────────────────────────────────────┤ │ Encrypted Data │ Variable │ AES-256-IGE encrypted │ └─────────────────────────────────────────────────────────┘ ``` ### Plaintext Structure ``` ┌─────────────────────────────────────────────────────────┐ │ Salt │ 8 bytes │ Server Salt │ ├─────────────────────────────────────────────────────────┤ │ Session ID │ 8 bytes │ Unique session ID │ ├─────────────────────────────────────────────────────────┤ │ Message ID │ 8 bytes │ Timestamp + sequence │ ├─────────────────────────────────────────────────────────┤ │ Sequence No │ 4 bytes │ Packet sequence │ ├─────────────────────────────────────────────────────────┤ │ Length │ 4 bytes │ Message body length │ ├─────────────────────────────────────────────────────────┤ │ Message Body │ Variable │ TL-serialized data │ ├─────────────────────────────────────────────────────────┤ │ Padding │ 0-15 B │ Random padding │ └─────────────────────────────────────────────────────────┘ ``` See [references/message-format.md](references/message-format.md) for details. ## TL Language ### Type Serialization ```tl // Constructor with ID user#938458c1 id:long first_name:string = User; // Method definition checkPhone#6fe51dfb phone_number:string = Bool; ``` See [references/tl-language.md](references/tl-language.md) for TL schema reference. ## Security Features ### Forward Secrecy - Auth Key derived from ephemeral DH exchange - Keys rotated periodically - Old key compromise doesn't expose history ### Anti-Replay Protection - Server Salt changes per connection - Message ID includes timestamp - Sequence number verification - Time window validation ### Integrity Verification - Message Key from SHA256 hash - AES-IGE error propagation - Length field validation ## Go Implementation ### Basic Connection ```go type MTProtoConn struct { conn net.Conn authKey []byte salt int64 sessionID int64 seqNo int32 } func (m *MTProtoConn) Send(msg TLObject) error { // 1. Serialize plaintext := msg.Encode() // 2. Calculate Message Key msgKey := calcMsgKey(plaintext, m.authKey) // 3. Derive AES keys aesKey, aesIV := deriveKeys(m.authKey, msgKey) // 4. Encrypt encrypted := encryptAESIGE(plaintext, aesKey, aesIV) // 5. Construct packet packet := constructPacket(m.authKey, msgKey, encrypted) // 6. Send _, err := m.conn.Write(packet) return err } ``` ## References - [Handshake Details](references/handshake.md) - Complete DH exchange - [Encryption Algorithm](references/encryption.md) - AES-256-IGE - [Message Format](references/message-format.md) - Binary protocol - [TL Language](references/tl-language.md) - Type serialization - [⚠️ Security Notice](references/security-notice.md) - **CRITICAL FIXES FOR PRODUCTION** ## Official Resources - [MTProto Official](https://core.telegram.org/mtproto) - [TL Schema](https://core.telegram.org/schema) - [Telegram API](https://core.telegram.org/api)

path: references/encryption.md
content: # AES-256-IGE Encryption

MTProto's custom encryption mode implementation.

## Overview

**IGE** (Infinite Garble Extension) is a block cipher mode that:
- Propagates errors throughout the message
- Is self-synchronizing
- Provides integrity checking through error propagation

## Algorithm

### Basic Principle

```
For each block i:
    x_i = AES(x_{i-1} XOR plaintext_i) XOR y_{i-1}
    
Where:
    x_i = ciphertext block i
    y_i = plaintext block i
    x_{i-1} = previous ciphertext block (or IV part 1)
    y_{i-1} = previous plaintext block (or IV part 2)
```

### Encryption

```go
func encryptAESIGE(plaintext, key, iv []byte) []byte {
    block, _ := aes.NewCipher(key) // AES-256
    
    // IGE requires two IV blocks
    x := iv[:16]   // x_{-1}
    y := iv[16:]   // y_{-1}
    
    ciphertext := make([]byte, len(plaintext))
    
    for i := 0; i < len(plaintext); i += 16 {
        // Current plaintext block
        plainBlock := plaintext[i : i+16]
        
        // x_i = AES(x_{i-1} XOR plain_i)
        tmp := xor(x, plainBlock)
        encrypted := make([]byte, 16)
        block.Encrypt(encrypted, tmp)
        
        // x_i = encrypted XOR y_{i-1}
        x = xor(encrypted, y)
        
        copy(ciphertext[i:], x)
        y = plainBlock
    }
    
    return ciphertext
}
```

### Decryption

```go
func decryptAESIGE(ciphertext, key, iv []byte) ([]byte, error) {
    block, _ := aes.NewCipher(key)
    
    x := iv[:16]   // x_{-1}
    y := iv[16:]   // y_{-1}
    
    plaintext := make([]byte, len(ciphertext))
    
    for i := 0; i < len(ciphertext); i += 16 {
        // Current ciphertext block
        cipherBlock := ciphertext[i : i+16]
        
        // y_i = AES^{-1}(cipher_i XOR y_{i-1}) XOR x_{i-1}
        tmp := xor(cipherBlock, y)
        decrypted := make([]byte, 16)
        block.Decrypt(decrypted, tmp)
        
        y = xor(decrypted, x)
        
        copy(plaintext[i:], y)
        x = cipherBlock
    }
    
    return plaintext, nil
}
```

## Key Derivation

### From Auth Key and Message Key

```go
func deriveKeys(authKey, msgKey []byte) (aesKey, aesIV []byte) {
    // SHA256(msg_key + auth_key[:36])
    x := sha256.Sum256(append(msgKey, authKey[:36]...))
    
    // SHA256(auth_key[40:76] + msg_key)
    y := sha256.Sum256(append(authKey[40:76], msgKey...))
    
    // aes_key = x[:8] + y[8:24]
    aesKey = make([]byte, 32)
    copy(aesKey[0:8], x[0:8])
    copy(aesKey[8:24], y[8:24])
    
    // aes_iv = y[:8] + x[24:32]
    aesIV = make([]byte, 32)
    copy(aesIV[0:8], y[0:8])
    copy(aesIV[8:16], x[24:32])
    
    return
}
```

### From New Nonce and Server Nonce (Temp Keys)

```go
func generateTempKeys(newNonce, serverNonce []byte) (key, iv []byte) {
    // SHA1(new_nonce + server_nonce)
    hash1 := sha1.Sum(append(newNonce, serverNonce...))
    
    // SHA1(server_nonce + new_nonce)
    hash2 := sha1.Sum(append(serverNonce, newNonce...))
    
    // SHA1(new_nonce + new_nonce)
    hash3 := sha1.Sum(append(newNonce, newNonce...))
    
    // Key = hash1 + hash2[:12]
    key = make([]byte, 32)
    copy(key[0:20], hash1[:])
    copy(key[20:32], hash2[0:12])
    
    // IV = hash2[12:20] + hash3 + 0x00...0x00
    iv = make([]byte, 32)
    copy(iv[0:8], hash2[12:20])
    copy(iv[8:28], hash3[:])
    // Last 4 bytes are zeros
    
    return
}
```

## Helper Functions

### XOR Operation

```go
func xor(a, b []byte) []byte {
    result := make([]byte, len(a))
    for i := range a {
        result[i] = a[i] ^ b[i]
    }
    return result
}
```

### PKCS7 Padding

```go
func pkcs7Pad(data []byte, blockSize int) []byte {
    padding := blockSize - len(data)%blockSize
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(data, padtext...)
}

func pkcs7Unpad(data []byte) ([]byte, error) {
    if len(data) == 0 {
        return nil, errors.New("empty data")
    }
    padding := int(data[len(data)-1])
    if padding > len(data) || padding == 0 {
        return nil, errors.New("invalid padding")
    }
    return data[:len(data)-padding], nil
}
```

## Complete Encryption Flow

```go
func encryptMessage(plaintext, authKey []byte) ([]byte, error) {
    // 1. Add random padding (0-15 bytes)
    padding := generateRandom(rand.Intn(16))
    plaintext = append(plaintext, padding...)
    
    // 2. Calculate message key
    msgKey := calcMsgKey(plaintext, authKey)
    
    // 3. Derive AES keys
    aesKey, aesIV := deriveKeys(authKey, msgKey)
    
    // 4. Encrypt
    ciphertext := encryptAESIGE(plaintext, aesKey, aesIV)
    
    // 5. Construct final message
    authKeyID := calcAuthKeyID(authKey)
    
    result := make([]byte, 8+16+len(ciphertext))
    copy(result[0:8], authKeyID)
    copy(result[8:24], msgKey)
    copy(result[24:], ciphertext)
    
    return result, nil
}
```

## Security Properties

### Error Propagation

If one ciphertext block is corrupted:
- That plaintext block is completely garbled
- The next plaintext block has bit errors at the same positions
- All subsequent blocks are completely garbled

```
Corrupted block i:
- plaintext_i: completely random
- plaintext_{i+1}: XOR with error pattern
- plaintext_{i+2...}: completely garbled
```

### Self-Synchronization

After corruption, the cipher resynchronizes after 2 blocks:
```
Block i:   corrupted
Block i+1: corrupted (with error pattern)
Block i+2: correct decryption resumes
```

## Comparison with Other Modes

| Mode | Error Propagation | Parallelizable | Integrity |
|------|------------------|----------------|-----------|
| CBC  | 2 blocks         | Decrypt only   | No        |
| CTR  | 1 bit            | Yes            | No        |
| GCM  | Complete message | Yes            | Yes       |
| **IGE** | **All remaining** | **No** | **Partial** |

## Common Mistakes

### 1. Wrong IV Size

```go
// Wrong - using 16-byte IV
iv := make([]byte, 16)

// Correct - using 32-byte IV
iv := make([]byte, 32)
```

### 2. Reusing IV

```go
// Wrong - same IV for multiple messages
ciphertext1 := encryptAESIGE(plaintext1, key, iv)
ciphertext2 := encryptAESIGE(plaintext2, key, iv) // Don't do this!

// Correct - unique IV per message
```

### 3. Not Checking Plaintext Length

```go
// Wrong - not padding
if len(plaintext)%16 != 0 {
    panic("plaintext not aligned")
}

// Correct - always pad
plaintext = pkcs7Pad(plaintext, 16)
```

## Testing

### Test Vectors

```go
func TestAESIGE(t *testing.T) {
    key := make([]byte, 32)
    iv := make([]byte, 32)
    plaintext := []byte("Hello, MTProto!")
    
    // Pad plaintext
    plaintext = pkcs7Pad(plaintext, 16)
    
    // Encrypt
    ciphertext := encryptAESIGE(plaintext, key, iv)
    
    // Decrypt
    decrypted, err := decryptAESIGE(ciphertext, key, iv)
    if err != nil {
        t.Fatal(err)
    }
    
    // Unpad
    decrypted, err = pkcs7Unpad(decrypted)
    if err != nil {
        t.Fatal(err)
    }
    
    // Verify
    if !bytes.Equal(plaintext[:len(plaintext)-16+len("Hello, MTProto!")], decrypted) {
        t.Fatal("decryption failed")
    }
}
```

## Performance Considerations

### Not Parallelizable

IGE cannot be parallelized - each block depends on the previous:
```
Block 0: depends on IV
Block 1: depends on Block 0
Block 2: depends on Block 1
...
```

### Use Hardware AES

Go's crypto/aes automatically uses AES-NI when available:
```go
import _ "crypto/aes"
// Automatically uses hardware acceleration
```

### For Large Messages

Consider chunking for very large messages (>1MB):
```go
const maxChunkSize = 65536  // 64KB chunks

for i := 0; i < len(plaintext); i += maxChunkSize {
    end := min(i+maxChunkSize, len(plaintext))
    chunk := plaintext[i:end]
    // Encrypt chunk
}
```


path: references/handshake.md
content: # MTProto 2.0 Handshake Protocol

Complete DH key exchange implementation.

## Overview

The handshake establishes a shared 256-bit Auth Key using Diffie-Hellman key exchange over RSA-encrypted channels.

## Step-by-Step Implementation

### Step 1: req_pq

**Client Request:**
```go
type ReqPq struct {
    Nonce [16]byte  // 128-bit random
}

func (c *Client) reqPq() (*ResPq, error) {
    req := &ReqPq{
        Nonce: generateNonce(16),
    }
    return c.send(req)
}
```

**Server Response:**
```go
type ResPq struct {
    Nonce                    [16]byte
    ServerNonce              [16]byte
    Pq                       []byte  // pq = p * q (two primes)
    ServerPublicKeyFingerprints []int64
}
```

### Step 2: req_DH_params

**Factorize pq:**
```go
func factorize(pq []byte) (p, q []byte) {
    // Use Pollard's rho or trial division
    // Return p < q
}
```

**Generate new_nonce:**
```go
newNonce := generateNonce(32)  // 256-bit
```

**Encrypt with RSA:**
```go
data := &P_q_inner_data{
    Pq:        pq,
    P:         p,
    Q:         q,
    Nonce:     req.Nonce,
    ServerNonce: res.ServerNonce,
    NewNonce:  newNonce,
}

encrypted := rsaEncrypt(data.Encode(), serverPublicKey)
```

**Send request:**
```go
type ReqDHParams struct {
    Nonce                    [16]byte
    ServerNonce              [16]byte
    P                        []byte
    Q                        []byte
    PublicKeyFingerprint     int64
    EncryptedData            []byte
}
```

### Step 3: server_DH_params

**Server Response:**
```go
type ServerDHParamsOk struct {
    Nonce         [16]byte
    ServerNonce   [16]byte
    EncryptedAnswer []byte  // Contains Server_DH_inner_data
}

// Decrypted data contains:
type ServerDHInnerData struct {
    Nonce       [16]byte
    ServerNonce [16]byte
    G           int32   // Generator
    DHPrime     []byte  // Safe prime
    GA          []byte  // g^a mod p
    ServerTime  int32
}
```

### Step 4: set_client_DH_params

**Generate b and compute gb:**
```go
// Random b (2048 bits)
b := generateRandom(256)

// gb = g^b mod dh_prime
gb := new(big.Int).Exp(
    big.NewInt(int64(serverData.G)),
    new(big.Int).SetBytes(b),
    new(big.Int).SetBytes(serverData.DHPrime),
).Bytes()
```

**Compute Auth Key:**
```go
// auth_key = (g^a)^b mod p = g^(ab) mod p
authKey := new(big.Int).Exp(
    new(big.Int).SetBytes(serverData.GA),
    new(big.Int).SetBytes(b),
    new(big.Int).SetBytes(serverData.DHPrime),
).Bytes()
```

**Compute key identifier:**
```go
// auth_key_aux_hash = SHA1(auth_key)[0:8]
// auth_key_id = auth_key_aux_hash XOR new_nonce[0:8]
```

**Send confirmation:**
```go
data := &ClientDHInnerData{
    Nonce:       nonce,
    ServerNonce: serverNonce,
    GB:          gb,
}

encrypted := encryptAES(data.Encode(), tmpKey, tmpIV)

req := &SetClientDHParams{
    Nonce:         nonce,
    ServerNonce:   serverNonce,
    EncryptedData: encrypted,
}
```

### Step 5: dh_gen_ok

**Server Response:**
```go
type DhGenOk struct {
    Nonce         [16]byte
    ServerNonce   [16]byte
    NewNonceHash1 [16]byte  // SHA1(new_nonce + auth_key)[4:20]
}
```

**Verify:**
```go
expectedHash := sha1.Sum(append(newNonce, authKey...))
if !bytes.Equal(newNonceHash1, expectedHash[4:20]) {
    return errors.New("handshake verification failed")
}
```

## Security Considerations

### Prime Verification

Always verify DH parameters:
```go
func verifyDHParams(g int32, p []byte) error {
    pInt := new(big.Int).SetBytes(p)
    
    // 1. p is prime
    if !pInt.ProbablyPrime(20) {
        return errors.New("p is not prime")
    }
    
    // 2. (p-1)/2 is prime (safe prime)
    q := new(big.Int).Div(new(big.Int).Sub(pInt, big.NewInt(1)), big.NewInt(2))
    if !q.ProbablyPrime(20) {
        return errors.New("(p-1)/2 is not prime")
    }
    
    // 3. 2^2047 <= p < 2^2048
    if pInt.BitLen() != 2048 {
        return errors.New("p is not 2048 bits")
    }
    
    // 4. g is generator
    if g != 2 && g != 3 && g != 4 && g != 5 && g != 6 && g != 7 {
        return errors.New("invalid generator")
    }
    
    return nil
}
```

### Replay Protection

```go
// Server must validate:
// 1. nonce matches req_pq
// 2. server_nonce is one we generated
// 3. new_nonce is fresh (not seen before)
```

## Error Handling

### dh_gen_retry

When server asks client to retry with different parameters:
```go
type DhGenRetry struct {
    Nonce         [16]byte
    ServerNonce   [16]byte
    NewNonceHash2 [16]byte
}

// Client should retry with different b
```

### dh_gen_fail

When handshake fails:
```go
type DhGenFail struct {
    Nonce         [16]byte
    ServerNonce   [16]byte
    NewNonceHash3 [16]byte
}
```

## Go Implementation Example

```go
func (c *Client) performHandshake() ([]byte, error) {
    // Step 1: req_pq
    resPQ, err := c.reqPq()
    if err != nil {
        return nil, err
    }
    
    // Step 2: req_DH_params
    p, q := factorize(resPQ.Pq)
    newNonce := generateNonce(32)
    
    innerData := &P_q_inner_data{
        Pq:          resPQ.Pq,
        P:           p,
        Q:           q,
        Nonce:       resPQ.Nonce,
        ServerNonce: resPQ.ServerNonce,
        NewNonce:    newNonce,
    }
    
    serverKey := c.getServerKey(resPQ.ServerPublicKeyFingerprints[0])
    encrypted := rsa.EncryptOAEP(sha256.New(), rand.Reader, serverKey, innerData.Encode(), nil)
    
    reqDH := &ReqDHParams{
        Nonce:                resPQ.Nonce,
        ServerNonce:          resPQ.ServerNonce,
        P:                    p,
        Q:                    q,
        PublicKeyFingerprint: resPQ.ServerPublicKeyFingerprints[0],
        EncryptedData:        encrypted,
    }
    
    serverDH, err := c.reqDHParams(reqDH)
    if err != nil {
        return nil, err
    }
    
    // Step 3: Decrypt server_DH_params
    tmpKey, tmpIV := generateTempKeys(newNonce, serverDH.ServerNonce)
    decrypted := decryptAES(serverDH.EncryptedAnswer, tmpKey, tmpIV)
    serverDHInner := &ServerDHInnerData{}
    serverDHInner.Decode(decrypted)
    
    // Verify DH params
    if err := verifyDHParams(serverDHInner.G, serverDHInner.DHPrime); err != nil {
        return nil, err
    }
    
    // Step 4: set_client_DH_params
    b := generateRandom(256)
    gb := modExp(serverDHInner.G, b, serverDHInner.DHPrime)
    authKey := modExp(serverDHInner.GA, b, serverDHInner.DHPrime)
    
    clientDHInner := &ClientDHInnerData{
        Nonce:       resPQ.Nonce,
        ServerNonce: resPQ.ServerNonce,
        GB:          gb,
    }
    
    encryptedClientDH := encryptAES(clientDHInner.Encode(), tmpKey, tmpIV)
    
    setDH := &SetClientDHParams{
        Nonce:         resPQ.Nonce,
        ServerNonce:   resPQ.ServerNonce,
        EncryptedData: encryptedClientDH,
    }
    
    dhGenOk, err := c.setClientDHParams(setDH)
    if err != nil {
        return nil, err
    }
    
    // Step 5: Verify
    expectedHash := sha1.Sum(append(newNonce, authKey...))
    if !bytes.Equal(dhGenOk.NewNonceHash1, expectedHash[4:20]) {
        return nil, errors.New("handshake verification failed")
    }
    
    return authKey, nil
}
```


path: references/message-format.md
content: # MTProto Message Format

Binary protocol structure for encrypted messages.

## Encrypted Message Structure

```
┌─────────────────────────────────────────────────────────┐
│ Auth Key ID     │ 8 bytes  │ SHA1(authKey)[96:128]      │
├─────────────────────────────────────────────────────────┤
│ Message Key     │ 16 bytes │ SHA256(plaintext)[8:24]    │
├─────────────────────────────────────────────────────────┤
│ Encrypted Data  │ Variable │ AES-256-IGE encrypted      │
└─────────────────────────────────────────────────────────┘

Total: 24 bytes header + encrypted payload
```

## Auth Key ID

⚠️ **CRITICAL**: Auth Key ID is **8 bytes** (64 bits), not 4 bytes.

```go
func calcAuthKeyID(authKey []byte) []byte {
    // SHA1(auth_key)[96:128] bits = [12:16] bytes = 4 bytes
    // But MTProto expects 8 bytes, so we pad or use full 64-bit
    
    // CORRECT: Use 64-bit from SHA1
    hash := sha1.Sum(authKey)
    authKeyID := make([]byte, 8)
    copy(authKeyID[0:4], hash[12:16])  // Lower 32 bits
    // Upper 32 bits can be zero or derived from additional hash
    return authKeyID
}

// Alternative: Use SHA256 and take first 8 bytes
func calcAuthKeyIDSecure(authKey []byte) []byte {
    hash := sha256.Sum256(authKey)
    return hash[0:8]  // First 8 bytes
}
```

**Purpose**: 
- Identifies which auth_key was used
- Server can quickly find the correct key
- **MUST be 8 bytes** in the encrypted message header

## Message Key

```go
func calcMsgKey(plaintext, authKey []byte) []byte {
    // SHA256(plaintext + auth_key)[8:24]
    data := append(plaintext, authKey...)
    hash := sha256.Sum256(data)
    return hash[8:24]  // Middle 16 bytes
}
```

**Purpose**:
- Message integrity verification
- IV generation for AES-IGE
- Duplicate message detection

## Plaintext Structure

### Layout

```
┌─────────────────────────────────────────────────────────┐
│ Salt            │ 8 bytes  │ Server salt                │
├─────────────────────────────────────────────────────────┤
│ Session ID      │ 8 bytes  │ Unique session identifier  │
├─────────────────────────────────────────────────────────┤
│ Message ID      │ 8 bytes  │ Timestamp + sequence       │
├─────────────────────────────────────────────────────────┤
│ Sequence Number │ 4 bytes  │ Packet sequence            │
├─────────────────────────────────────────────────────────┤
│ Length          │ 4 bytes  │ Message body length        │
├─────────────────────────────────────────────────────────┤
│ Message Body    │ Variable │ TL-serialized data         │
├─────────────────────────────────────────────────────────┤
│ Padding         │ 0-15 B   │ Random padding             │
└─────────────────────────────────────────────────────────┘
```

### Salt (8 bytes)

```go
type Salt struct {
    Value int64
}

// Server provides salt during handshake
// Client must include current salt in every message
// Salt changes periodically (every 1 hour typically)
```

**Anti-replay protection**:
- Server validates salt is recent
- Old salts are rejected

### Session ID (8 bytes)

```go
sessionID := generateRandom(8)  // 64-bit random
```

**Purpose**:
- Identifies connection session
- Multiple sessions per auth_key
- Session-specific state

### Message ID (8 bytes)

```go
// Structure (64 bits):
// | 32-bit timestamp (seconds) | 1-bit direction | 31-bit sequence |
// Or alternatively:
// | 32-bit timestamp | 32-bit counter |

func generateMessageID() int64 {
    now := time.Now().Unix()
    // Use high bits for timestamp
    // Low bits for sequence counter
    return (now << 32) | atomic.AddInt32(&counter, 1)
}
```

**Rules**:
- Must be strictly increasing per session
- Time-based component prevents replay
- Direction bit distinguishes client/server

### Sequence Number (4 bytes)

```go
type SeqNo struct {
    Value int32
}

// Even numbers: container or content-related
// Odd numbers:  RPC call
```

**Even seqno**:
- 0, 2, 4, 6... for containers
- Messages requiring acknowledgment

**Odd seqno**:
- 1, 3, 5, 7... for RPC calls
- Methods requiring response

### Length (4 bytes)

```go
length := uint32(len(messageBody)) / 4  // Length in 4-byte words
```

**Note**: Length is in 4-byte words, not bytes!

### Message Body

TL-serialized object:
```
┌─────────────────────────────────────────────────────────┐
│ Constructor ID  │ 4 bytes  │ Object type identifier     │
├─────────────────────────────────────────────────────────┤
│ Fields          │ Variable │ Serialized field values    │
└─────────────────────────────────────────────────────────┘
```

Example:
```go
// messages.sendMessage#520c3870 flags:# ... = Updates;
// Constructor ID: 0x520c3870

msg := &mtproto.TLMessagesSendMessage{
    Peer:     peer,
    Message:  "Hello",
    RandomId: rand.Int63(),
}

body := msg.Encode()
// [0x70, 0x38, 0x0c, 0x52, ...]  // Little-endian constructor ID
```

### Padding (0-15 bytes)

```go
padding := make([]byte, rand.Intn(16))  // 0-15 random bytes
rand.Read(padding)
```

**Purpose**:
- Obscure message length
- Prevent traffic analysis
- Must be stripped during decryption

## Container Messages

For batching multiple messages:

```go
type MsgContainer struct {
    Messages []*Message
}

// Structure:
// 0x73f1f8dc (container constructor)
// count: int32
// messages: [Message, Message, ...]

type Message struct {
    MsgID    int64
    SeqNo    int32
    Bytes    int32  // Body length in bytes
    Body     []byte // Serialized TL object
}
```

## Go Implementation

```go
func constructPlaintext(salt, sessionID int64, seqNo int32, body []byte) []byte {
    // Calculate padding
    baseSize := 8 + 8 + 8 + 4 + 4 + len(body)  // Without padding
    paddingSize := (16 - (baseSize % 16)) % 16   // Align to 16 bytes
    padding := generateRandom(paddingSize)
    
    // Build plaintext
    plaintext := make([]byte, 0, baseSize+paddingSize)
    
    // Salt
    plaintext = append(plaintext, int64ToBytes(salt)...)
    
    // Session ID
    plaintext = append(plaintext, int64ToBytes(sessionID)...)
    
    // Message ID
    msgID := generateMessageID()
    plaintext = append(plaintext, int64ToBytes(msgID)...)
    
    // Sequence Number
    plaintext = append(plaintext, int32ToBytes(seqNo)...)
    
    // Length (in 4-byte words)
    length := uint32(len(body) + len(padding)) / 4
    plaintext = append(plaintext, int32ToBytes(int32(length))...)
    
    // Body
    plaintext = append(plaintext, body...)
    
    // Padding
    plaintext = append(plaintext, padding...)
    
    return plaintext
}

func parsePlaintext(plaintext []byte) (*MessageInfo, []byte, error) {
    if len(plaintext) < 32 {
        return nil, nil, errors.New("plaintext too short")
    }
    
    msg := &MessageInfo{}
    
    // Salt
    msg.Salt = bytesToInt64(plaintext[0:8])
    
    // Session ID
    msg.SessionID = bytesToInt64(plaintext[8:16])
    
    // Message ID
    msg.MessageID = bytesToInt64(plaintext[16:24])
    
    // Sequence Number
    msg.SeqNo = bytesToInt32(plaintext[24:28])
    
    // Length
    length := bytesToInt32(plaintext[28:32]) * 4  // Convert to bytes
    
    // Body + Padding
    if int(32+length) > len(plaintext) {
        return nil, nil, errors.New("invalid length")
    }
    
    bodyWithPadding := plaintext[32 : 32+length]
    
    // Strip padding (find actual body length from TL)
    body := stripPadding(bodyWithPadding)
    
    return msg, body, nil
}
```

## Validation Rules

### Server-Side Validation

```go
func validateMessage(msg *MessageInfo) error {
    // 1. Salt must be recent
    if !isValidSalt(msg.Salt) {
        return errors.New("invalid salt")
    }
    
    // 2. Message ID must increase
    if msg.MessageID <= lastMessageID {
        return errors.New("message ID too old")
    }
    
    // 3. Time window (±5 minutes)
    msgTime := msg.MessageID >> 32
    now := time.Now().Unix()
    if abs(now-msgTime) > 300 {
        return errors.New("message outside time window")
    }
    
    // 4. Sequence number parity
    if msg.SeqNo%2 != expectedParity {
        return errors.New("invalid sequence number parity")
    }
    
    return nil
}
```

## Message Acknowledgment

### ACK Message

```go
// msgs_ack#62d6b459 msg_ids:Vector<long> = MsgsAck;

ack := &mtproto.TLMsgsAck{
    MsgIds: []int64{msgID1, msgID2, msgID3},
}
```

### RPC Result

```go
// rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult;

result := &mtproto.TLRpcResult{
    ReqMsgId: requestMsgID,
    Result:   response,
}
```

## Common Issues

### 1. Alignment Errors

```go
// Wrong - not aligned to 16 bytes
plaintext := make([]byte, 30)  // Not divisible by 16!

// Correct
plaintext := make([]byte, 32)  // Divisible by 16
```

### 2. Wrong Length Encoding

```go
// Wrong - length in bytes
length := uint32(len(body))

// Correct - length in 4-byte words
length := uint32(len(body)) / 4
```

### 3. Session Confusion

```go
// Wrong - using wrong session ID
msg.SessionID = session1.ID

// But encrypting with session2's auth key!
ciphertext := encrypt(msg, session2.AuthKey)

// Correct - always match session and auth key
```


path: references/security-notice.md
content: # Security Notice & Corrections

⚠️ **CRITICAL ISSUES IDENTIFIED IN v1.0.0**

This document lists known issues and their corrections for production use.

## Issue 1: Auth Key ID Size Mismatch

### Problem
Original code returned 4 bytes, but MTProto protocol requires 8 bytes.

### Incorrect (v1.0.0)
```go
func calcAuthKeyID(authKey []byte) []byte {
    hash := sha1.Sum(authKey)
    return hash[12:16]  // ❌ Only 4 bytes!
}
```

### Correct
```go
func calcAuthKeyID(authKey []byte) []byte {
    hash := sha1.Sum(authKey)
    // Take lower 64 bits from SHA1
    authKeyID := make([]byte, 8)
    copy(authKeyID, hash[12:20])  // ✅ 8 bytes
    return authKeyID
}
```

## Issue 2: Using math/rand Instead of crypto/rand

### Problem
math/rand is NOT cryptographically secure and MUST NOT be used for:
- Nonce generation
- Key material
- Padding
- Session IDs

### Incorrect
```go
import "math/rand"

nonce := make([]byte, 16)
rand.Read(nonce)  // ❌ Not secure!
```

### Correct
```go
import "crypto/rand"

nonce := make([]byte, 16)
_, err := rand.Read(nonce)  // ✅ Cryptographically secure
if err != nil {
    panic(err)
}
```

## Issue 3: Padding Generation

### Incorrect
```go
padding := make([]byte, rand.Intn(16))  // ❌ math/rand
rand.Read(padding)
```

### Correct
```go
import "crypto/rand"

paddingSize := 0
binary.Read(rand.Reader, binary.LittleEndian, &paddingSize)
paddingSize = paddingSize % 16

padding := make([]byte, paddingSize)
_, err := rand.Read(padding)
if err != nil {
    panic(err)
}
```

## Issue 4: Off-By-One Errors in Message Format

### Problem
Confusion between length-in-words vs length-in-bytes.

### Header Structure (CORRECT)
```
Auth Key ID:     8 bytes
Message Key:     16 bytes
Encrypted Data:  variable (must be multiple of 16)

Total header: 24 bytes
```

### Plaintext Structure (CORRECT)
```
Salt:            8 bytes
Session ID:      8 bytes
Message ID:      8 bytes
Sequence Number: 4 bytes
Length:          4 bytes  (in 4-byte words!)
Message Body:    variable
Padding:         0-15 bytes (to align to 16)
```

### Important: Length Field
```go
// Length is in 4-byte words, not bytes!
lengthInWords := uint32(len(bodyWithPadding)) / 4
```

## Issue 5: Session ID Generation

### Incorrect
```go
sessionID := rand.Int63()  // ❌ math/rand
```

### Correct
```go
sessionIDBytes := make([]byte, 8)
_, err := rand.Read(sessionIDBytes)
if err != nil {
    panic(err)
}
sessionID := int64(binary.LittleEndian.Uint64(sessionIDBytes))
```

## Issue 6: DH Private Key Generation

### Incorrect
```go
b := make([]byte, 256)
rand.Read(b)  // ❌ math/rand
```

### Correct
```go
import (
    "crypto/rand"
    "math/big"
)

// Generate private key in range [1, p-1]
b, err := rand.Int(rand.Reader, new(big.Int).Sub(p, big.NewInt(1)))
if err != nil {
    panic(err)
}
b.Add(b, big.NewInt(1))  // Ensure b >= 1
```

## Production Checklist

Before deploying to production:

- [ ] Replace ALL `math/rand` with `crypto/rand`
- [ ] Verify Auth Key ID is 8 bytes everywhere
- [ ] Test against Telegram's official reference vectors
- [ ] Validate all buffer sizes match protocol spec
- [ ] Run constant-time comparison for MAC verification
- [ ] Implement proper error handling (no panic in production)
- [ ] Add comprehensive logging (without leaking keys)
- [ ] Have security audit by experienced cryptographer

## Testing Against Official Vectors

```go
// Official test vectors from Telegram
func TestOfficialVectors(t *testing.T) {
    // Auth Key ID test
    authKey := hexToBytes("...")
    expectedID := hexToBytes("...")
    
    actualID := calcAuthKeyID(authKey)
    if !bytes.Equal(actualID, expectedID) {
        t.Errorf("Auth Key ID mismatch")
    }
    
    // Message Key test
    plaintext := hexToBytes("...")
    expectedMsgKey := hexToBytes("...")
    
    actualMsgKey := calcMsgKey(plaintext, authKey)
    if !bytes.Equal(actualMsgKey, expectedMsgKey) {
        t.Errorf("Message Key mismatch")
    }
}
```

## References

- [Telegram MTProto Docs](https://core.telegram.org/mtproto)
- [Go crypto/rand Package](https://golang.org/pkg/crypto/rand/)
- [Constant Time Comparison](https://pkg.go.dev/crypto/subtle#ConstantTimeCompare)

## Disclaimer

This skill is for educational purposes. The code examples illustrate protocol concepts but may contain errors. ALWAYS:

1. Verify against official Telegram documentation
2. Test with official reference implementations
3. Have security experts review before production use
4. Never use example code directly in production

Report issues: https://clawhub.ai/skills/mtproto-2-0


path: references/tl-language.md
content: # TL Language (Type Language)

Telegram's binary serialization format.

## Overview

TL is a compact, type-safe binary serialization format used by Telegram.

**Key Features**:
- Compact binary encoding
- Strong typing
- Backward compatible
- Self-describing (constructor IDs)

## Basic Concepts

### Constructor ID

Every TL type has a unique 32-bit identifier:

```
0x1cb5c415  // Vector constructor
0x73f1f8dc  // MsgContainer constructor
```

### Type Declaration

```tl
// Simple type
int  // 32-bit signed integer
long // 64-bit signed integer
string  // UTF-8 string with length prefix
bytes   // Raw bytes with length prefix
bool    // Boolean (0xbc799737 or 0x997275b5)
double  // 64-bit floating point
```

## Composite Types

### Structures

```tl
// Constructor with fields
user#938458c1 id:long first_name:string last_name:string = User;

// Breakdown:
// user          - Name (for documentation)
// #938458c1     - Constructor ID (hex)
// id:long       - Field: name:type
// first_name:string
// last_name:string
// = User        - Result type
```

### Methods

```tl
// RPC method declaration
checkPhone#6fe51dfb phone_number:string = Bool;

// Parameters:
// phone_number:string - Input parameter
// = Bool              - Return type
```

## Serialization Examples

### Integer

```go
// int32 - Little-endian
func encodeInt32(v int32) []byte {
    return []byte{
        byte(v),
        byte(v >> 8),
        byte(v >> 16),
        byte(v >> 24),
    }
}

// Example: 0x12345678
// [0x78, 0x56, 0x34, 0x12]
```

### Long (int64)

```go
// int64 - Little-endian
func encodeInt64(v int64) []byte {
    return []byte{
        byte(v),
        byte(v >> 8),
        byte(v >> 16),
        byte(v >> 24),
        byte(v >> 32),
        byte(v >> 40),
        byte(v >> 48),
        byte(v >> 56),
    }
}
```

### String

```go
// String format:
// [length:1-3 bytes][data:length bytes]
// Length encoded as variable-length integer

func encodeString(s string) []byte {
    data := []byte(s)
    length := len(data)
    
    if length <= 253 {
        // Single byte length
        return append([]byte{byte(length)}, data...)
    }
    else {
        // 3-byte length (0xfe + 24-bit little-endian)
        result := []byte{0xfe, byte(length), byte(length >> 8), byte(length >> 16)}
        return append(result, data...)
    }
}

// Example: "Hi"
// [0x02, 0x48, 0x69]
```

### Bytes

Same encoding as string.

### Bool

```go
// True:  0x997275b5
// False: 0xbc799737

var (
    BoolTrue  = []byte{0xb5, 0x75, 0x72, 0x99}
    BoolFalse = []byte{0x37, 0x97, 0x72, 0xbc}
)
```

### Vector (Array)

```go
// Vector format:
// 0x1cb5c415 [count:int32] [items...]

func encodeVector(items [][]byte) []byte {
    result := []byte{0x15, 0xc4, 0xb5, 0x1c}  // Vector constructor
    result = append(result, encodeInt32(int32(len(items)))...)
    
    for _, item := range items {
        result = append(result, item...)
    }
    
    return result
}
```

## Complete Example

```go
// TL Definition:
// user#938458c1 id:long first_name:string last_name:string = User;

type TLUser struct {
    ID        int64
    FirstName string
    LastName  string
}

func (u *TLUser) Encode() []byte {
    // Constructor ID
    result := []byte{0xc1, 0x58, 0x84, 0x93}
    
    // ID (long)
    result = append(result, encodeInt64(u.ID)...)
    
    // FirstName (string)
    result = append(result, encodeString(u.FirstName)...)
    
    // LastName (string)
    result = append(result, encodeString(u.LastName)...)
    
    return result
}

func (u *TLUser) Decode(data []byte) error {
    if len(data) < 4 {
        return errors.New("data too short")
    }
    
    // Verify constructor
    if binary.LittleEndian.Uint32(data[0:4]) != 0x938458c1 {
        return errors.New("wrong constructor")
    }
    
    offset := 4
    
    // ID
    u.ID = int64(binary.LittleEndian.Uint64(data[offset:offset+8]))
    offset += 8
    
    // FirstName
    firstName, n := decodeString(data[offset:])
    u.FirstName = firstName
    offset += n
    
    // LastName
    lastName, n := decodeString(data[offset:])
    u.LastName = lastName
    
    return nil
}
```

## Flags System

### Optional Fields

```tl
// Flags indicate optional fields
user#8d4d2247 flags:# id:long 
    first_name:flags.0?string 
    last_name:flags.1?string 
    username:flags.2?string = User;

// flags.# - 32-bit bitfield
// flags.N?type - Field present if bit N is set
```

```go
type TLUserFlags struct {
    Flags     uint32
    ID        int64
    FirstName *string  // flags.0
    LastName  *string  // flags.1
    Username  *string  // flags.2
}

func (u *TLUserFlags) Encode() []byte {
    // Calculate flags
    var flags uint32
    if u.FirstName != nil {
        flags |= 1 << 0
    }
    if u.LastName != nil {
        flags |= 1 << 1
    }
    if u.Username != nil {
        flags |= 1 << 2
    }
    
    // Encode...
}
```

## Go Implementation

### Code Generation

Use mtprotoc to generate Go code from .tl files:

```bash
# Generate Go code
mtprotoc -I schema/ -o generated/ schema/mtproto.tl

# Output:
# generated/user.go
# generated/messages.go
# ...
```

### Manual Implementation

```go
package mtproto

//go:generate mtprotoc -I ../schema -o . ../schema/mtproto.tl

type TLObject interface {
    Encode() []byte
    Decode([]byte) error
    ConstructorID() uint32
}
```

## Common Patterns

### Polymorphism

```tl
// Abstract type
InputPeer = InputPeerEmpty | InputPeerSelf | InputPeerChat | InputPeerUser;

// Constructors
inputPeerEmpty#7f3b18ea = InputPeer;
inputPeerSelf#7da07ec9 = InputPeer;
inputPeerChat#179be863 chat_id:long = InputPeer;
inputPeerUser#7b8e7de6 user_id:long access_hash:long = InputPeer;
```

```go
// Go implementation using interface
type InputPeer interface {
    TLObject
    isInputPeer()
}

type TLInputPeerChat struct {
    ChatID int64
}

func (*TLInputPeerChat) isInputPeer() {}
```

### Error Handling

```tl
// rpc_error#2144ca19 error_code:int error_message:string = RpcError;
```

```go
func decodeResponse(data []byte) (TLObject, error) {
    constructor := binary.LittleEndian.Uint32(data[0:4])
    
    switch constructor {
    case 0x2144ca19:  // rpc_error
        err := &TLRpcError{}
        err.Decode(data)
        return nil, fmt.Errorf("RPC error %d: %s", err.ErrorCode, err.ErrorMessage)
        
    // ... other cases
    }
}
```

## Schema Resources

### Official Schemas

- [MTProto Schema](https://core.telegram.org/schema)
- [TL Language Spec](https://core.telegram.org/mtproto/TL)

### Layer Versions

| Layer | Features |
|-------|----------|
| 165   | Current stable |
| 166+  | Newer features |

## Best Practices

1. **Always check constructor IDs** before decoding
2. **Handle unknown constructors** gracefully
3. **Use generated code** instead of manual encoding
4. **Validate data length** before accessing offsets
5. **Handle optional fields** using flags system


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)