1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use thiserror::Error;
use time::{error::Format, format_description::well_known::Rfc3339, OffsetDateTime};

type HmacSha256 = Hmac<Sha256>;

/// Error type for signing.
#[derive(Debug, Error)]
pub enum SignError {
    /// Format timestamp error.
    #[error("format timestamp error: {0}")]
    FormatTimestamp(#[from] Format),

    /// Convert timetsamp error.
    #[error("convert timestamp error: {0}")]
    ConvertTimestamp(#[from] time::error::ComponentRange),

    /// SecretKey length error.
    #[error("secretkey length error")]
    SecretKeyLength,
}

/// Key.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Key {
    /// APIKey.
    pub apikey: String,
    /// SecretKey.
    pub secretkey: String,
    /// Passphrase.
    pub passphrase: String,
}

/// Signature
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Signature {
    /// Signature.
    #[serde(rename = "sign")]
    pub signature: String,

    /// Timestamp.
    pub timestamp: String,
}

impl Key {
    /// Create a new [`Key`].
    pub fn new(apikey: &str, secretkey: &str, passphrase: &str) -> Self {
        Self {
            apikey: apikey.to_string(),
            secretkey: secretkey.to_string(),
            passphrase: passphrase.to_string(),
        }
    }

    /// Sign with this [`Key`].
    pub fn sign(
        &self,
        method: &str,
        uri: &str,
        timestamp: OffsetDateTime,
        use_unix_timestamp: bool,
    ) -> Result<Signature, SignError> {
        let secret = self.secretkey.as_str();
        let timestamp = timestamp.replace_millisecond(timestamp.millisecond())?;
        let timestamp = if use_unix_timestamp {
            timestamp.unix_timestamp().to_string()
        } else {
            timestamp.format(&Rfc3339)?
        };
        let raw_sign = timestamp.clone() + method + uri;
        tracing::debug!("message to sign: {}", raw_sign);
        let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
            .map_err(|_| SignError::SecretKeyLength)?;
        mac.update(raw_sign.as_bytes());

        Ok(Signature {
            signature: base64::encode(&mac.finalize().into_bytes()),
            timestamp,
        })
    }

    /// Sign now.
    pub fn sign_now(
        &self,
        method: &str,
        uri: &str,
        use_unix_timestamp: bool,
    ) -> Result<Signature, SignError> {
        self.sign(method, uri, OffsetDateTime::now_utc(), use_unix_timestamp)
    }
}