binance/
crypto.rs

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
59/// Return milliseconds.
60pub 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&timestamp=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}