1use hmac::{Hmac, Mac};
2use sha2::Sha512;
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::time::{SystemTime, UNIX_EPOCH};
5
6use crate::errors::IndodaxError;
7
8#[derive(Debug)]
9pub struct Signer {
10 api_key: String,
11 secret_key: String,
12 last_nonce: AtomicU64,
13}
14
15impl Signer {
16 pub fn new(api_key: &str, secret_key: &str) -> Self {
17 Self {
18 api_key: api_key.to_string(),
19 secret_key: secret_key.to_string(),
20 last_nonce: AtomicU64::new(0),
21 }
22 }
23
24 pub fn api_key(&self) -> &str {
25 &self.api_key
26 }
27
28 pub fn next_nonce_str(&self) -> String {
29 self.next_nonce().to_string()
30 }
31
32 fn next_nonce(&self) -> u64 {
33 let now = SystemTime::now()
34 .duration_since(UNIX_EPOCH)
35 .unwrap_or_default()
36 .as_millis() as u64;
37 loop {
38 let prev = self.last_nonce.load(Ordering::Acquire);
39 let next = if now > prev { now } else { prev + 1 };
40 if self
41 .last_nonce
42 .compare_exchange(prev, next, Ordering::Release, Ordering::Relaxed)
43 .is_ok()
44 {
45 return next;
46 }
47 }
48 }
49
50 pub fn now_millis() -> u64 {
51 SystemTime::now()
52 .duration_since(UNIX_EPOCH)
53 .unwrap_or_default()
54 .as_millis() as u64
55 }
56
57 pub fn sign_v1(&self, payload: &str) -> Result<(String, String), IndodaxError> {
58 let signature = self.hmac_sha512(payload, &self.secret_key)?;
59 let encoded_sign = hex::encode(signature);
60
61 Ok((payload.to_string(), encoded_sign))
62 }
63
64 pub fn sign_v2(&self, query_string: &str, _timestamp: u64) -> Result<String, IndodaxError> {
65 let signature = self.hmac_sha512(query_string, &self.secret_key)?;
66 Ok(hex::encode(signature))
67 }
68
69 fn hmac_sha512(&self, data: &str, key: &str) -> Result<Vec<u8>, IndodaxError> {
70 let mut mac = Hmac::<Sha512>::new_from_slice(key.as_bytes())
71 .map_err(|e| IndodaxError::Other(format!("HMAC initialization failed: {}", e)))?;
72 mac.update(data.as_bytes());
73 Ok(mac.finalize().into_bytes().to_vec())
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn test_signer_new() {
83 let signer = Signer::new("api_key", "secret_key");
84 assert_eq!(signer.api_key(), "api_key");
85 }
86
87 #[test]
88 fn test_signer_api_key() {
89 let signer = Signer::new("my_api_key", "my_secret");
90 assert_eq!(signer.api_key(), "my_api_key");
91 }
92
93 #[test]
94 fn test_signer_next_nonce_str() {
95 let signer = Signer::new("key", "secret");
96 let nonce = signer.next_nonce_str();
97 assert!(!nonce.is_empty());
98 assert!(nonce.parse::<u64>().is_ok());
100 }
101
102 #[test]
103 fn test_signer_next_nonce_is_increasing() {
104 let signer = Signer::new("key", "secret");
105 let nonce1 = signer.next_nonce();
106 let nonce2 = signer.next_nonce();
107 assert!(nonce2 >= nonce1);
109 }
110
111 #[test]
112 fn test_signer_now_millis() {
113 let millis = Signer::now_millis();
114 assert!(millis > 0);
115 assert!(millis > 1_000_000_000_000); }
118
119 #[test]
120 fn test_signer_sign_v1() {
121 let signer = Signer::new("key", "secret");
122 let (payload, signature) = signer.sign_v1("method=test&nonce=123").unwrap();
123 assert_eq!(payload, "method=test&nonce=123");
124 assert!(!signature.is_empty());
125 assert!(hex::decode(&signature).is_ok());
127 }
128
129 #[test]
130 fn test_signer_sign_v1_different_secrets() {
131 let signer1 = Signer::new("key", "secret1");
132 let signer2 = Signer::new("key", "secret2");
133 let (_payload, sig1) = signer1.sign_v1("test").unwrap();
134 let (_payload2, sig2) = signer2.sign_v1("test").unwrap();
135 assert_ne!(sig1, sig2);
136 }
137
138 #[test]
139 fn test_signer_sign_v2() {
140 let signer = Signer::new("key", "secret");
141 let signature = signer.sign_v2("param1=value1", 1234567890).unwrap();
142 assert!(!signature.is_empty());
143 assert!(hex::decode(&signature).is_ok());
145 }
146
147 #[test]
148 fn test_signer_sign_v2_with_timestamp() {
149 let signer = Signer::new("key", "secret");
150 let query_string = "symbol=BTCIDR";
151 let timestamp = 1234567890000u64;
152 let signature = signer.sign_v2(query_string, timestamp).unwrap();
153
154 let _expected_payload = format!("{}×tamp={}&recvWindow=10000", query_string, timestamp);
156 let _decoded = hex::decode(&signature).unwrap();
157 assert!(!signature.is_empty());
159 }
160
161 #[test]
162 fn test_hmac_sha512_output_length() {
163 let signer = Signer::new("key", "secret");
164 let result = signer.hmac_sha512("test data", "secret").unwrap();
165 assert_eq!(result.len(), 64);
167 }
168
169 #[test]
170 fn test_signer_multiple_signatures_different() {
171 let signer = Signer::new("key", "secret");
172 let (_, sig1) = signer.sign_v1("payload1").unwrap();
173 let (_, sig2) = signer.sign_v1("payload2").unwrap();
174 assert_ne!(sig1, sig2);
175 }
176}