Skip to main content

layer_mtproto/
authentication.rs

1// Copyright (c) Ankit Chaubey <ankitchaubey.dev@gmail.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4// NOTE:
5// The "Layer" project is no longer maintained or supported.
6// Its original purpose for personal SDK/APK experimentation and learning
7// has been fulfilled.
8//
9// Please use Ferogram instead:
10// https://github.com/ankit-chaubey/ferogram
11// Ferogram will receive future updates and development, although progress
12// may be slower.
13//
14// Ferogram is an async Telegram MTProto client library written in Rust.
15// Its implementation follows the behaviour of the official Telegram clients,
16// particularly Telegram Desktop and TDLib, and aims to provide a clean and
17// modern async interface for building Telegram clients and tools.
18
19//! Sans-IO MTProto authorization key generation.
20//!
21//! # Flow
22//!
23//! ```text
24//! let (req, s1) = authentication::step1()?;
25//! // send req, receive resp (ResPQ)
26//! let (req, s2) = authentication::step2(s1, resp, dc_id)?;
27//! // send req, receive resp (ServerDhParams)
28//! let (req, s3) = authentication::step3(s2, resp)?;
29//! // send req, receive resp (SetClientDhParamsAnswer)
30//! let result = authentication::finish(s3, resp)?;
31//! // on FinishResult::Done(d): d.auth_key is ready
32//! // on FinishResult::Retry{..}: call retry_step3() + finish() up to 5 times
33//! ```
34
35use std::fmt;
36use std::time::{SystemTime, UNIX_EPOCH};
37
38use layer_crypto::{AuthKey, aes, check_p_and_g, factorize, generate_key_data_from_nonce, rsa};
39use layer_tl_types::{Cursor, Deserializable, Serializable};
40use num_bigint::BigUint;
41use sha1::{Digest, Sha1};
42
43// ---------------------------------------------------------------------------
44// Manual TL serialization helper for PQInnerDataDc
45//
46// Constructor: p_q_inner_data_dc#a9f55f95
47//   pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int
48//
49// TL "string" (bytes) encoding: if len < 254 → [len_byte, data..., 0-pad to 4-align],
50// else [0xfe, len_lo, len_mid, len_hi, data..., 0-pad to 4-align].
51// ---------------------------------------------------------------------------
52fn tl_serialize_bytes(v: &[u8]) -> Vec<u8> {
53    let len = v.len();
54    let mut out = Vec::new();
55    if len < 254 {
56        out.push(len as u8);
57        out.extend_from_slice(v);
58        let total = 1 + len;
59        let pad = (4 - total % 4) % 4;
60        out.extend(std::iter::repeat_n(0u8, pad));
61    } else {
62        out.push(0xfe);
63        out.push((len & 0xff) as u8);
64        out.push(((len >> 8) & 0xff) as u8);
65        out.push(((len >> 16) & 0xff) as u8);
66        out.extend_from_slice(v);
67        let total = 4 + len;
68        let pad = (4 - total % 4) % 4;
69        out.extend(std::iter::repeat_n(0u8, pad));
70    }
71    out
72}
73
74/// Serialize a `p_q_inner_data_dc` (constructor 0xa9f55f95) from raw fields.
75/// This is needed because the generated TL bindings only expose `PQInnerData`
76/// (legacy, no DC id) which Telegram rejects for non-DC2 connections.
77fn serialize_pq_inner_data_dc(
78    pq: &[u8],
79    p: &[u8],
80    q: &[u8],
81    nonce: &[u8; 16],
82    server_nonce: &[u8; 16],
83    new_nonce: &[u8; 32],
84    dc_id: i32,
85) -> Vec<u8> {
86    let mut out = Vec::new();
87    // Constructor id (little-endian)
88    out.extend_from_slice(&0xa9f55f95_u32.to_le_bytes());
89    out.extend(tl_serialize_bytes(pq));
90    out.extend(tl_serialize_bytes(p));
91    out.extend(tl_serialize_bytes(q));
92    out.extend_from_slice(nonce);
93    out.extend_from_slice(server_nonce);
94    out.extend_from_slice(new_nonce);
95    out.extend_from_slice(&dc_id.to_le_bytes());
96    out
97}
98
99// Error
100
101/// Errors that can occur during auth key generation.
102#[allow(missing_docs)]
103#[derive(Clone, Debug, PartialEq)]
104pub enum Error {
105    InvalidNonce {
106        got: [u8; 16],
107        expected: [u8; 16],
108    },
109    InvalidPqSize {
110        size: usize,
111    },
112    UnknownFingerprints {
113        fingerprints: Vec<i64>,
114    },
115    DhParamsFail,
116    InvalidServerNonce {
117        got: [u8; 16],
118        expected: [u8; 16],
119    },
120    EncryptedResponseNotPadded {
121        len: usize,
122    },
123    InvalidDhInnerData {
124        error: layer_tl_types::deserialize::Error,
125    },
126    InvalidDhPrime {
127        source: layer_crypto::DhError,
128    },
129    GParameterOutOfRange {
130        value: BigUint,
131        low: BigUint,
132        high: BigUint,
133    },
134    DhGenRetry,
135    DhGenFail,
136    InvalidAnswerHash {
137        got: [u8; 20],
138        expected: [u8; 20],
139    },
140    InvalidNewNonceHash {
141        got: [u8; 16],
142        expected: [u8; 16],
143    },
144}
145
146impl std::error::Error for Error {}
147
148impl fmt::Display for Error {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        match self {
151            Self::InvalidNonce { got, expected } => {
152                write!(f, "nonce mismatch: got {got:?}, expected {expected:?}")
153            }
154            Self::InvalidPqSize { size } => write!(f, "pq size {size} invalid (expected 8)"),
155            Self::UnknownFingerprints { fingerprints } => {
156                write!(f, "no known fingerprint in {fingerprints:?}")
157            }
158            Self::DhParamsFail => write!(f, "server returned DH params failure"),
159            Self::InvalidServerNonce { got, expected } => write!(
160                f,
161                "server_nonce mismatch: got {got:?}, expected {expected:?}"
162            ),
163            Self::EncryptedResponseNotPadded { len } => {
164                write!(f, "encrypted answer len {len} is not 16-byte aligned")
165            }
166            Self::InvalidDhInnerData { error } => {
167                write!(f, "DH inner data deserialization error: {error}")
168            }
169            Self::InvalidDhPrime { source } => {
170                write!(f, "DH prime/generator validation failed: {source}")
171            }
172            Self::GParameterOutOfRange { value, low, high } => {
173                write!(f, "g={value} not in range ({low}, {high})")
174            }
175            Self::DhGenRetry => write!(f, "DH gen retry requested"),
176            Self::DhGenFail => write!(f, "DH gen failed"),
177            Self::InvalidAnswerHash { got, expected } => write!(
178                f,
179                "answer hash mismatch: got {got:?}, expected {expected:?}"
180            ),
181            Self::InvalidNewNonceHash { got, expected } => write!(
182                f,
183                "new nonce hash mismatch: got {got:?}, expected {expected:?}"
184            ),
185        }
186    }
187}
188
189// Step state
190
191/// State after step 1.
192pub struct Step1 {
193    nonce: [u8; 16],
194}
195
196/// State after step 2.
197#[derive(Clone)]
198pub struct Step2 {
199    nonce: [u8; 16],
200    server_nonce: [u8; 16],
201    new_nonce: [u8; 32],
202}
203
204/// Pre-processed server DH parameters retained so that step 3 can be
205/// repeated on `dh_gen_retry` without having to re-decrypt the server response.
206#[derive(Clone)]
207pub struct DhParamsForRetry {
208    /// Server-supplied DH prime (big-endian bytes).
209    pub dh_prime: Vec<u8>,
210    /// DH generator `g`.
211    pub g: u32,
212    /// Server's public DH value `g_a` (big-endian bytes).
213    pub g_a: Vec<u8>,
214    /// Server's reported Unix timestamp (used to compute `time_offset`).
215    pub server_time: i32,
216    /// AES key derived from nonces for this session's IGE encryption.
217    pub aes_key: [u8; 32],
218    /// AES IV derived from nonces for this session's IGE encryption.
219    pub aes_iv: [u8; 32],
220}
221
222/// State after step 3.
223pub struct Step3 {
224    nonce: [u8; 16],
225    server_nonce: [u8; 16],
226    new_nonce: [u8; 32],
227    time_offset: i32,
228    /// Auth key candidate bytes (needed to derive `auth_key_aux_hash` on retry).
229    auth_key: [u8; 256],
230    /// The processed DH parameters stored so `retry_step3` can re-derive g_b
231    /// without re-parsing the encrypted server response.
232    pub dh_params: DhParamsForRetry,
233}
234
235/// Result of [`finish`] either the handshake is done, or the server wants us
236/// to retry step 3 with the `auth_key_aux_hash` as `retry_id`.
237pub enum FinishResult {
238    /// Handshake complete.
239    Done(Finished),
240    /// Server sent `dh_gen_retry`.  Call [`retry_step3`] with the returned
241    /// `retry_id` and the stored [`DhParamsForRetry`] from the previous Step3.
242    Retry {
243        /// The `auth_key_aux_hash` to embed as `retry_id` in the next attempt.
244        retry_id: i64,
245        /// DH parameters to feed back into [`retry_step3`].
246        dh_params: DhParamsForRetry,
247        /// Client nonce from the original step 1.
248        nonce: [u8; 16],
249        /// Server nonce from the ResPQ response.
250        server_nonce: [u8; 16],
251        /// Fresh nonce generated in step 2.
252        new_nonce: [u8; 32],
253    },
254}
255
256/// The final output of a successful auth key handshake.
257#[derive(Clone, Debug, PartialEq)]
258pub struct Finished {
259    /// The 256-byte Telegram authorization key.
260    pub auth_key: [u8; 256],
261    /// Clock skew in seconds relative to the server.
262    pub time_offset: i32,
263    /// Initial server salt.
264    pub first_salt: i64,
265}
266
267// Step 1: req_pq_multi
268
269/// Generate a `req_pq_multi` request. Returns the request + opaque state.
270pub fn step1() -> Result<(layer_tl_types::functions::ReqPqMulti, Step1), Error> {
271    let mut buf = [0u8; 16];
272    getrandom::getrandom(&mut buf).expect("getrandom");
273    do_step1(&buf)
274}
275
276fn do_step1(random: &[u8; 16]) -> Result<(layer_tl_types::functions::ReqPqMulti, Step1), Error> {
277    let nonce = *random;
278    Ok((
279        layer_tl_types::functions::ReqPqMulti { nonce },
280        Step1 { nonce },
281    ))
282}
283
284// Step 2: req_DH_params
285
286/// Process `ResPQ` and generate `req_DH_params`.
287///
288/// `dc_id` must be the numerical DC id of the server we are connecting to
289/// (e.g. 1 … 5).  It is embedded in the `PQInnerDataDc` payload so that
290/// Telegram can reject misrouted handshakes on non-DC2 endpoints.
291pub fn step2(
292    data: Step1,
293    response: layer_tl_types::enums::ResPq,
294    dc_id: i32,
295) -> Result<(layer_tl_types::functions::ReqDhParams, Step2), Error> {
296    let mut rnd = [0u8; 256];
297    getrandom::getrandom(&mut rnd).expect("getrandom");
298    do_step2(data, response, &rnd, dc_id)
299}
300
301fn do_step2(
302    data: Step1,
303    response: layer_tl_types::enums::ResPq,
304    random: &[u8; 256],
305    dc_id: i32,
306) -> Result<(layer_tl_types::functions::ReqDhParams, Step2), Error> {
307    let Step1 { nonce } = data;
308
309    // ResPq has a single constructor: resPQ → variant ResPq
310    let layer_tl_types::enums::ResPq::ResPq(res_pq) = response;
311
312    check_nonce(&res_pq.nonce, &nonce)?;
313
314    if res_pq.pq.len() != 8 {
315        return Err(Error::InvalidPqSize {
316            size: res_pq.pq.len(),
317        });
318    }
319
320    let pq = u64::from_be_bytes(res_pq.pq.as_slice().try_into().unwrap());
321    let (p, q) = factorize(pq);
322
323    let mut new_nonce = [0u8; 32];
324    new_nonce.copy_from_slice(&random[..32]);
325
326    // random[32..256] is 224 bytes for RSA padding
327    let rnd224: &[u8; 224] = random[32..].try_into().unwrap();
328
329    fn trim_be(v: u64) -> Vec<u8> {
330        let b = v.to_be_bytes();
331        let skip = b.iter().position(|&x| x != 0).unwrap_or(7);
332        b[skip..].to_vec()
333    }
334
335    let p_bytes = trim_be(p);
336    let q_bytes = trim_be(q);
337
338    // Serialize PQInnerDataDc (constructor 0xa9f55f95) manually.
339    // The legacy PQInnerData constructor (#83c95aec, no dc field) is rejected
340    // by Telegram servers for connections to non-DC2 endpoints.
341    let pq_inner = serialize_pq_inner_data_dc(
342        &pq.to_be_bytes(),
343        &p_bytes,
344        &q_bytes,
345        &nonce,
346        &res_pq.server_nonce,
347        &new_nonce,
348        dc_id,
349    );
350
351    let fingerprint = res_pq
352        .server_public_key_fingerprints
353        .iter()
354        .copied()
355        .find(|&fp| key_for_fingerprint(fp).is_some())
356        .ok_or_else(|| Error::UnknownFingerprints {
357            fingerprints: res_pq.server_public_key_fingerprints.clone(),
358        })?;
359
360    let key = key_for_fingerprint(fingerprint).unwrap();
361    let ciphertext = rsa::encrypt_hashed(&pq_inner, &key, rnd224);
362
363    Ok((
364        layer_tl_types::functions::ReqDhParams {
365            nonce,
366            server_nonce: res_pq.server_nonce,
367            p: p_bytes,
368            q: q_bytes,
369            public_key_fingerprint: fingerprint,
370            encrypted_data: ciphertext,
371        },
372        Step2 {
373            nonce,
374            server_nonce: res_pq.server_nonce,
375            new_nonce,
376        },
377    ))
378}
379
380// Step 3: set_client_DH_params
381
382/// Process `ServerDhParams` into a reusable [`DhParamsForRetry`] + send the
383/// first `set_client_DH_params` request.
384///
385/// `retry_id` should be 0 on the first call, or `auth_key_aux_hash` (returned
386/// by [`finish`] as [`FinishResult::Retry`]) on subsequent attempts.
387pub fn step3(
388    data: Step2,
389    response: layer_tl_types::enums::ServerDhParams,
390) -> Result<(layer_tl_types::functions::SetClientDhParams, Step3), Error> {
391    let mut rnd = [0u8; 272];
392    getrandom::getrandom(&mut rnd).expect("getrandom");
393    let now = SystemTime::now()
394        .duration_since(UNIX_EPOCH)
395        .unwrap()
396        .as_secs() as i32;
397    do_step3(data, response, &rnd, now, 0)
398}
399
400/// Re-run the client DH params generation after a `dh_gen_retry` response.
401/// Feed the `dh_params`, `nonce`, `server_nonce`, `new_nonce` from
402/// [`FinishResult::Retry`] and the `retry_id` (= `auth_key_aux_hash`).
403pub fn retry_step3(
404    dh_params: &DhParamsForRetry,
405    nonce: [u8; 16],
406    server_nonce: [u8; 16],
407    new_nonce: [u8; 32],
408    retry_id: i64,
409) -> Result<(layer_tl_types::functions::SetClientDhParams, Step3), Error> {
410    let mut rnd = [0u8; 272];
411    getrandom::getrandom(&mut rnd).expect("getrandom");
412    let now = SystemTime::now()
413        .duration_since(UNIX_EPOCH)
414        .unwrap()
415        .as_secs() as i32;
416    generate_client_dh_params(
417        dh_params,
418        nonce,
419        server_nonce,
420        new_nonce,
421        retry_id,
422        &rnd,
423        now,
424    )
425}
426
427fn generate_client_dh_params(
428    dh: &DhParamsForRetry,
429    nonce: [u8; 16],
430    server_nonce: [u8; 16],
431    new_nonce: [u8; 32],
432    retry_id: i64,
433    random: &[u8; 272],
434    now: i32,
435) -> Result<(layer_tl_types::functions::SetClientDhParams, Step3), Error> {
436    let dh_prime = BigUint::from_bytes_be(&dh.dh_prime);
437    let g = BigUint::from(dh.g);
438    let g_a = BigUint::from_bytes_be(&dh.g_a);
439    let time_offset = dh.server_time - now;
440
441    let b = BigUint::from_bytes_be(&random[..256]);
442    let g_b = g.modpow(&b, &dh_prime);
443
444    let one = BigUint::from(1u32);
445    let safety = one.clone() << (2048 - 64);
446    check_g_in_range(&g_b, &one, &(&dh_prime - &one))?;
447    check_g_in_range(&g_b, &safety, &(&dh_prime - &safety))?;
448
449    let client_dh_inner = layer_tl_types::enums::ClientDhInnerData::ClientDhInnerData(
450        layer_tl_types::types::ClientDhInnerData {
451            nonce,
452            server_nonce,
453            retry_id,
454            g_b: g_b.to_bytes_be(),
455        },
456    )
457    .to_bytes();
458
459    let digest: [u8; 20] = {
460        let mut sha = Sha1::new();
461        sha.update(&client_dh_inner);
462        sha.finalize().into()
463    };
464
465    let pad_len = (16 - ((20 + client_dh_inner.len()) % 16)) % 16;
466    let rnd16 = &random[256..256 + pad_len.min(16)];
467
468    let mut hashed = Vec::with_capacity(20 + client_dh_inner.len() + pad_len);
469    hashed.extend_from_slice(&digest);
470    hashed.extend_from_slice(&client_dh_inner);
471    hashed.extend_from_slice(&rnd16[..pad_len]);
472
473    let key: [u8; 32] = dh.aes_key;
474    let iv: [u8; 32] = dh.aes_iv;
475    aes::ige_encrypt(&mut hashed, &key, &iv);
476
477    // Compute auth_key = g_a^b mod dh_prime for this attempt.
478    let mut auth_key_bytes = [0u8; 256];
479    let gab_bytes = g_a.modpow(&b, &dh_prime).to_bytes_be();
480    let skip = 256 - gab_bytes.len();
481    auth_key_bytes[skip..].copy_from_slice(&gab_bytes);
482
483    Ok((
484        layer_tl_types::functions::SetClientDhParams {
485            nonce,
486            server_nonce,
487            encrypted_data: hashed,
488        },
489        Step3 {
490            nonce,
491            server_nonce,
492            new_nonce,
493            time_offset,
494            auth_key: auth_key_bytes,
495            dh_params: dh.clone(),
496        },
497    ))
498}
499
500fn do_step3(
501    data: Step2,
502    response: layer_tl_types::enums::ServerDhParams,
503    random: &[u8; 272],
504    now: i32,
505    retry_id: i64,
506) -> Result<(layer_tl_types::functions::SetClientDhParams, Step3), Error> {
507    let Step2 {
508        nonce,
509        server_nonce,
510        new_nonce,
511    } = data;
512
513    let mut server_dh_ok = match response {
514        layer_tl_types::enums::ServerDhParams::Fail(f) => {
515            check_nonce(&f.nonce, &nonce)?;
516            check_server_nonce(&f.server_nonce, &server_nonce)?;
517            return Err(Error::DhParamsFail);
518        }
519        layer_tl_types::enums::ServerDhParams::Ok(x) => x,
520    };
521
522    check_nonce(&server_dh_ok.nonce, &nonce)?;
523    check_server_nonce(&server_dh_ok.server_nonce, &server_nonce)?;
524
525    if server_dh_ok.encrypted_answer.len() % 16 != 0 {
526        return Err(Error::EncryptedResponseNotPadded {
527            len: server_dh_ok.encrypted_answer.len(),
528        });
529    }
530
531    let (key_arr, iv_arr) = generate_key_data_from_nonce(&server_nonce, &new_nonce);
532    aes::ige_decrypt(&mut server_dh_ok.encrypted_answer, &key_arr, &iv_arr);
533    let plain = server_dh_ok.encrypted_answer;
534
535    let got_hash: [u8; 20] = plain[..20].try_into().unwrap();
536    let mut cursor = Cursor::from_slice(&plain[20..]);
537
538    let inner = match layer_tl_types::enums::ServerDhInnerData::deserialize(&mut cursor) {
539        Ok(layer_tl_types::enums::ServerDhInnerData::ServerDhInnerData(x)) => x,
540        Err(e) => return Err(Error::InvalidDhInnerData { error: e }),
541    };
542
543    let expected_hash: [u8; 20] = {
544        let mut sha = Sha1::new();
545        sha.update(&plain[20..20 + cursor.pos()]);
546        sha.finalize().into()
547    };
548    if got_hash != expected_hash {
549        return Err(Error::InvalidAnswerHash {
550            got: got_hash,
551            expected: expected_hash,
552        });
553    }
554
555    check_nonce(&inner.nonce, &nonce)?;
556    check_server_nonce(&inner.server_nonce, &server_nonce)?;
557
558    check_p_and_g(&inner.dh_prime, inner.g as u32)
559        .map_err(|source| Error::InvalidDhPrime { source })?;
560
561    // Validate g_a range.
562    let dh_prime_bn = BigUint::from_bytes_be(&inner.dh_prime);
563    let one = BigUint::from(1u32);
564    let g_a_bn = BigUint::from_bytes_be(&inner.g_a);
565    let safety = one.clone() << (2048 - 64);
566    check_g_in_range(&g_a_bn, &safety, &(&dh_prime_bn - &safety))?;
567
568    let dh = DhParamsForRetry {
569        dh_prime: inner.dh_prime,
570        g: inner.g as u32,
571        g_a: inner.g_a,
572        server_time: inner.server_time,
573        aes_key: key_arr,
574        aes_iv: iv_arr,
575    };
576
577    generate_client_dh_params(&dh, nonce, server_nonce, new_nonce, retry_id, random, now)
578}
579
580// finish: create_key
581
582/// Finalise the handshake.
583///
584/// Returns [`FinishResult::Done`] on success or [`FinishResult::Retry`] when
585/// the server sends `dh_gen_retry` (up to 5 attempts are typical). On retry,
586/// call [`retry_step3`] with the returned fields, send the new request, receive
587/// the answer, then call `finish` again.
588pub fn finish(
589    data: Step3,
590    response: layer_tl_types::enums::SetClientDhParamsAnswer,
591) -> Result<FinishResult, Error> {
592    let Step3 {
593        nonce,
594        server_nonce,
595        new_nonce,
596        time_offset,
597        auth_key: auth_key_bytes,
598        dh_params,
599    } = data;
600
601    struct DhData {
602        nonce: [u8; 16],
603        server_nonce: [u8; 16],
604        hash: [u8; 16],
605        num: u8,
606    }
607
608    let dh = match response {
609        // Variant names come from the constructor names: dh_gen_ok → DhGenOk, etc.
610        layer_tl_types::enums::SetClientDhParamsAnswer::DhGenOk(x) => DhData {
611            nonce: x.nonce,
612            server_nonce: x.server_nonce,
613            hash: x.new_nonce_hash1,
614            num: 1,
615        },
616        layer_tl_types::enums::SetClientDhParamsAnswer::DhGenRetry(x) => DhData {
617            nonce: x.nonce,
618            server_nonce: x.server_nonce,
619            hash: x.new_nonce_hash2,
620            num: 2,
621        },
622        layer_tl_types::enums::SetClientDhParamsAnswer::DhGenFail(x) => DhData {
623            nonce: x.nonce,
624            server_nonce: x.server_nonce,
625            hash: x.new_nonce_hash3,
626            num: 3,
627        },
628    };
629
630    check_nonce(&dh.nonce, &nonce)?;
631    check_server_nonce(&dh.server_nonce, &server_nonce)?;
632
633    let auth_key = AuthKey::from_bytes(auth_key_bytes);
634    let expected_hash = auth_key.calc_new_nonce_hash(&new_nonce, dh.num);
635    check_new_nonce_hash(&dh.hash, &expected_hash)?;
636
637    let first_salt = {
638        let mut buf = [0u8; 8];
639        for ((dst, a), b) in buf.iter_mut().zip(&new_nonce[..8]).zip(&server_nonce[..8]) {
640            *dst = a ^ b;
641        }
642        i64::from_le_bytes(buf)
643    };
644
645    match dh.num {
646        1 => Ok(FinishResult::Done(Finished {
647            auth_key: auth_key.to_bytes(),
648            time_offset,
649            first_salt,
650        })),
651        2 => {
652            // dh_gen_retry: compute auth_key_aux_hash = SHA1(auth_key)[0..8] as i64 LE.
653            let aux_hash: [u8; 20] = {
654                let mut sha = Sha1::new();
655                sha.update(auth_key.to_bytes());
656                sha.finalize().into()
657            };
658            let retry_id = i64::from_le_bytes(aux_hash[..8].try_into().unwrap());
659            Ok(FinishResult::Retry {
660                retry_id,
661                dh_params,
662                nonce,
663                server_nonce,
664                new_nonce,
665            })
666        }
667        _ => Err(Error::DhGenFail),
668    }
669}
670
671// Helpers
672
673fn check_nonce(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
674    if got == expected {
675        Ok(())
676    } else {
677        Err(Error::InvalidNonce {
678            got: *got,
679            expected: *expected,
680        })
681    }
682}
683fn check_server_nonce(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
684    if got == expected {
685        Ok(())
686    } else {
687        Err(Error::InvalidServerNonce {
688            got: *got,
689            expected: *expected,
690        })
691    }
692}
693fn check_new_nonce_hash(got: &[u8; 16], expected: &[u8; 16]) -> Result<(), Error> {
694    if got == expected {
695        Ok(())
696    } else {
697        Err(Error::InvalidNewNonceHash {
698            got: *got,
699            expected: *expected,
700        })
701    }
702}
703fn check_g_in_range(val: &BigUint, lo: &BigUint, hi: &BigUint) -> Result<(), Error> {
704    if lo < val && val < hi {
705        Ok(())
706    } else {
707        Err(Error::GParameterOutOfRange {
708            value: val.clone(),
709            low: lo.clone(),
710            high: hi.clone(),
711        })
712    }
713}
714
715/// RSA key by server fingerprint. Includes both production and test DC keys.
716#[allow(clippy::unreadable_literal)]
717pub fn key_for_fingerprint(fp: i64) -> Option<rsa::Key> {
718    Some(match fp {
719        // Production DC key (fingerprint -3414540481677951611)
720        -3414540481677951611 => rsa::Key::new(
721            "29379598170669337022986177149456128565388431120058863768162556424047512191330847455146576344487764408661701890505066208632169112269581063774293102577308490531282748465986139880977280302242772832972539403531316010870401287642763009136156734339538042419388722777357134487746169093539093850251243897188928735903389451772730245253062963384108812842079887538976360465290946139638691491496062099570836476454855996319192747663615955633778034897140982517446405334423701359108810182097749467210509584293428076654573384828809574217079944388301239431309115013843331317877374435868468779972014486325557807783825502498215169806323",
722            "65537",
723        )?,
724        // Test DC key (fingerprint -5595554452916591101)
725        -5595554452916591101 => rsa::Key::new(
726            "25342889448840415564971689590713473206898847759084779052582026594546022463853940585885215951168491965708222649399180603818074200620463776135424884632162512403163793083921641631564740959529419359595852941166848940585952337613333022396096584117954892216031229237302943701877588456738335398602461675225081791820393153757504952636234951323237820036543581047826906120927972487366805292115792231423684261262330394324750785450942589751755390156647751460719351439969059949569615302809050721500330239005077889855323917509948255722081644689442127297605422579707142646660768825302832201908302295573257427896031830742328565032949",
727            "65537",
728        )?,
729        _ => return None,
730    })
731}