1use hex;
2use hmac::{Hmac, Mac};
3use sha2::Sha256;
4use std::fmt::{self, Display, Formatter};
5
6use crate::spot::Timestamp;
7
8#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
9pub struct SensitiveString(String);
10
11impl Display for SensitiveString {
12 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
13 write!(f, "REDACTED")
14 }
15}
16
17impl std::ops::Deref for SensitiveString {
18 type Target = str;
19 fn deref(&self) -> &Self::Target {
20 &self.0
21 }
22}
23
24impl From<String> for SensitiveString {
25 fn from(s: String) -> Self {
26 SensitiveString(s)
27 }
28}
29
30impl From<&str> for SensitiveString {
31 fn from(s: &str) -> Self {
32 SensitiveString(s.to_string())
33 }
34}
35
36impl AsRef<str> for SensitiveString {
37 fn as_ref(&self) -> &str {
38 &self.0
39 }
40}
41
42impl SensitiveString {
43 pub fn expose(&self) -> &str {
44 &self.0
45 }
46}
47
48pub fn hmac_sha256(key: impl AsRef<[u8]>, message: impl AsRef<[u8]>) -> String {
49 let mut mac = Hmac::<Sha256>::new_from_slice(key.as_ref()).unwrap();
50 mac.update(message.as_ref());
51 let mac = mac.finalize().into_bytes().to_vec();
52 hex::encode(&mac)
53}
54
55pub fn make_sign(api_secret: SensitiveString) -> impl Fn(&str) -> String {
56 move |s: &str| format!("{}&signature={}", s, hmac_sha256(api_secret.expose(), s))
57}
58
59pub fn timestamp() -> Timestamp {
61 std::time::UNIX_EPOCH.elapsed().unwrap().as_millis() as Timestamp
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 #[test]
69 fn sign_query() {
70 let api_secret = SensitiveString(
71 "NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j".to_string(),
72 );
73 let sign = make_sign(api_secret);
74 let query = "symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000×tamp=1499827319559";
75 let signature = "c8db56825ae71d6d79447849e617115f4a920fa2acdcab2b053c4b2838bd6b71";
76 let expected = format!("{query}&signature={signature}");
77
78 let body = sign(query);
79
80 assert_eq!(expected, body);
81 }
82}