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::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 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) -> 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 = crate::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").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 signature = signer.sign_v2(query_string).unwrap();
152
153 assert!(!signature.is_empty());
155 }
156
157 #[test]
158 fn test_hmac_sha512_output_length() {
159 let signer = Signer::new("key", "secret");
160 let result = signer.hmac_sha512("test data", "secret").unwrap();
161 assert_eq!(result.len(), 64);
163 }
164
165 #[test]
166 fn test_signer_multiple_signatures_different() {
167 let signer = Signer::new("key", "secret");
168 let (_, sig1) = signer.sign_v1("payload1").unwrap();
169 let (_, sig2) = signer.sign_v1("payload2").unwrap();
170 assert_ne!(sig1, sig2);
171 }
172}