api_signature/
lib.rs

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/// Signing Object
16/// This object is for creating a API key signature.
17///
18/// This this example a static nonce is used to generate a API signature. This is to confirm the signature is as expected.
19/// The example is also using the default signature configuration.
20/// ```rust
21/// use std::sync::Arc;
22/// use api_signature::Signature;
23/// use base64;
24///
25/// let mut signing = Signature::default();
26/// let nonce = 1616492376594usize;
27///
28/// let validated_sign = "4/dpxb3iT4tp/ZCVEwSnEsLxx0bqyhLpdfOpc6fn7OR8+UClSV5n9E6aSS8MPtnRfp32bAb0nmbRn6H8ndwLUQ==".as_bytes();
29///
30/// let cal_sign = signing
31///   .var("payload", "ordertype=limit&pair=XBTUSD&price=37500&type=buy&volume=1.25")
32///   .var("secret_key", "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg==")
33///   .var("url", "/0/private/AddOrder")
34///   .nonce(Arc::new(move || -> Vec<u8> {nonce.to_string().as_bytes().to_vec()}))
35///   .sign();
36///
37/// assert_eq!(validated_sign, cal_sign)
38/// ```
39///
40/// At the time of signing is might be usefull to locking the nonce. By locking the nonce you will prevent
41/// change in the next signing.
42/// This is usefull in the default signing configuration, and if the nonce is not predictable.
43///
44/// In this example the signature will only generate a base64 encoded value.
45///
46/// ```rust
47/// use std::sync::Arc;
48/// use api_signature::Signature;
49/// use api_signature::SignCal;
50/// use base64;
51///
52/// let mut signing = Signature::default();
53///
54/// let cal_sign = signing
55///     .config(SignCal::Base64Encode(SignCal::VarString("nonce".to_string()).into()));
56/// let nonce = cal_sign.nonce_lock();
57///
58/// let b64_nonce = base64::encode(nonce).into_bytes();
59///
60///
61/// assert_eq!(b64_nonce, cal_sign.sign());
62/// ```
63/// > Note:
64/// > Using nonce_lock will lock the nonce until the next signing, as soon as a signing has happened the lock will be removed!
65/// > Also running the lock multiple times will force the signature generator to create new nonce values.
66#[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}