Skip to main content

chaincraft_rust/crypto/
pow.rs

1//! Proof of Work implementation
2
3use crate::crypto::KeylessCryptoPrimitive;
4use crate::error::{ChaincraftError, CryptoError, Result};
5use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
9use std::sync::Arc;
10use tokio::task;
11
12/// Proof of Work parameters and configuration
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ProofOfWorkConfig {
15    /// Difficulty target (number of leading zeros required)
16    pub difficulty: u32,
17    /// Maximum nonce to try before giving up
18    pub max_nonce: u64,
19    /// Number of worker threads to use for mining
20    pub threads: usize,
21}
22
23impl Default for ProofOfWorkConfig {
24    fn default() -> Self {
25        Self {
26            difficulty: 4,
27            max_nonce: u64::MAX,
28            threads: num_cpus::get(),
29        }
30    }
31}
32
33/// Proof of Work challenge
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub struct PoWChallenge {
36    pub data: String,
37}
38
39impl PoWChallenge {
40    pub fn new(data: impl Into<String>) -> Self {
41        Self { data: data.into() }
42    }
43}
44
45/// Proof of Work proof/solution
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47pub struct PoWProof {
48    pub nonce: u64,
49    pub hash: String,
50}
51
52impl PoWProof {
53    pub fn new(nonce: u64, hash: String) -> Self {
54        Self { nonce, hash }
55    }
56}
57
58/// High-performance Proof of Work implementation
59#[derive(Debug, Clone)]
60pub struct ProofOfWork {
61    config: ProofOfWorkConfig,
62}
63
64impl ProofOfWork {
65    /// Create a new Proof of Work instance with default configuration
66    pub fn new() -> Self {
67        Self {
68            config: ProofOfWorkConfig::default(),
69        }
70    }
71
72    /// Create a new Proof of Work instance with custom configuration
73    pub fn with_config(config: ProofOfWorkConfig) -> Self {
74        Self { config }
75    }
76
77    /// Create a simple PoW with specified difficulty
78    pub fn with_difficulty(difficulty: u32) -> Self {
79        Self {
80            config: ProofOfWorkConfig {
81                difficulty,
82                ..ProofOfWorkConfig::default()
83            },
84        }
85    }
86
87    /// Calculate hash for given data and nonce
88    fn calculate_hash(data: &str, nonce: u64) -> String {
89        let mut hasher = Sha256::new();
90        hasher.update(data.as_bytes());
91        hasher.update(nonce.to_le_bytes());
92        hex::encode(hasher.finalize())
93    }
94
95    /// Check if hash meets difficulty requirement
96    fn meets_difficulty(hash: &str, difficulty: u32) -> bool {
97        hash.starts_with(&"0".repeat(difficulty as usize))
98    }
99
100    /// Mine a single block using CPU-bound work
101    async fn mine_worker(
102        data: String,
103        difficulty: u32,
104        start_nonce: u64,
105        nonce_step: u64,
106        max_nonce: u64,
107        should_stop: Arc<AtomicBool>,
108        best_nonce: Arc<AtomicU64>,
109    ) -> Option<PoWProof> {
110        task::spawn_blocking(move || {
111            let mut nonce = start_nonce;
112
113            while nonce < max_nonce && !should_stop.load(Ordering::Relaxed) {
114                let hash = Self::calculate_hash(&data, nonce);
115
116                if Self::meets_difficulty(&hash, difficulty) {
117                    // Found a solution!
118                    best_nonce.store(nonce, Ordering::Relaxed);
119                    should_stop.store(true, Ordering::Relaxed);
120                    return Some(PoWProof::new(nonce, hash));
121                }
122
123                nonce = nonce.saturating_add(nonce_step);
124
125                // Yield occasionally to allow other tasks to run
126                if nonce % 10000 == 0 && should_stop.load(Ordering::Relaxed) {
127                    break;
128                }
129            }
130
131            None
132        })
133        .await
134        .unwrap_or(None)
135    }
136
137    /// Verify proof efficiently
138    pub fn verify_sync(&self, challenge: &PoWChallenge, proof: &PoWProof) -> Result<bool> {
139        // Verify the hash matches the nonce
140        let calculated_hash = Self::calculate_hash(&challenge.data, proof.nonce);
141        if calculated_hash != proof.hash {
142            return Ok(false);
143        }
144
145        // Verify the hash meets difficulty
146        Ok(Self::meets_difficulty(&proof.hash, self.config.difficulty))
147    }
148
149    /// Get the current difficulty
150    pub fn difficulty(&self) -> u32 {
151        self.config.difficulty
152    }
153
154    /// Set the difficulty
155    pub fn set_difficulty(&mut self, difficulty: u32) {
156        self.config.difficulty = difficulty;
157    }
158
159    /// Estimate time to mine based on hash rate
160    pub fn estimate_time(&self, hash_rate: f64) -> std::time::Duration {
161        let target = 2_u64.pow(self.config.difficulty);
162        let expected_hashes = target as f64;
163        let seconds = expected_hashes / hash_rate;
164        std::time::Duration::from_secs_f64(seconds)
165    }
166}
167
168impl Default for ProofOfWork {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174#[async_trait]
175impl KeylessCryptoPrimitive for ProofOfWork {
176    type Input = String;
177    type Output = String;
178    type Challenge = PoWChallenge;
179    type Proof = PoWProof;
180
181    async fn compute(&self, input: Self::Input) -> Result<Self::Output> {
182        // Simple hash computation without proof of work
183        let mut hasher = Sha256::new();
184        hasher.update(input.as_bytes());
185        Ok(hex::encode(hasher.finalize()))
186    }
187
188    async fn create_proof(&self, challenge: Self::Challenge) -> Result<Self::Proof> {
189        let should_stop = Arc::new(AtomicBool::new(false));
190        let best_nonce = Arc::new(AtomicU64::new(0));
191        let mut handles = Vec::new();
192
193        let threads = self.config.threads;
194        let nonce_step = threads as u64;
195
196        // Spawn multiple worker tasks for parallel mining
197        for i in 0..threads {
198            let data = challenge.data.clone();
199            let difficulty = self.config.difficulty;
200            let start_nonce = i as u64;
201            let max_nonce = self.config.max_nonce;
202            let should_stop_clone = should_stop.clone();
203            let best_nonce_clone = best_nonce.clone();
204
205            let handle = task::spawn(Self::mine_worker(
206                data,
207                difficulty,
208                start_nonce,
209                nonce_step,
210                max_nonce,
211                should_stop_clone,
212                best_nonce_clone,
213            ));
214
215            handles.push(handle);
216        }
217
218        // Wait for any worker to find a solution
219        for handle in handles {
220            if let Ok(Some(proof)) = handle.await {
221                should_stop.store(true, Ordering::Relaxed);
222                return Ok(proof);
223            }
224        }
225
226        Err(ChaincraftError::Crypto(CryptoError::ProofOfWorkFailed))
227    }
228
229    async fn verify_proof(&self, challenge: Self::Challenge, proof: Self::Proof) -> Result<bool> {
230        // Verification is fast, but we'll make it async for consistency
231        let difficulty = self.config.difficulty;
232        task::spawn_blocking(move || {
233            let calculated_hash = Self::calculate_hash(&challenge.data, proof.nonce);
234            if calculated_hash != proof.hash {
235                return false;
236            }
237            Self::meets_difficulty(&proof.hash, difficulty)
238        })
239        .await
240        .map_err(|_| ChaincraftError::Generic("Task join error".to_string()))
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    #[tokio::test]
249    async fn test_pow_creation_and_verification() {
250        let pow = ProofOfWork::with_difficulty(1); // Easy difficulty for testing
251        let challenge = PoWChallenge::new("test data");
252
253        let proof = pow.create_proof(challenge.clone()).await.unwrap();
254        let is_valid = pow.verify_proof(challenge, proof).await.unwrap();
255
256        assert!(is_valid);
257    }
258
259    #[test]
260    fn test_sync_verification() {
261        let pow = ProofOfWork::with_difficulty(2);
262        let challenge = PoWChallenge::new("test");
263
264        // This is a known valid proof for "test" with difficulty 2
265        let proof = PoWProof::new(0, ProofOfWork::calculate_hash("test", 0));
266
267        // Find a valid nonce manually for testing
268        for nonce in 0..10000 {
269            let hash = ProofOfWork::calculate_hash("test", nonce);
270            if ProofOfWork::meets_difficulty(&hash, 2) {
271                let proof = PoWProof::new(nonce, hash);
272                assert!(pow.verify_sync(&challenge, &proof).unwrap());
273                break;
274            }
275        }
276    }
277
278    #[test]
279    fn test_difficulty_check() {
280        assert!(ProofOfWork::meets_difficulty("00abc", 2));
281        assert!(!ProofOfWork::meets_difficulty("0abc", 2));
282        assert!(ProofOfWork::meets_difficulty("000abc", 3));
283    }
284}