#[cfg(feature = "cuda-runtime")]
use super::pipeline::GpuProofPipeline;
#[cfg(feature = "cuda-runtime")]
use super::cuda_executor::CudaFftError;
use std::time::{Duration, Instant};
#[cfg(feature = "cuda-runtime")]
use std::collections::HashMap;
#[cfg(feature = "cuda-runtime")]
use std::sync::{Arc, Mutex};
pub type UserId = u64;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SessionState {
New,
Active {
started_at: Instant,
},
Idle {
idle_since: Instant,
},
Expired,
Dead,
}
impl SessionState {
pub fn is_usable(&self) -> bool {
matches!(self, SessionState::New | SessionState::Idle { .. })
}
pub fn is_active(&self) -> bool {
matches!(self, SessionState::Active { .. })
}
}
#[derive(Debug, Clone)]
pub enum SecureSessionError {
SessionExpired,
InvalidState(String),
GpuError(String),
CryptoError(String),
AttestationError(String),
MaxRetriesExceeded,
SessionNotFound(UserId),
ResourceLimitExceeded(String),
}
#[cfg(feature = "cuda-runtime")]
impl From<CudaFftError> for SecureSessionError {
fn from(e: CudaFftError) -> Self {
SecureSessionError::GpuError(format!("{:?}", e))
}
}
impl std::fmt::Display for SecureSessionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SecureSessionError::SessionExpired => write!(f, "Session has expired"),
SecureSessionError::InvalidState(msg) => write!(f, "Invalid session state: {}", msg),
SecureSessionError::GpuError(msg) => write!(f, "GPU error: {}", msg),
SecureSessionError::CryptoError(msg) => write!(f, "Crypto error: {}", msg),
SecureSessionError::AttestationError(msg) => write!(f, "Attestation error: {}", msg),
SecureSessionError::MaxRetriesExceeded => write!(f, "Maximum retries exceeded"),
SecureSessionError::SessionNotFound(id) => write!(f, "Session not found: {}", id),
SecureSessionError::ResourceLimitExceeded(msg) => write!(f, "Resource limit exceeded: {}", msg),
}
}
}
impl std::error::Error for SecureSessionError {}
#[derive(Debug)]
pub struct TeeContext {
session_key: [u8; 32],
attestation_quote: Vec<u8>,
user_id: UserId,
}
impl TeeContext {
pub fn new(user_id: UserId) -> Self {
Self::new_software(user_id)
}
pub fn new_software(user_id: UserId) -> Self {
let session_key = Self::generate_session_key(user_id);
Self {
session_key,
attestation_quote: Vec::new(), user_id,
}
}
pub fn new_with_attestation(user_id: UserId, attestation_quote: Vec<u8>) -> Self {
let session_key = Self::generate_session_key(user_id);
Self {
session_key,
attestation_quote,
user_id,
}
}
fn generate_session_key(_user_id: UserId) -> [u8; 32] {
#[cfg(feature = "cuda-runtime")]
{
let mut key = [0u8; 32];
getrandom::getrandom(&mut key).expect("OS CSPRNG (getrandom) should not fail");
return key;
}
#[cfg(not(feature = "cuda-runtime"))]
{
use blake2::{Blake2s256, Digest};
let mut hasher = Blake2s256::new();
hasher.update(&std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos()
.to_le_bytes());
hasher.update(&std::process::id().to_le_bytes());
hasher.update(&_user_id.to_le_bytes());
let key_var: u64 = 0;
hasher.update(&(&key_var as *const _ as usize).to_le_bytes());
hasher.update(format!("{:?}", std::thread::current().id()).as_bytes());
hasher.finalize().into()
}
}
#[cfg(feature = "cuda-runtime")]
pub fn decrypt(&self, encrypted_data: &[u8]) -> Result<Vec<u8>, SecureSessionError> {
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Nonce,
};
if encrypted_data.len() < 28 {
return Err(SecureSessionError::CryptoError(
"Encrypted data too short (minimum 28 bytes for nonce + tag)".into()
));
}
let nonce = Nonce::from_slice(&encrypted_data[..12]);
let ciphertext_with_tag = &encrypted_data[12..];
let cipher = Aes256Gcm::new_from_slice(&self.session_key)
.map_err(|e| SecureSessionError::CryptoError(format!("Invalid key: {}", e)))?;
let plaintext = cipher.decrypt(nonce, ciphertext_with_tag)
.map_err(|_| SecureSessionError::CryptoError(
"Decryption failed: invalid ciphertext or authentication tag".into()
))?;
Ok(plaintext)
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn decrypt(&self, _encrypted_data: &[u8]) -> Result<Vec<u8>, SecureSessionError> {
Err(SecureSessionError::CryptoError(
"AES-GCM encryption requires cuda-runtime feature".into()
))
}
#[cfg(feature = "cuda-runtime")]
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, SecureSessionError> {
use aes_gcm::{
aead::{Aead, KeyInit},
Aes256Gcm, Nonce,
};
let mut nonce_bytes = [0u8; 12];
getrandom::getrandom(&mut nonce_bytes)
.map_err(|e| SecureSessionError::CryptoError(format!("Failed to generate nonce: {}", e)))?;
let nonce = Nonce::from_slice(&nonce_bytes);
let cipher = Aes256Gcm::new_from_slice(&self.session_key)
.map_err(|e| SecureSessionError::CryptoError(format!("Invalid key: {}", e)))?;
let ciphertext_with_tag = cipher.encrypt(nonce, plaintext)
.map_err(|e| SecureSessionError::CryptoError(format!("Encryption failed: {}", e)))?;
let mut result = Vec::with_capacity(12 + ciphertext_with_tag.len());
result.extend_from_slice(&nonce_bytes);
result.extend_from_slice(&ciphertext_with_tag);
Ok(result)
}
#[cfg(not(feature = "cuda-runtime"))]
pub fn encrypt(&self, _plaintext: &[u8]) -> Result<Vec<u8>, SecureSessionError> {
Err(SecureSessionError::CryptoError(
"AES-GCM encryption requires cuda-runtime feature".into()
))
}
pub fn attestation_quote(&self) -> &[u8] {
&self.attestation_quote
}
pub fn user_id(&self) -> UserId {
self.user_id
}
pub fn secure_destroy(&mut self) {
for byte in self.session_key.iter_mut() {
unsafe { std::ptr::write_volatile(byte, 0); }
}
std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);
self.attestation_quote.clear();
}
}
impl Drop for TeeContext {
fn drop(&mut self) {
self.secure_destroy();
}
}
#[cfg(feature = "cuda-runtime")]
pub struct GpuSecureSession {
user_id: UserId,
tee_context: TeeContext,
pipeline: GpuProofPipeline,
state: SessionState,
created_at: Instant,
proofs_generated: u64,
config: SessionConfig,
}
#[derive(Debug, Clone)]
pub struct SessionConfig {
pub idle_timeout: Duration,
pub active_timeout: Duration,
pub max_retries: u32,
pub secure_clear: bool,
}
impl Default for SessionConfig {
fn default() -> Self {
Self {
idle_timeout: Duration::from_secs(300), active_timeout: Duration::from_secs(3600), max_retries: 3,
secure_clear: true,
}
}
}
#[cfg(feature = "cuda-runtime")]
impl GpuSecureSession {
pub fn new(user_id: UserId, log_size: u32) -> Result<Self, SecureSessionError> {
Self::new_with_config(user_id, log_size, SessionConfig::default())
}
pub fn new_with_config(
user_id: UserId,
log_size: u32,
config: SessionConfig,
) -> Result<Self, SecureSessionError> {
tracing::info!(
user_id = user_id,
log_size = log_size,
"Creating new GpuSecureSession"
);
let tee_context = TeeContext::new(user_id);
let pipeline = GpuProofPipeline::new(log_size)?;
Ok(Self {
user_id,
tee_context,
pipeline,
state: SessionState::New,
created_at: Instant::now(),
proofs_generated: 0,
config,
})
}
pub fn user_id(&self) -> UserId {
self.user_id
}
pub fn state(&self) -> &SessionState {
&self.state
}
pub fn proofs_generated(&self) -> u64 {
self.proofs_generated
}
pub fn uptime(&self) -> Duration {
self.created_at.elapsed()
}
pub fn attestation_quote(&self) -> &[u8] {
self.tee_context.attestation_quote()
}
pub fn check_timeout(&mut self) -> bool {
match &self.state {
SessionState::Idle { idle_since } => {
if idle_since.elapsed() > self.config.idle_timeout {
tracing::warn!(
user_id = self.user_id,
idle_duration = ?idle_since.elapsed(),
"Session idle timeout"
);
self.state = SessionState::Expired;
return true;
}
}
SessionState::Active { started_at } => {
if started_at.elapsed() > self.config.active_timeout {
tracing::error!(
user_id = self.user_id,
active_duration = ?started_at.elapsed(),
"Session active timeout - proof taking too long"
);
self.state = SessionState::Expired;
return true;
}
}
_ => {}
}
false
}
pub fn upload_trace<'a>(
&mut self,
polynomials: impl Iterator<Item = &'a [u32]>,
) -> Result<usize, SecureSessionError> {
if !self.state.is_usable() {
return Err(SecureSessionError::InvalidState(
format!("Cannot upload in state {:?}", self.state)
));
}
if self.check_timeout() {
return Err(SecureSessionError::SessionExpired);
}
self.state = SessionState::Active {
started_at: Instant::now(),
};
let num_polys = self.pipeline.upload_polynomials_bulk(polynomials)?;
tracing::debug!(
user_id = self.user_id,
num_polynomials = num_polys,
"Uploaded trace to GPU"
);
Ok(num_polys)
}
pub fn upload_encrypted_trace(
&mut self,
encrypted_data: &[u8],
num_polynomials: usize,
) -> Result<usize, SecureSessionError> {
if !self.state.is_usable() {
return Err(SecureSessionError::InvalidState(
format!("Cannot upload in state {:?}", self.state)
));
}
if self.check_timeout() {
return Err(SecureSessionError::SessionExpired);
}
self.state = SessionState::Active {
started_at: Instant::now(),
};
let decrypted = self.tee_context.decrypt(encrypted_data)?;
let n = 1usize << self.pipeline.log_size();
let poly_size_bytes = n * 4;
if decrypted.len() != num_polynomials * poly_size_bytes {
return Err(SecureSessionError::CryptoError(format!(
"Decrypted size {} doesn't match expected {} polynomials × {} bytes",
decrypted.len(),
num_polynomials,
poly_size_bytes
)));
}
for i in 0..num_polynomials {
let start = i * poly_size_bytes;
let end = start + poly_size_bytes;
let poly_bytes = &decrypted[start..end];
let poly_u32: Vec<u32> = poly_bytes
.chunks_exact(4)
.map(|chunk| u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
.collect();
self.pipeline.upload_polynomial(&poly_u32)?;
}
tracing::debug!(
user_id = self.user_id,
num_polynomials = num_polynomials,
"Uploaded encrypted trace to GPU (decrypted in TEE)"
);
Ok(num_polynomials)
}
pub fn interpolate_all(&mut self) -> Result<(), SecureSessionError> {
if !self.state.is_active() {
return Err(SecureSessionError::InvalidState(
"Must be in Active state to interpolate".into()
));
}
let num_polys = self.pipeline.num_polynomials();
for i in 0..num_polys {
self.pipeline.ifft_with_denormalize(i)?;
}
tracing::debug!(
user_id = self.user_id,
num_polynomials = num_polys,
"Interpolated all polynomials on GPU"
);
Ok(())
}
pub fn evaluate_all(&mut self) -> Result<(), SecureSessionError> {
if !self.state.is_active() {
return Err(SecureSessionError::InvalidState(
"Must be in Active state to evaluate".into()
));
}
let num_polys = self.pipeline.num_polynomials();
for i in 0..num_polys {
self.pipeline.fft(i)?;
}
tracing::debug!(
user_id = self.user_id,
num_polynomials = num_polys,
"Evaluated all polynomials on GPU"
);
Ok(())
}
pub fn fri_fold(
&mut self,
input_idx: usize,
itwiddles: &[u32],
alpha: &[u32; 4],
) -> Result<usize, SecureSessionError> {
if !self.state.is_active() {
return Err(SecureSessionError::InvalidState(
"Must be in Active state to FRI fold".into()
));
}
let output_idx = self.pipeline.fri_fold_line(input_idx, itwiddles, alpha)?;
Ok(output_idx)
}
pub fn generate_proof(&mut self) -> Result<ProofOutput, SecureSessionError> {
self.generate_proof_with_recovery()
}
fn generate_proof_with_recovery(&mut self) -> Result<ProofOutput, SecureSessionError> {
for attempt in 0..self.config.max_retries {
match self.generate_proof_internal() {
Ok(proof) => return Ok(proof),
Err(e) => {
if Self::is_recoverable_error(&e) {
tracing::warn!(
user_id = self.user_id,
attempt = attempt,
error = %e,
"Proof generation failed, retrying"
);
self.pipeline.sync()?;
continue;
} else {
self.state = SessionState::Dead;
return Err(e);
}
}
}
}
Err(SecureSessionError::MaxRetriesExceeded)
}
fn is_recoverable_error(e: &SecureSessionError) -> bool {
matches!(e, SecureSessionError::GpuError(_))
}
fn generate_proof_internal(&mut self) -> Result<ProofOutput, SecureSessionError> {
if !self.state.is_active() {
return Err(SecureSessionError::InvalidState(
"Must be in Active state to generate proof".into()
));
}
let start = Instant::now();
let num_polys = self.pipeline.num_polynomials();
tracing::info!(
user_id = self.user_id,
num_polynomials = num_polys,
"Starting proof generation on GPU"
);
for i in 0..num_polys {
self.pipeline.ifft_with_denormalize(i)?;
}
for i in 0..num_polys {
self.pipeline.fft(i)?;
}
self.pipeline.sync()?;
let polynomial_data = self.pipeline.download_polynomials_bulk()?;
let duration = start.elapsed();
self.proofs_generated += 1;
self.state = SessionState::Idle {
idle_since: Instant::now(),
};
tracing::info!(
user_id = self.user_id,
duration_ms = duration.as_millis(),
proofs_generated = self.proofs_generated,
"Proof generation complete"
);
Ok(ProofOutput {
polynomial_evaluations: polynomial_data,
duration,
num_polynomials: num_polys,
})
}
pub fn clear_for_next(&mut self) -> Result<(), SecureSessionError> {
if matches!(self.state, SessionState::Dead | SessionState::Expired) {
return Err(SecureSessionError::InvalidState(
"Cannot clear expired/dead session".into()
));
}
self.pipeline.clear_polynomials();
self.state = SessionState::Idle {
idle_since: Instant::now(),
};
tracing::debug!(
user_id = self.user_id,
"Cleared session for next job"
);
Ok(())
}
pub fn secure_destroy(&mut self) {
tracing::info!(
user_id = self.user_id,
proofs_generated = self.proofs_generated,
uptime_secs = self.created_at.elapsed().as_secs(),
"Destroying secure session"
);
self.pipeline.clear_polynomials();
self.tee_context.secure_destroy();
self.state = SessionState::Dead;
}
pub fn pipeline(&self) -> &GpuProofPipeline {
&self.pipeline
}
pub fn pipeline_mut(&mut self) -> &mut GpuProofPipeline {
&mut self.pipeline
}
}
#[cfg(feature = "cuda-runtime")]
impl Drop for GpuSecureSession {
fn drop(&mut self) {
if !matches!(self.state, SessionState::Dead) {
self.secure_destroy();
}
}
}
#[derive(Debug)]
pub struct ProofOutput {
pub polynomial_evaluations: Vec<Vec<u32>>,
pub duration: Duration,
pub num_polynomials: usize,
}
#[cfg(feature = "cuda-runtime")]
pub struct SessionManager {
sessions: HashMap<UserId, GpuSecureSession>,
max_sessions: usize,
default_log_size: u32,
config: SessionConfig,
}
#[cfg(feature = "cuda-runtime")]
impl SessionManager {
pub fn new(max_sessions: usize, default_log_size: u32) -> Self {
Self {
sessions: HashMap::new(),
max_sessions,
default_log_size,
config: SessionConfig::default(),
}
}
pub fn with_config(
max_sessions: usize,
default_log_size: u32,
config: SessionConfig,
) -> Self {
Self {
sessions: HashMap::new(),
max_sessions,
default_log_size,
config,
}
}
pub fn get_or_create_session(
&mut self,
user_id: UserId,
) -> Result<&mut GpuSecureSession, SecureSessionError> {
self.get_or_create_session_with_size(user_id, self.default_log_size)
}
pub fn get_or_create_session_with_size(
&mut self,
user_id: UserId,
log_size: u32,
) -> Result<&mut GpuSecureSession, SecureSessionError> {
self.cleanup_expired();
let should_remove = self.sessions.get(&user_id)
.map(|s| !s.state.is_usable() && !s.state.is_active())
.unwrap_or(false);
if should_remove {
self.sessions.remove(&user_id);
}
if self.sessions.contains_key(&user_id) {
return Ok(self.sessions.get_mut(&user_id).unwrap());
}
if self.sessions.len() >= self.max_sessions {
self.evict_oldest_session()?;
}
let session = GpuSecureSession::new_with_config(
user_id,
log_size,
self.config.clone(),
)?;
self.sessions.insert(user_id, session);
Ok(self.sessions.get_mut(&user_id).unwrap())
}
pub fn destroy_session(&mut self, user_id: UserId) -> Option<()> {
if let Some(mut session) = self.sessions.remove(&user_id) {
session.secure_destroy();
Some(())
} else {
None
}
}
pub fn active_session_count(&self) -> usize {
self.sessions.len()
}
pub fn active_users(&self) -> Vec<UserId> {
self.sessions.keys().copied().collect()
}
fn cleanup_expired(&mut self) {
let expired: Vec<UserId> = self.sessions
.iter_mut()
.filter_map(|(&user_id, session)| {
if session.check_timeout() {
Some(user_id)
} else {
None
}
})
.collect();
for user_id in expired {
if let Some(mut session) = self.sessions.remove(&user_id) {
session.secure_destroy();
}
}
}
fn evict_oldest_session(&mut self) -> Result<(), SecureSessionError> {
let oldest = self.sessions
.iter()
.filter_map(|(&user_id, session)| {
if let SessionState::Idle { idle_since } = &session.state {
Some((user_id, *idle_since))
} else {
None
}
})
.min_by_key(|(_, idle_since)| *idle_since)
.map(|(user_id, _)| user_id);
if let Some(user_id) = oldest {
tracing::info!(
evicted_user = user_id,
"Evicting oldest session to make room"
);
self.destroy_session(user_id);
Ok(())
} else {
Err(SecureSessionError::ResourceLimitExceeded(
"All sessions are active, cannot evict".into()
))
}
}
}
#[cfg(feature = "cuda-runtime")]
impl Drop for SessionManager {
fn drop(&mut self) {
let user_ids: Vec<UserId> = self.sessions.keys().copied().collect();
for user_id in user_ids {
self.destroy_session(user_id);
}
}
}
#[cfg(feature = "cuda-runtime")]
pub type GlobalSessionManager = Arc<Mutex<SessionManager>>;
#[cfg(feature = "cuda-runtime")]
pub fn create_global_session_manager(
max_sessions: usize,
default_log_size: u32,
) -> GlobalSessionManager {
Arc::new(Mutex::new(SessionManager::new(max_sessions, default_log_size)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tee_context_creation() {
let ctx = TeeContext::new(12345);
assert_eq!(ctx.user_id, 12345);
assert!(ctx.attestation_quote.is_empty());
assert_ne!(ctx.session_key, [0u8; 32]);
}
#[test]
#[cfg(feature = "cuda-runtime")]
fn test_tee_encrypt_decrypt_aes_gcm() {
let ctx = TeeContext::new(12345);
let plaintext = b"Hello, World!";
let encrypted = ctx.encrypt(plaintext).unwrap();
assert_eq!(encrypted.len(), 12 + plaintext.len() + 16);
assert_ne!(&encrypted[12..], plaintext);
let decrypted = ctx.decrypt(&encrypted).unwrap();
assert_eq!(&decrypted, plaintext);
}
#[test]
#[cfg(feature = "cuda-runtime")]
fn test_tee_decrypt_detects_tampering() {
let ctx = TeeContext::new(12345);
let plaintext = b"Sensitive data";
let mut encrypted = ctx.encrypt(plaintext).unwrap();
if encrypted.len() > 15 {
encrypted[15] ^= 0xFF;
}
let result = ctx.decrypt(&encrypted);
assert!(result.is_err());
}
#[test]
#[cfg(feature = "cuda-runtime")]
fn test_tee_decrypt_rejects_short_input() {
let ctx = TeeContext::new(12345);
let short_data = vec![0u8; 20];
let result = ctx.decrypt(&short_data);
assert!(result.is_err());
}
#[test]
fn test_session_state_transitions() {
assert!(SessionState::New.is_usable());
assert!(!SessionState::New.is_active());
let active = SessionState::Active { started_at: Instant::now() };
assert!(!active.is_usable());
assert!(active.is_active());
let idle = SessionState::Idle { idle_since: Instant::now() };
assert!(idle.is_usable());
assert!(!idle.is_active());
assert!(!SessionState::Expired.is_usable());
assert!(!SessionState::Dead.is_usable());
}
#[test]
fn test_session_config_defaults() {
let config = SessionConfig::default();
assert_eq!(config.idle_timeout, Duration::from_secs(300));
assert_eq!(config.active_timeout, Duration::from_secs(3600));
assert_eq!(config.max_retries, 3);
assert!(config.secure_clear);
}
}