kraken_async_rs/crypto/
signatures.rs1use base64::engine::general_purpose::STANDARD as base64;
3use base64::Engine;
4use hmac::{Hmac, Mac};
5use sha2::{Digest, Sha256, Sha512};
6
7pub struct Signature {
9 pub body_data: String,
10 pub signature: String,
11}
12
13pub fn generate_signature(
24 nonce: u64,
25 secret: &str,
26 endpoint: &str,
27 encoded_data: String,
28) -> Signature {
29 let mut hmac = Hmac::<Sha512>::new_from_slice(&base64.decode(secret.as_bytes()).unwrap())
30 .expect("Could not use private key to create HMAC");
31
32 let mut sha256 = Sha256::new();
33
34 sha256.update(nonce.to_string().as_bytes());
35 sha256.update(encoded_data.as_bytes());
36
37 let payload = sha256.finalize();
38
39 hmac.update(endpoint.as_bytes());
40 hmac.update(&payload[..]);
41
42 Signature {
43 body_data: encoded_data,
44 signature: base64.encode(hmac.finalize().into_bytes()),
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use serde::Serialize;
52 use to_query_params::{QueryParams, ToQueryParams};
53 use url::form_urlencoded;
54
55 #[derive(QueryParams, Serialize)]
56 struct QueryData {
57 #[serde(rename = "ordertype")]
58 #[query(required, rename = "ordertype")]
59 order_type: String,
60 #[query(required)]
61 pair: String,
62 #[query(required)]
63 price: String,
64 #[query(required)]
65 #[query(rename = "type")]
66 #[serde(rename = "type")]
67 order_side: String,
68 #[query(required)]
69 volume: String,
70 }
71
72 #[test]
73 fn test_generate_signature_form_data() {
74 let expected = "4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ==";
75 let key =
76 "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==";
77
78 let nonce = 1616492376594_u64;
79
80 let post_data = QueryData {
81 order_type: "limit".into(),
82 pair: "XBTUSD".into(),
83 price: "37500".into(),
84 order_side: "buy".into(),
85 volume: "1.25".into(),
86 };
87
88 let mut query_params = form_urlencoded::Serializer::new(String::new());
89 query_params.append_pair("nonce", &nonce.to_string());
90
91 for (key, value) in post_data.to_query_params().iter() {
92 query_params.append_pair(key, value);
93 }
94
95 let encoded_data = query_params.finish();
96
97 let signature = generate_signature(nonce, key, "/0/private/AddOrder", encoded_data);
98
99 assert_eq!(expected, signature.signature);
100 }
101
102 #[test]
103 fn test_generate_signature_json_data() {
104 let expected = "oTOXlYtwCD1eL/j45C8gSWB49XQO1Sguv3nnScc8TTNpgmsnDvAA3yu6geyXXjGIsfCUEOzslsv4ugTZNsM7RA==";
105 let key =
106 "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==";
107
108 let nonce = 1616492376594_u64;
109
110 let post_data = QueryData {
111 order_type: "limit".into(),
112 pair: "XBTUSD".into(),
113 price: "37500".into(),
114 order_side: "buy".into(),
115 volume: "1.25".into(),
116 };
117
118 let encoded_data = serde_json::to_string(&post_data).unwrap();
119
120 let signature = generate_signature(nonce, key, "/0/private/AddOrder", encoded_data);
121
122 assert_eq!(expected, signature.signature);
123 }
124}