Compare commits

..

2 Commits

Author SHA1 Message Date
Xe Iaso
c8b792668e chore: fix spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
2025-08-29 20:28:38 +00:00
Xe Iaso
648b1b7eb2 chore: introduce issue templates 2025-08-01 07:37:39 -04:00
18 changed files with 357 additions and 337 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: 'bug:'
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: 'feature:'
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

9
.github/ISSUE_TEMPLATE/security.md vendored Normal file
View File

@@ -0,0 +1,9 @@
---
name: Security report
about: Do not file security reports here. Email security@techaro.lol.
title: "security:"
labels: ""
assignees: Xe
---
Do not file security reports here. Email security@techaro.lol.

View File

@@ -141,7 +141,6 @@ httpdebug
Huawei Huawei
hypertext hypertext
iaskspider iaskspider
iaso
iat iat
ifm ifm
Imagesift Imagesift
@@ -233,7 +232,6 @@ promauto
promhttp promhttp
proofofwork proofofwork
publicsuffix publicsuffix
purejs
pwcmd pwcmd
pwuser pwuser
qualys qualys
@@ -256,6 +254,7 @@ runtimedir
runtimedirectory runtimedirectory
sas sas
sasl sasl
screenshots
searchbot searchbot
searx searx
sebest sebest
@@ -311,6 +310,7 @@ Varis
Velen Velen
vendored vendored
vhosts vhosts
videotest
VKE VKE
Vultr Vultr
waitloop waitloop

View File

@@ -19,17 +19,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- The [`segments`](./admin/configuration/expressions.mdx#segments) function was added for splitting a path into its slash-separated segments. - The [`segments`](./admin/configuration/expressions.mdx#segments) function was added for splitting a path into its slash-separated segments.
- When issuing a challenge, Anubis stores information about that challenge into the store. That stored information is later used to validate challenge responses. This works around nondeterminism in bot rules. ([#917](https://github.com/TecharoHQ/anubis/issues/917)) - When issuing a challenge, Anubis stores information about that challenge into the store. That stored information is later used to validate challenge responses. This works around nondeterminism in bot rules. ([#917](https://github.com/TecharoHQ/anubis/issues/917))
- When parsing [Open Graph tags](./admin/configuration/open-graph.mdx), add any URLs found in the responses to a temporary "allow cache" so that social preview images work. - When parsing [Open Graph tags](./admin/configuration/open-graph.mdx), add any URLs found in the responses to a temporary "allow cache" so that social preview images work.
- Proof of work solving has had a complete overhaul and rethink based on feedback from browser engine developers, frontend experts, and overall performance profiling.
- One of the biggest sources of lag in Firefox has been eliminated: the use of WebCrypto. Now whenever Anubis detects the client is using Firefox (or Pale Moon), it will swap over to a pure-JS implementation of SHA-256 for speed.
- Web Workers are stored as dedicated JavaScript files in `static/js/workers/*.mjs`.
- Pave the way for non-SHA256 solver methods and eventually one that uses WebAssembly (or WebAssembly code compiled to JS for those that disable WebAssembly).
- Legacy JavaScript code has been eliminated.
- The contact email in the LibreJS header has been changed.
- The hard dependency on WebCrypto has been removed, allowing a proof of work challenge to work over plain (unencrypted) HTTP.
### Breaking changes
- The "slow" frontend solver has been removed in order to reduce maintenance burden. Any existing uses of it will still work, but issue a warning upon startup asking administrators to upgrade to the "fast" frontend solver.
## v1.21.3: Minfilia Warde - Echo 3 ## v1.21.3: Minfilia Warde - Echo 3

View File

@@ -149,10 +149,6 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
if parsedBot.Challenge.Algorithm == "" { if parsedBot.Challenge.Algorithm == "" {
parsedBot.Challenge.Algorithm = config.DefaultAlgorithm parsedBot.Challenge.Algorithm = config.DefaultAlgorithm
} }
if parsedBot.Challenge.Algorithm == "slow" {
slog.Warn("use of deprecated algorithm \"slow\" detected, please update this to \"fast\" when possible", "name", parsedBot.Name)
}
} }
if b.Weight != nil { if b.Weight != nil {
@@ -167,10 +163,6 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
} }
for _, t := range c.Thresholds { for _, t := range c.Thresholds {
if t.Challenge != nil && t.Challenge.Algorithm == "slow" {
slog.Warn("use of deprecated algorithm \"slow\" detected, please update this to \"fast\" when possible", "name", t.Name)
}
if t.Name == "legacy-anubis-behaviour" && t.Expression.String() == "true" { if t.Name == "legacy-anubis-behaviour" && t.Expression.String() == "true" {
if !warnedAboutThresholds.Load() { if !warnedAboutThresholds.Load() {
slog.Warn("configuration file does not contain thresholds, see docs for details on how to upgrade", "fname", fname, "docs_url", "https://anubis.techaro.lol/docs/admin/configuration/thresholds/") slog.Warn("configuration file does not contain thresholds, see docs for details on how to upgrade", "fname", fname, "docs_url", "https://anubis.techaro.lol/docs/admin/configuration/thresholds/")

97
package-lock.json generated
View File

@@ -8,9 +8,6 @@
"name": "@techaro/anubis", "name": "@techaro/anubis",
"version": "1.21.3", "version": "1.21.3",
"license": "ISC", "license": "ISC",
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0"
},
"devDependencies": { "devDependencies": {
"cssnano": "^7.1.0", "cssnano": "^7.1.0",
"cssnano-preset-advanced": "^7.0.8", "cssnano-preset-advanced": "^7.0.8",
@@ -22,44 +19,6 @@
"postcss-url": "^10.1.3" "postcss-url": "^10.1.3"
} }
}, },
"node_modules/@aws-crypto/sha256-js": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz",
"integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/util": "^5.2.0",
"@aws-sdk/types": "^3.222.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/@aws-crypto/util": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz",
"integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==",
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "^3.222.0",
"@smithy/util-utf8": "^2.0.0",
"tslib": "^2.6.2"
}
},
"node_modules/@aws-sdk/types": {
"version": "3.840.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz",
"integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/types": "^4.3.1",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.8", "version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
@@ -502,56 +461,6 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@smithy/is-array-buffer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
"integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/types": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz",
"integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@smithy/util-buffer-from": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
"integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/is-array-buffer": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-utf8": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
"integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
"license": "Apache-2.0",
"dependencies": {
"@smithy/util-buffer-from": "^2.2.0",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -2606,12 +2515,6 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/universalify": { "node_modules/universalify": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",

View File

@@ -26,8 +26,5 @@
"postcss-import": "^16.1.1", "postcss-import": "^16.1.1",
"postcss-import-url": "^7.2.0", "postcss-import-url": "^7.2.0",
"postcss-url": "^10.1.3" "postcss-url": "^10.1.3"
},
"dependencies": {
"@aws-crypto/sha256-js": "^5.2.0"
} }
} }

View File

@@ -8,7 +8,7 @@ LICENSE='/*
@licstart The following is the entire license notice for the @licstart The following is the entire license notice for the
JavaScript code in this page. JavaScript code in this page.
Copyright (c) 2025 Xe Iaso <xe.iaso@techaro.lol> Copyright (c) 2025 Xe Iaso <me@xeiaso.net>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -28,9 +28,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
Includes code from https://github.com/aws/aws-sdk-js-crypto-helpers which is
used under the terms of the Apache 2 license.
@licend The above is the entire license notice @licend The above is the entire license notice
for the JavaScript code in this page. for the JavaScript code in this page.
*/' */'
@@ -39,9 +36,9 @@ for the JavaScript code in this page.
mkdir -p static/locales mkdir -p static/locales
cp ../lib/localization/locales/*.json static/locales/ cp ../lib/localization/locales/*.json static/locales/
for file in js/*.mjs js/worker/*.mjs; do esbuild js/main.mjs --sourcemap --bundle --minify --outfile=static/js/main.mjs "--banner:js=${LICENSE}"
esbuild "${file}" --sourcemap --bundle --minify --outfile=static/"${file}" --banner:js="${LICENSE}" gzip -f -k -n static/js/main.mjs
gzip -f -k -n static/${file} zstd -f -k --ultra -22 static/js/main.mjs
zstd -f -k --ultra -22 static/${file} brotli -fZk static/js/main.mjs
brotli -fZk static/${file}
done esbuild js/bench.mjs --sourcemap --bundle --minify --outfile=static/js/bench.mjs

View File

@@ -1,77 +0,0 @@
export default function process(
{ basePrefix, version },
data,
difficulty = 5,
signal = null,
progressCallback = null,
threads = Math.max(navigator.hardwareConcurrency / 2, 1),
) {
console.debug("fast algo");
let workerMethod = window.crypto !== undefined ? "webcrypto" : "purejs";
if (navigator.userAgent.includes("Firefox") || navigator.userAgent.includes("Goanna")) {
console.log("Firefox detected, using pure-JS fallback");
workerMethod = "purejs";
}
return new Promise((resolve, reject) => {
let webWorkerURL = `${basePrefix}/.within.website/x/cmd/anubis/static/js/worker/sha256-${workerMethod}.mjs?cacheBuster=${version}`;
console.log(webWorkerURL);
const workers = [];
let settled = false;
const cleanup = () => {
if (settled) {
return;
}
settled = true;
workers.forEach((w) => w.terminate());
if (signal != null) {
signal.removeEventListener("abort", onAbort);
}
};
const onAbort = () => {
console.log("PoW aborted");
cleanup();
reject(new DOMException("Aborted", "AbortError"));
};
if (signal != null) {
if (signal.aborted) {
return onAbort();
}
signal.addEventListener("abort", onAbort, { once: true });
}
for (let i = 0; i < threads; i++) {
let worker = new Worker(webWorkerURL);
worker.onmessage = (event) => {
if (typeof event.data === "number") {
progressCallback?.(event.data);
} else {
cleanup();
resolve(event.data);
}
};
worker.onerror = (event) => {
cleanup();
reject(event);
};
worker.postMessage({
data,
difficulty,
nonce: i,
threads,
});
workers.push(worker);
}
});
}

View File

@@ -1,6 +0,0 @@
import fast from "./fast.mjs";
export default {
fast: fast,
slow: fast, // XXX(Xe): slow is deprecated, but keep this around in case anything goes bad
}

View File

@@ -1,6 +1,11 @@
import algorithms from "./algorithms/index.mjs"; import processFast from "./proof-of-work.mjs";
import processSlow from "./proof-of-work-slow.mjs";
const defaultDifficulty = 4; const defaultDifficulty = 4;
const algorithms = {
fast: processFast,
slow: processSlow,
};
const status = document.getElementById("status"); const status = document.getElementById("status");
const difficultyInput = document.getElementById("difficulty-input"); const difficultyInput = document.getElementById("difficulty-input");
@@ -37,7 +42,7 @@ const benchmarkTrial = async (stats, difficulty, algorithm, signal) => {
.join(""); .join("");
const t0 = performance.now(); const t0 = performance.now();
const { hash, nonce } = await process({ basePrefix: "/", version: "devel" }, challenge, Number(difficulty), signal); const { hash, nonce } = await process(challenge, Number(difficulty), signal);
const t1 = performance.now(); const t1 = performance.now();
console.log({ hash, nonce }); console.log({ hash, nonce });

View File

@@ -1,4 +1,10 @@
import algorithms from "./algorithms/index.mjs"; import processFast from "./proof-of-work.mjs";
import processSlow from "./proof-of-work-slow.mjs";
const algorithms = {
fast: processFast,
slow: processSlow,
};
// from Xeact // from Xeact
const u = (url = "", params = {}) => { const u = (url = "", params = {}) => {
@@ -69,6 +75,11 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
await initTranslations(); await initTranslations();
const dependencies = [ const dependencies = [
{
name: "WebCrypto",
msg: t('web_crypto_error'),
value: window.crypto,
},
{ {
name: "Web Workers", name: "Web Workers",
msg: t('web_workers_error'), msg: t('web_workers_error'),
@@ -108,6 +119,15 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
progress.style.display = "none"; progress.style.display = "none";
}; };
if (!window.isSecureContext) {
ohNoes({
titleMsg: t('context_not_secure'),
statusMsg: t('context_not_secure_msg'),
imageSrc: imageURL("reject", anubisVersion, basePrefix),
});
return;
}
status.innerHTML = t('calculating'); status.innerHTML = t('calculating');
for (const { value, name, msg } of dependencies) { for (const { value, name, msg } of dependencies) {
@@ -151,7 +171,6 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
try { try {
const t0 = Date.now(); const t0 = Date.now();
const { hash, nonce } = await process( const { hash, nonce } = await process(
{ basePrefix, version: anubisVersion },
challenge, challenge,
rules.difficulty, rules.difficulty,
null, null,

View File

@@ -0,0 +1,99 @@
// https://dev.to/ratmd/simple-proof-of-work-in-javascript-3kgm
export default function process(
data,
difficulty = 5,
signal = null,
progressCallback = null,
_threads = 1,
) {
console.debug("slow algo");
return new Promise((resolve, reject) => {
let webWorkerURL = URL.createObjectURL(
new Blob(["(", processTask(), ")()"], { type: "application/javascript" }),
);
let worker = new Worker(webWorkerURL);
let settled = false;
const cleanup = () => {
if (settled) return;
settled = true;
worker.terminate();
if (signal != null) {
signal.removeEventListener("abort", onAbort);
}
URL.revokeObjectURL(webWorkerURL);
};
const onAbort = () => {
console.log("PoW aborted");
cleanup();
reject(new DOMException("Aborted", "AbortError"));
};
if (signal != null) {
if (signal.aborted) {
return onAbort();
}
signal.addEventListener("abort", onAbort, { once: true });
}
worker.onmessage = (event) => {
if (typeof event.data === "number") {
progressCallback?.(event.data);
} else {
cleanup();
resolve(event.data);
}
};
worker.onerror = (event) => {
cleanup();
reject(event);
};
worker.postMessage({
data,
difficulty,
});
});
}
function processTask() {
return function () {
const sha256 = (text) => {
const encoded = new TextEncoder().encode(text);
return crypto.subtle.digest("SHA-256", encoded.buffer).then((result) =>
Array.from(new Uint8Array(result))
.map((c) => c.toString(16).padStart(2, "0"))
.join(""),
);
};
addEventListener("message", async (event) => {
let data = event.data.data;
let difficulty = event.data.difficulty;
let hash;
let nonce = 0;
do {
if ((nonce & 1023) === 0) {
postMessage(nonce);
}
hash = await sha256(data + nonce++);
} while (
hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")
);
nonce -= 1; // last nonce was post-incremented
postMessage({
hash,
data,
difficulty,
nonce,
});
});
}.toString();
}

137
web/js/proof-of-work.mjs Normal file
View File

@@ -0,0 +1,137 @@
export default function process(
data,
difficulty = 5,
signal = null,
progressCallback = null,
threads = Math.max(navigator.hardwareConcurrency / 2, 1),
) {
console.debug("fast algo");
return new Promise((resolve, reject) => {
let webWorkerURL = URL.createObjectURL(
new Blob(["(", processTask(), ")()"], { type: "application/javascript" }),
);
const workers = [];
let settled = false;
const cleanup = () => {
if (settled) {
return;
}
settled = true;
workers.forEach((w) => w.terminate());
if (signal != null) {
signal.removeEventListener("abort", onAbort);
}
URL.revokeObjectURL(webWorkerURL);
};
const onAbort = () => {
console.log("PoW aborted");
cleanup();
reject(new DOMException("Aborted", "AbortError"));
};
if (signal != null) {
if (signal.aborted) {
return onAbort();
}
signal.addEventListener("abort", onAbort, { once: true });
}
for (let i = 0; i < threads; i++) {
let worker = new Worker(webWorkerURL);
worker.onmessage = (event) => {
if (typeof event.data === "number") {
progressCallback?.(event.data);
} else {
cleanup();
resolve(event.data);
}
};
worker.onerror = (event) => {
cleanup();
reject(event);
};
worker.postMessage({
data,
difficulty,
nonce: i,
threads,
});
workers.push(worker);
}
});
}
function processTask() {
return function () {
const sha256 = (text) => {
const encoded = new TextEncoder().encode(text);
return crypto.subtle.digest("SHA-256", encoded.buffer);
};
function uint8ArrayToHexString(arr) {
return Array.from(arr)
.map((c) => c.toString(16).padStart(2, "0"))
.join("");
}
addEventListener("message", async (event) => {
let data = event.data.data;
let difficulty = event.data.difficulty;
let hash;
let nonce = event.data.nonce;
let threads = event.data.threads;
const threadId = nonce;
let localIterationCount = 0;
while (true) {
const currentHash = await sha256(data + nonce);
const thisHash = new Uint8Array(currentHash);
let valid = true;
for (let j = 0; j < difficulty; j++) {
const byteIndex = Math.floor(j / 2); // which byte we are looking at
const nibbleIndex = j % 2; // which nibble in the byte we are looking at (0 is high, 1 is low)
let nibble =
(thisHash[byteIndex] >> (nibbleIndex === 0 ? 4 : 0)) & 0x0f; // Get the nibble
if (nibble !== 0) {
valid = false;
break;
}
}
if (valid) {
hash = uint8ArrayToHexString(thisHash);
console.log(hash);
break;
}
nonce += threads;
// send a progress update every 1024 iterations so that the user can be informed of
// the state of the challenge.
if (threadId == 0 && localIterationCount === 1024) {
postMessage(nonce);
localIterationCount = 0;
}
localIterationCount++;
}
postMessage({
hash,
data,
difficulty,
nonce,
});
});
}.toString();
}

16
web/js/video.mjs Normal file
View File

@@ -0,0 +1,16 @@
const videoElement = `<video id="videotest" width="0" height="0" src="/.within.website/x/cmd/anubis/static/testdata/black.mp4"></video>`;
export const testVideo = async (testarea) => {
testarea.innerHTML = videoElement;
return await new Promise((resolve) => {
const video = document.getElementById("videotest");
video.oncanplay = () => {
testarea.style.display = "none";
resolve(true);
};
video.onerror = () => {
testarea.style.display = "none";
resolve(false);
};
});
};

View File

@@ -1,61 +0,0 @@
import { Sha256 } from '@aws-crypto/sha256-js';
const calculateSHA256 = (text) => {
const hash = new Sha256();
hash.update(text);
return hash.digest();
};
function uint8ArrayToHexString(arr) {
return Array.from(arr)
.map((c) => c.toString(16).padStart(2, "0"))
.join("");
}
addEventListener('message', async ({ data: eventData }) => {
const { data, difficulty, threads } = eventData;
let nonce = eventData.nonce;
const isMainThread = nonce === 0;
let iterations = 0;
const requiredZeroBytes = Math.floor(difficulty / 2);
const isDifficultyOdd = difficulty % 2 !== 0;
for (; ;) {
const hashBuffer = await calculateSHA256(data + nonce);
const hashArray = new Uint8Array(hashBuffer);
let isValid = true;
for (let i = 0; i < requiredZeroBytes; i++) {
if (hashArray[i] !== 0) {
isValid = false;
break;
}
}
if (isValid && isDifficultyOdd) {
if ((hashArray[requiredZeroBytes] >> 4) !== 0) {
isValid = false;
}
}
if (isValid) {
const finalHash = toHexString(hashArray);
postMessage({
hash: finalHash,
data,
difficulty,
nonce,
});
return; // Exit worker
}
nonce += threads;
iterations++;
// Send a progress update from the main thread every 1024 iterations.
if (isMainThread && (iterations & 1023) === 0) {
postMessage(nonce);
}
}
});

View File

@@ -1,57 +0,0 @@
const encoder = new TextEncoder();
const calculateSHA256 = async (input) => {
const data = encoder.encode(input);
return await crypto.subtle.digest("SHA-256", data);
};
const toHexString = (byteArray) => {
return byteArray.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
};
addEventListener("message", async ({ data: eventData }) => {
const { data, difficulty, threads } = eventData;
let nonce = eventData.nonce;
const isMainThread = nonce === 0;
let iterations = 0;
const requiredZeroBytes = Math.floor(difficulty / 2);
const isDifficultyOdd = difficulty % 2 !== 0;
for (; ;) {
const hashBuffer = await calculateSHA256(data + nonce);
const hashArray = new Uint8Array(hashBuffer);
let isValid = true;
for (let i = 0; i < requiredZeroBytes; i++) {
if (hashArray[i] !== 0) {
isValid = false;
break;
}
}
if (isValid && isDifficultyOdd) {
if ((hashArray[requiredZeroBytes] >> 4) !== 0) {
isValid = false;
}
}
if (isValid) {
const finalHash = toHexString(hashArray);
postMessage({
hash: finalHash,
data,
difficulty,
nonce,
});
return; // Exit worker
}
nonce += threads;
iterations++;
// Send a progress update from the main thread every 1024 iterations.
if (isMainThread && (iterations & 1023) === 0) {
postMessage(nonce);
}
}
});