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