| |
| |
| |
|
|
| use wasm_bindgen::prelude::*; |
| use sha2::{Sha512, Digest}; |
| use hmac::{Hmac, Mac}; |
|
|
| |
| |
| |
| |
| |
| |
| |
| #[wasm_bindgen] |
| pub fn sha512(input: &str) -> String { |
| let mut hasher = Sha512::new(); |
| hasher.update(input.as_bytes()); |
| let result = hasher.finalize(); |
| format!("{:x}", result) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| #[wasm_bindgen] |
| pub fn verify_sha512(input: &str, hash: &str) -> bool { |
| let computed = sha512(input); |
| constant_time_compare(&computed, hash) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| #[wasm_bindgen] |
| pub fn blake3(input: &str) -> String { |
| let hash = blake3::hash(input.as_bytes()); |
| hash.to_hex().to_string() |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| #[wasm_bindgen] |
| pub fn verify_blake3(input: &str, hash: &str) -> bool { |
| let computed = blake3(input); |
| constant_time_compare(&computed, hash) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| #[wasm_bindgen] |
| pub fn hmac_sha512(key: &str, message: &str) -> String { |
| type HmacSha512 = Hmac<Sha512>; |
|
|
| let mut mac = HmacSha512::new_from_slice(key.as_bytes()) |
| .expect("HMAC key can be any length"); |
| mac.update(message.as_bytes()); |
|
|
| let result = mac.finalize(); |
| format!("{:x}", result.into_bytes()) |
| } |
|
|
| |
| |
| |
| #[wasm_bindgen] |
| pub fn is_valid_sha512_hex(s: &str) -> bool { |
| if s.len() != 128 { |
| return false; |
| } |
| s.chars().all(|c| matches!(c, '0'..='9' | 'a'..='f' | 'A'..='F')) |
| } |
|
|
| |
| |
| |
| #[wasm_bindgen] |
| pub fn is_valid_blake3_hex(s: &str) -> bool { |
| if s.len() != 64 { |
| return false; |
| } |
| s.chars().all(|c| matches!(c, '0'..='9' | 'a'..='f' | 'A'..='F')) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #[wasm_bindgen] |
| pub fn constant_time_compare(a: &str, b: &str) -> bool { |
| |
| let len_match = a.len() == b.len(); |
|
|
| |
| let a_bytes = a.as_bytes(); |
| let b_bytes = b.as_bytes(); |
|
|
| let mut result = 0u8; |
|
|
| |
| |
| for i in 0..a_bytes.len().max(b_bytes.len()) { |
| let byte_a = if i < a_bytes.len() { a_bytes[i] } else { 0 }; |
| let byte_b = if i < b_bytes.len() { b_bytes[i] } else { 0 }; |
| result |= byte_a ^ byte_b; |
| } |
|
|
| |
| len_match && result == 0 |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn test_sha512_deterministic() { |
| let input = "hello"; |
| let hash1 = sha512(input); |
| let hash2 = sha512(input); |
| assert_eq!(hash1, hash2); |
| } |
|
|
| #[test] |
| fn test_sha512_known_value() { |
| |
| let input = "hello"; |
| let hash = sha512(input); |
| |
| assert_eq!(hash.len(), 128); |
| } |
|
|
| #[test] |
| fn test_sha512_empty_string() { |
| let hash = sha512(""); |
| assert_eq!(hash.len(), 128); |
| } |
|
|
| #[test] |
| fn test_sha512_long_input() { |
| let input = "a".repeat(10000); |
| let hash = sha512(&input); |
| assert_eq!(hash.len(), 128); |
| } |
|
|
| #[test] |
| fn test_verify_sha512_correct() { |
| let input = "test"; |
| let hash = sha512(input); |
| assert!(verify_sha512(input, &hash)); |
| } |
|
|
| #[test] |
| fn test_verify_sha512_incorrect() { |
| let input = "test"; |
| let wrong_hash = "0".repeat(128); |
| assert!(!verify_sha512(input, &wrong_hash)); |
| } |
|
|
| #[test] |
| fn test_blake3_deterministic() { |
| let input = "hello"; |
| let hash1 = blake3(input); |
| let hash2 = blake3(input); |
| assert_eq!(hash1, hash2); |
| } |
|
|
| #[test] |
| fn test_blake3_format() { |
| let hash = blake3("test"); |
| assert_eq!(hash.len(), 64); |
| assert!(hash.chars().all(|c| matches!(c, '0'..='9' | 'a'..='f'))); |
| } |
|
|
| #[test] |
| fn test_blake3_empty_string() { |
| let hash = blake3(""); |
| assert_eq!(hash.len(), 64); |
| } |
|
|
| #[test] |
| fn test_verify_blake3_correct() { |
| let input = "test"; |
| let hash = blake3(input); |
| assert!(verify_blake3(input, &hash)); |
| } |
|
|
| #[test] |
| fn test_verify_blake3_incorrect() { |
| let input = "test"; |
| let wrong_hash = "0".repeat(64); |
| assert!(!verify_blake3(input, &wrong_hash)); |
| } |
|
|
| #[test] |
| fn test_hmac_sha512_deterministic() { |
| let key = "secret_key"; |
| let message = "message"; |
| let hmac1 = hmac_sha512(key, message); |
| let hmac2 = hmac_sha512(key, message); |
| assert_eq!(hmac1, hmac2); |
| } |
|
|
| #[test] |
| fn test_hmac_sha512_format() { |
| let hmac = hmac_sha512("key", "message"); |
| assert_eq!(hmac.len(), 128); |
| assert!(hmac.chars().all(|c| matches!(c, '0'..='9' | 'a'..='f'))); |
| } |
|
|
| #[test] |
| fn test_hmac_sha512_key_sensitivity() { |
| let message = "message"; |
| let hmac1 = hmac_sha512("key1", message); |
| let hmac2 = hmac_sha512("key2", message); |
| assert_ne!(hmac1, hmac2); |
| } |
|
|
| #[test] |
| fn test_hmac_sha512_message_sensitivity() { |
| let key = "key"; |
| let hmac1 = hmac_sha512(key, "message1"); |
| let hmac2 = hmac_sha512(key, "message2"); |
| assert_ne!(hmac1, hmac2); |
| } |
|
|
| #[test] |
| fn test_hmac_sha512_empty_key() { |
| let hmac = hmac_sha512("", "message"); |
| assert_eq!(hmac.len(), 128); |
| } |
|
|
| #[test] |
| fn test_hmac_sha512_empty_message() { |
| let hmac = hmac_sha512("key", ""); |
| assert_eq!(hmac.len(), 128); |
| } |
|
|
| #[test] |
| fn test_is_valid_sha512_hex_correct() { |
| let valid = "0".repeat(128); |
| assert!(is_valid_sha512_hex(&valid)); |
| } |
|
|
| #[test] |
| fn test_is_valid_sha512_hex_mixed_case() { |
| |
| let pattern = "aAbBcCdD"; |
| let valid = pattern.repeat(16); |
| assert_eq!(valid.len(), 128); |
| assert!(is_valid_sha512_hex(&valid)); |
| } |
|
|
| #[test] |
| fn test_is_valid_sha512_hex_too_short() { |
| assert!(!is_valid_sha512_hex(&"0".repeat(127))); |
| } |
|
|
| #[test] |
| fn test_is_valid_sha512_hex_too_long() { |
| assert!(!is_valid_sha512_hex(&"0".repeat(129))); |
| } |
|
|
| #[test] |
| fn test_is_valid_sha512_hex_invalid_chars() { |
| let invalid = "0".repeat(127) + "g"; |
| assert!(!is_valid_sha512_hex(&invalid)); |
| } |
|
|
| #[test] |
| fn test_is_valid_blake3_hex_correct() { |
| let valid = "0".repeat(64); |
| assert!(is_valid_blake3_hex(&valid)); |
| } |
|
|
| #[test] |
| fn test_is_valid_blake3_hex_mixed_case() { |
| |
| let pattern = "aAbBcCdD"; |
| let valid = pattern.repeat(8); |
| assert_eq!(valid.len(), 64); |
| assert!(is_valid_blake3_hex(&valid)); |
| } |
|
|
| #[test] |
| fn test_is_valid_blake3_hex_too_short() { |
| assert!(!is_valid_blake3_hex(&"0".repeat(63))); |
| } |
|
|
| #[test] |
| fn test_is_valid_blake3_hex_too_long() { |
| assert!(!is_valid_blake3_hex(&"0".repeat(65))); |
| } |
|
|
| #[test] |
| fn test_is_valid_blake3_hex_invalid_chars() { |
| let invalid = "0".repeat(63) + "g"; |
| assert!(!is_valid_blake3_hex(&invalid)); |
| } |
|
|
| #[test] |
| fn test_constant_time_compare_equal() { |
| assert!(constant_time_compare("hello", "hello")); |
| assert!(constant_time_compare("", "")); |
| } |
|
|
| #[test] |
| fn test_constant_time_compare_not_equal() { |
| assert!(!constant_time_compare("hello", "world")); |
| assert!(!constant_time_compare("a", "b")); |
| } |
|
|
| #[test] |
| fn test_constant_time_compare_different_length() { |
| assert!(!constant_time_compare("hello", "hello world")); |
| assert!(!constant_time_compare("", "a")); |
| } |
|
|
| #[test] |
| fn test_constant_time_compare_case_sensitive() { |
| assert!(!constant_time_compare("Hello", "hello")); |
| } |
|
|
| #[test] |
| fn test_constant_time_compare_unicode() { |
| assert!(constant_time_compare("café", "café")); |
| assert!(!constant_time_compare("cafe", "café")); |
| } |
|
|
| #[test] |
| fn test_constant_time_compare_long_strings() { |
| let s1 = "a".repeat(10000); |
| let s2 = "a".repeat(10000); |
| assert!(constant_time_compare(&s1, &s2)); |
|
|
| let s3 = "a".repeat(9999) + "b"; |
| assert!(!constant_time_compare(&s1, &s3)); |
| } |
|
|
| #[test] |
| fn test_constant_time_compare_hex_strings() { |
| let hex1 = "abc123def456"; |
| let hex2 = "abc123def456"; |
| assert!(constant_time_compare(hex1, hex2)); |
|
|
| let hex3 = "abc123def457"; |
| assert!(!constant_time_compare(hex1, hex3)); |
| } |
| } |
|
|