mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-25 09:32:43 +00:00
feat(wasm): support "pure JS" mode
Closes #1159 This uses the binaryen tool wasm2js to compile the Anubis WASM blobs to JavaScript. This produces biblically large (520Ki) outputs when you inline both hashx and sha256 solvers, but this is a tradeoff that I'm willing to accept. The performance is good enough in my testing with JIT enabled. I fear that this may end up being terrible with JIT disabled. I have no idea if this will work on big endian or not. Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
@@ -4,7 +4,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY go.mod go.sum package.json package-lock.json ./
|
COPY go.mod go.sum package.json package-lock.json ./
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get -y install zstd brotli redis uuid-runtime \
|
&& apt-get -y install zstd brotli redis uuid-runtime binaryen \
|
||||||
&& mkdir -p /home/vscode/.local/share/fish \
|
&& mkdir -p /home/vscode/.local/share/fish \
|
||||||
&& chown -R vscode:vscode /home/vscode/.local/share/fish \
|
&& chown -R vscode:vscode /home/vscode/.local/share/fish \
|
||||||
&& chown -R vscode:vscode /go
|
&& chown -R vscode:vscode /go
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ jobs:
|
|||||||
cache: false
|
cache: false
|
||||||
target: wasm32-unknown-unknown
|
target: wasm32-unknown-unknown
|
||||||
|
|
||||||
|
- name: Setup Binaryen
|
||||||
|
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ jobs:
|
|||||||
cache: false
|
cache: false
|
||||||
target: wasm32-unknown-unknown
|
target: wasm32-unknown-unknown
|
||||||
|
|
||||||
|
- name: Setup Binaryen
|
||||||
|
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||||
|
|
||||||
- name: Log into registry
|
- name: Log into registry
|
||||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ jobs:
|
|||||||
cache: false
|
cache: false
|
||||||
target: wasm32-unknown-unknown
|
target: wasm32-unknown-unknown
|
||||||
|
|
||||||
|
- name: Setup Binaryen
|
||||||
|
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||||
|
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ jobs:
|
|||||||
cache: false
|
cache: false
|
||||||
target: wasm32-unknown-unknown
|
target: wasm32-unknown-unknown
|
||||||
|
|
||||||
|
- name: Setup Binaryen
|
||||||
|
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||||
|
|
||||||
- name: install node deps
|
- name: install node deps
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ jobs:
|
|||||||
cache: false
|
cache: false
|
||||||
target: wasm32-unknown-unknown
|
target: wasm32-unknown-unknown
|
||||||
|
|
||||||
|
- name: Setup Binaryen
|
||||||
|
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||||
|
|
||||||
- name: install node deps
|
- name: install node deps
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
|
|||||||
+5
-2
@@ -8,8 +8,11 @@
|
|||||||
"test:integration": "npm run assets && go test -v ./internal/test",
|
"test:integration": "npm run assets && go test -v ./internal/test",
|
||||||
"test:integration:podman": "npm run assets && go test -v ./internal/test --playwright-runner=podman",
|
"test:integration:podman": "npm run assets && go test -v ./internal/test --playwright-runner=podman",
|
||||||
"test:integration:docker": "npm run assets && go test -v ./internal/test --playwright-runner=docker",
|
"test:integration:docker": "npm run assets && go test -v ./internal/test --playwright-runner=docker",
|
||||||
|
"generate": "go generate ./...",
|
||||||
|
"assets:js": "./web/build.sh",
|
||||||
|
"assets:css": "./xess/build.sh",
|
||||||
"assets:wasm": "bash ./scripts/build_wasm.sh",
|
"assets:wasm": "bash ./scripts/build_wasm.sh",
|
||||||
"assets": "go generate ./... && ./web/build.sh && ./xess/build.sh && npm run assets:wasm",
|
"assets": "npm run generate && npm run assets:wasm && npm run assets:js && npm run assets:css",
|
||||||
"build": "npm run assets && go build -o ./var/anubis ./cmd/anubis",
|
"build": "npm run assets && go build -o ./var/anubis ./cmd/anubis",
|
||||||
"dev": "npm run assets && go run ./cmd/anubis --use-remote-address --target http://localhost:3000",
|
"dev": "npm run assets && go run ./cmd/anubis --use-remote-address --target http://localhost:3000",
|
||||||
"container": "npm run assets && go run ./cmd/containerbuild",
|
"container": "npm run assets && go run ./cmd/containerbuild",
|
||||||
@@ -37,4 +40,4 @@
|
|||||||
"preact": "^10.27.2",
|
"preact": "^10.27.2",
|
||||||
"wasm-feature-detect": "^1.8.0"
|
"wasm-feature-detect": "^1.8.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+10
-1
@@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
mkdir -p ./web/static/wasm/{simd128,baseline}
|
mkdir -p ./web/static/wasm/{simd128,baseline}
|
||||||
|
|
||||||
cargo clean
|
cargo clean
|
||||||
@@ -12,4 +14,11 @@ cargo clean
|
|||||||
|
|
||||||
# Without simd128
|
# Without simd128
|
||||||
cargo build --release --target wasm32-unknown-unknown
|
cargo build --release --target wasm32-unknown-unknown
|
||||||
cp -vf ./target/wasm32-unknown-unknown/release/*.wasm ./web/static/wasm/baseline
|
cp -vf ./target/wasm32-unknown-unknown/release/*.wasm ./web/static/wasm/baseline
|
||||||
|
|
||||||
|
for file in ./web/static/wasm/baseline/*.wasm; do
|
||||||
|
echo $file
|
||||||
|
rm -f ${file%.*}.wasmjs
|
||||||
|
wasm2js $file -all -O4 --strip-debug --rse --rereloop --optimize-for-js --flatten --dce --dfo --fpcast-emu --denan --dealign --remove-imports --remove-unused-names --remove-unused-brs --reorder-functions --reorder-locals --strip-target-features --untee --vacuum -s 4 -ffm -lmu -tnh -iit -n -o ${file%.*}.mjs
|
||||||
|
sed -i '1s$.*$const anubis_update_nonce = (_ignored) => { };$' ${file%.*}.mjs
|
||||||
|
done
|
||||||
+1
-1
@@ -49,7 +49,7 @@ for file in js/**/*.ts js/**/*.mjs; do
|
|||||||
|
|
||||||
mkdir -p "$(dirname "$out")"
|
mkdir -p "$(dirname "$out")"
|
||||||
|
|
||||||
esbuild "$file" --sourcemap --minify --bundle --outfile="$out" --banner:js="$LICENSE"
|
esbuild "$file" --sourcemap --bundle --outfile="$out" --banner:js="$LICENSE"
|
||||||
gzip -f -k -n "$out"
|
gzip -f -k -n "$out"
|
||||||
zstd -f -k --ultra -22 "$out"
|
zstd -f -k --ultra -22 "$out"
|
||||||
brotli -fZk "$out"
|
brotli -fZk "$out"
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {
|
||||||
|
memory,
|
||||||
|
data_ptr,
|
||||||
|
set_data_length,
|
||||||
|
anubis_work,
|
||||||
|
anubis_validate,
|
||||||
|
result_hash_ptr,
|
||||||
|
result_hash_size,
|
||||||
|
verification_hash_ptr,
|
||||||
|
verification_hash_size,
|
||||||
|
} from "../../../static/wasm/baseline/hashx.mjs";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
memory,
|
||||||
|
data_ptr,
|
||||||
|
set_data_length,
|
||||||
|
anubis_work,
|
||||||
|
anubis_validate,
|
||||||
|
result_hash_ptr,
|
||||||
|
result_hash_size,
|
||||||
|
verification_hash_ptr,
|
||||||
|
verification_hash_size
|
||||||
|
};
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { u } from "../../lib/xeact";
|
import { u } from "../../../lib/xeact";
|
||||||
import { simd } from "wasm-feature-detect";
|
import { simd } from "wasm-feature-detect";
|
||||||
// import { compile } from '@haribala/wasm2js';
|
// import { compile } from '@haribala/wasm2js';
|
||||||
|
|
||||||
@@ -13,20 +13,21 @@ interface ProcessOptions {
|
|||||||
const getHardwareConcurrency = () =>
|
const getHardwareConcurrency = () =>
|
||||||
navigator.hardwareConcurrency !== undefined ? navigator.hardwareConcurrency : 1;
|
navigator.hardwareConcurrency !== undefined ? navigator.hardwareConcurrency : 1;
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/47879864/how-can-i-check-if-a-browser-supports-webassembly
|
// // https://stackoverflow.com/questions/47879864/how-can-i-check-if-a-browser-supports-webassembly
|
||||||
const isWASMSupported = (() => {
|
// const isWASMSupported = (() => {
|
||||||
try {
|
// try {
|
||||||
if (typeof WebAssembly === "object"
|
// if (typeof WebAssembly === "object"
|
||||||
&& typeof WebAssembly.instantiate === "function") {
|
// && typeof WebAssembly.instantiate === "function") {
|
||||||
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
|
// const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
|
||||||
if (module instanceof WebAssembly.Module)
|
// if (module instanceof WebAssembly.Module)
|
||||||
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
|
// return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
|
||||||
}
|
// }
|
||||||
} catch (e) {
|
// } catch (e) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
return false;
|
// return false;
|
||||||
})();
|
// })();
|
||||||
|
const isWASMSupported = false;
|
||||||
|
|
||||||
export default function process(
|
export default function process(
|
||||||
options: ProcessOptions,
|
options: ProcessOptions,
|
||||||
@@ -38,8 +39,10 @@ export default function process(
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { basePrefix, version, algorithm } = options;
|
const { basePrefix, version, algorithm } = options;
|
||||||
|
|
||||||
|
let worker = "wasm";
|
||||||
|
|
||||||
if (!isWASMSupported) {
|
if (!isWASMSupported) {
|
||||||
throw new Error("WebAssembly is not supported on this platform. Please report ALL details about your browser, environment, OS, CPU, device vendor, and other details to https://github.com/TecharoHQ/anubis/issues/1116. Remember: being polite means you get this fixed.");
|
worker = "wasm2js";
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
@@ -49,10 +52,7 @@ export default function process(
|
|||||||
wasmFeatures = "simd128";
|
wasmFeatures = "simd128";
|
||||||
}
|
}
|
||||||
|
|
||||||
const module = await fetch(u(`${basePrefix}/.within.website/x/cmd/anubis/static/wasm/${wasmFeatures}/${algorithm}.wasm?cacheBuster=${version}`))
|
const webWorkerURL = `${basePrefix}/.within.website/x/cmd/anubis/static/js/worker/${worker}.mjs?cacheBuster=${version}`;
|
||||||
.then(x => WebAssembly.compileStreaming(x));
|
|
||||||
|
|
||||||
const webWorkerURL = `${options.basePrefix}/.within.website/x/cmd/anubis/static/js/worker/wasm.mjs?cacheBuster=${version}`;
|
|
||||||
|
|
||||||
const workers: Worker[] = [];
|
const workers: Worker[] = [];
|
||||||
let settled = false;
|
let settled = false;
|
||||||
@@ -103,7 +103,7 @@ export default function process(
|
|||||||
difficulty,
|
difficulty,
|
||||||
nonce: i,
|
nonce: i,
|
||||||
threads,
|
threads,
|
||||||
module,
|
algorithm,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {
|
||||||
|
memory,
|
||||||
|
data_ptr,
|
||||||
|
set_data_length,
|
||||||
|
anubis_work,
|
||||||
|
anubis_validate,
|
||||||
|
result_hash_ptr,
|
||||||
|
result_hash_size,
|
||||||
|
verification_hash_ptr,
|
||||||
|
verification_hash_size,
|
||||||
|
} from "../../../static/wasm/baseline/sha256.mjs";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
memory,
|
||||||
|
data_ptr,
|
||||||
|
set_data_length,
|
||||||
|
anubis_work,
|
||||||
|
anubis_validate,
|
||||||
|
result_hash_ptr,
|
||||||
|
result_hash_size,
|
||||||
|
verification_hash_ptr,
|
||||||
|
verification_hash_size
|
||||||
|
};
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import hashx from "../algorithms/wasm/hashx";
|
||||||
|
import sha256 from "../algorithms/wasm/sha256";
|
||||||
|
|
||||||
|
export interface Args {
|
||||||
|
data: string;
|
||||||
|
difficulty: number;
|
||||||
|
nonce: number;
|
||||||
|
threads: number;
|
||||||
|
algorithm: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnubisExports {
|
||||||
|
anubis_work: (difficulty: number, initialNonce: number, threads: number) => number;
|
||||||
|
data_ptr: () => number;
|
||||||
|
result_hash_ptr: () => number;
|
||||||
|
result_hash_size: () => number;
|
||||||
|
set_data_length: (len: number) => void;
|
||||||
|
memory: WebAssembly.Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
const algorithms: Record<string, AnubisExports> = {
|
||||||
|
"hashx": hashx as AnubisExports,
|
||||||
|
"sha256": sha256 as AnubisExports,
|
||||||
|
};
|
||||||
|
|
||||||
|
addEventListener("message", async (event: MessageEvent<Args>) => {
|
||||||
|
const { data, difficulty, threads, algorithm } = event.data;
|
||||||
|
let { nonce } = event.data;
|
||||||
|
|
||||||
|
const obj = algorithms[algorithm];
|
||||||
|
if (obj == undefined) {
|
||||||
|
throw new Error(`unknown algorithm ${algorithm}, file a bug please`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
anubis_work,
|
||||||
|
data_ptr,
|
||||||
|
result_hash_ptr,
|
||||||
|
result_hash_size,
|
||||||
|
set_data_length,
|
||||||
|
memory
|
||||||
|
} = obj;
|
||||||
|
function uint8ArrayToHex(arr: Uint8Array) {
|
||||||
|
return Array.from(arr)
|
||||||
|
.map((c) => c.toString(16).padStart(2, "0"))
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToUint8Array(hexString: string): Uint8Array {
|
||||||
|
// Remove whitespace and optional '0x' prefix
|
||||||
|
hexString = hexString.replace(/\s+/g, '').replace(/^0x/, '');
|
||||||
|
|
||||||
|
// Check for valid length
|
||||||
|
if (hexString.length % 2 !== 0) {
|
||||||
|
throw new Error('Invalid hex string length');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for valid characters
|
||||||
|
if (!/^[0-9a-fA-F]+$/.test(hexString)) {
|
||||||
|
throw new Error('Invalid hex characters');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to Uint8Array
|
||||||
|
const byteArray = new Uint8Array(hexString.length / 2);
|
||||||
|
for (let i = 0; i < byteArray.length; i++) {
|
||||||
|
const byteValue = parseInt(hexString.substr(i * 2, 2), 16);
|
||||||
|
byteArray[i] = byteValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write data to buffer
|
||||||
|
function writeToBuffer(data: Uint8Array) {
|
||||||
|
if (data.length > 1024) throw new Error("Data exceeds buffer size");
|
||||||
|
|
||||||
|
// Get pointer and create view
|
||||||
|
const offset = data_ptr();
|
||||||
|
const buffer = new Uint8Array(memory.buffer, offset, data.length);
|
||||||
|
|
||||||
|
// Copy data
|
||||||
|
buffer.set(data);
|
||||||
|
|
||||||
|
// Set data length
|
||||||
|
set_data_length(data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFromChallenge() {
|
||||||
|
const offset = result_hash_ptr();
|
||||||
|
const buffer = new Uint8Array(memory.buffer, offset, result_hash_size());
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeToBuffer(hexToUint8Array(data));
|
||||||
|
|
||||||
|
nonce = anubis_work(difficulty, nonce, threads);
|
||||||
|
const challenge = readFromChallenge();
|
||||||
|
const result = uint8ArrayToHex(challenge);
|
||||||
|
|
||||||
|
postMessage({
|
||||||
|
hash: result,
|
||||||
|
difficulty,
|
||||||
|
nonce,
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user