Skip to main content

pow_buster/
lib.rs

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")]
16/// Web client for end-to-end PoW solving
17pub mod client;
18
19#[cfg(feature = "server")]
20/// Server for end-to-end PoW solving
21pub 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        /// String manipulation functions
30        pub mod strings;
31    } else {
32        mod strings;
33    }
34}
35
36/// SHA-256 primitives
37mod sha256;
38
39/// BLAKE3 primitives
40mod blake3;
41
42/// Message builders
43pub mod message;
44
45/// Solvers
46pub mod solver;
47
48#[cfg(feature = "adapter")]
49/// Data structures and adapters for end-to-end PoW solving without IO dependencies.
50pub mod adapter;
51
52/// A trait for a trivial aligner that has no function except setting the alignment and transparently holding a value of type `T`.
53///
54/// # Safety
55///
56/// Implementor must ensure their memory layout is valid.
57pub unsafe trait AlignerTo<T>:
58    core::ops::Deref<Target = T> + core::ops::DerefMut<Target = T> + From<T>
59{
60    /// The alignment of the aligner.
61    type Alignment: Unsigned + PowerOfTwo + IsGreater<U0, Output = B1>;
62    /// The type of the aligner.
63    type Output;
64
65    /// Create a `core::alloc::Layout` for the aligner.
66    ///
67    /// # Panics
68    ///
69    /// Panics if the request memory size is rejected by the allocator API.
70    fn create_layout() -> core::alloc::Layout;
71}
72
73#[repr(align(16))]
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75/// Align to 16 bytes
76pub 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)]
107/// Align to 64 bytes
108pub 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
124// Ref downcast to Align16
125impl<'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
131// Ref downcast to Align16
132impl<'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        /// Single block solver
168        pub type SingleBlockSolver = crate::solver::avx512::SingleBlockSolver;
169        /// Double block solver
170        pub type DoubleBlockSolver = crate::solver::avx512::DoubleBlockSolver;
171        /// Dynamic dispatching Decimal solver
172        pub type DecimalSolver = crate::solver::avx512::DecimalSolver;
173        /// Go away solver
174        pub type GoAwaySolver = crate::solver::avx512::GoAwaySolver;
175        /// Binary solver
176        pub type BinarySolver = crate::solver::avx512::BinarySolver;
177        /// Solver name
178        pub const SOLVER_NAME: &str = "AVX-512";
179    } else if #[cfg(target_feature = "sha")] {
180        /// Single block solver
181        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        /// Double block solver
188        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        /// Dynamic dispatching Decimal solver
195        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        /// Go away solver
202        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        /// Binary solver
209        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        /// Solver name
216        pub const SOLVER_NAME: &str = "SHA-NI";
217    } else {
218        /// Single block solver
219        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        /// Double block solver
231        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        /// Dynamic dispatching Decimal solver
243        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        /// Go away solver
255        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        /// Binary solver
267        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        /// Solver name
274        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        /// Cerberus solver
282        pub type CerberusSolver = crate::solver::avx512::CerberusSolver;
283        /// Blake3 solver name
284        pub const BLAKE3_SOLVER_NAME: &str = "AVX-512";
285    } else if #[cfg(target_feature = "avx2")] {
286        /// Cerberus solver
287        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        /// Blake3 solver name
294        pub const BLAKE3_SOLVER_NAME: &str = "AVX2";
295    } else {
296        /// Cerberus solver
297        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        /// Blake3 solver name
310        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        /// Single block solver
318        pub type SingleBlockSolver = crate::solver::simd128::SingleBlockSolver;
319        /// Double block solver
320        pub type DoubleBlockSolver = crate::solver::simd128::DoubleBlockSolver;
321        /// Dynamic dispatching Decimal solver
322        pub type DecimalSolver = crate::solver::simd128::DecimalSolver;
323        /// Go away solver
324        pub type GoAwaySolver = crate::solver::simd128::GoAwaySolver;
325        /// Binary solver
326        pub type BinarySolver = crate::solver::safe::BinarySolver;
327        /// Cerberus solver
328        pub type CerberusSolver = crate::solver::simd128::CerberusSolver;
329        /// Solver name
330        pub const SOLVER_NAME: &str = "SIMD128";
331        /// Blake3 solver name
332        pub const BLAKE3_SOLVER_NAME: &str = "SIMD128";
333    } else {
334        /// Single block solver
335        pub type SingleBlockSolver = crate::solver::safe::SingleBlockSolver;
336        /// Double block solver
337        pub type DoubleBlockSolver = crate::solver::safe::DoubleBlockSolver;
338        /// Dynamic dispatching Decimal solver
339        pub type DecimalSolver = crate::solver::safe::DecimalSolver;
340        /// Go away solver
341        pub type GoAwaySolver = crate::solver::safe::GoAwaySolver;
342        /// Binary solver
343        pub type BinarySolver = crate::solver::safe::BinarySolver;
344        /// Cerberus solver
345        pub type CerberusSolver = crate::solver::safe::CerberusSolver;
346        /// Solver name
347        pub const SOLVER_NAME: &str = "Fallback";
348        /// Blake3 solver name
349        pub const BLAKE3_SOLVER_NAME: &str = "Fallback";
350    }
351}
352
353/// Build a prefix for mCaptcha PoW
354pub 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/// Compute a plausible time for a SHA-256 PoW to prevent tainting metrics
365///
366/// # Arguments
367///
368/// * `hashes`: The number of hashes that have been computed
369///
370/// # Returns
371///
372/// The plausible time in milliseconds
373#[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
378/// Compute the target for an mCaptcha PoW
379pub const fn compute_target_mcaptcha(difficulty_factor: u64) -> u64 {
380    u64::MAX - u64::MAX / difficulty_factor
381}
382
383/// Compute the mask for an Anubis PoW (mask & (V\[0] << 32 | V\[1]) == 0)
384pub const fn compute_mask_anubis(difficulty_factor: NonZeroU8) -> u64 {
385    !(!0u64 >> (difficulty_factor.get() * 4))
386}
387
388/// Compute the mask for a GoAway PoW (mask & (V\[0] << 32) == 0)
389pub const fn compute_mask_goaway(difficulty_factor: NonZeroU8) -> u64 {
390    !(!0u64 >> (difficulty_factor.get()))
391}
392
393/// Compute a mask for a Cerberus PoW (mask & (V\[0] << 32) == 0)
394pub 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
400/// Extract top 128 bits from a 64-bit word array
401pub 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
405/// Encode a sha-256 hash into hex
406pub 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
426/// Encode a blake3 hash into hex
427pub 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            // https://github.com/sjtug/cerberus/blob/ee8f903f1311da7022aec68c8686739b40f4a168/pow/src/check_dubit.rs
514            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        // hash[0] is the LSB of the H0 register
527
528        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}