#![cfg_attr(not(any(test, feature = "std")), no_std)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
use sha2::digest::{
consts::{B1, U0, U16, U64},
typenum::{IsGreater, PowerOfTwo, Unsigned},
};
use core::num::NonZeroU8;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "client")]
pub mod client;
#[cfg(feature = "server")]
pub mod server;
#[cfg(all(target_arch = "wasm32", feature = "adapter"))]
mod wasm_ffi;
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
cfg_if::cfg_if! {
if #[cfg(feature = "internals")] {
pub mod strings;
} else {
mod strings;
}
}
mod sha256;
mod blake3;
pub mod message;
pub mod solver;
#[cfg(feature = "adapter")]
pub mod adapter;
pub unsafe trait AlignerTo<T>:
core::ops::Deref<Target = T> + core::ops::DerefMut<Target = T> + From<T>
{
type Alignment: Unsigned + PowerOfTwo + IsGreater<U0, Output = B1>;
type Output;
fn create_layout() -> core::alloc::Layout;
}
#[repr(align(16))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Align16<T>(pub T);
unsafe impl<T> AlignerTo<T> for Align16<T> {
type Alignment = U16;
type Output = T;
fn create_layout() -> core::alloc::Layout {
core::alloc::Layout::new::<Align16<T>>()
}
}
impl<T> From<T> for Align16<T> {
fn from(value: T) -> Self {
Align16(value)
}
}
impl<T> core::ops::Deref for Align16<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> core::ops::DerefMut for Align16<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[repr(align(64))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Align64<T>(pub T);
unsafe impl<T> AlignerTo<T> for Align64<T> {
type Alignment = U64;
type Output = T;
fn create_layout() -> core::alloc::Layout {
core::alloc::Layout::new::<Align64<T>>()
}
}
impl<T> From<T> for Align64<T> {
fn from(value: T) -> Self {
Align64(value)
}
}
impl<'a, T> From<&'a Align64<T>> for &'a Align16<T> {
fn from(this: &'a Align64<T>) -> &'a Align16<T> {
unsafe { core::mem::transmute(this) }
}
}
impl<'a, T> From<&'a mut Align64<T>> for &'a mut Align16<T> {
fn from(this: &'a mut Align64<T>) -> &'a mut Align16<T> {
unsafe { core::mem::transmute(this) }
}
}
impl<T> core::ops::Deref for Align64<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> core::ops::DerefMut for Align64<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cold]
fn unlikely() {}
const SWAP_DWORD_BYTE_ORDER: [usize; 64] = {
let mut data = [0; 64];
let mut i = 0;
while i < 64 {
data[i] = i / 4 * 4 + 3 - i % 4;
i += 1;
}
data
};
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))] {
pub type SingleBlockSolver = crate::solver::avx512::SingleBlockSolver;
pub type DoubleBlockSolver = crate::solver::avx512::DoubleBlockSolver;
pub type DecimalSolver = crate::solver::avx512::DecimalSolver;
pub type GoAwaySolver = crate::solver::avx512::GoAwaySolver;
pub type BinarySolver = crate::solver::avx512::BinarySolver;
pub const SOLVER_NAME: &str = "AVX-512";
} else if #[cfg(target_feature = "sha")] {
pub type SingleBlockSolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::SingleBlockMessage,
crate::solver::avx512::SingleBlockSolver,
crate::solver::sha_ni::SingleBlockSolver,
>;
pub type DoubleBlockSolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::DoubleBlockMessage,
crate::solver::avx512::DoubleBlockSolver,
crate::solver::sha_ni::DoubleBlockSolver,
>;
pub type DecimalSolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::DecimalMessage,
crate::solver::avx512::DecimalSolver,
crate::solver::sha_ni::DecimalSolver,
>;
pub type GoAwaySolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::GoAwayMessage,
crate::solver::avx512::GoAwaySolver,
crate::solver::sha_ni::GoAwaySolver,
>;
pub type BinarySolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::BinaryMessage,
crate::solver::avx512::BinarySolver,
crate::solver::safe::BinarySolver,
>;
pub const SOLVER_NAME: &str = "SHA-NI";
} else {
pub type SingleBlockSolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::SingleBlockMessage,
crate::solver::avx512::SingleBlockSolver,
crate::solver::SolverRouter<
crate::solver::sha_ni::RequiredFeatures,
crate::message::SingleBlockMessage,
crate::solver::sha_ni::SingleBlockSolver,
crate::solver::safe::SingleBlockSolver,
>,
>;
pub type DoubleBlockSolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::DoubleBlockMessage,
crate::solver::avx512::DoubleBlockSolver,
crate::solver::SolverRouter<
crate::solver::sha_ni::RequiredFeatures,
crate::message::DoubleBlockMessage,
crate::solver::sha_ni::DoubleBlockSolver,
crate::solver::safe::DoubleBlockSolver,
>,
>;
pub type DecimalSolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::DecimalMessage,
crate::solver::avx512::DecimalSolver,
crate::solver::SolverRouter<
crate::solver::sha_ni::RequiredFeatures,
crate::message::DecimalMessage,
crate::solver::sha_ni::DecimalSolver,
crate::solver::safe::DecimalSolver,
>,
>;
pub type GoAwaySolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::GoAwayMessage,
crate::solver::avx512::GoAwaySolver,
crate::solver::SolverRouter<
crate::solver::sha_ni::RequiredFeatures,
crate::message::GoAwayMessage,
crate::solver::sha_ni::GoAwaySolver,
crate::solver::safe::GoAwaySolver,
>,
>;
pub type BinarySolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::BinaryMessage,
crate::solver::avx512::BinarySolver,
crate::solver::safe::BinarySolver,
>;
pub const SOLVER_NAME: &str = "Fallback";
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
cfg_if::cfg_if! {
if #[cfg(all(target_arch = "x86_64", target_feature = "avx512f"))] {
pub type CerberusSolver = crate::solver::avx512::CerberusSolver;
pub const BLAKE3_SOLVER_NAME: &str = "AVX-512";
} else if #[cfg(target_feature = "avx2")] {
pub type CerberusSolver = crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::CerberusMessage,
crate::solver::avx512::CerberusSolver,
crate::solver::avx2::CerberusSolver,
>;
pub const BLAKE3_SOLVER_NAME: &str = "AVX2";
} else {
pub type CerberusSolver =
crate::solver::SolverRouter<
crate::solver::avx512::RequiredFeatures,
crate::message::CerberusMessage,
crate::solver::avx512::CerberusSolver,
crate::solver::SolverRouter<
crate::solver::avx2::RequiredFeatures,
crate::message::CerberusMessage,
crate::solver::avx2::CerberusSolver,
crate::solver::safe::CerberusSolver,
>,
>;
pub const BLAKE3_SOLVER_NAME: &str = "Fallback";
}
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] {
pub type SingleBlockSolver = crate::solver::simd128::SingleBlockSolver;
pub type DoubleBlockSolver = crate::solver::simd128::DoubleBlockSolver;
pub type DecimalSolver = crate::solver::simd128::DecimalSolver;
pub type GoAwaySolver = crate::solver::simd128::GoAwaySolver;
pub type BinarySolver = crate::solver::safe::BinarySolver;
pub type CerberusSolver = crate::solver::simd128::CerberusSolver;
pub const SOLVER_NAME: &str = "SIMD128";
pub const BLAKE3_SOLVER_NAME: &str = "SIMD128";
} else {
pub type SingleBlockSolver = crate::solver::safe::SingleBlockSolver;
pub type DoubleBlockSolver = crate::solver::safe::DoubleBlockSolver;
pub type DecimalSolver = crate::solver::safe::DecimalSolver;
pub type GoAwaySolver = crate::solver::safe::GoAwaySolver;
pub type BinarySolver = crate::solver::safe::BinarySolver;
pub type CerberusSolver = crate::solver::safe::CerberusSolver;
pub const SOLVER_NAME: &str = "Fallback";
pub const BLAKE3_SOLVER_NAME: &str = "Fallback";
}
}
pub fn build_mcaptcha_prefix<E: Extend<u8>>(out: &mut E, string: &str, salt: &str) {
out.extend(salt.as_bytes().iter().copied());
out.extend((string.len() as u64).to_le_bytes());
out.extend(string.as_bytes().iter().copied());
}
pub(crate) const fn decompose_blocks_mut(inp: &mut [u32; 16]) -> &mut [u8; 64] {
unsafe { core::mem::transmute(inp) }
}
#[cfg_attr(not(any(feature = "server", feature = "client")), expect(unused))]
pub(crate) fn compute_plausible_time_sha256(hashes: u64) -> u64 {
hashes / 512
}
pub const fn compute_target_mcaptcha(difficulty_factor: u64) -> u64 {
u64::MAX - u64::MAX / difficulty_factor
}
pub const fn compute_mask_anubis(difficulty_factor: NonZeroU8) -> u64 {
!(!0u64 >> (difficulty_factor.get() * 4))
}
pub const fn compute_mask_goaway(difficulty_factor: NonZeroU8) -> u64 {
!(!0u64 >> (difficulty_factor.get()))
}
pub const fn compute_mask_cerberus(difficulty_factor: NonZeroU8) -> u64 {
let tmp = !(!0u64 >> (difficulty_factor.get() * 2));
let (tmp_hi, tmp_lo) = ((tmp >> 32) as u32, tmp as u32);
(tmp_hi.swap_bytes() as u64) << 32 | (tmp_lo.swap_bytes() as u64)
}
pub const fn extract128_be(inp: [u32; 8]) -> u128 {
(inp[0] as u128) << 96 | (inp[1] as u128) << 64 | (inp[2] as u128) << 32 | (inp[3] as u128)
}
pub fn encode_hex(out: &mut [u8; 64], inp: [u32; 8]) {
for w in 0..8 {
let be_bytes = inp[w].to_be_bytes();
be_bytes.iter().enumerate().for_each(|(i, b)| {
let high_nibble = b >> 4;
let low_nibble = b & 0xf;
out[w * 8 + i * 2] = if high_nibble < 10 {
high_nibble + b'0'
} else {
high_nibble + b'a' - 10
};
out[w * 8 + i * 2 + 1] = if low_nibble < 10 {
low_nibble + b'0'
} else {
low_nibble + b'a' - 10
};
});
}
}
pub fn encode_hex_le(out: &mut [u8; 64], inp: [u32; 8]) {
for w in 0..8 {
let be_bytes = inp[w].to_le_bytes();
be_bytes.iter().enumerate().for_each(|(i, b)| {
let high_nibble = b >> 4;
let low_nibble = b & 0xf;
out[w * 8 + i * 2] = if high_nibble < 10 {
high_nibble + b'0'
} else {
high_nibble + b'a' - 10
};
out[w * 8 + i * 2 + 1] = if low_nibble < 10 {
low_nibble + b'0'
} else {
low_nibble + b'a' - 10
};
});
}
}
#[cfg(test)]
mod tests {
use super::*;
pub fn build_prefix_official<W: std::io::Write>(
out: &mut W,
string: &str,
salt: &str,
) -> std::io::Result<()> {
out.write_all(salt.as_bytes())?;
match bincode::serialize_into(out, string) {
Ok(_) => (),
Err(e) => match *e {
bincode::ErrorKind::Io(e) => return Err(e),
_ => unreachable!(),
},
};
Ok(())
}
#[test]
fn test_encode_hex() {
let mut out = [0u8; 64];
encode_hex(
&mut out,
[
0x12345678, 0x9abcdef0, 0x12345678, 0x9abcdef0, 0x12345678, 0x9abcdef0, 0x12345678,
0x9abcdef0,
],
);
assert_eq!(
unsafe { std::str::from_utf8_unchecked(&out) },
"123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0"
);
}
#[test]
fn test_compute_mask_anubis() {
assert_eq!(
compute_mask_anubis(NonZeroU8::new(1).unwrap()),
0xfu64.reverse_bits(),
);
assert_eq!(
compute_mask_anubis(NonZeroU8::new(2).unwrap()),
0xffu64.reverse_bits(),
);
assert_eq!(
compute_mask_anubis(NonZeroU8::new(3).unwrap()),
0xfffu64.reverse_bits(),
);
}
#[test]
fn test_bincode_string_serialize() {
let string = "hello";
let mut homegrown = Vec::new();
build_mcaptcha_prefix(&mut homegrown, string, "z");
let mut official = Vec::new();
build_prefix_official(&mut official, string, "z").unwrap();
assert_eq!(homegrown, official);
}
#[test]
fn test_cerberus_mask() {
fn check_small(hash: &[u8; 32], n: usize) -> bool {
let first_word: u32 = (hash[0] as u32) << 24
| (hash[1] as u32) << 16
| (hash[2] as u32) << 8
| (hash[3] as u32);
first_word.leading_zeros() >= (n as u32 * 2)
}
for i in 1..8 {
let mask = compute_mask_cerberus(NonZeroU8::new(i).unwrap());
eprintln!("mask: {:016x}", mask);
}
let mask = compute_mask_cerberus(NonZeroU8::new(7).unwrap());
let (mask_hi, mask_lo) = ((mask >> 32) as u32, mask as u32);
assert_eq!(mask_lo, 0);
let hash_partial = (!mask_hi).to_le_bytes();
eprintln!("hash_partial: {:02x?}", hash_partial);
let mut test = [0; 32];
test[..4].copy_from_slice(&hash_partial);
assert!(check_small(&test, 7));
}
}