use qudag_protocol::{
node::Node,
message::Message,
config::Config,
state::State,
validation::Validator,
};
use std::time::{Duration, Instant};
use std::collections::{HashMap, HashSet};
use proptest::prelude::*;
#[cfg(test)]
mod protocol_security_tests {
use super::*;
#[test]
fn test_protocol_state_integrity() {
let config = Config::default();
let mut node = Node::new(config);
let initial_state = node.get_state().clone();
let messages = vec![
Message::new("test1", vec![1, 2, 3]),
Message::new("test2", vec![4, 5, 6]),
Message::new("test3", vec![7, 8, 9]),
];
for msg in messages {
let pre_state = node.get_state().clone();
node.process_message(msg);
let post_state = node.get_state().clone();
assert!(post_state.height() >= pre_state.height(),
"State height decreased: {} -> {}", pre_state.height(), post_state.height());
if pre_state.height() > 0 {
assert!(post_state.previous_hash() == pre_state.current_hash(),
"State hash chain broken");
}
}
}
#[test]
fn test_message_validation_bypass_attempts() {
let config = Config::default();
let validator = Validator::new(config);
let malicious_messages = vec![
Message::new("oversized", vec![0u8; 1024 * 1024]),
Message::new("", vec![1, 2, 3]),
Message::new(&"x".repeat(1000), vec![1, 2, 3]),
Message::new("test\0bypass", vec![1, 2, 3]),
Message::new("test\u{202e}bypass", vec![1, 2, 3]),
Message::new("test\x00\x01\x02", vec![1, 2, 3]),
];
for (i, msg) in malicious_messages.iter().enumerate() {
let result = validator.validate_message(msg);
assert!(result.is_err(),
"Malicious message {} was accepted: {:?}", i, msg);
let error_msg = format!("{:?}", result.unwrap_err());
assert!(!error_msg.contains("internal"),
"Error message leaks internal information: {}", error_msg);
}
}
#[test]
fn test_replay_attack_prevention() {
let config = Config::default();
let mut node = Node::new(config);
let msg = Message::new("test_replay", vec![1, 2, 3]);
let result1 = node.process_message(msg.clone());
assert!(result1.is_ok(), "First message processing failed");
let result2 = node.process_message(msg.clone());
assert!(result2.is_err(), "Replay attack succeeded");
let mut modified_msg = msg.clone();
modified_msg.set_timestamp(Instant::now());
let result3 = node.process_message(modified_msg);
assert!(result3.is_err(), "Replay attack with modified timestamp succeeded");
}
#[test]
fn test_denial_of_service_resistance() {
let config = Config::default();
let mut node = Node::new(config);
let start_time = Instant::now();
let mut processed_count = 0;
let mut rejected_count = 0;
for i in 0..1000 {
let msg = Message::new(&format!("flood_{}", i), vec![i as u8]);
let result = node.process_message(msg);
match result {
Ok(_) => processed_count += 1,
Err(_) => rejected_count += 1,
}
if start_time.elapsed() > Duration::from_secs(5) {
break;
}
}
assert!(rejected_count > 0, "No rate limiting detected");
assert!(processed_count > 0, "All messages rejected - overly aggressive limiting");
println!("DoS test: {} processed, {} rejected in {:?}",
processed_count, rejected_count, start_time.elapsed());
}
#[test]
fn test_memory_exhaustion_prevention() {
let config = Config::default();
let mut node = Node::new(config);
let initial_memory = get_memory_usage();
for i in 0..100 {
let large_payload = vec![0u8; 1024 * 1024]; let msg = Message::new(&format!("large_{}", i), large_payload);
let result = node.process_message(msg);
let current_memory = get_memory_usage();
let memory_increase = current_memory - initial_memory;
if memory_increase > 50 * 1024 * 1024 { assert!(result.is_err(),
"Memory exhaustion attack succeeded - {} bytes used", memory_increase);
break;
}
}
}
#[test]
fn test_cryptographic_downgrade_prevention() {
let config = Config::default();
let node = Node::new(config);
let weak_configs = vec![
Config::builder().key_size(128).build(), Config::builder().hash_function("md5").build(), Config::builder().signature_scheme("rsa1024").build(), ];
for weak_config in weak_configs {
let result = Node::try_new(weak_config);
assert!(result.is_err(),
"Weak cryptographic configuration accepted");
}
}
#[test]
fn test_side_channel_information_disclosure() {
let config = Config::default();
let mut node = Node::new(config);
let test_messages = vec![
Message::new("short", vec![1]),
Message::new("medium", vec![1; 100]),
Message::new("long", vec![1; 1000]),
];
let mut timing_data = HashMap::new();
for msg in test_messages {
let mut timings = Vec::new();
for _ in 0..100 {
let start = Instant::now();
let _ = node.process_message(msg.clone());
timings.push(start.elapsed());
}
timing_data.insert(msg.id().to_string(), timings);
}
for (id, timings) in timing_data {
let mean = timings.iter().sum::<Duration>() / timings.len() as u32;
let variance = timings.iter()
.map(|t| {
let diff = t.as_nanos() as i128 - mean.as_nanos() as i128;
diff * diff
})
.sum::<i128>() / timings.len() as i128;
let cv = (variance as f64).sqrt() / mean.as_nanos() as f64;
assert!(cv < 0.1,
"High timing variance for {}: CV = {:.3}", id, cv);
}
}
#[test]
fn test_configuration_injection_attacks() {
let config = Config::default();
let mut node = Node::new(config);
let injection_attempts = vec![
Message::new("config_inject", b"key_size=128".to_vec()),
Message::new("config_inject", b"admin=true".to_vec()),
Message::new("config_inject", b"debug=true".to_vec()),
Message::new("config_inject", b"bypass_validation=true".to_vec()),
];
for msg in injection_attempts {
let result = node.process_message(msg);
let current_config = node.get_config();
assert_eq!(current_config.key_size(), Config::default().key_size(),
"Configuration was modified through message injection");
}
}
#[test]
fn test_consensus_manipulation_resistance() {
let config = Config::default();
let mut nodes = vec![
Node::new(config.clone()),
Node::new(config.clone()),
Node::new(config.clone()),
];
let honest_message = Message::new("honest", vec![1, 2, 3]);
let malicious_message = Message::new("malicious", vec![4, 5, 6]);
nodes[0].process_message(honest_message.clone()).unwrap();
nodes[1].process_message(honest_message.clone()).unwrap();
nodes[2].process_message(malicious_message).unwrap();
let consensus = calculate_consensus(&nodes);
assert!(consensus.contains(&honest_message.id()),
"Honest consensus was not maintained");
assert!(!consensus.contains(&"malicious"),
"Malicious message achieved consensus");
}
proptest! {
#[test]
fn prop_message_ordering_invariants(
messages in prop::collection::vec(
(prop::string::string_regex("[a-zA-Z0-9]{1,100}").unwrap(),
prop::collection::vec(any::<u8>(), 0..1000)),
1..50
)
) {
let config = Config::default();
let mut node = Node::new(config);
for (id, payload) in messages {
let msg = Message::new(&id, payload);
let _ = node.process_message(msg);
}
let state = node.get_state();
prop_assert!(state.is_consistent(), "State consistency violated");
prop_assert!(state.height() >= 0, "Invalid state height");
}
#[test]
fn prop_cryptographic_integrity(
payloads in prop::collection::vec(
prop::collection::vec(any::<u8>(), 1..1000),
1..100
)
) {
let config = Config::default();
let mut node = Node::new(config);
for (i, payload) in payloads.iter().enumerate() {
let msg = Message::new(&format!("msg_{}", i), payload.clone());
let result = node.process_message(msg);
if result.is_err() {
let error = result.unwrap_err();
let error_str = format!("{:?}", error);
prop_assert!(
error_str.contains("validation") ||
error_str.contains("security") ||
error_str.contains("rate_limit"),
"Unexpected rejection reason: {}", error_str
);
}
}
}
}
fn get_memory_usage() -> usize {
std::process::id() as usize * 1024 }
fn calculate_consensus(nodes: &[Node]) -> HashSet<String> {
let mut consensus = HashSet::new();
for node in nodes {
let state = node.get_state();
for msg_id in state.message_ids() {
consensus.insert(msg_id.to_string());
}
}
consensus
}
}
mod mocks {
use super::*;
impl Config {
pub fn default() -> Self {
Config {
key_size: 256,
hash_function: "blake3".to_string(),
signature_scheme: "ed25519".to_string(),
}
}
pub fn builder() -> ConfigBuilder {
ConfigBuilder::default()
}
pub fn key_size(&self) -> u32 {
self.key_size
}
}
#[derive(Default)]
pub struct ConfigBuilder {
key_size: Option<u32>,
hash_function: Option<String>,
signature_scheme: Option<String>,
}
impl ConfigBuilder {
pub fn key_size(mut self, size: u32) -> Self {
self.key_size = Some(size);
self
}
pub fn hash_function(mut self, func: &str) -> Self {
self.hash_function = Some(func.to_string());
self
}
pub fn signature_scheme(mut self, scheme: &str) -> Self {
self.signature_scheme = Some(scheme.to_string());
self
}
pub fn build(self) -> Config {
Config {
key_size: self.key_size.unwrap_or(256),
hash_function: self.hash_function.unwrap_or("blake3".to_string()),
signature_scheme: self.signature_scheme.unwrap_or("ed25519".to_string()),
}
}
}
}
pub struct Config {
key_size: u32,
hash_function: String,
signature_scheme: String,
}