use crate::error::{Result, SpliceError};
use crate::proof::data_structures::{GraphSnapshot, ProofChecksums, RefactoringProof};
use sha2::{Digest, Sha256};
use std::path::Path;
pub fn compute_snapshot_hash(snapshot: &GraphSnapshot) -> Result<String> {
let json = serde_json::to_string(snapshot)
.map_err(|e| SpliceError::Other(format!("Failed to serialize snapshot: {}", e)))?;
let mut hasher = Sha256::new();
hasher.update(json.as_bytes());
let hash = hasher.finalize();
Ok(format!("{:x}", hash))
}
pub fn compute_proof_checksums(proof: &RefactoringProof) -> Result<ProofChecksums> {
let before_hash = compute_snapshot_hash(&proof.before)?;
let after_hash = compute_snapshot_hash(&proof.after)?;
let proof_hash_input = format!(
"{}:{}:{}",
serde_json::to_string(&proof.metadata)
.map_err(|e| SpliceError::Other(format!("Failed to serialize metadata: {}", e)))?,
serde_json::to_string(&proof.invariants)
.map_err(|e| SpliceError::Other(format!("Failed to serialize invariants: {}", e)))?,
proof.before.timestamp );
let mut hasher = Sha256::new();
hasher.update(proof_hash_input.as_bytes());
let proof_hash = format!("{:x}", hasher.finalize());
Ok(ProofChecksums {
before_hash,
after_hash,
proof_hash,
})
}
pub fn validate_proof_checksums(proof: &RefactoringProof) -> Result<bool> {
let checksums = match &proof.checksums {
Some(c) => c,
None => return Ok(false), };
let current = compute_proof_checksums(proof)?;
if checksums.before_hash != current.before_hash {
return Err(SpliceError::Other(format!(
"Before snapshot hash mismatch: expected {}, got {}",
checksums.before_hash, current.before_hash
)));
}
if checksums.after_hash != current.after_hash {
return Err(SpliceError::Other(format!(
"After snapshot hash mismatch: expected {}, got {}",
checksums.after_hash, current.after_hash
)));
}
if checksums.proof_hash != current.proof_hash {
return Err(SpliceError::Other(format!(
"Proof hash mismatch: expected {}, got {}",
checksums.proof_hash, current.proof_hash
)));
}
Ok(true)
}
pub fn validate_proof_file(proof_path: &Path) -> Result<bool> {
let json = std::fs::read_to_string(proof_path).map_err(|e| SpliceError::Io {
path: proof_path.to_path_buf(),
source: e,
})?;
let proof: RefactoringProof = serde_json::from_str(&json)
.map_err(|e| SpliceError::Other(format!("Failed to deserialize proof: {}", e)))?;
validate_proof_checksums(&proof)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::proof::data_structures::{GraphStats, ProofMetadata};
use std::collections::HashMap;
use std::path::PathBuf;
#[test]
fn test_compute_snapshot_hash() {
let snapshot = GraphSnapshot {
timestamp: 1234567890,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
};
let hash = compute_snapshot_hash(&snapshot).unwrap();
assert_eq!(hash.len(), 64); assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_snapshot_hash_consistency() {
let snapshot = GraphSnapshot {
timestamp: 1234567890,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
};
let hash1 = compute_snapshot_hash(&snapshot).unwrap();
let hash2 = compute_snapshot_hash(&snapshot).unwrap();
assert_eq!(hash1, hash2);
}
#[test]
fn test_snapshot_hash_uniqueness() {
let snapshot1 = GraphSnapshot {
timestamp: 1234567890,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
};
let snapshot2 = GraphSnapshot {
timestamp: 1234567891, symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
};
let hash1 = compute_snapshot_hash(&snapshot1).unwrap();
let hash2 = compute_snapshot_hash(&snapshot2).unwrap();
assert_ne!(hash1, hash2);
}
#[test]
fn test_compute_proof_checksums() {
let metadata = ProofMetadata {
operation: "test".to_string(),
user: None,
timestamp: 1234567890,
git_commit: None,
splice_version: "2.2.4".to_string(),
database_path: PathBuf::from("/test/db"),
};
let before = GraphSnapshot {
timestamp: 1234567890,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
};
let after = GraphSnapshot {
timestamp: 1234567891,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
};
let proof = RefactoringProof {
metadata,
before,
after,
invariants: vec![],
checksums: None,
};
let checksums = compute_proof_checksums(&proof).unwrap();
assert_eq!(checksums.before_hash.len(), 64);
assert_eq!(checksums.after_hash.len(), 64);
assert_eq!(checksums.proof_hash.len(), 64);
assert!(checksums.before_hash.chars().all(|c| c.is_ascii_hexdigit()));
assert!(checksums.after_hash.chars().all(|c| c.is_ascii_hexdigit()));
assert!(checksums.proof_hash.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn test_validate_proof_checksums_missing() {
let proof = RefactoringProof {
metadata: ProofMetadata {
operation: "test".to_string(),
user: None,
timestamp: 1234567890,
git_commit: None,
splice_version: "2.2.4".to_string(),
database_path: PathBuf::from("/test/db"),
},
before: GraphSnapshot {
timestamp: 1234567890,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
},
after: GraphSnapshot {
timestamp: 1234567891,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
},
invariants: vec![],
checksums: None,
};
let result = validate_proof_checksums(&proof).unwrap();
assert!(!result); }
#[test]
fn test_validate_proof_checksums_valid() {
let metadata = ProofMetadata {
operation: "test".to_string(),
user: None,
timestamp: 1234567890,
git_commit: None,
splice_version: "2.2.4".to_string(),
database_path: PathBuf::from("/test/db"),
};
let before = GraphSnapshot {
timestamp: 1234567890,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
};
let after = GraphSnapshot {
timestamp: 1234567891,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
};
let mut proof = RefactoringProof {
metadata,
before,
after,
invariants: vec![],
checksums: None,
};
proof.checksums = Some(compute_proof_checksums(&proof).unwrap());
let result = validate_proof_checksums(&proof).unwrap();
assert!(result);
}
#[test]
fn test_validate_proof_checksums_invalid() {
let metadata = ProofMetadata {
operation: "test".to_string(),
user: None,
timestamp: 1234567890,
git_commit: None,
splice_version: "2.2.4".to_string(),
database_path: PathBuf::from("/test/db"),
};
let before = GraphSnapshot {
timestamp: 1234567890,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
};
let after = GraphSnapshot {
timestamp: 1234567891,
symbols: HashMap::new(),
edges: HashMap::new(),
entry_points: vec![],
stats: GraphStats {
total_symbols: 0,
total_edges: 0,
entry_point_count: 0,
max_complexity: None,
},
};
let mut proof = RefactoringProof {
metadata,
before,
after,
invariants: vec![],
checksums: None,
};
let checksums = compute_proof_checksums(&proof).unwrap();
let tampered_checksums = ProofChecksums {
before_hash: "0".repeat(64), after_hash: checksums.after_hash,
proof_hash: checksums.proof_hash,
};
proof.checksums = Some(tampered_checksums);
let result = validate_proof_checksums(&proof);
assert!(result.is_err());
}
}