/// Crypto Engine — deterministic cryptographic operations with timing-safe comparison /// Provides SHA-512, Blake3, HMAC, and cryptographic utilities for secure operations. /// All functions are deterministic (same input = same output) and timing-safe. use wasm_bindgen::prelude::*; use sha2::{Sha512, Digest}; use hmac::{Hmac, Mac}; /// SHA-512 hash /// /// # Arguments /// * `input` - UTF-8 string to hash /// /// # Returns /// SHA-512 hash as 128 hexadecimal characters (512 bits / 4 bits per char) #[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) } /// Verify SHA-512 hash /// /// # Arguments /// * `input` - Original UTF-8 string /// * `hash` - Expected SHA-512 hash (128 hex characters) /// /// # Returns /// true if computed hash matches provided hash (constant-time comparison) #[wasm_bindgen] pub fn verify_sha512(input: &str, hash: &str) -> bool { let computed = sha512(input); constant_time_compare(&computed, hash) } /// Blake3 hash /// /// # Arguments /// * `input` - UTF-8 string to hash /// /// # Returns /// Blake3 hash as 64 hexadecimal characters (256 bits / 4 bits per char) #[wasm_bindgen] pub fn blake3(input: &str) -> String { let hash = blake3::hash(input.as_bytes()); hash.to_hex().to_string() } /// Verify Blake3 hash /// /// # Arguments /// * `input` - Original UTF-8 string /// * `hash` - Expected Blake3 hash (64 hex characters) /// /// # Returns /// true if computed hash matches provided hash (constant-time comparison) #[wasm_bindgen] pub fn verify_blake3(input: &str, hash: &str) -> bool { let computed = blake3(input); constant_time_compare(&computed, hash) } /// HMAC-SHA512 /// /// # Arguments /// * `key` - HMAC key as UTF-8 string /// * `message` - Message to authenticate /// /// # Returns /// HMAC-SHA512 as 128 hexadecimal characters #[wasm_bindgen] pub fn hmac_sha512(key: &str, message: &str) -> String { type HmacSha512 = Hmac; 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()) } /// Validate SHA-512 hex string format /// /// SHA-512 must be exactly 128 hexadecimal characters (512 bits) #[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')) } /// Validate Blake3 hex string format /// /// Blake3 must be exactly 64 hexadecimal characters (256 bits) #[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')) } /// Timing-safe constant-time string comparison /// /// Compares two strings in constant time to prevent timing side-channel attacks. /// Returns true only if strings are identical and same length. /// /// # Arguments /// * `a` - First string /// * `b` - Second string /// /// # Returns /// true if strings are equal (constant-time, safe for cryptographic comparison) #[wasm_bindgen] pub fn constant_time_compare(a: &str, b: &str) -> bool { // First, compare lengths without early exit (constant-time length check) let len_match = a.len() == b.len(); // Always compare full byte sequences to prevent timing leaks let a_bytes = a.as_bytes(); let b_bytes = b.as_bytes(); let mut result = 0u8; // Compare each byte, accumulating differences // This runs in constant time regardless of where differences occur 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; } // result is 0 if all bytes matched 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() { // Known SHA-512 hash of "hello" let input = "hello"; let hash = sha512(input); // Note: just verify it's 128 chars and deterministic 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() { // 128 chars = 32 * 4 char pattern let pattern = "aAbBcCdD"; // 8 chars let valid = pattern.repeat(16); // 8 * 16 = 128 chars 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"; // 'g' is not hex 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() { // 64 chars = 8 * 8 char pattern let pattern = "aAbBcCdD"; // 8 chars let valid = pattern.repeat(8); // 8 * 8 = 64 chars 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)); } }