1use hmac::{Hmac, Mac};
2use sha2::Sha256;
3
4use crate::errors::SignError;
5use std::{
6 collections::HashMap,
7 time::{SystemTime, UNIX_EPOCH},
8};
9
10type HmacSha256 = Hmac<Sha256>;
11
12pub fn sign(
14 payload: &HashMap<String, String>,
15 bot_token: &str,
16 auth_time: SystemTime,
17) -> Result<String, SignError> {
18 let auth_date = auth_time.duration_since(UNIX_EPOCH)?;
19
20 let mut pairs: Vec<(String, String)> = payload
22 .iter()
23 .filter(|&(k, _)| k != "hash" && k != "auth_date")
24 .map(|(k, v)| (k.clone(), v.clone()))
25 .collect();
26
27 pairs.push(("auth_date".to_string(), auth_date.as_secs().to_string()));
29
30 pairs.sort_by(|a, b| a.0.cmp(&b.0));
32
33 let payload_string = pairs
35 .iter()
36 .map(|(k, v)| format!("{}={}", k, v))
37 .collect::<Vec<String>>()
38 .join("\n");
39
40 let mut sk_hmac = HmacSha256::new_from_slice(b"WebAppData")
42 .map_err(|_| SignError::CouldNotProcessSignature)?;
43 sk_hmac.update(bot_token.as_bytes());
44 let secret_key = sk_hmac.finalize().into_bytes();
45
46 let mut imp_hmac =
48 HmacSha256::new_from_slice(&secret_key).map_err(|_| SignError::CouldNotProcessSignature)?;
49 imp_hmac.update(payload_string.as_bytes());
50
51 let result = imp_hmac.finalize().into_bytes();
53
54 Ok(hex::encode(result))
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use std::time::{Duration, UNIX_EPOCH};
61
62 #[test]
63 fn test_sign() {
64 let mut payload: HashMap<String, String> = HashMap::new();
65 payload.insert(
66 "query_id".to_string(),
67 "AAHdF6IQAAAAAN0XohDhrOrc".to_string(),
68 );
69 payload.insert(
70 "user".to_string(),
71 r#"{"id":279058397,"first_name":"Vladislav","last_name":"Kibenko","username":"vdkfrost","language_code":"ru","is_premium":true}"#.to_string(),
72 );
73 let bot_token = "5768337691:AAH5YkoiEuPk8-FZa32hStHTqXiLPtAEhx8";
74 let auth_time = UNIX_EPOCH + Duration::from_secs(1662771648);
75
76 let expected_hash =
77 "c501b71e775f74ce10e377dea85a7ea24ecd640b223ea86dfe453e0eaed2e2b2".to_string();
78 let result = sign(&payload, bot_token, auth_time).unwrap();
79 assert_eq!(result, expected_hash);
80 }
81}