1extern crate base64;
2extern crate bs58;
3extern crate hmac;
4extern crate serde;
5extern crate simple_serde;
6
7mod helpers;
8
9use serde_derive::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::ops::Deref;
12use std::sync::Arc;
13use std::time::{Duration, SystemTime, UNIX_EPOCH};
14
15#[derive(Clone)]
67pub struct Signature {
68 pub(crate) config: Option<SignCal>,
69 pub(crate) nonce: Arc<dyn Fn() -> Vec<u8>>,
70 pub(crate) variables: HashMap<String, Variable>,
71 pub(crate) nonce_lock: Option<Vec<u8>>,
72}
73
74impl Signature {
75 pub fn new() -> Self {
76 Signature {
77 config: None,
78 nonce: Arc::new(|| -> Vec<u8> {
79 SystemTime::now()
80 .duration_since(UNIX_EPOCH)
81 .unwrap_or(Duration::new(0, 0))
82 .as_nanos()
83 .to_string()
84 .into_bytes()
85 }),
86 variables: Default::default(),
87 nonce_lock: None,
88 }
89 }
90
91 pub fn nonce(&mut self, o: Arc<dyn Fn() -> Vec<u8>>) -> &mut Self {
92 self.nonce = o;
93 self
94 }
95
96 pub fn nonce_lock(&mut self) -> Vec<u8> {
97 let nonce_fn = self.nonce.clone();
98 let nonce = nonce_fn();
99 self.nonce_lock = Some(nonce.clone());
100 nonce
101 }
102
103 pub fn nonce_unlock(&mut self) -> &mut Self {
104 self.nonce_lock = None;
105 self
106 }
107
108 pub fn config(&mut self, config: SignCal) -> &mut Self {
109 self.config = Some(config);
110 self
111 }
112
113 pub fn var<T: Into<Variable>>(&mut self, key: &str, value: T) -> &mut Self {
114 self.variables.insert(key.to_string(), value.into());
115 self
116 }
117
118 pub fn compare<T: Into<Vec<u8>>>(&mut self, signature: T, nonce: Vec<u8>) -> bool {
119 let mut _self = self.clone();
120 _self.nonce = Arc::new(move || -> Vec<u8> { nonce.clone() });
121 signature.into() == self.sign()
122 }
123
124 pub fn sign(&mut self) -> Vec<u8> {
125 let nonce_fn = &self.nonce;
126 let mut variables = self.variables.clone();
127 variables.insert(
128 "nonce".to_string(),
129 Variable::Data(if let Some(nonce) = self.nonce_lock.clone() {
130 self.nonce_lock = None;
131 nonce.clone()
132 } else {
133 nonce_fn()
134 }),
135 );
136 sign_calc(
137 self.config.as_ref().unwrap_or(&SignCal::default()),
138 &variables,
139 )
140 }
141}
142
143impl Default for Signature {
144 fn default() -> Self {
145 Signature::new()
146 }
147}
148
149fn sign_calc(config: &SignCal, variables: &HashMap<String, Variable>) -> Vec<u8> {
150 match config {
151 SignCal::HmacSha512(k, c) => {
152 helpers::hmac_sha512(&sign_calc(k, variables), &sign_calc(c.deref(), variables))
153 }
154 SignCal::HmacSha256(k, c) => {
155 helpers::hmac_sha256(&sign_calc(k, variables), &sign_calc(c.deref(), variables))
156 }
157 SignCal::Sha256(c) => helpers::sha256(&sign_calc(c, variables)),
158 SignCal::Base64Encode(c) => helpers::base64encode(&sign_calc(c, variables)),
159 SignCal::Base64Decode(c) => helpers::base64decode(&sign_calc(c, variables)),
160 SignCal::Base58Encode(c) => helpers::base58encode(&sign_calc(c, variables)),
161 SignCal::Base58Decode(c) => helpers::base58decode(&sign_calc(c, variables)),
162 SignCal::Sha512(c) => helpers::sha512(&sign_calc(c, variables)),
163 SignCal::Append(a) => a
164 .iter()
165 .flat_map(|t| sign_calc(t, variables))
166 .collect::<Vec<u8>>(),
167 SignCal::JoinAsString(a) => a
168 .iter()
169 .flat_map(|t| String::from_utf8(sign_calc(t, variables)))
170 .collect::<Vec<String>>()
171 .join("")
172 .into_bytes(),
173 SignCal::JoinAsBytes(a) => a
174 .iter()
175 .flat_map(|t| sign_calc(t, variables))
176 .collect::<Vec<u8>>(),
177 SignCal::VarData(k) => variables
178 .get(k)
179 .unwrap_or(&Variable::Data(Vec::new()))
180 .into(),
181 SignCal::VarString(k) => variables
182 .get(k)
183 .unwrap_or(&Variable::Data(Vec::new()))
184 .into(),
185 SignCal::VarInteger(k) => variables
186 .get(k)
187 .unwrap_or(&Variable::Data(Vec::new()))
188 .into(),
189 SignCal::Raw(v) => v.clone(),
190 SignCal::String(s) => s.clone().into_bytes(),
191 }
192}
193
194#[derive(Clone, Serialize, Deserialize)]
195pub enum SignCal {
196 HmacSha256(Box<SignCal>, Box<SignCal>),
197 HmacSha512(Box<SignCal>, Box<SignCal>),
198 Sha256(Box<SignCal>),
199 Sha512(Box<SignCal>),
200 Base64Encode(Box<SignCal>),
201 Base64Decode(Box<SignCal>),
202 Base58Encode(Box<SignCal>),
203 Base58Decode(Box<SignCal>),
204 Append(Vec<SignCal>),
205 JoinAsString(Vec<SignCal>),
206 JoinAsBytes(Vec<SignCal>),
207 VarData(String),
208 VarString(String),
209 VarInteger(String),
210 Raw(Vec<u8>),
211 String(String),
212}
213
214impl Default for SignCal {
215 fn default() -> Self {
216 use SignCal::*;
217 Base64Encode(
218 HmacSha512(
219 Base64Decode(VarString("secret_key".to_string()).into()).into(),
220 Append(vec![
221 VarString("url".to_string()),
222 Sha256(
223 Append(vec![
224 VarInteger("nonce".to_string()),
225 JoinAsString(vec![
226 Raw("nonce=".to_string().into_bytes()),
227 VarInteger("nonce".to_string()),
228 Raw("&".to_string().into_bytes()),
229 VarString("payload".to_string()),
230 ]),
231 ])
232 .into(),
233 ),
234 ])
235 .into(),
236 )
237 .into(),
238 )
239 }
240}
241
242#[derive(Clone)]
243pub enum Variable {
244 Data(Vec<u8>),
245 String(String),
246 Integer(usize),
247}
248
249impl From<Variable> for Vec<u8> {
250 fn from(v: Variable) -> Self {
251 match v {
252 Variable::Integer(i) => i.to_string().into_bytes(),
253 Variable::Data(d) => d,
254 Variable::String(s) => s.into_bytes(),
255 }
256 }
257}
258
259impl From<&Variable> for Vec<u8> {
260 fn from(v: &Variable) -> Self {
261 match v {
262 Variable::Integer(i) => i.to_string().into_bytes(),
263 Variable::Data(d) => d.clone(),
264 Variable::String(s) => s.clone().into_bytes(),
265 }
266 }
267}
268
269impl From<String> for Variable {
270 fn from(s: String) -> Self {
271 Variable::String(s)
272 }
273}
274impl From<&str> for Variable {
275 fn from(s: &str) -> Self {
276 Variable::String(s.to_string())
277 }
278}
279impl From<usize> for Variable {
280 fn from(i: usize) -> Self {
281 Variable::Integer(i)
282 }
283}
284impl From<u8> for Variable {
285 fn from(i: u8) -> Self {
286 Variable::Integer(i as usize)
287 }
288}
289impl From<u32> for Variable {
290 fn from(i: u32) -> Self {
291 Variable::Integer(i as usize)
292 }
293}
294impl From<u64> for Variable {
295 fn from(i: u64) -> Self {
296 Variable::Integer(i as usize)
297 }
298}
299impl From<u128> for Variable {
300 fn from(i: u128) -> Self {
301 Variable::Integer(i as usize)
302 }
303}
304impl From<i8> for Variable {
305 fn from(i: i8) -> Self {
306 Variable::Integer(i as usize)
307 }
308}
309impl From<i32> for Variable {
310 fn from(i: i32) -> Self {
311 Variable::Integer(i as usize)
312 }
313}
314impl From<i64> for Variable {
315 fn from(i: i64) -> Self {
316 Variable::Integer(i as usize)
317 }
318}
319impl From<i128> for Variable {
320 fn from(i: i128) -> Self {
321 Variable::Integer(i as usize)
322 }
323}
324impl From<Vec<u8>> for Variable {
325 fn from(v: Vec<u8>) -> Self {
326 Variable::Data(v)
327 }
328}
329impl From<&[u8]> for Variable {
330 fn from(v: &[u8]) -> Self {
331 Variable::Data(v.to_vec())
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use crate::helpers::base64decode;
338 use crate::{SignCal, Signature};
339 use hex;
340 use hex::FromHex;
341 use std::sync::Arc;
342
343 #[test]
344 fn it_works() {
345 let result = 2 + 2;
346 assert_eq!(result, 4);
347 }
348
349 #[test]
350 fn test_base58() {
351 use SignCal::*;
352 assert_eq!(
353 b"3mJr7AoUCHxNqd".to_vec(),
354 Signature::default()
355 .config(Base58Encode(Raw(b"1234567890".to_vec()).into()))
356 .sign()
357 );
358 }
359
360 #[test]
361 fn test_base58_decode() {
362 use SignCal::*;
363 assert_eq!(
364 b"1234567890".to_vec(),
365 Signature::default()
366 .config(Base58Decode(Raw(b"3mJr7AoUCHxNqd".to_vec()).into()))
367 .sign()
368 );
369 }
370
371 #[test]
372 fn test_base64() {
373 use SignCal::*;
374 assert_eq!(
375 b"MTIzNDU2Nzg5MA==".to_vec(),
376 Signature::default()
377 .config(Base64Encode(Raw(b"1234567890".to_vec()).into()))
378 .sign()
379 );
380 }
381
382 #[test]
383 fn test_base64_decode() {
384 use SignCal::*;
385 assert_eq!(
386 b"1234567890".to_vec(),
387 Signature::default()
388 .config(Base64Decode(Raw(b"MTIzNDU2Nzg5MA==".to_vec()).into()))
389 .sign()
390 );
391 }
392
393 #[test]
394 fn test_sha256() {
395 use SignCal::*;
396
397 let signature = Signature::default()
398 .config(Sha256(
399 Raw("1234567890".to_string().as_bytes().to_vec()).into(),
400 ))
401 .sign();
402
403 assert_eq!(
404 hex::decode("c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646")
405 .unwrap(),
406 signature
407 );
408 }
409
410 #[test]
411 fn test_sha512() {
412 use SignCal::*;
413 assert_eq!(
414 hex::decode("12b03226a6d8be9c6e8cd5e55dc6c7920caaa39df14aab92d5e3ea9340d1c8a4d3d0b8e4314f1f6ef131ba4bf1ceb9186ab87c801af0d5c95b1befb8cedae2b9").unwrap(),
415 Signature::default()
416 .config(Sha512(Raw(b"1234567890".to_vec()).into()))
417 .sign()
418 );
419 }
420
421 #[test]
422 fn test_sign_cal_with_control_signature() {
423 use SignCal::*;
424
425 let nonce = 1616492376594usize;
426 let mut signature = Signature::default();
427 signature.var("payload", format!("ordertype=limit&pair=XBTUSD&price=37500&type=buy&volume=1.25"))
428 .var("secret_key", "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==")
429 .var("url", "/0/private/AddOrder")
430 .nonce(Arc::new(move || -> Vec<u8> {nonce.to_string().as_bytes().to_vec()}))
431 .config(Base64Encode(
432 HmacSha512(
433 Base64Decode(VarString("secret_key".to_string()).into()).into(),
434 Append(vec![
435 VarString("url".to_string()),
436 Sha256(
437 Append(vec![
438 VarInteger("nonce".to_string()),
439 JoinAsString(vec![
440 Raw("nonce=".to_string().into_bytes()),
441 VarInteger("nonce".to_string()),
442 Raw("&".to_string().into_bytes()),
443 VarString("payload".to_string()),
444 ])
445 ])
446 .into(),
447 ),
448 ])
449 .into(),
450 )
451 .into(),
452 ));
453
454 let api_sign = b"4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ==".to_vec();
455
456 assert_eq!(api_sign, signature.sign());
457 }
458
459 #[test]
460 fn test_sign_cal_compare_with_control_signature() {
461 use SignCal::*;
462
463 let nonce = 1616492376594usize;
464 let mut signature = Signature::default();
465 signature.var("payload", format!("ordertype=limit&pair=XBTUSD&price=37500&type=buy&volume=1.25"))
466 .var("secret_key", "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==")
467 .var("url", "/0/private/AddOrder")
468 .nonce(Arc::new(move || -> Vec<u8> {nonce.to_string().as_bytes().to_vec()}));
469
470 let api_sign = "4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ==";
471
472 assert!(signature.compare(api_sign, nonce.to_string().as_bytes().to_vec()));
473 }
474
475 #[test]
476 fn test_nonce_lock() {
477 let mut signing = Signature::default();
478 let cal_sign = signing.config(SignCal::Base64Encode(
479 SignCal::VarString("nonce".to_string()).into(),
480 ));
481 let nonce = cal_sign.nonce_lock();
482 let b64_nonce = base64::encode(nonce).into_bytes();
483
484 assert_eq!(b64_nonce, cal_sign.sign());
485 }
486
487 #[test]
488 fn test_confirm_unlock() {
489 let mut signing = Signature::default();
490 let cal_sign = signing.config(SignCal::Base64Encode(
491 SignCal::VarString("nonce".to_string()).into(),
492 ));
493 let nonce = cal_sign.nonce_lock();
494 assert!(cal_sign.nonce_lock.is_some());
495 cal_sign.nonce_unlock();
496 assert!(cal_sign.nonce_lock.is_none());
497 }
498}