synor/crates/synor-mining/src/kheavyhash.rs
Gulshan Yadav 8bdd28e455 fix: replace all unstable is_multiple_of with modulo operator
Rust's is_multiple_of is an unstable feature (issue #128101).
Replace with standard modulo operator for compatibility with stable Rust.
2026-02-02 01:28:26 +05:30

783 lines
23 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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<PowHash> {
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<F>(
&self,
header: &[u8],
target: &Target,
start_nonce: u64,
max_tries: u64,
mut callback: F,
) -> Option<PowHash>
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<KHeavyHash>,
/// 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<PowHash> {
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);
}
}