Skip to main content

pow_buster/
solver.rs

1/// AVX-512 solver
2#[cfg(target_arch = "x86_64")]
3pub mod avx512;
4
5/// AVX2 solver
6#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
7pub mod avx2;
8
9#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
10/// SHA-NI solver
11pub mod sha_ni;
12
13/// SIMD128 solver
14#[cfg(target_arch = "wasm32")]
15pub mod simd128;
16
17/// Safe solver
18pub mod safe;
19
20/// Less than test (such as Anubis and GoAway)
21pub const SOLVE_TYPE_LT: u8 = 1;
22/// Greater than test (such as mCaptcha)
23pub const SOLVE_TYPE_GT: u8 = 2;
24/// Mask test (such as Cap.js)
25pub const SOLVE_TYPE_MASK: u8 = 4;
26
27/// A token for checking CPU features
28pub trait CpuIDToken: Default + Copy + Clone + 'static {
29    /// Get the CPU feature status
30    fn get() -> bool;
31}
32
33/// A solver router that can take a solver or a fallback solver based on the CPU features
34#[derive(Debug, Copy, Clone)]
35#[allow(missing_docs)]
36pub enum SolverRouter<T: CpuIDToken, M, S: Solver + From<M>, F: Solver + From<M>> {
37    Taken {
38        solver: S,
39        _marker: core::marker::PhantomData<(T, M)>,
40    },
41    Fallback {
42        solver: F,
43        _marker: core::marker::PhantomData<(T, M)>,
44    },
45}
46
47impl<T: CpuIDToken, M, S: Solver + From<M>, F: Solver + From<M>> From<M>
48    for SolverRouter<T, M, S, F>
49{
50    fn from(value: M) -> Self {
51        if T::get() {
52            Self::Taken {
53                solver: value.into(),
54                _marker: core::marker::PhantomData,
55            }
56        } else {
57            Self::Fallback {
58                solver: value.into(),
59                _marker: core::marker::PhantomData,
60            }
61        }
62    }
63}
64
65impl<T: CpuIDToken, M, S: Solver + From<M>, F: Solver + From<M>> Solver
66    for SolverRouter<T, M, S, F>
67{
68    fn set_limit(&mut self, limit: u64) {
69        match self {
70            Self::Taken { solver, .. } => solver.set_limit(limit),
71            Self::Fallback { solver, .. } => solver.set_limit(limit),
72        }
73    }
74    fn get_attempted_nonces(&self) -> u64 {
75        match self {
76            Self::Taken { solver, .. } => solver.get_attempted_nonces(),
77            Self::Fallback { solver, .. } => solver.get_attempted_nonces(),
78        }
79    }
80
81    #[inline]
82    fn solve_nonce_only<const TYPE: u8>(&mut self, target: u64, mask: u64) -> Option<u64> {
83        match self {
84            Self::Taken { solver, .. } => solver.solve_nonce_only::<TYPE>(target, mask),
85            Self::Fallback { solver, .. } => solver.solve_nonce_only::<TYPE>(target, mask),
86        }
87    }
88
89    #[inline]
90    fn solve<const TYPE: u8>(&mut self, target: u64, mask: u64) -> Option<(u64, [u32; 8])> {
91        match self {
92            Self::Taken { solver, .. } => solver.solve::<TYPE>(target, mask),
93            Self::Fallback { solver, .. } => solver.solve::<TYPE>(target, mask),
94        }
95    }
96}
97
98/// A generic solver trait
99pub trait Solver {
100    /// Returns a valid nonce and its corresponding hash value.
101    ///
102    /// Supported schemes:
103    ///
104    /// - `SOLVE_TYPE_LT`: Less than test (such as Anubis and GoAway)
105    /// - `SOLVE_TYPE_GT`: Greater than test (such as mCaptcha)
106    /// - `SOLVE_TYPE_MASK`: Mask test (such as Cap.js)
107    ///
108    /// Currently bitmasking `SOLVE_TYPE_MASK` with `SOLVE_TYPE_GT` or `SOLVE_TYPE_LT` is not supported and may result in unexpected behavior.
109    ///
110    /// Returns None when the solver cannot solve the prefix.
111    ///
112    /// Failure is usually because the key space is exhausted (or presumed exhausted).
113    /// It should by design happen extremely rarely for common difficulty settings.
114    fn solve<const TYPE: u8>(&mut self, target: u64, mask: u64) -> Option<(u64, [u32; 8])>;
115
116    /// Returns a valid nonce without the actual hash.
117    ///
118    /// A trivial implementation is provided by default.
119    #[inline]
120    fn solve_nonce_only<const TYPE: u8>(&mut self, target: u64, mask: u64) -> Option<u64> {
121        self.solve::<TYPE>(target, mask).map(|(nonce, _)| nonce)
122    }
123
124    /// Set the limit.
125    fn set_limit(&mut self, limit: u64);
126
127    /// Get the attempted nonces.
128    fn get_attempted_nonces(&self) -> u64;
129}
130
131/// A dyn-dispatching wrapper for Solver
132pub trait SolverDyn {
133    /// A dynamic dispatching wrapper for solve
134    fn solve_dyn(&mut self, target: u64, ty: u8, mask: u64) -> Option<(u64, [u32; 8])>;
135    /// A dynamic dispatching wrapper for solve_nonce_only
136    fn solve_nonce_only_dyn(&mut self, target: u64, ty: u8, mask: u64) -> Option<u64>;
137}
138
139impl<S: Solver> SolverDyn for S {
140    // A dynamic dispatching wrapper for solve
141    fn solve_dyn(&mut self, target: u64, ty: u8, mask: u64) -> Option<(u64, [u32; 8])> {
142        match ty {
143            SOLVE_TYPE_LT => self.solve::<SOLVE_TYPE_LT>(target, mask),
144            SOLVE_TYPE_GT => self.solve::<SOLVE_TYPE_GT>(target, mask),
145            _ => self.solve::<SOLVE_TYPE_MASK>(target, mask),
146        }
147    }
148
149    // A dynamic dispatching wrapper for solve_nonce_only
150    fn solve_nonce_only_dyn(&mut self, target: u64, ty: u8, mask: u64) -> Option<u64> {
151        match ty {
152            SOLVE_TYPE_LT => self.solve_nonce_only::<SOLVE_TYPE_LT>(target, mask),
153            SOLVE_TYPE_GT => self.solve_nonce_only::<SOLVE_TYPE_GT>(target, mask),
154            _ => self.solve_nonce_only::<SOLVE_TYPE_MASK>(target, mask),
155        }
156    }
157}
158
159#[cfg(test)]
160pub(crate) mod tests {
161    use core::num::NonZeroU8;
162    use std::io::Write;
163
164    use sha2::{Digest, Sha256};
165
166    mod pow_sha256;
167
168    use crate::{
169        compute_mask_anubis, compute_mask_cerberus, compute_mask_goaway, compute_target_mcaptcha,
170        extract128_be, message::IEEE754LosslessFixupPrefix,
171    };
172
173    use super::*;
174
175    /// Extract top 64 bits from a 64-bit word array
176    const fn extract64_be(inp: [u32; 8]) -> u64 {
177        (inp[0] as u64) << 32 | (inp[1] as u64)
178    }
179
180    pub(crate) fn test_decimal_validator<
181        S: Solver,
182        F: for<'a> FnMut(&'a [u8], u32) -> Option<S>,
183    >(
184        mut factory: F,
185    ) {
186        for phrase_len in 0..64 {
187            let mut concatenated_prefix = b"abc".to_vec();
188            let phrase_str = String::from_iter(std::iter::repeat('a').take(phrase_len));
189            concatenated_prefix.extend_from_slice(&bincode::serialize(&phrase_str).unwrap());
190
191            let config = pow_sha256::Config {
192                salt: String::from("abc"),
193            };
194            const DIFFICULTY: u32 = 100_000;
195            const ANUBIS_DIFFICULTY: NonZeroU8 = NonZeroU8::new(4).unwrap();
196
197            for search_space in [0, 1, 9, 10, 11] {
198                let Some(mut solver) = factory(&concatenated_prefix, search_space) else {
199                    assert_ne!(
200                        search_space, 0,
201                        "solver is None for search_space: {}",
202                        search_space
203                    );
204                    break;
205                };
206                let Some(mut anubis_solver) = factory(&concatenated_prefix, search_space) else {
207                    assert_ne!(
208                        search_space, 0,
209                        "anubis solver is None for search_space: {}",
210                        search_space
211                    );
212                    break;
213                };
214                let Some(mut eq_solver) = factory(&concatenated_prefix, search_space) else {
215                    assert_ne!(
216                        search_space, 0,
217                        "eq solver is None for search_space: {}",
218                        search_space
219                    );
220                    break;
221                };
222
223                let target_bytes = compute_target_mcaptcha(DIFFICULTY as u64).to_be_bytes();
224                let target_u64 = u64::from_be_bytes(target_bytes[..8].try_into().unwrap());
225                let mask_anubis = compute_mask_anubis(ANUBIS_DIFFICULTY);
226                let (nonce, result) = solver
227                    .solve::<SOLVE_TYPE_GT>(target_u64, !0)
228                    .expect("solver failed");
229                let result_u128 = extract128_be(result);
230                let (anubis_nonce, anubis_result) = anubis_solver
231                    .solve::<SOLVE_TYPE_MASK>(0, mask_anubis)
232                    .expect("solver failed");
233                let anubis_result_u64 = extract64_be(anubis_result);
234                let anubis_expected_hash = {
235                    let mut msg = concatenated_prefix.clone();
236                    write!(msg, "{}", anubis_nonce).unwrap();
237                    sha2::Sha256::digest(msg.as_slice())
238                };
239                let anubis_result_bytes = anubis_result_u64.to_be_bytes();
240                assert_eq!(anubis_expected_hash[..8], anubis_result_bytes);
241                assert_eq!(
242                    ((anubis_result[0] as u64) << 32 | (anubis_result[1] as u64)) & mask_anubis,
243                    0,
244                    "[{}] anubis_result: {:016x} & mask_anubis: {:016x} == 0 (solver: {}, search_space: {})",
245                    core::any::type_name::<S>(),
246                    anubis_result_u64,
247                    mask_anubis,
248                    core::any::type_name::<S>(),
249                    search_space
250                );
251
252                let test_response = pow_sha256::PoW::new(nonce, result_u128.to_string());
253                assert_eq!(
254                    config.calculate(&test_response, &phrase_str).unwrap(),
255                    result_u128,
256                    "test_response: {:?} (solver: {}, search_space: {})",
257                    test_response,
258                    core::any::type_name::<S>(),
259                    search_space
260                );
261
262                assert!(
263                    config.is_valid_proof(&test_response, &phrase_str),
264                    "{} is not valid proof (solver: {})",
265                    result_u128,
266                    core::any::type_name::<S>()
267                );
268
269                assert!(
270                    config.is_sufficient_difficulty(&test_response, DIFFICULTY),
271                    "{:016x} is not sufficient difficulty, expected {:016x} (solver: {})",
272                    result_u128,
273                    compute_target_mcaptcha(DIFFICULTY as u64),
274                    core::any::type_name::<S>()
275                );
276
277                // based on proof-of-work.mjs
278                for i in 0..ANUBIS_DIFFICULTY.get() as usize {
279                    let byte_index = i / 2;
280                    let nibble_index = (1 - i % 2) as u8;
281
282                    let nibble = (anubis_result_bytes[byte_index] >> (nibble_index * 4)) & 0x0f;
283                    assert_eq!(
284                        nibble,
285                        0,
286                        "{:08x} is not valid anubis proof (solver: {}, nibble: {})",
287                        anubis_result_u64,
288                        core::any::type_name::<S>(),
289                        i
290                    );
291                }
292
293                let u64_target = 0b10111 << (64 - 5);
294
295                let (_, eq_result) = eq_solver
296                    .solve::<{ SOLVE_TYPE_MASK }>(u64_target, !0 << (64 - 5))
297                    .expect("solver failed");
298                let eq_solver_result_u128 = extract128_be(eq_result);
299                assert_eq!(
300                    eq_solver_result_u128 >> (128 - 5),
301                    0b10111,
302                    "eq_solver_result_u128: {:016x} (solver: {}, search_space: {})",
303                    eq_solver_result_u128,
304                    core::any::type_name::<S>(),
305                    search_space
306                );
307
308                #[cfg(all(feature = "compare-64bit", target_arch = "x86_64"))]
309                {
310                    // test that the target can be placed in the second register and it still works
311                    let u64_target = 0b10111 << (32 - 5);
312                    let u64_mask = 0b11111 << (32 - 5);
313                    let (_, eq_result) = eq_solver
314                        .solve::<{ SOLVE_TYPE_MASK }>(u64_target, u64_mask)
315                        .expect("solver failed");
316                    let eq_solver_result_u64 = extract64_be(eq_result);
317                    assert_eq!(
318                        eq_solver_result_u64 & u64_mask,
319                        u64_target,
320                        "eq_solver_result_u64: {:016x} (solver: {}, search_space: {})",
321                        eq_solver_result_u64,
322                        core::any::type_name::<S>(),
323                        search_space
324                    );
325                }
326            }
327        }
328    }
329
330    pub(crate) fn test_binary_validator<S: Solver, F: for<'a> FnMut(&'a [u8], NonZeroU8) -> S>(
331        mut factory: F,
332    ) {
333        for nonce_byte_count in [4, 5, 8] {
334            for prefix_len in 0..64 {
335                let mut msg = Vec::from_iter(b"abc".iter().cloned().cycle().take(prefix_len));
336                let mut solver = factory(&msg, NonZeroU8::new(nonce_byte_count).unwrap());
337                const DIFFICULTY: u64 = 100_000;
338                let target = compute_target_mcaptcha(DIFFICULTY as u64);
339                let (nonce, result) = solver
340                    .solve::<SOLVE_TYPE_GT>(target, !0)
341                    .expect("solver failed");
342                msg.extend_from_slice(&nonce.to_le_bytes()[..nonce_byte_count as usize]);
343                let expected_digest = sha2::Sha256::digest(&msg);
344                let mut got_digest = [0; 32];
345                for i in 0..8 {
346                    got_digest[i * 4..][..4].copy_from_slice(&result[i].to_be_bytes());
347                }
348                assert_eq!(
349                    expected_digest.as_slice(),
350                    got_digest.as_slice(),
351                    "nonce: {}, prefix_len: {}",
352                    nonce,
353                    prefix_len,
354                );
355                let got_difficulty = u64::from_be_bytes(got_digest[..8].try_into().unwrap());
356                assert!(
357                    got_difficulty >= target,
358                    "got_difficulty: {:016x} < target: {:016x} (nonce: {}, prefix_len: {})",
359                    got_difficulty,
360                    target,
361                    nonce,
362                    prefix_len,
363                );
364            }
365        }
366    }
367
368    pub(crate) fn test_decimal_validator_f64_safe<
369        S: Solver,
370        F: for<'a> FnMut(&'a [u8], u32) -> Option<(S, Option<IEEE754LosslessFixupPrefix>)>,
371    >(
372        mut factory: F,
373    ) {
374        use std::io::Write;
375        for c in b'a'..=b'c' {
376            let salts = [c; 64];
377            for len in 1..=64 {
378                let u64_target = 0b10111 << (64 - 5);
379                let u64_mask = !0 << (64 - 5);
380                let (mut solver, fixup_prefix) = factory(&salts[..len], 0).unwrap();
381
382                let (nonce, result) = solver
383                    .solve::<SOLVE_TYPE_MASK>(u64_target, u64_mask)
384                    .expect("solver failed");
385                let mut final_message = salts[..len].to_vec();
386                let mut final_message_f64 = final_message.clone();
387                let mut final_nonce = nonce as f64;
388                if let Some(ref fixup_prefix) = fixup_prefix {
389                    final_message.extend_from_slice(fixup_prefix.as_ref());
390                    final_nonce = fixup_prefix.fixup(nonce);
391                }
392                if fixup_prefix.is_some() {
393                    assert_ne!(nonce % 10, 0);
394                }
395                write!(final_message, "{:09}", nonce).unwrap();
396                write!(final_message_f64, "{}", final_nonce).unwrap();
397                assert_eq!(
398                    final_message_f64,
399                    final_message,
400                    "final_nonce: {}, final_nonce_f64: {} (fixup_prefix: {:?})",
401                    String::from_utf8_lossy(&final_message),
402                    String::from_utf8_lossy(&final_message_f64),
403                    fixup_prefix,
404                );
405                let hash_result = sha2::Sha256::digest(&final_message);
406                let hash_result_u64 = u64::from_be_bytes(hash_result[..8].try_into().unwrap());
407                assert_eq!(
408                    hash_result_u64 & u64_mask,
409                    u64_target,
410                    "hash_result_u64: {:016x} (solver: {}, len: {}, message: {:?})",
411                    hash_result_u64,
412                    core::any::type_name::<S>(),
413                    len,
414                    String::from_utf8_lossy(&final_message)
415                );
416                assert_eq!(
417                    hash_result_u64,
418                    ((result[0] as u64) << 32 | (result[1] as u64))
419                );
420            }
421        }
422    }
423
424    pub(crate) fn test_cerberus_decimal_validator<
425        S: Solver,
426        F: for<'a> FnMut(&'a [u8]) -> Option<S>,
427    >(
428        mut factory: F,
429    ) {
430        use std::io::Write;
431
432        for df in (5..=7).chain(core::iter::once(9)) {
433            let mask = compute_mask_cerberus(df.try_into().unwrap());
434            eprintln!("mask: {:08x}", mask);
435
436            let test_seed: [u8; 128] = core::array::from_fn(|i| b'a'.wrapping_add(i as u8));
437
438            for seed_len in 0..128 {
439                let Some(mut solver) = factory(&test_seed[..seed_len]) else {
440                    if seed_len < 64 {
441                        eprintln!("solver is None for seed_len: {}", seed_len);
442                    }
443                    continue;
444                };
445
446                let (nonce, hash) = solver
447                    .solve::<{ crate::solver::SOLVE_TYPE_MASK }>(0, mask as u64)
448                    .unwrap();
449                let mut msg = test_seed[..seed_len].to_vec();
450                write!(&mut msg, "{}", nonce).unwrap();
451
452                fn check_small(hash: &[u8; 32], n: usize) -> bool {
453                    // https://github.com/sjtug/cerberus/blob/ee8f903f1311da7022aec68c8686739b40f4a168/pow/src/check_dubit.rs
454                    let first_word: u32 = (hash[0] as u32) << 24
455                        | (hash[1] as u32) << 16
456                        | (hash[2] as u32) << 8
457                        | (hash[3] as u32);
458                    first_word.leading_zeros() >= (n as u32 * 2)
459                }
460
461                let mut ref_hasher = blake3::Hasher::new();
462                ref_hasher.update(msg.as_slice());
463                let ref_hash = ref_hasher.finalize();
464                let ref_hash_bytes = ref_hash.as_bytes();
465                let ref_hash = core::array::from_fn(|i| {
466                    u32::from_le_bytes([
467                        ref_hash_bytes[i * 4],
468                        ref_hash_bytes[i * 4 + 1],
469                        ref_hash_bytes[i * 4 + 2],
470                        ref_hash_bytes[i * 4 + 3],
471                    ])
472                });
473                let hit = ((ref_hash[0] as u64) << 32 | (ref_hash[1] as u64)) & mask == 0;
474                assert_eq!(
475                    hash, ref_hash,
476                    "incorrect output: {} (seed_len: {})",
477                    nonce, seed_len
478                );
479                assert!(hit);
480                assert!(check_small(&ref_hash_bytes, df as usize));
481            }
482        }
483    }
484
485    pub(crate) fn test_cerberus_binary_validator<
486        S: Solver,
487        F: for<'a> FnMut(&'a [u8]) -> Option<S>,
488    >(
489        mut factory: F,
490    ) {
491        for seed in [b"a", b"b"] {
492            let seed_hash = ::blake3::hash(seed).to_hex();
493
494            for df in (5..=7).chain(core::iter::once(9)) {
495                let mask = compute_mask_cerberus(df.try_into().unwrap());
496                eprintln!("mask: {:08x}", mask);
497
498                let Some(mut solver) = factory(seed) else {
499                    panic!("solver is None for seed: {:?}", seed);
500                };
501
502                let (nonce, hash) = solver
503                    .solve::<{ crate::solver::SOLVE_TYPE_MASK }>(0, mask as u64)
504                    .unwrap();
505                let mut msg = seed_hash.as_bytes().to_vec();
506                msg.extend_from_slice(nonce.rotate_right(32).to_le_bytes().as_slice());
507
508                fn check_small(hash: &[u8; 32], n: usize) -> bool {
509                    // https://github.com/sjtug/cerberus/blob/ee8f903f1311da7022aec68c8686739b40f4a168/pow/src/check_dubit.rs
510                    let first_word: u32 = (hash[0] as u32) << 24
511                        | (hash[1] as u32) << 16
512                        | (hash[2] as u32) << 8
513                        | (hash[3] as u32);
514                    first_word.leading_zeros() >= (n as u32 * 2)
515                }
516
517                let mut ref_hasher = blake3::Hasher::new();
518                ref_hasher.update(msg.as_slice());
519                let ref_hash = ref_hasher.finalize();
520                let ref_hash_bytes = ref_hash.as_bytes();
521                let ref_hash = core::array::from_fn(|i| {
522                    u32::from_le_bytes([
523                        ref_hash_bytes[i * 4],
524                        ref_hash_bytes[i * 4 + 1],
525                        ref_hash_bytes[i * 4 + 2],
526                        ref_hash_bytes[i * 4 + 3],
527                    ])
528                });
529                let hit = ((ref_hash[0] as u64) << 32 | (ref_hash[1] as u64)) & mask == 0;
530                assert_eq!(hash, ref_hash, "incorrect output: {}", nonce);
531                assert!(hit);
532                assert!(check_small(&ref_hash_bytes, df as usize));
533            }
534        }
535    }
536
537    pub(crate) fn test_goaway_validator<S: Solver, F: for<'a> FnMut(&'a [u8; 32]) -> S>(
538        mut factory: F,
539    ) {
540        const DIFFICULTY: NonZeroU8 = NonZeroU8::new(10).unwrap();
541        let mask = compute_mask_goaway(DIFFICULTY);
542        let test_prefix = core::array::from_fn(|i| i as u8);
543
544        let mut solver = factory(&test_prefix);
545        let mut eq_solver = factory(&test_prefix);
546
547        let (nonce, result) = solver
548            .solve::<SOLVE_TYPE_MASK>(0, mask)
549            .expect("solver failed");
550        assert!(result[0].leading_zeros() >= DIFFICULTY.get() as u32);
551
552        let mut hasher = Sha256::default();
553        hasher.update(&test_prefix);
554        hasher.update(&nonce.to_be_bytes());
555        let hash = hasher.finalize();
556        assert!(u128::from_be_bytes(hash[..16].try_into().unwrap()) >= DIFFICULTY.get() as _);
557
558        let eq_target = 0b10111 << (64 - 5);
559        let eq_mask = !0 << (64 - 5);
560        let (_, eq_result) = eq_solver
561            .solve::<{ SOLVE_TYPE_MASK }>(eq_target, eq_mask)
562            .expect("solver failed");
563        let eq_result_u128 = extract128_be(eq_result);
564        assert_eq!(
565            eq_result_u128 >> (128 - 5),
566            0b10111,
567            "eq_result: {:016x} (solver: {})",
568            eq_result_u128,
569            core::any::type_name::<S>()
570        );
571
572        #[cfg(all(feature = "compare-64bit", target_arch = "x86_64"))]
573        {
574            let eq_target = 0b10111 << (32 - 5);
575            let eq_mask = 0b11111 << (32 - 5);
576            let (_, eq_result) = eq_solver
577                .solve::<{ SOLVE_TYPE_MASK }>(eq_target, eq_mask)
578                .expect("solver failed");
579            let eq_result_u64 = extract64_be(eq_result);
580            assert_eq!(
581                eq_result_u64 & eq_mask,
582                eq_target,
583                "eq_result_u64: {:016x} (solver: {})",
584                eq_result_u64,
585                core::any::type_name::<S>()
586            );
587        }
588    }
589}