use hex; use hmac::{Hmac, Mac}; use sha2::Sha256; use std::str; use std::time::{SystemTime, UNIX_EPOCH}; const SEP: &str = "-"; pub fn sign(key: &str, raw: &str) -> Result { let nonce = get_nonce()?; let joined = format!("{nonce}{SEP}{raw}"); let signature = get_signature(key, &joined)?; Ok(format!("{signature}{SEP}{joined}")) } pub fn verify(key: &str, signed: &str) -> Result { let mut iter = signed.split(SEP); match (iter.next(), iter.next()) { (Some(signature), Some(nonce)) => { let raw = iter.collect::>().join(SEP); if signature == get_signature(key, &format!("{nonce}{SEP}{raw}"))? { Ok(raw) } else { Err("Signature does not match".to_string()) } } _ => Err("Malformed signed".to_string()), } } fn get_signature(key: &str, message: &str) -> Result { let mut mac = Hmac::::new_from_slice(key.as_bytes()) .map_err(|e| format!("Error initializing MAC: {e}"))?; mac.update(message.as_bytes()); let result = mac.finalize(); Ok(hex::encode(result.into_bytes())) } fn get_nonce() -> Result { Ok(SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|e| format!("Failure getting unix expoch: {e}"))? .as_millis() .to_string()) } #[cfg(test)] mod tests { use super::*; #[test] fn sign_and_validate() { let key = "xagrlBUobnTj32Rm8tvmsZ6mh8qLfip5".to_string(); assert_eq!(verify(&key, &sign(&key, "").unwrap()), Ok("".to_string())); assert_eq!( verify(&key, &sign(&key, "hello").unwrap()), Ok("hello".to_string()) ); assert_eq!( verify(&key, &sign(&key, "with-sep").unwrap()), Ok("with-sep".to_string()) ); } #[test] fn fail_when_key_mismatch() { let key1 = "xagrlBUobnTj32Rm8tvmsZ6mh8qLfip5".to_string(); let key2 = "8KJBK6axEr9wQ390GgdWA8Pjn8FwILDa".to_string(); assert!(verify(&key1, &sign(&key2, "hello").unwrap()).is_err()); } }