1#![cfg_attr(not(any(test, feature = "std")), no_std)]
2#![doc = include_str!("../README.md")]
3#![warn(missing_docs)]
4
5use sha2::digest::{
6 consts::{B1, U0, U16, U64},
7 typenum::{IsGreater, PowerOfTwo, Unsigned},
8};
9
10use core::num::NonZeroU8;
11
12#[cfg(feature = "alloc")]
13extern crate alloc;
14
15#[cfg(feature = "client")]
16pub mod client;
18
19#[cfg(feature = "server")]
20pub mod server;
22
23#[cfg(all(target_arch = "wasm32", feature = "adapter"))]
24mod wasm_ffi;
25
26#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
27cfg_if::cfg_if! {
28 if #[cfg(feature = "internals")] {
29 pub mod strings;
31 } else {
32 mod strings;
33 }
34}
35
36mod sha256;
38
39mod blake3;
41
42pub mod message;
44
45pub mod solver;
47
48#[cfg(feature = "adapter")]
49pub mod adapter;
51
52pub unsafe trait AlignerTo<T>:
58 core::ops::Deref<Target = T> + core::ops::DerefMut<Target = T> + From<T>
59{
60 type Alignment: Unsigned + PowerOfTwo + IsGreater<U0, Output = B1>;
62 type Output;
64
65 fn create_layout() -> core::alloc::Layout;
71}
72
73#[repr(align(16))]
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub struct Align16<T>(pub T);
77
78unsafe impl<T> AlignerTo<T> for Align16<T> {
79 type Alignment = U16;
80 type Output = T;
81 fn create_layout() -> core::alloc::Layout {
82 core::alloc::Layout::new::<Align16<T>>()
83 }
84}
85
86impl<T> From<T> for Align16<T> {
87 fn from(value: T) -> Self {
88 Align16(value)
89 }
90}
91
92impl<T> core::ops::Deref for Align16<T> {
93 type Target = T;
94 fn deref(&self) -> &Self::Target {
95 &self.0
96 }
97}
98
99impl<T> core::ops::DerefMut for Align16<T> {
100 fn deref_mut(&mut self) -> &mut Self::Target {
101 &mut self.0
102 }
103}
104
105#[repr(align(64))]
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub struct Align64<T>(pub T);
109
110unsafe impl<T> AlignerTo<T> for Align64<T> {
111 type Alignment = U64;
112 type Output = T;
113 fn create_layout() -> core::alloc::Layout {
114 core::alloc::Layout::new::<Align64<T>>()
115 }
116}
117
118impl<T> From<T> for Align64<T> {
119 fn from(value: T) -> Self {
120 Align64(value)
121 }
122}
123
124impl<'a, T> From<&'a Align64<T>> for &'a Align16<T> {
126 fn from(this: &'a Align64<T>) -> &'a Align16<T> {
127 unsafe { core::mem::transmute(this) }
128 }
129}
130
131impl<'a, T> From<&'a mut Align64<T>> for &'a mut Align16<T> {
133 fn from(this: &'a mut Align64<T>) -> &'a mut Align16<T> {
134 unsafe { core::mem::transmute(this) }
135 }
136}
137
138impl<T> core::ops::Deref for Align64<T> {
139 type Target = T;
140 fn deref(&self) -> &Self::Target {
141 &self.0
142 }
143}
144
145impl<T> core::ops::DerefMut for Align64<T> {
146 fn deref_mut(&mut self) -> &mut Self::Target {
147 &mut self.0
148 }
149}
150
151#[cold]
152fn unlikely() {}
153
154const SWAP_DWORD_BYTE_ORDER: [usize; 64] = {
155 let mut data = [0; 64];
156 let mut i = 0;
157 while i < 64 {
158 data[i] = i / 4 * 4 + 3 - i % 4;
159 i += 1;
160 }
161 data
162};
163
164#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
165cfg_if::cfg_if! {
166 if #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))] {
167 pub type SingleBlockSolver = crate::solver::avx512::SingleBlockSolver;
169 pub type DoubleBlockSolver = crate::solver::avx512::DoubleBlockSolver;
171 pub type DecimalSolver = crate::solver::avx512::DecimalSolver;
173 pub type GoAwaySolver = crate::solver::avx512::GoAwaySolver;
175 pub type BinarySolver = crate::solver::avx512::BinarySolver;
177 pub const SOLVER_NAME: &str = "AVX-512";
179 } else if #[cfg(target_feature = "sha")] {
180 pub type SingleBlockSolver = crate::solver::SolverRouter<
182 crate::solver::avx512::RequiredFeatures,
183 crate::message::SingleBlockMessage,
184 crate::solver::avx512::SingleBlockSolver,
185 crate::solver::sha_ni::SingleBlockSolver,
186 >;
187 pub type DoubleBlockSolver = crate::solver::SolverRouter<
189 crate::solver::avx512::RequiredFeatures,
190 crate::message::DoubleBlockMessage,
191 crate::solver::avx512::DoubleBlockSolver,
192 crate::solver::sha_ni::DoubleBlockSolver,
193 >;
194 pub type DecimalSolver = crate::solver::SolverRouter<
196 crate::solver::avx512::RequiredFeatures,
197 crate::message::DecimalMessage,
198 crate::solver::avx512::DecimalSolver,
199 crate::solver::sha_ni::DecimalSolver,
200 >;
201 pub type GoAwaySolver = crate::solver::SolverRouter<
203 crate::solver::avx512::RequiredFeatures,
204 crate::message::GoAwayMessage,
205 crate::solver::avx512::GoAwaySolver,
206 crate::solver::sha_ni::GoAwaySolver,
207 >;
208 pub type BinarySolver = crate::solver::SolverRouter<
210 crate::solver::avx512::RequiredFeatures,
211 crate::message::BinaryMessage,
212 crate::solver::avx512::BinarySolver,
213 crate::solver::safe::BinarySolver,
214 >;
215 pub const SOLVER_NAME: &str = "SHA-NI";
217 } else {
218 pub type SingleBlockSolver = crate::solver::SolverRouter<
220 crate::solver::avx512::RequiredFeatures,
221 crate::message::SingleBlockMessage,
222 crate::solver::avx512::SingleBlockSolver,
223 crate::solver::SolverRouter<
224 crate::solver::sha_ni::RequiredFeatures,
225 crate::message::SingleBlockMessage,
226 crate::solver::sha_ni::SingleBlockSolver,
227 crate::solver::safe::SingleBlockSolver,
228 >,
229 >;
230 pub type DoubleBlockSolver = crate::solver::SolverRouter<
232 crate::solver::avx512::RequiredFeatures,
233 crate::message::DoubleBlockMessage,
234 crate::solver::avx512::DoubleBlockSolver,
235 crate::solver::SolverRouter<
236 crate::solver::sha_ni::RequiredFeatures,
237 crate::message::DoubleBlockMessage,
238 crate::solver::sha_ni::DoubleBlockSolver,
239 crate::solver::safe::DoubleBlockSolver,
240 >,
241 >;
242 pub type DecimalSolver = crate::solver::SolverRouter<
244 crate::solver::avx512::RequiredFeatures,
245 crate::message::DecimalMessage,
246 crate::solver::avx512::DecimalSolver,
247 crate::solver::SolverRouter<
248 crate::solver::sha_ni::RequiredFeatures,
249 crate::message::DecimalMessage,
250 crate::solver::sha_ni::DecimalSolver,
251 crate::solver::safe::DecimalSolver,
252 >,
253 >;
254 pub type GoAwaySolver = crate::solver::SolverRouter<
256 crate::solver::avx512::RequiredFeatures,
257 crate::message::GoAwayMessage,
258 crate::solver::avx512::GoAwaySolver,
259 crate::solver::SolverRouter<
260 crate::solver::sha_ni::RequiredFeatures,
261 crate::message::GoAwayMessage,
262 crate::solver::sha_ni::GoAwaySolver,
263 crate::solver::safe::GoAwaySolver,
264 >,
265 >;
266 pub type BinarySolver = crate::solver::SolverRouter<
268 crate::solver::avx512::RequiredFeatures,
269 crate::message::BinaryMessage,
270 crate::solver::avx512::BinarySolver,
271 crate::solver::safe::BinarySolver,
272 >;
273 pub const SOLVER_NAME: &str = "Fallback";
275 }
276}
277
278#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
279cfg_if::cfg_if! {
280 if #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))] {
281 pub type CerberusSolver = crate::solver::avx512::CerberusSolver;
283 pub const BLAKE3_SOLVER_NAME: &str = "AVX-512";
285 } else if #[cfg(target_feature = "avx2")] {
286 pub type CerberusSolver = crate::solver::SolverRouter<
288 crate::solver::avx512::RequiredFeatures,
289 crate::message::CerberusMessage,
290 crate::solver::avx512::CerberusSolver,
291 crate::solver::avx2::CerberusSolver,
292 >;
293 pub const BLAKE3_SOLVER_NAME: &str = "AVX2";
295 } else {
296 pub type CerberusSolver =
298 crate::solver::SolverRouter<
299 crate::solver::avx512::RequiredFeatures,
300 crate::message::CerberusMessage,
301 crate::solver::avx512::CerberusSolver,
302 crate::solver::SolverRouter<
303 crate::solver::avx2::RequiredFeatures,
304 crate::message::CerberusMessage,
305 crate::solver::avx2::CerberusSolver,
306 crate::solver::safe::CerberusSolver,
307 >,
308 >;
309 pub const BLAKE3_SOLVER_NAME: &str = "Fallback";
311 }
312}
313
314#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
315cfg_if::cfg_if! {
316 if #[cfg(target_arch = "wasm32")] {
317 pub type SingleBlockSolver = crate::solver::simd128::SingleBlockSolver;
319 pub type DoubleBlockSolver = crate::solver::simd128::DoubleBlockSolver;
321 pub type DecimalSolver = crate::solver::simd128::DecimalSolver;
323 pub type GoAwaySolver = crate::solver::simd128::GoAwaySolver;
325 pub type BinarySolver = crate::solver::safe::BinarySolver;
327 pub type CerberusSolver = crate::solver::simd128::CerberusSolver;
329 pub const SOLVER_NAME: &str = "SIMD128";
331 pub const BLAKE3_SOLVER_NAME: &str = "SIMD128";
333 } else {
334 pub type SingleBlockSolver = crate::solver::safe::SingleBlockSolver;
336 pub type DoubleBlockSolver = crate::solver::safe::DoubleBlockSolver;
338 pub type DecimalSolver = crate::solver::safe::DecimalSolver;
340 pub type GoAwaySolver = crate::solver::safe::GoAwaySolver;
342 pub type BinarySolver = crate::solver::safe::BinarySolver;
344 pub type CerberusSolver = crate::solver::safe::CerberusSolver;
346 pub const SOLVER_NAME: &str = "Fallback";
348 pub const BLAKE3_SOLVER_NAME: &str = "Fallback";
350 }
351}
352
353pub fn build_mcaptcha_prefix<E: Extend<u8>>(out: &mut E, string: &str, salt: &str) {
355 out.extend(salt.as_bytes().iter().copied());
356 out.extend((string.len() as u64).to_le_bytes());
357 out.extend(string.as_bytes().iter().copied());
358}
359
360pub(crate) const fn decompose_blocks_mut(inp: &mut [u32; 16]) -> &mut [u8; 64] {
361 unsafe { core::mem::transmute(inp) }
362}
363
364#[cfg_attr(not(any(feature = "server", feature = "client")), expect(unused))]
374pub(crate) fn compute_plausible_time_sha256(hashes: u64) -> u64 {
375 hashes / 512
376}
377
378pub const fn compute_target_mcaptcha(difficulty_factor: u64) -> u64 {
380 u64::MAX - u64::MAX / difficulty_factor
381}
382
383pub const fn compute_mask_anubis(difficulty_factor: NonZeroU8) -> u64 {
385 !(!0u64 >> (difficulty_factor.get() * 4))
386}
387
388pub const fn compute_mask_goaway(difficulty_factor: NonZeroU8) -> u64 {
390 !(!0u64 >> (difficulty_factor.get()))
391}
392
393pub const fn compute_mask_cerberus(difficulty_factor: NonZeroU8) -> u64 {
395 let tmp = !(!0u64 >> (difficulty_factor.get() * 2));
396 let (tmp_hi, tmp_lo) = ((tmp >> 32) as u32, tmp as u32);
397 (tmp_hi.swap_bytes() as u64) << 32 | (tmp_lo.swap_bytes() as u64)
398}
399
400pub const fn extract128_be(inp: [u32; 8]) -> u128 {
402 (inp[0] as u128) << 96 | (inp[1] as u128) << 64 | (inp[2] as u128) << 32 | (inp[3] as u128)
403}
404
405pub fn encode_hex(out: &mut [u8; 64], inp: [u32; 8]) {
407 for w in 0..8 {
408 let be_bytes = inp[w].to_be_bytes();
409 be_bytes.iter().enumerate().for_each(|(i, b)| {
410 let high_nibble = b >> 4;
411 let low_nibble = b & 0xf;
412 out[w * 8 + i * 2] = if high_nibble < 10 {
413 high_nibble + b'0'
414 } else {
415 high_nibble + b'a' - 10
416 };
417 out[w * 8 + i * 2 + 1] = if low_nibble < 10 {
418 low_nibble + b'0'
419 } else {
420 low_nibble + b'a' - 10
421 };
422 });
423 }
424}
425
426pub fn encode_hex_le(out: &mut [u8; 64], inp: [u32; 8]) {
428 for w in 0..8 {
429 let be_bytes = inp[w].to_le_bytes();
430 be_bytes.iter().enumerate().for_each(|(i, b)| {
431 let high_nibble = b >> 4;
432 let low_nibble = b & 0xf;
433 out[w * 8 + i * 2] = if high_nibble < 10 {
434 high_nibble + b'0'
435 } else {
436 high_nibble + b'a' - 10
437 };
438 out[w * 8 + i * 2 + 1] = if low_nibble < 10 {
439 low_nibble + b'0'
440 } else {
441 low_nibble + b'a' - 10
442 };
443 });
444 }
445}
446
447#[cfg(test)]
448mod tests {
449
450 use super::*;
451
452 pub fn build_prefix_official<W: std::io::Write>(
453 out: &mut W,
454 string: &str,
455 salt: &str,
456 ) -> std::io::Result<()> {
457 out.write_all(salt.as_bytes())?;
458 match bincode::serialize_into(out, string) {
459 Ok(_) => (),
460 Err(e) => match *e {
461 bincode::ErrorKind::Io(e) => return Err(e),
462 _ => unreachable!(),
463 },
464 };
465 Ok(())
466 }
467
468 #[test]
469 fn test_encode_hex() {
470 let mut out = [0u8; 64];
471 encode_hex(
472 &mut out,
473 [
474 0x12345678, 0x9abcdef0, 0x12345678, 0x9abcdef0, 0x12345678, 0x9abcdef0, 0x12345678,
475 0x9abcdef0,
476 ],
477 );
478 assert_eq!(
479 unsafe { std::str::from_utf8_unchecked(&out) },
480 "123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0"
481 );
482 }
483
484 #[test]
485 fn test_compute_mask_anubis() {
486 assert_eq!(
487 compute_mask_anubis(NonZeroU8::new(1).unwrap()),
488 0xfu64.reverse_bits(),
489 );
490 assert_eq!(
491 compute_mask_anubis(NonZeroU8::new(2).unwrap()),
492 0xffu64.reverse_bits(),
493 );
494 assert_eq!(
495 compute_mask_anubis(NonZeroU8::new(3).unwrap()),
496 0xfffu64.reverse_bits(),
497 );
498 }
499
500 #[test]
501 fn test_bincode_string_serialize() {
502 let string = "hello";
503 let mut homegrown = Vec::new();
504 build_mcaptcha_prefix(&mut homegrown, string, "z");
505 let mut official = Vec::new();
506 build_prefix_official(&mut official, string, "z").unwrap();
507 assert_eq!(homegrown, official);
508 }
509
510 #[test]
511 fn test_cerberus_mask() {
512 fn check_small(hash: &[u8; 32], n: usize) -> bool {
513 let first_word: u32 = (hash[0] as u32) << 24
515 | (hash[1] as u32) << 16
516 | (hash[2] as u32) << 8
517 | (hash[3] as u32);
518 first_word.leading_zeros() >= (n as u32 * 2)
519 }
520
521 for i in 1..8 {
522 let mask = compute_mask_cerberus(NonZeroU8::new(i).unwrap());
523 eprintln!("mask: {:016x}", mask);
524 }
525
526 let mask = compute_mask_cerberus(NonZeroU8::new(7).unwrap());
529 let (mask_hi, mask_lo) = ((mask >> 32) as u32, mask as u32);
530 assert_eq!(mask_lo, 0);
531 let hash_partial = (!mask_hi).to_le_bytes();
532 eprintln!("hash_partial: {:02x?}", hash_partial);
533 let mut test = [0; 32];
534 test[..4].copy_from_slice(&hash_partial);
535 assert!(check_small(&test, 7));
536 }
537}