spongefish_pow/
lib.rs

1#[cfg(feature = "blake3")]
2pub mod blake3;
3#[cfg(feature = "keccak")]
4pub mod keccak;
5
6/// Standalone proof-of-work grinder that can work with any byte challenge.
7///
8/// This structure provides a clean separation between the PoW solving logic
9/// and the transcript/sponge operations.
10pub struct PoWGrinder<S: PowStrategy> {
11    strategy: S,
12}
13
14impl<S: PowStrategy> PoWGrinder<S> {
15    /// Creates a new PoW grounder with the given challenge and difficulty.
16    ///
17    /// # Arguments
18    /// * `challenge` - A 32-byte challenge array
19    /// * `bits` - The difficulty in bits (logarithm of expected work)
20    #[must_use]
21    pub fn new(challenge: [u8; 32], bits: f64) -> Self {
22        Self {
23            strategy: S::new(challenge, bits),
24        }
25    }
26
27    /// Attempts to find a nonce that satisfies the proof-of-work requirement.
28    ///
29    /// Returns the minimal nonce that makes the hash fall below the target threshold,
30    /// or None if no valid nonce is found (extremely unlikely for reasonable difficulty).
31    pub fn grind(&mut self) -> Option<PoWSolution> {
32        self.strategy.solve()
33    }
34
35    /// Verifies that a given nonce satisfies the proof-of-work requirement.
36    pub fn verify(&mut self, nonce: u64) -> bool {
37        self.strategy.check(nonce)
38    }
39}
40
41pub struct PoWSolution {
42    pub challenge: [u8; 32],
43    pub nonce: u64,
44}
45
46/// Convenience functions for using PoW with byte arrays.
47pub mod convenience {
48    use crate::{PoWGrinder, PoWSolution, PowStrategy};
49
50    /// Performs proof-of-work on a challenge and returns the solution.
51    ///
52    /// This is a simple wrapper that creates a grounder and immediately grinds.
53    #[must_use]
54    pub fn grind_pow<S: PowStrategy>(challenge: [u8; 32], bits: f64) -> Option<PoWSolution> {
55        let mut grounder = PoWGrinder::<S>::new(challenge, bits);
56        grounder.grind()
57    }
58
59    /// Verifies a proof-of-work nonce.
60    #[must_use]
61    pub fn verify_pow<S: PowStrategy>(challenge: [u8; 32], bits: f64, nonce: u64) -> bool {
62        let mut grounder = PoWGrinder::<S>::new(challenge, bits);
63        grounder.verify(nonce)
64    }
65}
66
67pub trait PowStrategy: Clone + Sync {
68    /// Creates a new proof-of-work challenge.
69    /// The `challenge` is a 32-byte array that represents the challenge.
70    /// The `bits` is the binary logarithm of the expected amount of work.
71    /// When `bits` is large (i.e. close to 64), a valid solution may not be found.
72    fn new(challenge: [u8; 32], bits: f64) -> Self;
73
74    /// Check if the `nonce` satisfies the challenge.
75    fn check(&mut self, nonce: u64) -> bool;
76
77    /// Builds a solution given the input nonce
78    fn solution(&self, nonce: u64) -> PoWSolution;
79
80    /// Finds the minimal `nonce` that satisfies the challenge.
81    #[cfg(not(feature = "parallel"))]
82    fn solve(&mut self) -> Option<PoWSolution> {
83        (0..=u64::MAX)
84            .find(|&nonce| self.check(nonce))
85            .map(|nonce| self.solution(nonce))
86    }
87
88    #[cfg(feature = "parallel")]
89    fn solve(&mut self) -> Option<PoWSolution> {
90        // Split the work across all available threads.
91        // Use atomics to find the unique deterministic lowest satisfying nonce.
92
93        use std::sync::atomic::{AtomicU64, Ordering};
94
95        use rayon::broadcast;
96        let global_min = AtomicU64::new(u64::MAX);
97        let _ = broadcast(|ctx| {
98            let mut worker = self.clone();
99            let nonces = (ctx.index() as u64..).step_by(ctx.num_threads());
100            for nonce in nonces {
101                // Use relaxed ordering to eventually get notified of another thread's solution.
102                // (Propagation delay should be in the order of tens of nanoseconds.)
103                if nonce >= global_min.load(Ordering::Relaxed) {
104                    break;
105                }
106                if worker.check(nonce) {
107                    // We found a solution, store it in the global_min.
108                    // Use fetch_min to solve race condition with simultaneous solutions.
109                    global_min.fetch_min(nonce, Ordering::SeqCst);
110                    break;
111                }
112            }
113        });
114        let nonce = global_min.load(Ordering::SeqCst);
115        self.check(nonce).then(|| self.solution(nonce))
116    }
117}