Skip to main content

ferogram_mtproto/
authentication.rs

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