add experimental wasm solver
This commit is contained in:
@@ -91,3 +91,7 @@ docs/.vitepress/dist
|
||||
.vscode
|
||||
.data
|
||||
node_modules
|
||||
debug/
|
||||
target/
|
||||
**/*.rs.bk
|
||||
*.pdb
|
||||
@@ -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
@@ -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.
|
||||
|
||||
***
|
||||
---
|
||||
|
||||
[](https://www.bestpractices.dev/projects/9920)
|
||||
|
||||
|
||||
+2
-1
@@ -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
@@ -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");
|
||||
@@ -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
|
||||
@@ -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!`);
|
||||
@@ -0,0 +1 @@
|
||||
# Cap WASM
|
||||
@@ -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"
|
||||
}
|
||||
Generated
+218
@@ -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",
|
||||
]
|
||||
@@ -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"
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user