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