mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-09 10:08:45 +00:00
Compare commits
8 Commits
fix/nilpoi
...
Xe/purge-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
881bab8375 | ||
|
|
64e2d0d4ed | ||
|
|
a04620ed61 | ||
|
|
3c42963c85 | ||
|
|
b4fe0db7f4 | ||
|
|
8f4f521312 | ||
|
|
b499aaab68 | ||
|
|
c5618a0b2a |
3
.github/actions/spelling/expect.txt
vendored
3
.github/actions/spelling/expect.txt
vendored
@@ -141,6 +141,7 @@ httpdebug
|
|||||||
Huawei
|
Huawei
|
||||||
hypertext
|
hypertext
|
||||||
iaskspider
|
iaskspider
|
||||||
|
iaso
|
||||||
iat
|
iat
|
||||||
ifm
|
ifm
|
||||||
Imagesift
|
Imagesift
|
||||||
@@ -232,6 +233,7 @@ promauto
|
|||||||
promhttp
|
promhttp
|
||||||
proofofwork
|
proofofwork
|
||||||
publicsuffix
|
publicsuffix
|
||||||
|
purejs
|
||||||
pwcmd
|
pwcmd
|
||||||
pwuser
|
pwuser
|
||||||
qualys
|
qualys
|
||||||
@@ -309,7 +311,6 @@ Varis
|
|||||||
Velen
|
Velen
|
||||||
vendored
|
vendored
|
||||||
vhosts
|
vhosts
|
||||||
videotest
|
|
||||||
VKE
|
VKE
|
||||||
Vultr
|
Vultr
|
||||||
waitloop
|
waitloop
|
||||||
|
|||||||
@@ -19,6 +19,17 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,10 @@ 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 {
|
||||||
@@ -163,6 +167,10 @@ 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
97
package-lock.json
generated
@@ -8,6 +8,9 @@
|
|||||||
"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",
|
||||||
@@ -19,6 +22,44 @@
|
|||||||
"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",
|
||||||
@@ -461,6 +502,56 @@
|
|||||||
"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",
|
||||||
@@ -2515,6 +2606,12 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -26,5 +26,8 @@
|
|||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
web/build.sh
17
web/build.sh
@@ -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 <me@xeiaso.net>
|
Copyright (c) 2025 Xe Iaso <xe.iaso@techaro.lol>
|
||||||
|
|
||||||
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,6 +28,9 @@ 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.
|
||||||
*/'
|
*/'
|
||||||
@@ -36,9 +39,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/
|
||||||
|
|
||||||
esbuild js/main.mjs --sourcemap --bundle --minify --outfile=static/js/main.mjs "--banner:js=${LICENSE}"
|
for file in js/*.mjs js/worker/*.mjs; do
|
||||||
gzip -f -k -n static/js/main.mjs
|
esbuild "${file}" --sourcemap --bundle --minify --outfile=static/"${file}" --banner:js="${LICENSE}"
|
||||||
zstd -f -k --ultra -22 static/js/main.mjs
|
gzip -f -k -n static/${file}
|
||||||
brotli -fZk static/js/main.mjs
|
zstd -f -k --ultra -22 static/${file}
|
||||||
|
brotli -fZk static/${file}
|
||||||
esbuild js/bench.mjs --sourcemap --bundle --minify --outfile=static/js/bench.mjs
|
done
|
||||||
|
|||||||
77
web/js/algorithms/fast.mjs
Normal file
77
web/js/algorithms/fast.mjs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
6
web/js/algorithms/index.mjs
Normal file
6
web/js/algorithms/index.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
import processFast from "./proof-of-work.mjs";
|
import algorithms from "./algorithms/index.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");
|
||||||
@@ -42,7 +37,7 @@ const benchmarkTrial = async (stats, difficulty, algorithm, signal) => {
|
|||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
const { hash, nonce } = await process(challenge, Number(difficulty), signal);
|
const { hash, nonce } = await process({ basePrefix: "/", version: "devel" }, challenge, Number(difficulty), signal);
|
||||||
const t1 = performance.now();
|
const t1 = performance.now();
|
||||||
console.log({ hash, nonce });
|
console.log({ hash, nonce });
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,4 @@
|
|||||||
import processFast from "./proof-of-work.mjs";
|
import algorithms from "./algorithms/index.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 = {}) => {
|
||||||
@@ -75,11 +69,6 @@ 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'),
|
||||||
@@ -119,15 +108,6 @@ 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) {
|
||||||
@@ -171,6 +151,7 @@ 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,
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
// 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();
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
61
web/js/worker/sha256-purejs.mjs
Normal file
61
web/js/worker/sha256-purejs.mjs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
57
web/js/worker/sha256-webcrypto.mjs
Normal file
57
web/js/worker/sha256-webcrypto.mjs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user