RTIX / src /core /crypto.rs
github-actions
deploy: clean backend production release
c33971d
#![allow(deprecated)]
use aes_gcm::{
aead::{Aead, KeyInit, OsRng},
Aes256Gcm, Key, Nonce,
};
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
use once_cell::sync::Lazy;
use rand::RngCore;
use std::env;
static MASTER_KEY: Lazy<Vec<u8>> = Lazy::new(|| {
let key_str = env::var("RTIX_MASTER_KEY")
.or_else(|_| env::var("SOVEREIGN_MASTER_KEY"))
.or_else(|_| env::var("VANTIX_MASTER_KEY"))
.unwrap_or_else(|_| "01234567890123456789012345678901".to_string());
key_str.as_bytes().to_vec()
});
pub struct CryptoService;
impl CryptoService {
pub fn encrypt(data: &str) -> String {
if data.is_empty() {
return String::new();
}
let key = Key::<Aes256Gcm>::from_slice(&MASTER_KEY);
let cipher = Aes256Gcm::new(key);
// Institutional Grade: Use unique nonce per encryption
let mut nonce_bytes = [0u8; 12];
OsRng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from(nonce_bytes);
match cipher.encrypt(&nonce, data.as_bytes().as_ref()) {
Ok(ciphertext) => {
// Prepend nonce to ciphertext for decryption
let mut combined = nonce_bytes.to_vec();
combined.extend_from_slice(&ciphertext);
BASE64.encode(combined)
}
Err(_) => data.to_string(),
}
}
pub fn decrypt(encrypted_data: &str) -> String {
if encrypted_data.is_empty() {
return String::new();
}
let decoded = match BASE64.decode(encrypted_data) {
Ok(d) => d,
Err(_) => return encrypted_data.to_string(),
};
if decoded.len() < 12 {
return encrypted_data.to_string();
}
let key = Key::<Aes256Gcm>::from_slice(&MASTER_KEY);
let cipher = Aes256Gcm::new(key);
// Check if it's new-style (with prepended nonce) or old-style (static nonce)
// This is a heuristic: if we can't decrypt with prepended nonce, try static.
// Try new-style first
let (nonce_bytes, ciphertext) = decoded.split_at(12);
let nonce = Nonce::clone_from_slice(nonce_bytes);
match cipher.decrypt(&nonce, ciphertext.as_ref()) {
Ok(plaintext) => {
String::from_utf8(plaintext).unwrap_or_else(|_| encrypted_data.to_string())
}
Err(_) => {
// Fallback to old-style static nonce
let static_nonce = Nonce::clone_from_slice(b"unique nonce 12");
match cipher.decrypt(&static_nonce, decoded.as_ref()) {
Ok(plaintext) => {
String::from_utf8(plaintext).unwrap_or_else(|_| encrypted_data.to_string())
}
Err(_) => encrypted_data.to_string(),
}
}
}
}
pub fn deterministic_hash(data: &str) -> String {
use hmac::{Hmac, Mac};
use sha2::Sha256;
if data.is_empty() {
return String::new();
}
type HmacSha256 = Hmac<Sha256>;
let mut mac = <HmacSha256 as Mac>::new_from_slice(&MASTER_KEY)
.expect("HMAC key initialization error");
mac.update(data.as_bytes());
hex::encode(mac.finalize().into_bytes())
}
pub fn generate_zk_telemetry_proof(
transaction_id: &str,
media_bytes: &[u8],
lat: f64,
lng: f64,
) -> serde_json::Value {
use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256};
// 1. Calculate SHA-256 hash commit of media bytes
let mut hasher = Sha256::new();
hasher.update(media_bytes);
let hash_commit = hex::encode(hasher.finalize());
// 2. Create the telemetry record
let timestamp = chrono::Utc::now().to_rfc3339();
let telemetry_payload = format!(
"txnid:{};hash:{};gps:{:.6},{:.6};time:{}",
transaction_id, hash_commit, lat, lng, timestamp
);
// 3. Sign the telemetry payload using HMAC-SHA256
type HmacSha256 = Hmac<Sha256>;
let mut mac = <HmacSha256 as Mac>::new_from_slice(&MASTER_KEY)
.expect("HMAC key initialization error");
mac.update(telemetry_payload.as_bytes());
let signature = hex::encode(mac.finalize().into_bytes());
serde_json::json!({
"proof_type": "ZK_TELEMETRY",
"hash_commit": hash_commit,
"telemetry_payload": telemetry_payload,
"telemetry_signature": signature,
"gps_coordinates": {
"lat": lat,
"lng": lng
},
"timestamp": timestamp,
"is_zero_storage": true
})
}
}