use data_encoding::BASE32;
use ed25519_dalek::{SigningKey, VerifyingKey};
use freenet_scaffold::util::{fast_hash, FastHash};
use river_core::room_state::member::MemberId;
use std::collections::HashMap;
#[cfg(test)]
mod collision_tests {
use super::*;
use rand::rngs::OsRng;
#[test]
fn test_memberid_collision_analysis() {
println!("\n=== MemberId Collision Analysis ===");
let signing_key = SigningKey::generate(&mut OsRng);
let verifying_key = VerifyingKey::from(&signing_key);
let member_id: MemberId = verifying_key.into();
println!("1. MemberId Creation Process:");
println!(
" VerifyingKey (32 bytes): {}",
hex::encode(verifying_key.as_bytes())
);
println!(" FastHash value (i64): {}", member_id.0 .0);
println!(" Display string (8 chars): {}", member_id);
println!(" Debug format: {:?}", member_id);
let hash_bytes = member_id.0 .0.to_le_bytes();
let base32_full = BASE32.encode(&hash_bytes);
let base32_truncated = base32_full.chars().take(8).collect::<String>();
println!("\n2. Truncation Process:");
println!(" i64 as bytes: {:?}", hash_bytes);
println!(" Full BASE32: {}", base32_full);
println!(" Truncated (8 chars): {}", base32_truncated);
assert_eq!(format!("{}", member_id), base32_truncated);
println!("\n3. Collision Testing:");
let mut display_strings: HashMap<String, (Vec<u8>, i64)> = HashMap::new();
let mut collision_found = false;
for i in 0..10000 {
let signing_key = SigningKey::generate(&mut OsRng);
let verifying_key = VerifyingKey::from(&signing_key);
let member_id: MemberId = verifying_key.into();
let display = format!("{}", member_id);
if let Some((existing_vk, existing_hash)) = display_strings.get(&display) {
println!(" COLLISION FOUND after {} iterations!", i + 1);
println!(" Display string: {}", display);
println!(" Key 1: {}", hex::encode(existing_vk));
println!(" Key 2: {}", hex::encode(verifying_key.as_bytes()));
println!(" Hash 1: {}", existing_hash);
println!(" Hash 2: {}", member_id.0 .0);
collision_found = true;
break;
}
display_strings.insert(display, (verifying_key.as_bytes().to_vec(), member_id.0 .0));
}
if !collision_found {
println!(" No display collisions found in 10,000 samples");
}
println!("\n4. Display String Collision Demonstration:");
let base_hash = 0x123456789ABCDEF0_i64;
let test_values = [
base_hash,
base_hash ^ (0xFF_i64 << 56), base_hash ^ (0xFF00_i64 << 48), ];
for (i, &hash_val) in test_values.iter().enumerate() {
let member_id = MemberId(FastHash(hash_val));
let display = format!("{}", member_id);
println!(" Hash {}: {} -> {}", i + 1, hash_val, display);
}
println!("\n5. Collision Analysis:");
println!(" - Display uses 8 BASE32 chars = 8 * 5 = 40 bits");
println!(" - 2^40 = 1,099,511,627,776 possible display strings");
println!(
" - Birthday paradox: ~50% collision chance with sqrt(2^40) ≈ 2^20 ≈ 1M samples"
);
println!(
" - With ed25519 keys (2^256 space), display collisions are inevitable at scale"
);
println!("\n6. Security Implications:");
println!(" - Different VerifyingKeys can have identical display strings");
println!(" - Internal FastHash values remain different (no logical collision)");
println!(" - UI may show multiple users with same 8-character ID");
println!(" - Could cause user confusion in member lists/logs");
println!(" - Code comment about '3 * 10^59 years' refers to full key collision");
println!(" - Display string collisions are much more likely!");
}
#[test]
fn test_fasthash_properties() {
println!("\n=== FastHash Algorithm Analysis ===");
let test_data = [
&[0x00][..],
&[0xFF][..],
&[0x00, 0x01][..],
&[0x01, 0x00][..],
b"hello",
b"world",
];
for data in test_data {
let hash = fast_hash(data);
println!("Data: {:?} -> Hash: {}", data, hash.0);
}
let mut hash_map = HashMap::new();
for i in 0..1000 {
let data = [i as u8];
let hash = fast_hash(&data);
let bucket = (hash.0 % 10) as usize;
*hash_map.entry(bucket).or_insert(0) += 1;
}
println!("\nHash distribution across 10 buckets (1000 samples):");
for i in 0..10 {
println!(" Bucket {}: {} items", i, hash_map.get(&i).unwrap_or(&0));
}
}
}
fn hex_encode(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
mod hex {
pub fn encode(bytes: &[u8]) -> String {
super::hex_encode(bytes)
}
}