1#[cfg(target_arch = "x86_64")]
3pub mod avx512;
4
5#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
7pub mod avx2;
8
9#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
10pub mod sha_ni;
12
13#[cfg(target_arch = "wasm32")]
15pub mod simd128;
16
17pub mod safe;
19
20pub const SOLVE_TYPE_LT: u8 = 1;
22pub const SOLVE_TYPE_GT: u8 = 2;
24pub const SOLVE_TYPE_MASK: u8 = 4;
26
27pub trait CpuIDToken: Default + Copy + Clone + 'static {
29 fn get() -> bool;
31}
32
33#[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
98pub trait Solver {
100 fn solve<const TYPE: u8>(&mut self, target: u64, mask: u64) -> Option<(u64, [u32; 8])>;
115
116 #[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 fn set_limit(&mut self, limit: u64);
126
127 fn get_attempted_nonces(&self) -> u64;
129}
130
131pub trait SolverDyn {
133 fn solve_dyn(&mut self, target: u64, ty: u8, mask: u64) -> Option<(u64, [u32; 8])>;
135 fn solve_nonce_only_dyn(&mut self, target: u64, ty: u8, mask: u64) -> Option<u64>;
137}
138
139impl<S: Solver> SolverDyn for S {
140 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 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 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 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 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 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 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}