add experimental wasm solver

This commit is contained in:
Tiago
2025-04-23 12:46:42 +01:00
parent 6d5c99add4
commit fa44a89ce6
14 changed files with 943 additions and 4 deletions
+4
View File
@@ -91,3 +91,7 @@ docs/.vitepress/dist
.vscode
.data
node_modules
debug/
target/
**/*.rs.bk
*.pdb
+2
View File
@@ -33,6 +33,8 @@ There are also some other helpful packages:
- **[Standalone mode](https://capjs.js.org/guide/standalone.html)**: Docker image that helps you use Cap with any language or framework. It runs a simple REST API that can be used to create and validate challenges and an interactive UI to manage your keys.
- **@cap.js/wasm**: Experimental WASM solvers built using Rust.
It's designed to be a drop-in replacement for existing CAPTCHA solutions, with a focus on performance and UX.
Cap is built with JavaScript, runs on any JS runtime (Bun, Node.js, Deno), and has no dependencies. If you're not using any JS runtime, you can also use the standalone mode with Docker, which relies entirely on a simple REST API to create and validate challenges.
+3 -1
View File
@@ -65,6 +65,8 @@ There are also some other helpful packages:
- **[Standalone mode](https://capjs.js.org/guide/standalone.html)**: Docker image that helps you use Cap with any language or framework. It runs a simple REST API that can be used to create and validate challenges and an interactive UI to manage your keys.
- **@cap.js/wasm**: Experimental WASM solvers built using Rust.
It's designed to be a drop-in replacement for existing CAPTCHA solutions, with a focus on performance and UX.
Cap is built with JavaScript, runs on any JS runtime (Bun, Node.js, Deno), and has no dependencies. If you're not using any JS runtime, you can also use the standalone mode with Docker, which relies entirely on a simple REST API to create and validate challenges.
@@ -109,7 +111,7 @@ But unlike them, Cap is **computation-bound, not tracking-bound**.
Cap is licensed under the Apache License 2.0.
***
---
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9920/badge)](https://www.bestpractices.dev/projects/9920)
+2 -1
View File
@@ -222,6 +222,7 @@
"widget:publish": "cd widget && cd src && sudo npm publish --access public",
"server:publish": "cd server && sudo npm publish --access public",
"solver:publish": "cd solver && sudo npm publish --access public",
"cli:publish": "cd solver-cli && sudo npm publish --access public"
"cli:publish": "cd solver-cli && sudo npm publish --access public",
"wasm:publish": "cd solver-cli && sudo npm publish --access public"
}
}
+4 -2
View File
@@ -18,9 +18,10 @@ const CHALLENGES = [
["308758072931bb3b254a7b1ed351d04a", "3e49"],
["724f89bb167db4b881e1dc7b0949ac8f", "b82e"],
["8b79506e4630de15be225c18623eff65", "f0e5"],
["0c21ade6e63a4e37b13cb8b087f31863", "65c9"],
["0c21ade6e63a4e37b13cb8b087f31863", "65c9e"],
];
const timeStart = new Date().getTime();
const solutions = await solver(CHALLENGES, {
onProgress: (status) => {
process.stdout.moveCursor(0, -1);
@@ -32,4 +33,5 @@ const solutions = await solver(CHALLENGES, {
process.stdout.moveCursor(0, -1);
process.stdout.clearScreenDown();
console.log("Solutions:", solutions);
console.log("Solutions:", solutions);
console.log("Time taken:", ((new Date().getTime()) - timeStart).toFixed(2), "ms");
+34
View File
@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
+30
View File
@@ -0,0 +1,30 @@
import { execSync } from "child_process";
import path from "path";
import fs from "fs";
const rustSrcDir = path.join(process.cwd(), "./src/rust");
const nodeOutDir = path.join(process.cwd(), "./src/node");
const browserOutDir = path.join(process.cwd(), "./src/browser");
const packageName = "cap_wasm";
console.log(`Cleaning old build directories...`);
try {
fs.rmdirSync(nodeOutDir);
} catch {}
try {
fs.rmdirSync(browserOutDir);
} catch {}
console.log(`\n Building for Node...`);
execSync(
`wasm-pack build "${rustSrcDir}" --target nodejs --out-dir "${nodeOutDir}" --out-name "${packageName}"`,
{ stdio: "inherit" }
);
console.log(`\n Building for web...`);
execSync(
`wasm-pack build "${rustSrcDir}" --target web --out-dir "${browserOutDir}" --out-name "${packageName}"`,
{ stdio: "inherit" }
);
console.log(`\n🎉 All builds finished successfully!`);
+1
View File
@@ -0,0 +1 @@
# Cap WASM
+234
View File
@@ -0,0 +1,234 @@
{
"name": "@cap.js/wasm",
"version": "0.0.1",
"description": "WASM for Cap's solver",
"main": "index.js",
"files": [
"node/",
"browser/",
"README.md"
],
"keywords": [
"account security",
"abuse detection",
"abuse prevention",
"api abuse",
"api defense",
"api analytics",
"api protection",
"api security",
"algorithm",
"algorithm implementation",
"alerting",
"anti-abuse",
"anti-automation",
"anti-bot",
"anti-bot component",
"anti-ddos",
"anti-dos",
"anti-exploitation",
"anti-spam",
"application security",
"authentication",
"authentication protocol",
"automated attacks",
"automated scraping",
"behavioral analysis",
"bot defense",
"bot detection",
"bot detection monitoring",
"bot management",
"bot mitigation",
"bot prevention",
"bot traffic",
"bots",
"brute force",
"bun",
"bun runtime",
"challenge generator",
"challenge string",
"challenge verifier",
"challenge-response",
"challenge-response protocol",
"client defense",
"client-side",
"client-server architecture",
"compute",
"compute bound",
"computation",
"computational challenge",
"computational complexity",
"computational defense",
"computational puzzle",
"configuration",
"cpu bound",
"cross-platform",
"crypto",
"crypto puzzle",
"cryptographic algorithm",
"cryptographic challenge",
"cryptographic defense",
"cryptographic puzzle",
"cryptography",
"cryptography library",
"customization",
"cybersecurity",
"ddos",
"ddos protection",
"defense mechanism",
"degree of difficulty",
"dependencies",
"deployment",
"design",
"developer tool",
"development",
"development environment",
"difficulty",
"digest",
"digital security",
"distributed computing",
"dos",
"encryption",
"exploit mitigation",
"form security",
"fraud detection",
"fraud prevention",
"frontend development",
"frontend js",
"fullstack js",
"hash function",
"hash puzzle",
"hashcash",
"hashing",
"hashing library",
"hcaptcha",
"human test",
"human verification",
"image captcha",
"incident response",
"infrastructure security",
"integration",
"integrity",
"internet security",
"invisible captcha",
"js",
"js runtime",
"javascript",
"javascript tool",
"library",
"lightweight",
"logging",
"logic",
"login security",
"low overhead",
"machine learning",
"malicious traffic",
"minimal",
"mining",
"mitigation technique",
"module",
"modules",
"monitoring",
"minimalist",
"node",
"node.js",
"node.js runtime",
"node.js tool",
"nonce",
"non-blocking i/o",
"online security",
"open source",
"optimization",
"package",
"parallel computation",
"performant",
"performance",
"performance monitoring",
"performance optimization",
"performance testing",
"pnpm",
"pow",
"process",
"production",
"programming",
"proof",
"proof generation",
"proof generator",
"proof of work",
"proof verification",
"proof verifier",
"proof-of-work",
"quick",
"rate limiting",
"ratelimit",
"recaptcha",
"reliability",
"request",
"resilience",
"resource efficient",
"scrapers",
"secure hashing",
"security analytics",
"security challenge",
"security component",
"security library",
"security measure",
"security model",
"security monitoring",
"security protocol",
"security system",
"security technique",
"security testing",
"server defense",
"software development",
"solver",
"spam bots",
"spam filtering",
"spam mitigation",
"spam prevention",
"target difficulty",
"threat intelligence",
"traffic management",
"traffic monitoring",
"turing test",
"unwanted traffic",
"user experience",
"validation",
"validation process",
"verification",
"verification process",
"verifier",
"vulnerability",
"web application protection",
"web application security",
"web defense",
"web defense system",
"web development",
"web scraping",
"web security",
"web security tool",
"web services",
"web worker",
"web workers",
"website defense",
"website protection",
"website security",
"work validation",
"worker thread",
"workers",
"captcha solver"
],
"author": "Tiago",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/tiagorangel1/cap.git"
},
"bugs": {
"url": "https://github.com/tiagorangel1/cap/issues"
},
"scripts": {
"npm:publish": "sudo npm publish --access public"
},
"homepage": "https://github.com/tiagorangel1/cap#readme"
}
+218
View File
@@ -0,0 +1,218 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "cap_wasm"
version = "0.1.0"
dependencies = [
"hex",
"sha2",
"wasm-bindgen",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "syn"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "cap_wasm"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
sha2 = "0.10"
hex = "0.4"
+35
View File
@@ -0,0 +1,35 @@
use wasm_bindgen::prelude::*;
use sha2::{Sha256, Digest};
use hex;
#[wasm_bindgen]
pub fn solve_pow(salt: String, target: String) -> u64 {
let salt_bytes = salt.as_bytes();
let target_len = target.len();
// Determine how many bytes of the hash we need to
// compare based on the hex target length
let required_hash_bytes = (target_len + 1) / 2;
for nonce in 0..u64::MAX {
let nonce_str = nonce.to_string();
let nonce_bytes = nonce_str.as_bytes();
let mut hasher = Sha256::new();
hasher.update(salt_bytes);
hasher.update(nonce_bytes);
let hash_result = hasher.finalize(); // Returns GenericArray<u8, U32>
let hash_prefix_bytes = &hash_result[0..required_hash_bytes.min(32)];
let hash_prefix_hex = hex::encode(hash_prefix_bytes);
if hash_prefix_hex.starts_with(&target) {
return nonce;
}
}
unreachable!("Solution should be found before exhausting u64::MAX");
}
+322
View File
@@ -0,0 +1,322 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
crossorigin="anonymous"
/>
</head>
<body>
<div class="container mt-4">
<button id="solveButton" class="btn btn-primary mb-3">
Solve challenges
</button>
<div id="progress" class="mb-3">Status: Idle</div>
<h2>Results:</h2>
<ul
id="results"
class="list-group border rounded overflow-auto mb-4"
style="min-height: 200px; max-height: 500px"
></ul>
</div>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"
></script>
<script>
const solveButton = document.getElementById("solveButton");
const progressDiv = document.getElementById("progress");
const resultsDiv = document.getElementById("results");
const challenges = [
["083655eebec28dd11449f082b026e856", "4b00"],
["bd2e454fbe4efaad38c44ecd72cc4a7b", "f3ef"],
["739d2c3ae32e9d726b27231394a2db89", "bcfe"],
["724e8cf75e11586f881f4c5840204378", "3d20"],
["32b76ba7ee19b3db3f6e7de21124f3b0", "fba4"],
["59337d81ae468b50e3b45f19f96b816d", "a27a"],
["2b3194fd3f26be5671945578e32eff39", "4f3c"],
["a6050041bff37f9a31653dd0e8b884a9", "4520"],
["471162e18f09d41e3281b188edefd8de", "7861"],
["3f6d41cdc04e2d0e7784c1a839133df0", "26b5"],
["a4bb413d6e1c2e992a66cb56bd341a08", "cd05"],
["405910a8488466b7fe6067eb92a59232", "f693"],
["1c7d565e1b65df2bf8b26bcf9a6cfbbd", "a75b"],
["93b6cd8025737d92a0b60266dcf5c612", "c0d2"],
["0a799a1d82fd10979661eff665599cc2", "4997"],
["7f0c392563b1694c8066af2f397b8b82", "0d13"],
["b4c7ba684cb9b0c64da82560c3e6b4bc", "5de6"],
["2dd07a35171ed76878746f386d6d6df3", "4bea"],
];
const numWorkers = navigator.hardwareConcurrency || 8;
const workerCodeTemplate = (() => {
const baseURL = "${baseURL}";
const relativeWasmWrapperPath = "../src/browser/cap_wasm.js";
let solve_pow_function = null;
let initError = null;
let initPromise = null;
const absoluteWasmWrapperUrl = new URL(relativeWasmWrapperPath, baseURL)
.href;
initPromise = import(absoluteWasmWrapperUrl)
.then((wasmModule) => {
return wasmModule
.default()
.then((instance) => {
solve_pow_function = (
instance && instance.exports ? instance.exports : wasmModule
).solve_pow;
});
})
.catch((error) => {
console.error("[worker] setup failed", error);
self.postMessage({
error: `Worker setup failed: ${error.message || error}`,
});
});
self.onmessage = async (event) => {
const { salt, target, challengeIndex } = event.data;
try {
if (initPromise) {
await initPromise;
}
const startTime = performance.now();
const nonce = solve_pow_function(salt, target);
const endTime = performance.now();
const duration = (endTime - startTime).toFixed(2);
self.postMessage({
challengeIndex: challengeIndex,
salt: salt,
target: target,
nonce: Number(nonce),
durationMs: duration,
});
} catch (error) {
console.error(`[worker] (job #${challengeIndex})`, error);
self.postMessage({
challengeIndex: challengeIndex,
salt: salt,
target: target,
error: `Execution failed: ${error.message || error}`,
});
}
};
self.onerror = (error) => {
self.postMessage({
error: `Error: ${error.message || error}`,
});
};
})
.toString()
.replace("() => {", "")
.slice(0, -1);
let workerPool = [];
let workerBlobUrl = null;
let nextChallengeIndex = 0;
let challengesProcessed = 0;
let allResults = [];
let startTime = 0;
function updateProgress() {
const elapsed = ((performance.now() - startTime) / 1000).toFixed(2);
progressDiv.textContent = `Status: Processing... ${challengesProcessed}/${challenges.length} challenges complete. Elapsed: ${elapsed}s`;
progressDiv.classList.remove("text-danger", "fw-bold");
}
function displayResult(data) {
const item = document.createElement("li");
item.classList.add("list-group-item");
if (data.error) {
item.classList.add("list-group-item-danger");
item.innerHTML = `<strong>Challenge #${
data.challengeIndex === -1 ? "??" : data.challengeIndex + 1
} errored</strong> ${
data.salt ? `${data.salt}:${data.target}` : ""
}<br><pre class="bg-secondary bg-opacity-10 p-2 rounded mt-2 mb-0">${
data.error
}</pre>`;
} else {
item.classList.add("list-group-item-success");
item.innerHTML = `<strong>Challenge #${
data.challengeIndex + 1
} solved</strong> with nonce <span class="text-success fw-bold">${
data.nonce
}</span><br><small>${data.salt}:${data.target}<br>Took ${
data.durationMs
} ms</small>`;
}
resultsDiv.appendChild(item);
resultsDiv.scrollTop = resultsDiv.scrollHeight;
}
function cleanupWorkers() {
workerPool.forEach((worker) => worker.terminate());
workerPool = [];
if (workerBlobUrl) {
URL.revokeObjectURL(workerBlobUrl);
workerBlobUrl = null;
}
}
function startNextTask(worker) {
if (nextChallengeIndex < challenges.length) {
const currentIndex = nextChallengeIndex;
nextChallengeIndex++;
console.log(
`Assigning challenge index ${currentIndex} (Task ${
currentIndex + 1
}/${challenges.length}) to a worker.`
);
const challengeData = challenges[currentIndex];
const [salt, target] = challengeData;
worker.postMessage({ challengeIndex: currentIndex, salt, target });
}
}
function resetUI() {
resultsDiv.innerHTML = "";
progressDiv.textContent = "Status: Idle";
progressDiv.classList.remove("text-danger", "fw-bold");
solveButton.disabled = false;
solveButton.textContent = "Solve challenges";
}
solveButton.addEventListener("click", () => {
if (workerPool.length > 0) {
return;
}
resultsDiv.innerHTML = "";
challengesProcessed = 0;
nextChallengeIndex = 0;
allResults = new Array(challenges.length);
progressDiv.textContent = `Status: Initializing ${numWorkers} workers...`;
progressDiv.classList.remove("text-danger", "fw-bold");
solveButton.disabled = true;
solveButton.textContent = "Solving...";
startTime = performance.now();
try {
let baseURL;
const currentPath = window.location.pathname;
const directoryPath = currentPath.substring(
0,
currentPath.lastIndexOf("/") + 1
);
baseURL = window.location.origin + directoryPath;
if (!baseURL.endsWith("/")) {
baseURL += "/";
}
const finalWorkerCode = workerCodeTemplate.replace(
/\$\{baseURL\}/g,
baseURL
);
const blob = new Blob([finalWorkerCode], {
type: "application/javascript",
});
workerBlobUrl = URL.createObjectURL(blob);
for (let i = 0; i < numWorkers; i++) {
const worker = new Worker(workerBlobUrl, { type: "module" });
worker.onmessage = (event) => {
if (
event.data &&
typeof event.data.challengeIndex !== "undefined"
) {
challengesProcessed++;
allResults[event.data.challengeIndex] = event.data;
displayResult(event.data);
updateProgress();
if (challengesProcessed === challenges.length) {
const totalTime = (
(performance.now() - startTime) /
1000
).toFixed(2);
progressDiv.textContent = `Status: All ${challenges.length} challenges processed in ${totalTime} seconds.`;
progressDiv.classList.remove("text-danger", "fw-bold");
cleanupWorkers();
solveButton.disabled = false;
solveButton.textContent = "Solve challenges";
} else {
startNextTask(worker);
}
return;
}
console.error(
`Main: Initialization/Setup error from Worker ${i + 1}:`,
event.data.error
);
displayResult({
challengeIndex: -1,
error: `Worker ${i + 1} setup failed: ${event.data.error}`,
});
progressDiv.textContent = `Status: Worker error`;
progressDiv.classList.add("text-danger", "fw-bold");
cleanupWorkers();
solveButton.disabled = false;
solveButton.textContent = "Solve challenges";
};
worker.onerror = (error) => {
console.error(
`Main: Error reported from Worker ${i + 1}:`,
error
);
displayResult({
challengeIndex: -1,
error: `Worker ${i + 1} ${error.message || "Unknown error"}`,
});
progressDiv.textContent = `Status: Error in Worker ${
i + 1
}. Stopping.`;
progressDiv.classList.add("text-danger", "fw-bold");
cleanupWorkers();
solveButton.disabled = false;
solveButton.textContent = "Solve challenges";
};
workerPool.push(worker);
}
workerPool.forEach(startNextTask);
updateProgress();
} catch (error) {
progressDiv.textContent = `Status: Failed to create workers: ${error.message}`;
progressDiv.classList.add("text-danger", "fw-bold");
solveButton.disabled = false;
solveButton.textContent = "Solve challenges";
cleanupWorkers();
}
});
</script>
</body>
</html>
+42
View File
@@ -0,0 +1,42 @@
import { performance } from "perf_hooks";
import { solve_pow } from "../src/node/cap_wasm.js";
const challenges = [
["e455cea65e98bc3c36287f43769da211", "dceb"],
["fb8d25f6abac5aa9b6360051f37e010b", "93f1"],
["91ef47db578fbeb2565d3f9c82bb7960", "3698"],
["b7ad7667486a691cda8ef297098f64a7", "d72a"],
["1aca3fb7cef7a2be0dee563ed4136758", "3b58"],
["d9ec39af92b430e5a329274d8aa58fa8", "e1d3"],
["781a3cc9217d73c908a321d3fdabd62f", "22c6"],
["e37a0752c9ac2f3d2517747fde373ac9", "f6f1"],
["bba070197569f322beda5b240f639a95", "4751"],
["89297515aeac646bee9653ba405e0beb", "a7de"],
["444571a0d5039c15be6141d6cd8434f9", "a783"],
["ba75f2bf8e9b92cc32caa17237a52d14", "7e30"],
["22bfc18ba8e3ecee080c5d1ef64ed6e9", "5fcf"],
["885fb78ff76b4eddd2f5bc04ac5ee673", "93e5"],
["308758072931bb3b254a7b1ed351d04a", "3e49"],
["724f89bb167db4b881e1dc7b0949ac8f", "b82e"],
["8b79506e4630de15be225c18623eff65", "f0e5"],
["0c21ade6e63a4e37b13cb8b087f31863", "65c9"],
];
async function runSolverTest() {
const startTime = performance.now();
for (let i = 0; i < challenges.length; i++) {
const [salt, target] = challenges[i];
const nonce = solve_pow(salt, target);
console.log(`[${i + 1}/${challenges.length}] ${salt}:${target}:${nonce}`);
}
const endTime = performance.now();
const totalTime = (endTime - startTime) / 1000;
console.log(`Solved challenges in ${totalTime.toFixed(3)}s`);
}
runSolverTest();