sov-kernel-monster / frontend /wasm /src /crypto_engine.rs
SNAPKITTYWEST's picture
chore: push full sov-kernel-monster content from local build
9425aed verified
Raw
History Blame Contribute Delete
10.3 kB
/// 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<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())
}
/// 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));
}
}