//! kHeavyHash Proof of Work algorithm. //! //! The kHeavyHash algorithm is designed to be ASIC-resistant by combining: //! - SHA3-256 for cryptographic hashing //! - Matrix multiplication in GF(2^8) for memory hardness //! - Blake3 for fast nonce mixing //! //! Algorithm steps: //! 1. pre_hash = SHA3-256(header_without_nonce) //! 2. nonce_hash = Blake3(pre_hash || nonce) //! 3. matrix_out = HeavyMatrix × pre_hash (in GF(2^8)) //! 4. mixed = matrix_out XOR nonce_hash //! 5. pow_hash = SHA3-256(mixed) use sha3::{Digest, Sha3_256}; use synor_types::Hash256; use crate::matrix::OptimizedMatrix; use crate::Target; /// The kHeavyHash algorithm implementation. pub struct KHeavyHash { /// The optimized matrix for fast multiplication. matrix: OptimizedMatrix, } impl KHeavyHash { /// Creates a new kHeavyHash instance. pub fn new() -> Self { KHeavyHash { matrix: OptimizedMatrix::new(), } } /// Creates with a custom matrix seed. pub fn with_seed(seed: &[u8; 32]) -> Self { KHeavyHash { matrix: OptimizedMatrix::from_seed(seed), } } /// Computes the full kHeavyHash PoW. /// /// # Arguments /// * `header` - Block header bytes (without nonce) /// * `nonce` - The nonce to try /// /// # Returns /// The PoW hash that must be compared against target. pub fn hash(&self, header: &[u8], nonce: u64) -> PowHash { // Step 1: Pre-hash the header let pre_hash = self.pre_hash(header); // Step 2-5: Compute final PoW hash with nonce self.finalize(&pre_hash, nonce) } /// Pre-hashes the block header (can be cached). /// /// This is SHA3-256 of the header without the nonce. pub fn pre_hash(&self, header: &[u8]) -> Hash256 { let mut hasher = Sha3_256::new(); hasher.update(header); let result: [u8; 32] = hasher.finalize().into(); Hash256::from_bytes(result) } /// Finalizes the PoW hash given a pre-hash and nonce. /// /// This allows mining to cache the pre-hash and only vary the nonce. pub fn finalize(&self, pre_hash: &Hash256, nonce: u64) -> PowHash { // Step 2: Hash nonce with pre_hash using Blake3 let nonce_bytes = nonce.to_le_bytes(); let mut nonce_input = [0u8; 40]; // 32 + 8 nonce_input[..32].copy_from_slice(pre_hash.as_bytes()); nonce_input[32..].copy_from_slice(&nonce_bytes); let nonce_hash: [u8; 32] = blake3::hash(&nonce_input).into(); // Step 3: Matrix multiplication let matrix_out = self.matrix.multiply(pre_hash.as_bytes()); // Step 4: XOR matrix output with nonce hash let mut mixed = [0u8; 32]; for i in 0..32 { mixed[i] = matrix_out[i] ^ nonce_hash[i]; } // Step 5: Final SHA3-256 let mut hasher = Sha3_256::new(); hasher.update(mixed); let final_hash: [u8; 32] = hasher.finalize().into(); PowHash { hash: Hash256::from_bytes(final_hash), nonce, pre_hash: *pre_hash, } } /// Verifies a PoW solution. pub fn verify(&self, header: &[u8], nonce: u64, target: &Target) -> bool { let pow = self.hash(header, nonce); target.is_met_by(&pow.hash) } /// Mines a block by trying nonces until target is met. /// /// # Arguments /// * `header` - Block header bytes /// * `target` - Difficulty target /// * `start_nonce` - Starting nonce /// * `max_tries` - Maximum nonces to try (0 = unlimited) /// /// # Returns /// The solving nonce and PoW hash, or None if not found. pub fn mine( &self, header: &[u8], target: &Target, start_nonce: u64, max_tries: u64, ) -> Option { let pre_hash = self.pre_hash(header); let limit = if max_tries == 0 { u64::MAX } else { start_nonce.saturating_add(max_tries) }; for nonce in start_nonce..limit { let pow = self.finalize(&pre_hash, nonce); if target.is_met_by(&pow.hash) { return Some(pow); } } None } /// Mines with a callback for progress reporting. pub fn mine_with_callback( &self, header: &[u8], target: &Target, start_nonce: u64, max_tries: u64, mut callback: F, ) -> Option where F: FnMut(u64, u64) -> bool, // (nonces_tried, current_nonce) -> continue? { let pre_hash = self.pre_hash(header); let limit = if max_tries == 0 { u64::MAX } else { start_nonce.saturating_add(max_tries) }; let mut tried = 0u64; for nonce in start_nonce..limit { let pow = self.finalize(&pre_hash, nonce); tried += 1; if target.is_met_by(&pow.hash) { return Some(pow); } // Report progress every 10000 hashes if tried % 10000 == 0 && !callback(tried, nonce) { return None; // Cancelled } } None } /// Returns the matrix used by this hasher. pub fn matrix(&self) -> &OptimizedMatrix { &self.matrix } } impl Default for KHeavyHash { fn default() -> Self { Self::new() } } impl Clone for KHeavyHash { fn clone(&self) -> Self { // Matrix is large, but we need to clone for multi-threading KHeavyHash { matrix: OptimizedMatrix::new(), // Recreate from default seed } } } /// Result of a PoW computation. #[derive(Clone, Debug)] pub struct PowHash { /// The final PoW hash. pub hash: Hash256, /// The nonce that produced this hash. pub nonce: u64, /// The pre-hash (for verification). pub pre_hash: Hash256, } impl PowHash { /// Checks if this PoW meets a target. pub fn meets_target(&self, target: &Target) -> bool { target.is_met_by(&self.hash) } /// Returns the hash as bytes. pub fn as_bytes(&self) -> &[u8; 32] { self.hash.as_bytes() } } /// Parallel mining using multiple threads. pub struct ParallelMiner { /// kHeavyHash instances per thread. hashers: Vec, /// Number of threads. num_threads: usize, } impl ParallelMiner { /// Creates a parallel miner with the specified number of threads. pub fn new(num_threads: usize) -> Self { let threads = if num_threads == 0 { num_cpus() } else { num_threads }; let hashers = (0..threads).map(|_| KHeavyHash::new()).collect(); ParallelMiner { hashers, num_threads: threads, } } /// Returns the number of threads. pub fn num_threads(&self) -> usize { self.num_threads } /// Mines using all threads. /// /// Each thread gets a different nonce range to search. pub fn mine(&self, header: &[u8], target: &Target, start_nonce: u64) -> Option { use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; let found = Arc::new(AtomicBool::new(false)); let result = Arc::new(parking_lot::Mutex::new(None)); let nonce_step = u64::MAX / self.num_threads as u64; std::thread::scope(|s| { for (i, hasher) in self.hashers.iter().enumerate() { let thread_start = start_nonce.wrapping_add(i as u64 * nonce_step); let found = Arc::clone(&found); let result = Arc::clone(&result); let header = header.to_vec(); let target = *target; s.spawn(move || { let pre_hash = hasher.pre_hash(&header); let mut nonce = thread_start; loop { // Check if another thread found it if found.load(Ordering::Relaxed) { return; } let pow = hasher.finalize(&pre_hash, nonce); if target.is_met_by(&pow.hash) { found.store(true, Ordering::Relaxed); *result.lock() = Some(pow); return; } nonce = nonce.wrapping_add(1); // Don't wrap into another thread's range if nonce == thread_start.wrapping_add(nonce_step) { return; } } }); } }); Arc::try_unwrap(result).ok()?.into_inner() } } /// Returns the number of CPU cores. fn num_cpus() -> usize { std::thread::available_parallelism() .map(|n| n.get()) .unwrap_or(1) } /// Benchmark helper for hashrate measurement. pub struct HashrateBenchmark { hasher: KHeavyHash, } impl HashrateBenchmark { /// Creates a new benchmark. pub fn new() -> Self { HashrateBenchmark { hasher: KHeavyHash::new(), } } /// Runs a benchmark for the specified duration. /// /// Returns hashes per second. pub fn run(&self, duration_ms: u64) -> f64 { let header = [0x42u8; 80]; // Dummy header let pre_hash = self.hasher.pre_hash(&header); let start = std::time::Instant::now(); let mut count = 0u64; let mut nonce = 0u64; while start.elapsed().as_millis() < duration_ms as u128 { let _ = self.hasher.finalize(&pre_hash, nonce); nonce = nonce.wrapping_add(1); count += 1; } let elapsed = start.elapsed().as_secs_f64(); count as f64 / elapsed } } impl Default for HashrateBenchmark { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_kheavyhash_deterministic() { let hasher = KHeavyHash::new(); let header = b"test block header data"; let nonce = 12345u64; let hash1 = hasher.hash(header, nonce); let hash2 = hasher.hash(header, nonce); assert_eq!(hash1.hash, hash2.hash); assert_eq!(hash1.nonce, hash2.nonce); } #[test] fn test_kheavyhash_deterministic_different_instances() { let hasher1 = KHeavyHash::new(); let hasher2 = KHeavyHash::new(); let header = b"test block header data"; let nonce = 12345u64; let hash1 = hasher1.hash(header, nonce); let hash2 = hasher2.hash(header, nonce); assert_eq!(hash1.hash, hash2.hash); } #[test] fn test_kheavyhash_different_nonces() { let hasher = KHeavyHash::new(); let header = b"test block header data"; let hash1 = hasher.hash(header, 1); let hash2 = hasher.hash(header, 2); assert_ne!(hash1.hash, hash2.hash); } #[test] fn test_kheavyhash_different_nonces_sequential() { let hasher = KHeavyHash::new(); let header = b"sequential nonce test"; let mut hashes = Vec::new(); for nonce in 0..10 { let hash = hasher.hash(header, nonce); hashes.push(hash.hash); } for i in 0..hashes.len() { for j in (i + 1)..hashes.len() { assert_ne!(hashes[i], hashes[j]); } } } #[test] fn test_kheavyhash_different_headers_same_nonce() { let hasher = KHeavyHash::new(); let nonce = 12345u64; let hash1 = hasher.hash(b"header one", nonce); let hash2 = hasher.hash(b"header two", nonce); assert_ne!(hash1.hash, hash2.hash); } #[test] fn test_kheavyhash_different_headers_produce_different_hashes() { let hasher = KHeavyHash::new(); let nonce = 0; let headers = [ b"header A".as_slice(), b"header B".as_slice(), b"header C".as_slice(), b"longer header".as_slice(), ]; let mut hashes = Vec::new(); for header in &headers { let hash = hasher.hash(header, nonce); hashes.push(hash.hash); } for i in 0..hashes.len() { for j in (i + 1)..hashes.len() { assert_ne!(hashes[i], hashes[j]); } } } #[test] fn test_kheavyhash_empty_header() { let hasher = KHeavyHash::new(); let hash = hasher.hash(b"", 0); assert!(hash.hash.as_bytes().len() == 32); } #[test] fn test_kheavyhash_large_header() { let hasher = KHeavyHash::new(); let large_header = vec![0x42u8; 1024]; let hash = hasher.hash(&large_header, 0); assert!(hash.hash.as_bytes().len() == 32); } #[test] fn test_pre_hash_caching() { let hasher = KHeavyHash::new(); let header = b"test block header data"; let pre_hash = hasher.pre_hash(header); let pow1 = hasher.finalize(&pre_hash, 1); let pow2 = hasher.finalize(&pre_hash, 2); assert_eq!(pow1.pre_hash, pow2.pre_hash); assert_ne!(pow1.hash, pow2.hash); } #[test] fn test_pre_hash_deterministic() { let hasher = KHeavyHash::new(); let header = b"deterministic pre-hash test"; let pre_hash1 = hasher.pre_hash(header); let pre_hash2 = hasher.pre_hash(header); assert_eq!(pre_hash1, pre_hash2); } #[test] fn test_pre_hash_different_headers() { let hasher = KHeavyHash::new(); let pre_hash1 = hasher.pre_hash(b"header one"); let pre_hash2 = hasher.pre_hash(b"header two"); assert_ne!(pre_hash1, pre_hash2); } #[test] fn test_finalize_same_pre_hash_different_nonces() { let hasher = KHeavyHash::new(); let pre_hash = hasher.pre_hash(b"test header"); let pow1 = hasher.finalize(&pre_hash, 100); let pow2 = hasher.finalize(&pre_hash, 200); assert_eq!(pow1.pre_hash, pow2.pre_hash); assert_ne!(pow1.hash, pow2.hash); assert_eq!(pow1.nonce, 100); assert_eq!(pow2.nonce, 200); } #[test] fn test_finalize_consistency_with_hash() { let hasher = KHeavyHash::new(); let header = b"consistency test"; let nonce = 42u64; let direct = hasher.hash(header, nonce); let pre_hash = hasher.pre_hash(header); let indirect = hasher.finalize(&pre_hash, nonce); assert_eq!(direct.hash, indirect.hash); assert_eq!(direct.nonce, indirect.nonce); assert_eq!(direct.pre_hash, indirect.pre_hash); } #[test] fn test_verify_valid_solution() { let hasher = KHeavyHash::new(); let header = b"verify test"; let target = Target::max(); let pow = hasher.mine(header, &target, 0, 10000).unwrap(); assert!(hasher.verify(header, pow.nonce, &target)); } #[test] fn test_verify_returns_correct_bool() { let hasher = KHeavyHash::new(); let header = b"verify bool test"; let target = Target::max(); let pow = hasher.mine(header, &target, 0, 10000).unwrap(); let result = hasher.verify(header, pow.nonce, &target); assert!(result); } #[test] fn test_with_seed_creates_different_hasher() { let seed1 = [0x01u8; 32]; let seed2 = [0x02u8; 32]; let hasher1 = KHeavyHash::with_seed(&seed1); let hasher2 = KHeavyHash::with_seed(&seed2); let header = b"seed test"; let nonce = 0u64; let hash1 = hasher1.hash(header, nonce); let hash2 = hasher2.hash(header, nonce); assert_ne!(hash1.hash, hash2.hash); } #[test] fn test_with_seed_deterministic() { let seed = [0x42u8; 32]; let hasher1 = KHeavyHash::with_seed(&seed); let hasher2 = KHeavyHash::with_seed(&seed); let header = b"seed deterministic test"; let nonce = 0u64; let hash1 = hasher1.hash(header, nonce); let hash2 = hasher2.hash(header, nonce); assert_eq!(hash1.hash, hash2.hash); } #[test] fn test_kheavyhash_default_trait() { let hasher1 = KHeavyHash::default(); let hasher2 = KHeavyHash::new(); let header = b"default trait test"; let hash1 = hasher1.hash(header, 0); let hash2 = hasher2.hash(header, 0); assert_eq!(hash1.hash, hash2.hash); } #[test] fn test_kheavyhash_clone() { let hasher1 = KHeavyHash::new(); let hasher2 = hasher1.clone(); let header = b"clone test"; let hash1 = hasher1.hash(header, 0); let hash2 = hasher2.hash(header, 0); assert_eq!(hash1.hash, hash2.hash); } #[test] fn test_matrix_accessor() { let hasher = KHeavyHash::new(); let matrix = hasher.matrix(); let m1 = matrix.multiply(&[0x42u8; 32]); let m2 = matrix.multiply(&[0x42u8; 32]); assert_eq!(m1, m2); } #[test] fn test_mine_easy_target() { let hasher = KHeavyHash::new(); let header = b"mine me"; let target = Target::max(); let result = hasher.mine(header, &target, 0, 10000); assert!(result.is_some()); let pow = result.unwrap(); assert!(target.is_met_by(&pow.hash)); } #[test] fn test_mine_returns_valid_nonce() { let hasher = KHeavyHash::new(); let header = b"mining nonce test"; let target = Target::max(); let result = hasher.mine(header, &target, 0, 10000).unwrap(); let verification = hasher.hash(header, result.nonce); assert!(target.is_met_by(&verification.hash)); } #[test] fn test_mine_with_start_nonce() { let hasher = KHeavyHash::new(); let header = b"start nonce test"; let target = Target::max(); let start_nonce = 5000; let result = hasher.mine(header, &target, start_nonce, 10000); assert!(result.is_some()); let pow = result.unwrap(); assert!(pow.nonce >= start_nonce); } #[test] fn test_mine_impossible_target() { let hasher = KHeavyHash::new(); let header = b"impossible test"; let target = Target::from_bytes([0u8; 32]); let result = hasher.mine(header, &target, 0, 100); assert!(result.is_none()); } #[test] fn test_mine_with_callback_finds_solution() { let hasher = KHeavyHash::new(); let header = b"callback test"; let target = Target::max(); let result = hasher.mine_with_callback(header, &target, 0, 50000, |_, _| true); assert!(result.is_some()); } #[test] fn test_mine_with_callback_can_cancel() { let hasher = KHeavyHash::new(); let header = b"cancel test"; let target = Target::from_bytes([0u8; 32]); let mut called = false; let result = hasher.mine_with_callback(header, &target, 0, 100000, |_, _| { if called { false } else { called = true; true } }); assert!(result.is_none()); } #[test] fn test_pow_hash_meets_target() { let hasher = KHeavyHash::new(); let header = b"pow hash test"; let target = Target::max(); let pow = hasher.mine(header, &target, 0, 10000).unwrap(); assert!(pow.meets_target(&target)); } #[test] fn test_pow_hash_as_bytes() { let hasher = KHeavyHash::new(); let pow = hasher.hash(b"test", 0); let bytes = pow.as_bytes(); assert_eq!(bytes.len(), 32); assert_eq!(bytes, pow.hash.as_bytes()); } #[test] fn test_pow_hash_clone() { let hasher = KHeavyHash::new(); let pow = hasher.hash(b"clone test", 42); let cloned = pow.clone(); assert_eq!(cloned.hash, pow.hash); assert_eq!(cloned.nonce, pow.nonce); assert_eq!(cloned.pre_hash, pow.pre_hash); } #[test] fn test_pow_hash_debug() { let hasher = KHeavyHash::new(); let pow = hasher.hash(b"debug test", 0); let debug_str = format!("{:?}", pow); assert!(debug_str.contains("PowHash")); } #[test] fn test_parallel_miner_creation_with_thread_count() { let miner = ParallelMiner::new(4); assert_eq!(miner.num_threads(), 4); } #[test] fn test_parallel_miner_creation_single_thread() { let miner = ParallelMiner::new(1); assert_eq!(miner.num_threads(), 1); } #[test] fn test_parallel_miner_auto_thread_detection() { let miner = ParallelMiner::new(0); assert!(miner.num_threads() >= 1); } #[test] fn test_parallel_miner_mine_finds_solution() { let miner = ParallelMiner::new(2); let header = b"parallel mining test"; let target = Target::max(); let result = miner.mine(header, &target, 0); assert!(result.is_some()); } #[test] fn test_parallel_miner_solution_meets_target() { let miner = ParallelMiner::new(2); let header = b"parallel target test"; let target = Target::max(); let result = miner.mine(header, &target, 0).unwrap(); assert!(target.is_met_by(&result.hash)); } #[test] fn test_parallel_miner_solution_is_valid() { let miner = ParallelMiner::new(2); let header = b"parallel valid test"; let target = Target::max(); let result = miner.mine(header, &target, 0).unwrap(); let hasher = KHeavyHash::new(); let verification = hasher.hash(header, result.nonce); assert_eq!(verification.hash, result.hash); } #[test] fn test_parallel_miner_different_start_nonce() { let miner = ParallelMiner::new(2); let header = b"start nonce parallel test"; let target = Target::max(); let result = miner.mine(header, &target, 1000); assert!(result.is_some()); } #[test] fn test_hashrate_benchmark_returns_positive() { let bench = HashrateBenchmark::new(); let hashrate = bench.run(100); assert!(hashrate > 0.0); } #[test] fn test_hashrate_benchmark_is_finite() { let bench = HashrateBenchmark::new(); let hashrate = bench.run(100); assert!(hashrate.is_finite()); } #[test] fn test_hashrate_benchmark_consistency() { let bench = HashrateBenchmark::new(); let h1 = bench.run(100); let h2 = bench.run(100); assert!(h1 > 0.0); assert!(h2 > 0.0); let ratio = h1 / h2; assert!(ratio > 0.1 && ratio < 10.0); } #[test] fn test_hashrate_benchmark_default_trait() { let bench1 = HashrateBenchmark::default(); let bench2 = HashrateBenchmark::new(); let h1 = bench1.run(50); let h2 = bench2.run(50); assert!(h1 > 0.0); assert!(h2 > 0.0); } #[test] fn test_hashrate_benchmark_short_duration() { let bench = HashrateBenchmark::new(); let hashrate = bench.run(10); assert!(hashrate > 0.0); } #[test] fn test_hashrate_benchmark_longer_duration() { let bench = HashrateBenchmark::new(); let hashrate = bench.run(200); assert!(hashrate > 0.0); } }