pow-buster 0.2.0

SIMD accelerated solver for mCaptcha/Anubis/Cerberus/go-away/Cap.js PoW challenges, features comprehensive coverage for Anubis challenge workflow.
Documentation
use alloc::string::{String, ToString};

use wasm_bindgen::prelude::*;

use crate::solver::Solver;

#[wasm_bindgen(js_name = "AnubisResponse")]
#[derive(Debug, Clone)]
pub struct AnubisResponse {
    subtype: &'static str,
    delay: u32,
    nonce: u64,
    response: String,
    attempted_nonces: u64,
}

#[wasm_bindgen]
impl AnubisResponse {
    #[wasm_bindgen(getter)]
    pub fn subtype(&self) -> String {
        self.subtype.to_string()
    }
    #[wasm_bindgen(getter)]
    pub fn response(&self) -> String {
        self.response.clone()
    }
    #[wasm_bindgen(getter)]
    pub fn nonce(&self) -> u64 {
        self.nonce
    }
    #[wasm_bindgen(getter)]
    pub fn delay(&self) -> u32 {
        self.delay
    }
    #[wasm_bindgen(getter)]
    pub fn attempted_nonces(&self) -> u64 {
        self.attempted_nonces
    }
}

#[wasm_bindgen]
pub fn solve_json(input: &str) -> Result<AnubisResponse, JsError> {
    if let Ok(descriptor) =
        serde_json::from_str::<crate::adapter::cerberus::ChallengeDescriptor>(input)
    {
        return solve_cerberus_json(&descriptor, None);
    } else if let Ok(descriptor) =
        serde_json::from_str::<crate::adapter::anubis::ChallengeDescriptor>(input)
    {
        return solve_anubis_json(&descriptor);
    } else {
        return Err(JsError::new("invalid descriptor"));
    };
}

#[wasm_bindgen]
pub fn solve_json_set(input: &str, set: u32, iterand: u32) -> Result<AnubisResponse, JsError> {
    if let Ok(descriptor) =
        serde_json::from_str::<crate::adapter::cerberus::ChallengeDescriptor>(input)
    {
        return solve_cerberus_json(&descriptor, Some((set, iterand)));
    } else {
        return Err(JsError::new("invalid descriptor"));
    };
}

fn solve_anubis_json(
    descriptor: &crate::adapter::anubis::ChallengeDescriptor,
) -> Result<AnubisResponse, JsError> {
    if !descriptor.supported() {
        return Err(JsError::new(
            "unsupported algorithm (please choose one of fast, slow, preact)",
        ));
    }

    let (result, attempted_nonces) = descriptor.solve();

    let Some((nonce, result)) = result else {
        return Err(JsError::new("solver failed"));
    };

    let mut response = [0u8; 64];
    crate::encode_hex(&mut response, result);
    Ok(AnubisResponse {
        subtype: "anubis",
        delay: descriptor.delay() as u32,
        nonce,
        response: unsafe { alloc::string::String::from_utf8_unchecked(response.to_vec()) },
        attempted_nonces,
    })
}

fn solve_cerberus_json(
    descriptor: &crate::adapter::cerberus::ChallengeDescriptor,
    fixed_set: Option<(u32, u32)>,
) -> Result<AnubisResponse, JsError> {
    let mut starting_set = 0;
    let mut loop_iterand = 1;
    let mut msg = descriptor
        .build_msg(0)
        .ok_or_else(|| JsError::new("invalid challenge"))?;
    if let Some((set, iterand)) = fixed_set {
        starting_set = set;
        loop_iterand = iterand;
        msg = descriptor
            .build_msg(set)
            .ok_or_else(|| JsError::new("reached maximum supported inner parallelism"))?;
    }
    let mut solver = crate::CerberusSolver::from(msg);

    for next_working_set in (starting_set..).step_by(loop_iterand as usize).skip(1) {
        let result =
            solver.solve::<{ crate::solver::SOLVE_TYPE_MASK }>(0, descriptor.mask() as u64);

        let Some((nonce, result)) = result else {
            msg = descriptor
                .build_msg(next_working_set)
                .ok_or_else(|| JsError::new("search exhausted"))?;
            solver = crate::CerberusSolver::from(msg);
            continue;
        };

        let mut response = [0u8; 64];
        crate::encode_hex_le(&mut response, result);
        return Ok(AnubisResponse {
            subtype: "cerberus",
            delay: 0,
            nonce,
            response: unsafe { alloc::string::String::from_utf8_unchecked(response.to_vec()) },
            attempted_nonces: solver.get_attempted_nonces(),
        });
    }

    Err(JsError::new("search exhausted"))
}