Skip to main content

bybit_client/
auth.rs

1//! Authentication and request signing for Bybit API.
2
3use ring::hmac;
4
5/// Sign a message using HMAC-SHA256.
6///
7/// Returns the signature as a hex-encoded string.
8pub fn sign_hmac_sha256(message: &str, secret: &str) -> String {
9    let key = hmac::Key::new(hmac::HMAC_SHA256, secret.as_bytes());
10    let signature = hmac::sign(&key, message.as_bytes());
11    hex::encode(signature.as_ref())
12}
13
14/// Generate a signature for a REST API request.
15///
16/// The signature format for V5 API is:
17/// `HMAC_SHA256(timestamp + api_key + recv_window + payload, api_secret)`
18///
19/// - For GET requests, `payload` is the query string (without leading `?`)
20/// - For POST requests, `payload` is the JSON body
21pub fn sign_rest_request(
22    timestamp: u64,
23    api_key: &str,
24    recv_window: u32,
25    payload: &str,
26    api_secret: &str,
27) -> String {
28    let message = format!("{}{}{}{}", timestamp, api_key, recv_window, payload);
29    sign_hmac_sha256(&message, api_secret)
30}
31
32/// Generate a signature for WebSocket authentication.
33///
34/// The signature format is: `HMAC_SHA256("GET/realtime" + expires, api_secret)`
35pub fn sign_ws_auth(expires: u64, api_secret: &str) -> String {
36    let message = format!("GET/realtime{}", expires);
37    sign_hmac_sha256(&message, api_secret)
38}
39
40/// Get the current timestamp in milliseconds.
41pub fn current_timestamp_ms() -> u64 {
42    std::time::SystemTime::now()
43        .duration_since(std::time::UNIX_EPOCH)
44        .unwrap_or_else(|err| err.duration())
45        .as_millis() as u64
46}
47
48/// HTTP headers required for authenticated requests.
49pub mod headers {
50    /// API key header.
51    pub const API_KEY: &str = "X-BAPI-API-KEY";
52    /// Timestamp header (milliseconds).
53    pub const TIMESTAMP: &str = "X-BAPI-TIMESTAMP";
54    /// Signature header.
55    pub const SIGN: &str = "X-BAPI-SIGN";
56    /// Receive window header (milliseconds).
57    pub const RECV_WINDOW: &str = "X-BAPI-RECV-WINDOW";
58    /// Sign type header (always "2" for V5).
59    pub const SIGN_TYPE: &str = "X-BAPI-SIGN-TYPE";
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_hmac_signature() {
68        let secret = "test_secret";
69        let message = "test_message";
70        let sig = sign_hmac_sha256(message, secret);
71
72        assert_eq!(sig.len(), 64);
73        assert!(sig.chars().all(|c| c.is_ascii_hexdigit()));
74    }
75
76    #[test]
77    fn test_rest_signature() {
78        let timestamp: u64 = 1658384314791;
79        let api_key = "XXXXXXXX";
80        let recv_window = 5000;
81        let payload = r#"{"category":"linear","symbol":"BTCUSDT","side":"Buy","positionIdx":0,"orderType":"Limit","qty":"0.001","price":"18900","timeInForce":"GTC"}"#;
82        let api_secret = "YYYYYYYY";
83
84        let signature = sign_rest_request(timestamp, api_key, recv_window, payload, api_secret);
85
86        assert_eq!(signature.len(), 64);
87        assert!(signature.chars().all(|c| c.is_ascii_hexdigit()));
88    }
89
90    #[test]
91    fn test_ws_signature() {
92        let expires: u64 = 1658384314791;
93        let api_secret = "test_secret";
94
95        let signature = sign_ws_auth(expires, api_secret);
96
97        assert_eq!(signature.len(), 64);
98        assert!(signature.chars().all(|c| c.is_ascii_hexdigit()));
99    }
100
101    #[test]
102    fn test_signature_consistency() {
103        let sig1 = sign_hmac_sha256("test", "secret");
104        let sig2 = sign_hmac_sha256("test", "secret");
105        assert_eq!(sig1, sig2);
106
107        let sig3 = sign_hmac_sha256("test2", "secret");
108        assert_ne!(sig1, sig3);
109    }
110
111    #[test]
112    fn test_timestamp() {
113        let ts = current_timestamp_ms();
114        assert!(ts > 1577836800000); // Jan 1, 2020
115    }
116
117    #[test]
118    fn test_get_signature_format() {
119        let timestamp: u64 = 1658384314791;
120        let api_key = "testkey";
121        let recv_window = 5000;
122        let query = "category=linear&symbol=BTCUSDT";
123        let api_secret = "testsecret";
124
125        let sig = sign_rest_request(timestamp, api_key, recv_window, query, api_secret);
126        assert_eq!(sig.len(), 64);
127    }
128}