mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-05 16:28:17 +00:00
Compare commits
27 Commits
Xe/fix-bbo
...
Xe/wasm3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8cc2c4d07c | ||
|
|
158a3b8d77 | ||
|
|
1530eab1a4 | ||
|
|
accffa6d83 | ||
|
|
c04bd486e1 | ||
|
|
0699f331d2 | ||
|
|
7f1a7197f3 | ||
|
|
2ad7be2847 | ||
|
|
45c9cb6842 | ||
|
|
a29025c382 | ||
|
|
bba000e87e | ||
|
|
643b4719d8 | ||
|
|
705da2fa3c | ||
|
|
bd5613c699 | ||
|
|
a0df3d4428 | ||
|
|
097c9e9586 | ||
|
|
cf931cc0a5 | ||
|
|
8b60b4309b | ||
|
|
41bfbf7900 | ||
|
|
1c5ce190b4 | ||
|
|
bed126e641 | ||
|
|
9959cb0d06 | ||
|
|
f150e4b466 | ||
|
|
8999303eef | ||
|
|
a63cbc7ced | ||
|
|
03a6c07c73 | ||
|
|
908f85db91 |
@@ -4,7 +4,7 @@ WORKDIR /app
|
||||
|
||||
COPY go.mod go.sum package.json package-lock.json ./
|
||||
RUN apt-get update \
|
||||
&& apt-get -y install zstd brotli redis \
|
||||
&& apt-get -y install zstd brotli redis uuid-runtime binaryen \
|
||||
&& mkdir -p /home/vscode/.local/share/fish \
|
||||
&& chown -R vscode:vscode /home/vscode/.local/share/fish \
|
||||
&& chown -R vscode:vscode /go
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
"postStartCommand": "bash ./.devcontainer/poststart.sh",
|
||||
"features": {
|
||||
"ghcr.io/xe/devcontainer-features/ko:1.1.0": {},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {}
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {},
|
||||
"ghcr.io/devcontainers/features/rust:1": {
|
||||
"version": "latest",
|
||||
"targets": "wasm32-unknown-unknown"
|
||||
}
|
||||
},
|
||||
"initializeCommand": "mkdir -p ${localEnv:HOME}${localEnv:USERPROFILE}/.local/share/atuin",
|
||||
"customizations": {
|
||||
|
||||
@@ -5,5 +5,6 @@ pwd
|
||||
npm ci &
|
||||
go mod download &
|
||||
go install ./utils/cmd/... &
|
||||
cargo fetch &
|
||||
|
||||
wait
|
||||
|
||||
33
.github/actions/spelling/expect.txt
vendored
33
.github/actions/spelling/expect.txt
vendored
@@ -22,6 +22,7 @@ bbolt
|
||||
bdba
|
||||
berr
|
||||
bezier
|
||||
binaryen
|
||||
bingbot
|
||||
Bitcoin
|
||||
bitrate
|
||||
@@ -41,6 +42,7 @@ cachediptoasn
|
||||
Caddyfile
|
||||
caninetools
|
||||
Cardyb
|
||||
cdylib
|
||||
celchecker
|
||||
celphase
|
||||
cerr
|
||||
@@ -57,7 +59,9 @@ checkresult
|
||||
chibi
|
||||
cidranger
|
||||
ckie
|
||||
clippy
|
||||
cloudflare
|
||||
codegen
|
||||
Codespaces
|
||||
confd
|
||||
connnection
|
||||
@@ -69,11 +73,15 @@ crt
|
||||
Cscript
|
||||
daemonizing
|
||||
dayjob
|
||||
dce
|
||||
DDOS
|
||||
dealign
|
||||
Debian
|
||||
debrpm
|
||||
decaymap
|
||||
denan
|
||||
devcontainers
|
||||
dfo
|
||||
Diffbot
|
||||
discordapp
|
||||
discordbot
|
||||
@@ -92,6 +100,7 @@ eerror
|
||||
ellenjoe
|
||||
emacs
|
||||
enbyware
|
||||
equix
|
||||
etld
|
||||
everyones
|
||||
evilbot
|
||||
@@ -104,14 +113,18 @@ facebookgo
|
||||
Factset
|
||||
fastcgi
|
||||
fediverse
|
||||
fff
|
||||
ffm
|
||||
ffprobe
|
||||
financials
|
||||
finfos
|
||||
Firecrawl
|
||||
flagenv
|
||||
fnames
|
||||
Fordola
|
||||
forgejo
|
||||
forwardauth
|
||||
fpcast
|
||||
fsys
|
||||
fullchain
|
||||
gaissmai
|
||||
@@ -139,6 +152,7 @@ grpcprom
|
||||
grw
|
||||
Hashcash
|
||||
hashrate
|
||||
hashx
|
||||
headermap
|
||||
healthcheck
|
||||
healthz
|
||||
@@ -147,6 +161,7 @@ Hetzner
|
||||
hmc
|
||||
homelab
|
||||
hostable
|
||||
hostimport
|
||||
htmlc
|
||||
htmx
|
||||
httpdebug
|
||||
@@ -157,6 +172,7 @@ iaskspider
|
||||
iaso
|
||||
iat
|
||||
ifm
|
||||
iit
|
||||
Imagesift
|
||||
imgproxy
|
||||
impressum
|
||||
@@ -168,6 +184,7 @@ iptoasn
|
||||
isp
|
||||
iss
|
||||
isset
|
||||
iterand
|
||||
ivh
|
||||
Jenomis
|
||||
JGit
|
||||
@@ -181,6 +198,7 @@ kagibot
|
||||
Keyfunc
|
||||
keypair
|
||||
KHTML
|
||||
kilohashes
|
||||
kinda
|
||||
KUBECONFIG
|
||||
lcj
|
||||
@@ -195,9 +213,11 @@ limsa
|
||||
Linting
|
||||
linuxbrew
|
||||
LLU
|
||||
lmu
|
||||
loadbalancer
|
||||
lol
|
||||
lominsa
|
||||
lto
|
||||
maintainership
|
||||
malware
|
||||
mcr
|
||||
@@ -264,12 +284,16 @@ redir
|
||||
redirectscheme
|
||||
refactors
|
||||
reputational
|
||||
rereloop
|
||||
risc
|
||||
rse
|
||||
ruleset
|
||||
runlevels
|
||||
RUnlock
|
||||
runtimedir
|
||||
runtimedirectory
|
||||
RUSTFLAGS
|
||||
rustup
|
||||
Ryzen
|
||||
sas
|
||||
sasl
|
||||
@@ -285,10 +309,10 @@ shellcheck
|
||||
shirou
|
||||
shopt
|
||||
Sidetrade
|
||||
simd
|
||||
simprint
|
||||
sitemap
|
||||
sls
|
||||
Smartphone
|
||||
sni
|
||||
Spambot
|
||||
sparkline
|
||||
@@ -313,20 +337,25 @@ techarohq
|
||||
templ
|
||||
templruntime
|
||||
testarea
|
||||
tetratelabs
|
||||
Thancred
|
||||
thoth
|
||||
thothmock
|
||||
Tik
|
||||
Timpibot
|
||||
TLog
|
||||
tnh
|
||||
traefik
|
||||
trunc
|
||||
uberspace
|
||||
Unbreak
|
||||
unbreakdocker
|
||||
unifiedjs
|
||||
uninlined
|
||||
unmarshal
|
||||
unparseable
|
||||
untee
|
||||
usize
|
||||
uvx
|
||||
uwu
|
||||
UXP
|
||||
@@ -337,6 +366,8 @@ vendored
|
||||
vhosts
|
||||
VKE
|
||||
Vultr
|
||||
wasmjs
|
||||
wazero
|
||||
weblate
|
||||
webmaster
|
||||
webpage
|
||||
|
||||
13
.github/workflows/docker-pr.yml
vendored
13
.github/workflows/docker-pr.yml
vendored
@@ -2,7 +2,7 @@ name: Docker image builds (pull requests)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
DOCKER_METADATA_SET_OUTPUT_ENV: "true"
|
||||
@@ -45,6 +45,17 @@ jobs:
|
||||
run: |
|
||||
brew bundle
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@02be93da58aa71fb456aa9c43b301149248829d8 # v1.15.1
|
||||
with:
|
||||
cache: false
|
||||
target: wasm32-unknown-unknown
|
||||
|
||||
- name: Setup Binaryen
|
||||
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 108
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
|
||||
11
.github/workflows/docker.yml
vendored
11
.github/workflows/docker.yml
vendored
@@ -55,6 +55,17 @@ jobs:
|
||||
run: |
|
||||
brew bundle
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@02be93da58aa71fb456aa9c43b301149248829d8 # v1.15.1
|
||||
with:
|
||||
cache: false
|
||||
target: wasm32-unknown-unknown
|
||||
|
||||
- name: Setup Binaryen
|
||||
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 108
|
||||
|
||||
- name: Log into registry
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
with:
|
||||
|
||||
139
.github/workflows/go.yml
vendored
139
.github/workflows/go.yml
vendored
@@ -2,9 +2,9 @@ name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -15,77 +15,88 @@ jobs:
|
||||
#runs-on: alrest-techarohq
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: build essential
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
- name: build essential
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
/home/linuxbrew/.linuxbrew/bin
|
||||
/home/linuxbrew/.linuxbrew/etc
|
||||
/home/linuxbrew/.linuxbrew/include
|
||||
/home/linuxbrew/.linuxbrew/lib
|
||||
/home/linuxbrew/.linuxbrew/opt
|
||||
/home/linuxbrew/.linuxbrew/sbin
|
||||
/home/linuxbrew/.linuxbrew/share
|
||||
/home/linuxbrew/.linuxbrew/var
|
||||
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-homebrew-cellar-
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
/home/linuxbrew/.linuxbrew/bin
|
||||
/home/linuxbrew/.linuxbrew/etc
|
||||
/home/linuxbrew/.linuxbrew/include
|
||||
/home/linuxbrew/.linuxbrew/lib
|
||||
/home/linuxbrew/.linuxbrew/opt
|
||||
/home/linuxbrew/.linuxbrew/sbin
|
||||
/home/linuxbrew/.linuxbrew/share
|
||||
/home/linuxbrew/.linuxbrew/var
|
||||
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-homebrew-cellar-
|
||||
|
||||
- name: Install Brew dependencies
|
||||
run: |
|
||||
brew bundle
|
||||
- name: Install Brew dependencies
|
||||
run: |
|
||||
brew bundle
|
||||
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-golang-
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-golang-
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('**/go.sum') }}
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@02be93da58aa71fb456aa9c43b301149248829d8 # v1.15.1
|
||||
with:
|
||||
cache: false
|
||||
target: wasm32-unknown-unknown
|
||||
|
||||
- name: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
- name: Setup Binaryen
|
||||
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 108
|
||||
|
||||
- name: install playwright browsers
|
||||
run: |
|
||||
npx --no-install playwright@1.52.0 install --with-deps
|
||||
npx --no-install playwright@1.52.0 run-server --port 9001 &
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
- name: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Test
|
||||
run: npm run test
|
||||
- name: install playwright browsers
|
||||
run: |
|
||||
npx --no-install playwright@1.52.0 install --with-deps
|
||||
npx --no-install playwright@1.52.0 run-server --port 9001 &
|
||||
|
||||
- name: Lint with staticcheck
|
||||
uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0
|
||||
with:
|
||||
version: "latest"
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Govulncheck
|
||||
run: |
|
||||
go tool govulncheck ./...
|
||||
- name: Test
|
||||
run: npm run test
|
||||
|
||||
- name: Lint with staticcheck
|
||||
uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0
|
||||
with:
|
||||
version: "latest"
|
||||
|
||||
- name: Govulncheck
|
||||
run: |
|
||||
go tool govulncheck ./...
|
||||
|
||||
11
.github/workflows/package-builds-stable.yml
vendored
11
.github/workflows/package-builds-stable.yml
vendored
@@ -59,6 +59,17 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-golang-
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@02be93da58aa71fb456aa9c43b301149248829d8 # v1.15.1
|
||||
with:
|
||||
cache: false
|
||||
target: wasm32-unknown-unknown
|
||||
|
||||
- name: Setup Binaryen
|
||||
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 108
|
||||
|
||||
- name: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
113
.github/workflows/package-builds-unstable.yml
vendored
113
.github/workflows/package-builds-unstable.yml
vendored
@@ -2,9 +2,9 @@ name: Package builds (unstable)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -15,60 +15,71 @@ jobs:
|
||||
#runs-on: alrest-techarohq
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: build essential
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
- name: build essential
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
/home/linuxbrew/.linuxbrew/bin
|
||||
/home/linuxbrew/.linuxbrew/etc
|
||||
/home/linuxbrew/.linuxbrew/include
|
||||
/home/linuxbrew/.linuxbrew/lib
|
||||
/home/linuxbrew/.linuxbrew/opt
|
||||
/home/linuxbrew/.linuxbrew/sbin
|
||||
/home/linuxbrew/.linuxbrew/share
|
||||
/home/linuxbrew/.linuxbrew/var
|
||||
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-homebrew-cellar-
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
/home/linuxbrew/.linuxbrew/bin
|
||||
/home/linuxbrew/.linuxbrew/etc
|
||||
/home/linuxbrew/.linuxbrew/include
|
||||
/home/linuxbrew/.linuxbrew/lib
|
||||
/home/linuxbrew/.linuxbrew/opt
|
||||
/home/linuxbrew/.linuxbrew/sbin
|
||||
/home/linuxbrew/.linuxbrew/share
|
||||
/home/linuxbrew/.linuxbrew/var
|
||||
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-homebrew-cellar-
|
||||
|
||||
- name: Install Brew dependencies
|
||||
run: |
|
||||
brew bundle
|
||||
- name: Install Brew dependencies
|
||||
run: |
|
||||
brew bundle
|
||||
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-golang-
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # v4.2.4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-golang-
|
||||
|
||||
- name: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@02be93da58aa71fb456aa9c43b301149248829d8 # v1.15.1
|
||||
with:
|
||||
cache: false
|
||||
target: wasm32-unknown-unknown
|
||||
|
||||
- name: Build Packages
|
||||
run: |
|
||||
go tool yeet
|
||||
- name: Setup Binaryen
|
||||
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 108
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: packages
|
||||
path: var/*
|
||||
- name: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Build Packages
|
||||
run: |
|
||||
go tool yeet
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: packages
|
||||
path: var/*
|
||||
|
||||
35
.github/workflows/rust.yml
vendored
Normal file
35
.github/workflows/rust.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: "Rust tests"
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
rust-test:
|
||||
name: cargo test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@02be93da58aa71fb456aa9c43b301149248829d8 # v1.15.1
|
||||
with:
|
||||
cache: false
|
||||
target: wasm32-unknown-unknown
|
||||
- run: cargo test --all-features
|
||||
|
||||
# Check formatting with rustfmt
|
||||
rust-formatting:
|
||||
name: cargo fmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# Ensure rustfmt is installed and setup problem matcher
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@02be93da58aa71fb456aa9c43b301149248829d8 # v1.15.1
|
||||
with:
|
||||
components: rustfmt
|
||||
cache: false
|
||||
target: wasm32-unknown-unknown
|
||||
- name: Rustfmt Check
|
||||
uses: actions-rust-lang/rustfmt@559aa3035a47390ba96088dffa783b5d26da9326 # v1.1.1
|
||||
5
.github/workflows/smoke-tests.yml
vendored
5
.github/workflows/smoke-tests.yml
vendored
@@ -37,6 +37,11 @@ jobs:
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@02be93da58aa71fb456aa9c43b301149248829d8 # v1.15.1
|
||||
with:
|
||||
cache: false
|
||||
target: wasm32-unknown-unknown
|
||||
|
||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||
|
||||
- name: Install utils
|
||||
|
||||
37
.github/workflows/ssh-ci-runner-cron.yml
vendored
37
.github/workflows/ssh-ci-runner-cron.yml
vendored
@@ -1,37 +0,0 @@
|
||||
name: Regenerate ssh ci runner image
|
||||
|
||||
on:
|
||||
# pull_request:
|
||||
# branches: ["main"]
|
||||
schedule:
|
||||
- cron: "0 0 1,8,15,22 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
ssh-ci-rebuild:
|
||||
if: github.repository == 'TecharoHQ/anubis'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Log into registry
|
||||
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
- name: Build and push
|
||||
run: |
|
||||
cd ./test/ssh-ci
|
||||
docker buildx bake --push
|
||||
15
.github/workflows/ssh-ci.yml
vendored
15
.github/workflows/ssh-ci.yml
vendored
@@ -3,8 +3,8 @@ name: SSH CI
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
# pull_request:
|
||||
# branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -39,6 +39,17 @@ jobs:
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@02be93da58aa71fb456aa9c43b301149248829d8 # v1.15.1
|
||||
with:
|
||||
cache: false
|
||||
target: wasm32-unknown-unknown
|
||||
|
||||
- name: Setup Binaryen
|
||||
uses: Aandreba/setup-binaryen@77f25f9d7d30f09667a2535888bf9516b31a4cd7 # v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
version: 108
|
||||
|
||||
- name: Run CI
|
||||
run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }}
|
||||
env:
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -21,4 +21,8 @@ node_modules
|
||||
# how does this get here
|
||||
doc/VERSION
|
||||
|
||||
web/static/locales/*.json
|
||||
web/static/locales/*.json
|
||||
|
||||
# Rust
|
||||
target/*
|
||||
*.wasm
|
||||
431
Cargo.lock
generated
Normal file
431
Cargo.lock
generated
Normal file
@@ -0,0 +1,431 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anubis"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"wee_alloc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[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 = "block-buffer"
|
||||
version = "0.11.0-rc.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9ef36a6fcdb072aa548f3da057640ec10859eb4e91ddf526ee648d50c76a949"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e"
|
||||
|
||||
[[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 = "crypto-common"
|
||||
version = "0.2.0-rc.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.4",
|
||||
"crypto-common 0.1.6",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.11.0-rc.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6749b668519cd7149ee3d11286a442a8a8bdc3a9d529605f579777bfccc5a4bc"
|
||||
dependencies = [
|
||||
"block-buffer 0.11.0-rc.5",
|
||||
"const-oid",
|
||||
"crypto-common 0.2.0-rc.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dynasm"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7d4c414c94bc830797115b8e5f434d58e7e80cb42ba88508c14bc6ea270625"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"lazy_static",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dynasmrt"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "602f7458a3859195fb840e6e0cce5f4330dd9dfbfece0edaf31fe427af346f55"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"dynasm",
|
||||
"fnv",
|
||||
"memmap2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equix"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b48d834668afc862e90cda69065c0d85c192fe0a9364d1c5c05baf21c7454fc9"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"hashx 0.4.0",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
"visibility",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-capacity-vec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b31a14f5ee08ed1a40e1252b35af18bed062e3f39b69aab34decde36bc43e40"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[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 = "hashx"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anubis",
|
||||
"equix",
|
||||
"hashx 0.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashx"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8af708f5ff697572d409331c8714984a0ec1bb2eac190dd5bd75785bad6e764"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"blake2",
|
||||
"dynasmrt",
|
||||
"fixed-capacity-vec",
|
||||
"hex",
|
||||
"rand_core",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hybrid-array"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a09fa0190457fce307a699c050054974f81b6975b7a017f1e784eb7d9c2d4bc"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843a98750cd611cc2965a8213b53b43e715f13c37a9e096c6408e69990961db7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memory_units"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
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 = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.11.0-rc.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.3",
|
||||
"cpufeatures",
|
||||
"digest 0.11.0-rc.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha256"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anubis",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[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.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "visibility"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wee_alloc"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc",
|
||||
"memory_units",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["wasm/anubis", "wasm/pow/*"]
|
||||
|
||||
[profile.release]
|
||||
#strip = true
|
||||
opt-level = "s"
|
||||
lto = "thin"
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
9
Makefile
9
Makefile
@@ -1,17 +1,22 @@
|
||||
VERSION= $(shell cat ./VERSION)
|
||||
GO?= go
|
||||
NPM?= npm
|
||||
CARGO?= cargo
|
||||
|
||||
.PHONY: build assets deps lint prebaked-build test
|
||||
.PHONY: build assets assets-wasm deps lint prebaked-build test
|
||||
|
||||
all: build
|
||||
|
||||
deps:
|
||||
$(NPM) ci
|
||||
$(GO) mod download
|
||||
$(CARGO) fetch
|
||||
|
||||
assets-wasm:
|
||||
bash ./scripts/build_wasm.sh
|
||||
|
||||
assets: PATH:=$(PWD)/node_modules/.bin:$(PATH)
|
||||
assets: deps
|
||||
assets: deps assets-wasm
|
||||
$(GO) generate ./...
|
||||
./web/build.sh
|
||||
./xess/build.sh
|
||||
|
||||
@@ -32,6 +32,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Allow multiple consecutive slashes in a row in application paths ([#754](https://github.com/TecharoHQ/anubis/issues/754)).
|
||||
- Add option to set `targetSNI` to special keyword 'auto' to indicate that it should be automatically set to the request Host name ([424](https://github.com/TecharoHQ/anubis/issues/424)).
|
||||
|
||||
### Major new features
|
||||
|
||||
#### WebAssembly support
|
||||
|
||||
Anubis now supports running [WebAssembly based proof of work challenges](./admin/configuration/challenges/wasm.mdx) in addition to pure-JavaScript challenges. For more information, check the following links:
|
||||
|
||||
- [Proof of Work (WebAssembly)](./admin/configuration/challenges/wasm.mdx)
|
||||
- [WebAssembly based proof of work implementation details](./developer/wasm.mdx)
|
||||
|
||||
:::note
|
||||
|
||||
Clients that don't have WebAssembly enabled will instead be served a pure JavaScript variant of the WebAssembly module. This will be much slower than the WebAssembly module, but will work.
|
||||
|
||||
:::
|
||||
|
||||
If you are packaging this for a Linux distribution, Anubis requires [binaryen](https://github.com/WebAssembly/binaryen) at exactly version 108. We are working on mitigating this dependency.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
Sometimes the enhanced temporal assurance in [#1038](https://github.com/TecharoHQ/anubis/pull/1038) and [#1068](https://github.com/TecharoHQ/anubis/pull/1068) could backfire because Chromium and its ilk randomize the amount of time they wait in order to avoid a timing side channel attack. This has been fixed by both increasing the amount of time a client has to wait for the metarefresh and preact challenges as well as making the server side logic more permissive.
|
||||
|
||||
@@ -4,6 +4,7 @@ Anubis supports multiple challenge methods:
|
||||
|
||||
- [Meta Refresh](./metarefresh.mdx)
|
||||
- [Preact](./preact.mdx)
|
||||
- [Proof of Work](./proof-of-work.mdx)
|
||||
- [Proof of Work (JS)](./proof-of-work.mdx)
|
||||
- [Proof of Work (WebAssembly)](./wasm.mdx)
|
||||
|
||||
Read the documentation to know which method is best for you.
|
||||
|
||||
66
docs/docs/admin/configuration/challenges/wasm.mdx
Normal file
66
docs/docs/admin/configuration/challenges/wasm.mdx
Normal file
@@ -0,0 +1,66 @@
|
||||
# Proof of Work (WebAssembly)
|
||||
|
||||
Anubis supports using [WebAssembly](https://en.wikipedia.org/wiki/WebAssembly) to speed up proof of work validation. Proof of work functions as a randomized delay to prevent clients from overwhelming your server with traffic. All WebAssembly proof of work functions are written in Rust. If clients are running in a context without WebAssembly support, Anubis uses a variant of the WebAssembly code compiled to JavaScript.
|
||||
|
||||
Anubis offers the following WebAssembly proof of work functions:
|
||||
|
||||
- [hashx](#hashx) (via the [`hashx`](https://docs.rs/hashx/latest/hashx/index.html) crate)
|
||||
- [sha256](#sha256) (via the [`sha2`](https://docs.rs/sha2/latest/sha2/) crate)
|
||||
|
||||
:::note
|
||||
|
||||
The difficulty values for the WebAssembly based checks are going to be much higher than the equivalent difficulty values for the [JavaScript based checks](./proof-of-work.mdx). Generally these count the number of leading _bits_ that much match instead of the number of leading _nibbles_ that must match. Here's a rough translation table:
|
||||
|
||||
| `fast` difficulty | `sha256` difficulty | `hashx` difficulty |
|
||||
| :---------------- | :------------------ | :----------------- |
|
||||
| `4` | `16` | `15` |
|
||||
| `2` | `8` | `7` |
|
||||
| `6` | `24` | `20` |
|
||||
|
||||
:::
|
||||
|
||||
## `hashx`
|
||||
|
||||
Uses the ASIC-resistant [hashx](https://github.com/tevador/hashx) as the proof of work function. This is resistant to GPU and ASIC attacks. In practice this means that users need to be using a standards-compliant browser to pass the challenge.
|
||||
|
||||
Usage:
|
||||
|
||||
```yaml
|
||||
thresholds:
|
||||
- name: moderate-suspicion
|
||||
expression:
|
||||
all:
|
||||
- weight >= 10
|
||||
- weight < 20
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
algorithm: hashx
|
||||
difficulty: 16
|
||||
report_as: 16
|
||||
```
|
||||
|
||||
## `sha256`
|
||||
|
||||
Uses [SHA-256](https://en.wikipedia.org/wiki/SHA-2) as the proof of work function.
|
||||
|
||||
:::note
|
||||
|
||||
This is included mostly as a fallback for usecases that specifically require this hashing function. There are known tools that solve this particular challenge. Prefer [hashx](#hashx) unless you know what you are doing.
|
||||
|
||||
:::
|
||||
|
||||
Usage:
|
||||
|
||||
```yaml
|
||||
thresholds:
|
||||
- name: moderate-suspicion
|
||||
expression:
|
||||
all:
|
||||
- weight >= 10
|
||||
- weight < 20
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
algorithm: sha256
|
||||
difficulty: 16
|
||||
report_as: 16
|
||||
```
|
||||
75
docs/docs/developer/wasm.mdx
Normal file
75
docs/docs/developer/wasm.mdx
Normal file
@@ -0,0 +1,75 @@
|
||||
# WebAssembly based proof of work implementation details
|
||||
|
||||
When an administrator configures Anubis to use a [WebAssembly based proof of work check](../admin/configuration/challenges/wasm.mdx), Anubis serves [WebAssembly modules](https://webassembly.github.io/spec/core/syntax/modules.html) to clients. Clients execute these WebAssembly modules in order to solve challenges and the server executes the same WebAssembly modules in order to validate solutions. This architecture allows the client and server to be implemented in lockstep, making it much easier to add future challenge methods as long as they meet the API contract.
|
||||
|
||||
## Design goals
|
||||
|
||||
Anubis WebAssembly modules are meant to provide the following properties:
|
||||
|
||||
- **Small size** - Modules should not be bigger than 128Ki unless there is a very good reason for them to be larger.
|
||||
- **Minimal interfaces to the outside world** - These are meant to run hash functions. Minimize the inputs and outputs to the bare minimum required to run the program.
|
||||
- **The same code must run on both the client and server** - In order to simplify implementation, expansion, and technical debt: the same code must run on both the client and the server. When a client computes a solution, it uses the same validation code that the server uses to verify correctness.
|
||||
- **Execution as fast as possible** - This code is in a unique deployment scenario. At some level you want the code to execute slowly to better function as a rate limiter for incoming client requests. At another level, you want the code to execute as quickly as possible so that clients don't have a bad experience with the check taking "too long" or causing battery life impacts.
|
||||
|
||||
Given these constraints, the following compromises are made:
|
||||
|
||||
- WebAssembly modules are written in [Rust](https://rust-lang.org/), a modern systems programming language with best-in-class support for WebAssembly.
|
||||
- [Wazero](https://wazero.io/) is used on the server for running the validation logic in a secure sandbox.
|
||||
- A low-level Unix-like API is used to communicate between the host and the guest.
|
||||
|
||||
## API contract
|
||||
|
||||
Anubis WebAssembly modules have two main entrypoints:
|
||||
|
||||
- `anubis_work`: Reads the data buffer and works until the validation function says that the solution is correct.
|
||||
- `anubis_validate`: Reads the verification and data buffers and ensures that both of them match and the solution is correct.
|
||||
|
||||
For an example of an Anubis WebAssembly module, read the source code for the [`sha256` challenge](https://github.com/TecharoHQ/anubis/blob/main/wasm/pow/sha256/src/lib.rs).
|
||||
|
||||
Anubis WebAssembly modules have the following de-facto global variables:
|
||||
|
||||
- The data buffer: where the challenge-specific random data is stored and used for computing challenge results. Limit of 4096 bytes.
|
||||
- The result buffer: where the result hash is stored. Limit varies based on challenge.
|
||||
- The verification buffer: where a verification hash is written to for comparison with the computed hash in the result buffer. Limit varies based on challenge.
|
||||
|
||||
Other functions:
|
||||
|
||||
### Writing to the data buffer
|
||||
|
||||
The data buffer is a write-only buffer with a maximum capacity of 4096 bytes. The host writes data into the buffer and sets the buffer length. This functions similarly to a Go slice.
|
||||
|
||||
Usage:
|
||||
|
||||
- Host: call `data_ptr` to discover the base pointer of the data buffer.
|
||||
- Guest: return the pointer.
|
||||
- Host: write up to 4096 bytes to guest memory starting at the base pointer.
|
||||
- Host: call `set_data_length` to tell the guest how large the data buffer is.
|
||||
- Guest: update that length as a global mutable variable.
|
||||
- Host: call `anubis_work` or set the verification buffer and call `anubis_validate`.
|
||||
|
||||
### Reading from the result buffer
|
||||
|
||||
The result buffer is a read-only buffer that contains the result computed by `anubis_work`. The size of this buffer varies based on the challenge implementation.
|
||||
|
||||
After calling `anubis_work`:
|
||||
|
||||
- Host: call `result_hash_ptr` to discover the base pointer of the result buffer.
|
||||
- Guest: return the pointer.
|
||||
- Host: call `result_hash_size` to discover the size of the result buffer in bytes.
|
||||
- Guest: return the size.
|
||||
- Host: read exactly that number of bytes to host memory.
|
||||
- Host: use that result to continue on with normal execution.
|
||||
|
||||
### Writing to the verification buffer
|
||||
|
||||
The verification buffer is a write-only buffer that contains the result computed by a client. The size of this buffer varies based on the challenge implementation.
|
||||
|
||||
After setting the data buffer:
|
||||
|
||||
- Host: call `verification_hash_ptr` to discover the base pointer of the verification buffer.
|
||||
- Guest: return that pointer.
|
||||
- Host: call `verification_hash_size` to discover the size of the verification buffer in bytes.
|
||||
- Guest: return the size.
|
||||
- Host: write exactly that number of bytes to guest memory starting at the base pointer.
|
||||
- Host: call `anubis_validate` with settings from the server-side challenge information.
|
||||
- Guest: compute both hashes, compare them and validate against the server-side challenge level. If valid: return true. If invalid: return false.
|
||||
1
go.mod
1
go.mod
@@ -25,6 +25,7 @@ require (
|
||||
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a
|
||||
github.com/shirou/gopsutil/v4 v4.25.6
|
||||
github.com/testcontainers/testcontainers-go v0.38.0
|
||||
github.com/tetratelabs/wazero v1.9.0
|
||||
go.etcd.io/bbolt v1.4.2
|
||||
golang.org/x/net v0.42.0
|
||||
golang.org/x/text v0.27.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -395,6 +395,8 @@ github.com/suzuki-shunsuke/urfave-cli-help-all v0.0.4 h1:YGHgrVjGTYHY98II6zijXUH
|
||||
github.com/suzuki-shunsuke/urfave-cli-help-all v0.0.4/go.mod h1:sSi6xaUaHfaqu32ECLeyE7NTMv+ZM5dW0JikhllaalY=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
_ "github.com/TecharoHQ/anubis/lib/challenge/metarefresh"
|
||||
_ "github.com/TecharoHQ/anubis/lib/challenge/preact"
|
||||
_ "github.com/TecharoHQ/anubis/lib/challenge/proofofwork"
|
||||
_ "github.com/TecharoHQ/anubis/lib/challenge/wasm"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -58,7 +58,7 @@ type ValidateInput struct {
|
||||
|
||||
type Impl interface {
|
||||
// Setup registers any additional routes with the Impl for assets or API routes.
|
||||
Setup(mux *http.ServeMux)
|
||||
Setup(mux *http.ServeMux) error
|
||||
|
||||
// Issue a new challenge to the user, called by the Anubis.
|
||||
Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *IssueInput) (templ.Component, error)
|
||||
|
||||
@@ -21,7 +21,7 @@ func init() {
|
||||
|
||||
type Impl struct{}
|
||||
|
||||
func (i *Impl) Setup(mux *http.ServeMux) {}
|
||||
func (i *Impl) Setup(mux *http.ServeMux) error { return nil }
|
||||
|
||||
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
||||
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { render, h, Fragment } from "preact";
|
||||
import { useState, useEffect } from "preact/hooks";
|
||||
import { g, j, r, u, x } from "./xeact.js";
|
||||
import { g, j, r, u, x } from "../../../../web/lib/xeact";
|
||||
import { Sha256 } from "@aws-crypto/sha256-js";
|
||||
|
||||
/** @jsx h */
|
||||
|
||||
@@ -36,7 +36,7 @@ func init() {
|
||||
|
||||
type impl struct{}
|
||||
|
||||
func (i *impl) Setup(mux *http.ServeMux) {}
|
||||
func (i *impl) Setup(mux *http.ServeMux) error { return nil }
|
||||
|
||||
func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
||||
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
||||
|
||||
@@ -25,7 +25,7 @@ type Impl struct {
|
||||
Algorithm string
|
||||
}
|
||||
|
||||
func (i *Impl) Setup(mux *http.ServeMux) {}
|
||||
func (i *Impl) Setup(mux *http.ServeMux) error { return nil }
|
||||
|
||||
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *chall.IssueInput) (templ.Component, error) {
|
||||
loc := localization.GetLocalizer(r)
|
||||
|
||||
102
lib/challenge/wasm/wasm.go
Normal file
102
lib/challenge/wasm/wasm.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
chall "github.com/TecharoHQ/anubis/lib/challenge"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
"github.com/TecharoHQ/anubis/wasm"
|
||||
"github.com/TecharoHQ/anubis/web"
|
||||
"github.com/a-h/templ"
|
||||
)
|
||||
|
||||
//go:generate go tool github.com/a-h/templ/cmd/templ generate
|
||||
|
||||
func init() {
|
||||
chall.Register("hashx", &Impl{algorithm: "hashx"})
|
||||
chall.Register("sha256", &Impl{algorithm: "sha256"})
|
||||
}
|
||||
|
||||
type Impl struct {
|
||||
algorithm string
|
||||
runner *wasm.Runner
|
||||
}
|
||||
|
||||
func (i *Impl) Setup(mux *http.ServeMux) error {
|
||||
fname := fmt.Sprintf("static/wasm/simd128/%s.wasm", i.algorithm)
|
||||
fin, err := web.Static.Open(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fin.Close()
|
||||
|
||||
i.runner, err = wasm.NewRunner(context.Background(), fname, fin)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *chall.IssueInput) (templ.Component, error) {
|
||||
loc := localization.GetLocalizer(r)
|
||||
return page(loc), nil
|
||||
}
|
||||
|
||||
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
|
||||
nonceStr := r.FormValue("nonce")
|
||||
if nonceStr == "" {
|
||||
return chall.NewError("validate", "invalid response", fmt.Errorf("%w nonce", chall.ErrMissingField))
|
||||
}
|
||||
|
||||
nonce, err := strconv.Atoi(nonceStr)
|
||||
if err != nil {
|
||||
return chall.NewError("validate", "invalid response", fmt.Errorf("%w: nonce: %w", chall.ErrInvalidFormat, err))
|
||||
|
||||
}
|
||||
|
||||
elapsedTimeStr := r.FormValue("elapsedTime")
|
||||
if elapsedTimeStr == "" {
|
||||
return chall.NewError("validate", "invalid response", fmt.Errorf("%w elapsedTime", chall.ErrMissingField))
|
||||
}
|
||||
|
||||
elapsedTime, err := strconv.ParseFloat(elapsedTimeStr, 64)
|
||||
if err != nil {
|
||||
return chall.NewError("validate", "invalid response", fmt.Errorf("%w: elapsedTime: %w", chall.ErrInvalidFormat, err))
|
||||
}
|
||||
|
||||
response := r.FormValue("response")
|
||||
if response == "" {
|
||||
return chall.NewError("validate", "invalid response", fmt.Errorf("%w response", chall.ErrMissingField))
|
||||
}
|
||||
|
||||
challengeBytes, err := hex.DecodeString(in.Challenge.RandomData)
|
||||
if err != nil {
|
||||
return chall.NewError("validate", "invalid random data", fmt.Errorf("can't decode random data: %w", err))
|
||||
}
|
||||
|
||||
gotBytes, err := hex.DecodeString(response)
|
||||
if err != nil {
|
||||
return chall.NewError("validate", "invalid client data format", fmt.Errorf("%w response", chall.ErrInvalidFormat))
|
||||
}
|
||||
|
||||
ok, err := i.runner.Verify(r.Context(), challengeBytes, gotBytes, uint32(nonce), uint32(in.Rule.Challenge.Difficulty))
|
||||
if err != nil {
|
||||
return chall.NewError("validate", "internal WASM error", fmt.Errorf("can't run wasm validation logic: %w", err))
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return chall.NewError("verify", "client calculated wrong data", fmt.Errorf("%w: response invalid: %s", chall.ErrFailed, response))
|
||||
}
|
||||
|
||||
lg.Debug("challenge took", "elapsedTime", elapsedTime)
|
||||
chall.TimeTaken.WithLabelValues(i.algorithm).Observe(elapsedTime)
|
||||
|
||||
return nil
|
||||
}
|
||||
44
lib/challenge/wasm/wasm.templ
Normal file
44
lib/challenge/wasm/wasm.templ
Normal file
@@ -0,0 +1,44 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
)
|
||||
|
||||
templ page(localizer *localization.SimpleLocalizer) {
|
||||
<div class="centered-div">
|
||||
<img id="image" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version }/>
|
||||
<img style="display:none;" style="width:100%;max-width:256px;" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version }/>
|
||||
<p id="status">{ localizer.T("loading") }</p>
|
||||
<script async type="module" src={ anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version }></script>
|
||||
<div id="progress" role="progressbar" aria-labelledby="status">
|
||||
<div class="bar-inner"></div>
|
||||
</div>
|
||||
<details>
|
||||
if anubis.UseSimplifiedExplanation {
|
||||
<p>
|
||||
{ localizer.T("simplified_explanation") }
|
||||
</p>
|
||||
} else {
|
||||
<p>
|
||||
{ localizer.T("ai_companies_explanation") }
|
||||
</p>
|
||||
<p>
|
||||
{ localizer.T("anubis_compromise") }
|
||||
</p>
|
||||
<p>
|
||||
{ localizer.T("hack_purpose") }
|
||||
</p>
|
||||
<p>
|
||||
{ localizer.T("jshelter_note") }
|
||||
</p>
|
||||
}
|
||||
</details>
|
||||
<noscript>
|
||||
<p>
|
||||
{ localizer.T("javascript_required") }
|
||||
</p>
|
||||
</noscript>
|
||||
<div id="testarea"></div>
|
||||
</div>
|
||||
}
|
||||
190
lib/challenge/wasm/wasm_templ.go
generated
Normal file
190
lib/challenge/wasm/wasm_templ.go
generated
Normal file
@@ -0,0 +1,190 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.924
|
||||
package wasm
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"github.com/TecharoHQ/anubis"
|
||||
"github.com/TecharoHQ/anubis/lib/localization"
|
||||
)
|
||||
|
||||
func page(localizer *localization.SimpleLocalizer) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"centered-div\"><img id=\"image\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/pensive.webp?cacheBuster=" + anubis.Version)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `wasm.templ`, Line: 10, Col: 165}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\"> <img style=\"display:none;\" style=\"width:100%;max-width:256px;\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/img/happy.webp?cacheBuster=" + anubis.Version)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `wasm.templ`, Line: 11, Col: 174}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\"><p id=\"status\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("loading"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `wasm.templ`, Line: 12, Col: 41}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</p><script async type=\"module\" src=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(anubis.BasePrefix + "/.within.website/x/cmd/anubis/static/js/main.mjs?cacheBuster=" + anubis.Version)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `wasm.templ`, Line: 13, Col: 136}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\"></script><div id=\"progress\" role=\"progressbar\" aria-labelledby=\"status\"><div class=\"bar-inner\"></div></div><details>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if anubis.UseSimplifiedExplanation {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("simplified_explanation"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `wasm.templ`, Line: 20, Col: 44}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("ai_companies_explanation"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `wasm.templ`, Line: 24, Col: 46}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</p><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("anubis_compromise"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `wasm.templ`, Line: 27, Col: 39}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</p><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("hack_purpose"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `wasm.templ`, Line: 30, Col: 34}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</p><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("jshelter_note"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `wasm.templ`, Line: 33, Col: 35}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</details><noscript><p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(localizer.T("javascript_required"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `wasm.templ`, Line: 39, Col: 40}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</p></noscript><div id=\"testarea\"></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
@@ -175,7 +175,9 @@ func New(opts Options) (*Server, error) {
|
||||
|
||||
for _, implKind := range challenge.Methods() {
|
||||
impl, _ := challenge.Get(implKind)
|
||||
impl.Setup(mux)
|
||||
if err := impl.Setup(mux); err != nil {
|
||||
return nil, fmt.Errorf("failed to init challenge method %s: %w", implKind, err)
|
||||
}
|
||||
}
|
||||
|
||||
result.mux = mux
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -10,7 +10,9 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-js": "^5.2.0",
|
||||
"preact": "^10.27.2"
|
||||
"@haribala/wasm2js": "^1.1.1",
|
||||
"preact": "^10.27.2",
|
||||
"wasm-feature-detect": "^1.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cssnano": "^7.1.1",
|
||||
@@ -503,6 +505,12 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@haribala/wasm2js": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@haribala/wasm2js/-/wasm2js-1.1.1.tgz",
|
||||
"integrity": "sha512-PCZxbPNYJ3Nax1EPKb6oz2TSraSbhQyEXL2wZDlardsE7IwP6HHHVJVvDYWDz5p5HcASAvWRi74utGIepagTeA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"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",
|
||||
@@ -2685,6 +2693,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wasm-feature-detect": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz",
|
||||
"integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
|
||||
13
package.json
13
package.json
@@ -8,13 +8,20 @@
|
||||
"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:docker": "npm run assets && go test -v ./internal/test --playwright-runner=docker",
|
||||
"assets": "go generate ./... && ./web/build.sh && ./xess/build.sh",
|
||||
"generate": "go generate ./...",
|
||||
"assets:js": "./web/build.sh",
|
||||
"assets:css": "./xess/build.sh",
|
||||
"assets:wasm": "bash ./scripts/build_wasm.sh",
|
||||
"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",
|
||||
"dev": "npm run assets && go run ./cmd/anubis --use-remote-address --target http://localhost:3000",
|
||||
"container": "npm run assets && go run ./cmd/containerbuild",
|
||||
"package": "yeet",
|
||||
"lint": "make lint"
|
||||
},
|
||||
"imports": {
|
||||
"lib/*": "./web/lib/*"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
@@ -29,6 +36,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-js": "^5.2.0",
|
||||
"preact": "^10.27.2"
|
||||
"@haribala/wasm2js": "^1.1.1",
|
||||
"preact": "^10.27.2",
|
||||
"wasm-feature-detect": "^1.8.0"
|
||||
}
|
||||
}
|
||||
4
rustup-toolchain.toml
Normal file
4
rustup-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
components = ["rustfmt", "cargo", "clippy", "rust-analyzer", "rustfmt"]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
24
scripts/build_wasm.sh
Executable file
24
scripts/build_wasm.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
mkdir -p ./web/static/wasm/{simd128,baseline}
|
||||
|
||||
cargo clean
|
||||
|
||||
# With simd128
|
||||
RUSTFLAGS='-C target-feature=+simd128' cargo build --release --target wasm32-unknown-unknown
|
||||
cp -vf ./target/wasm32-unknown-unknown/release/*.wasm ./web/static/wasm/simd128
|
||||
|
||||
cargo clean
|
||||
|
||||
# Without simd128
|
||||
cargo build --release --target wasm32-unknown-unknown
|
||||
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
|
||||
@@ -76,6 +76,7 @@ require (
|
||||
github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.1 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.2 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
|
||||
@@ -211,6 +211,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0 h1:d7uEapLcv2P8AvH8ahLqDMMxda2W9gQN1nRbHS28HBw=
|
||||
github.com/testcontainers/testcontainers-go v0.38.0/go.mod h1:C52c9MoHpWO+C4aqmgSU+hxlR5jlEayWtgYrb8Pzz1w=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
|
||||
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
|
||||
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
ARG ALPINE_VERSION=3.22
|
||||
|
||||
FROM alpine:${ALPINE_VERSION}
|
||||
RUN apk add -U go nodejs git build-base git npm bash zstd brotli gzip
|
||||
LABEL org.opencontainers.image.source="https://github.com/TecharoHQ/anubis"
|
||||
@@ -1,26 +0,0 @@
|
||||
variable "ALPINE_VERSION" { default = "3.22" }
|
||||
|
||||
group "default" {
|
||||
targets = [
|
||||
"ci-runner",
|
||||
]
|
||||
}
|
||||
|
||||
target "ci-runner" {
|
||||
args = {
|
||||
ALPINE_VERSION = "3.22"
|
||||
}
|
||||
context = "."
|
||||
dockerfile = "./Dockerfile"
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
"linux/arm/v7",
|
||||
"linux/ppc64le",
|
||||
"linux/riscv64",
|
||||
]
|
||||
pull = true
|
||||
tags = [
|
||||
"ghcr.io/techarohq/anubis/ci-runner:latest"
|
||||
]
|
||||
}
|
||||
@@ -5,4 +5,4 @@ set -x
|
||||
|
||||
npm ci
|
||||
npm run build
|
||||
SKIP_INTEGRATION=1 go test ./...
|
||||
SKIP_INTEGRATION=1 go test -v ./...
|
||||
@@ -14,7 +14,7 @@ Hosts["ppc64le"]="ci@ppc64le.techaro.lol" # GOARCH=ppc64le GOOS=linux
|
||||
Hosts["aarch64-4k"]="rocky@192.168.2.52" # GOARCH=arm64 GOOS=linux 4k page size
|
||||
Hosts["aarch64-16k"]="ci@192.168.2.28" # GOARCH=arm64 GOOS=linux 16k page size
|
||||
|
||||
CIRunnerImage="ghcr.io/techarohq/anubis/ci-runner:latest"
|
||||
CIRunnerImage="ghcr.io/techarohq/ci-images/ssh-ci:latest"
|
||||
RunID=${GITHUB_RUN_ID:-$(uuidgen)}
|
||||
RunFolder="anubis/runs/${RunID}"
|
||||
Target="${Hosts["$1"]}"
|
||||
|
||||
7
wasm/anubis/Cargo.toml
Normal file
7
wasm/anubis/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "anubis"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
wee_alloc = "0.4"
|
||||
72
wasm/anubis/src/lib.rs
Normal file
72
wasm/anubis/src/lib.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
extern crate wee_alloc;
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod hostimport {
|
||||
use crate::{DATA_BUFFER, DATA_LENGTH};
|
||||
|
||||
#[link(wasm_import_module = "anubis")]
|
||||
unsafe extern "C" {
|
||||
/// The runtime expects this function to be defined. It is called whenever the Anubis check
|
||||
/// worker processes about 1024 hashes. This can be a no-op if you want.
|
||||
fn anubis_update_nonce(nonce: u32);
|
||||
}
|
||||
|
||||
/// Safe wrapper to `anubis_update_nonce`.
|
||||
pub fn update_nonce(nonce: u32) {
|
||||
unsafe {
|
||||
anubis_update_nonce(nonce);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn data_ptr() -> *const u8 {
|
||||
let challenge = &DATA_BUFFER;
|
||||
challenge.as_ptr()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn set_data_length(len: u32) {
|
||||
let mut data_length = DATA_LENGTH.lock().unwrap();
|
||||
*data_length = len as usize;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod hostimport {
|
||||
use crate::{DATA_BUFFER, DATA_LENGTH};
|
||||
|
||||
pub fn update_nonce(_nonce: u32) {
|
||||
// This is intentionally blank
|
||||
}
|
||||
|
||||
pub fn data_ptr() -> *const u8 {
|
||||
let challenge = &DATA_BUFFER;
|
||||
challenge.as_ptr()
|
||||
}
|
||||
|
||||
pub fn set_data_length(len: u32) {
|
||||
let mut data_length = DATA_LENGTH.lock().unwrap();
|
||||
*data_length = len as usize;
|
||||
}
|
||||
}
|
||||
|
||||
/// The data buffer is a bit weird in that it doesn't have an explicit length as it can
|
||||
/// and will change depending on the challenge input that was sent by the server.
|
||||
/// However, it can only fit 4096 bytes of data (one amd64 machine page). This is
|
||||
/// slightly overkill for the purposes of an Anubis check, but it's fine to assume
|
||||
/// that the browser can afford this much ram usage.
|
||||
///
|
||||
/// Callers should fetch the base data pointer, write up to 4096 bytes, and then
|
||||
/// `set_data_length` the number of bytes they have written
|
||||
///
|
||||
/// This is also functionally a write-only buffer, so it doesn't really matter that
|
||||
/// the length of this buffer isn't exposed.
|
||||
pub static DATA_BUFFER: LazyLock<[u8; 4096]> = LazyLock::new(|| [0; 4096]);
|
||||
pub static DATA_LENGTH: LazyLock<Mutex<usize>> = LazyLock::new(|| Mutex::new(0));
|
||||
|
||||
pub use hostimport::{data_ptr, set_data_length, update_nonce};
|
||||
22
wasm/pow/hashx/Cargo.toml
Normal file
22
wasm/pow/hashx/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "hashx"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
equix = "0.3"
|
||||
hashx = "0.4"
|
||||
|
||||
anubis = { path = "../../anubis" }
|
||||
|
||||
[lints.clippy]
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
unwrap_used = "warn"
|
||||
uninlined_format_args = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
cognitive_complexity = "allow"
|
||||
156
wasm/pow/hashx/src/lib.rs
Normal file
156
wasm/pow/hashx/src/lib.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use anubis::{DATA_BUFFER, DATA_LENGTH, update_nonce};
|
||||
use hashx::HashX;
|
||||
use std::boxed::Box;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
/// SHA-256 hashes are 32 bytes (256 bits). These are stored in static buffers due to the
|
||||
/// fact that you cannot easily pass data from host space to WebAssembly space.
|
||||
pub static RESULT_HASH: LazyLock<Box<Mutex<[u8; 32]>>> =
|
||||
LazyLock::new(|| Box::new(Mutex::new([0; 32])));
|
||||
|
||||
pub static VERIFICATION_HASH: LazyLock<Box<Mutex<[u8; 32]>>> =
|
||||
LazyLock::new(|| Box::new(Mutex::new([0; 32])));
|
||||
|
||||
/// Core validation function. Compare each bit in the hash by progressively masking bits until
|
||||
/// some are found to not be matching.
|
||||
///
|
||||
/// There are probably more clever ways to do this, likely involving lookup tables or something
|
||||
/// really fun like that. However in my testing this lets us get up to 200 kilohashes per second
|
||||
/// on my Ryzen 7950x3D, up from about 50 kilohashes per second in JavaScript.
|
||||
fn validate(hash: &[u8], difficulty: u32) -> bool {
|
||||
let mut remaining = difficulty;
|
||||
for &byte in hash {
|
||||
// If we're out of bits to check, exit. This is all good.
|
||||
if remaining == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
// If there are more than 8 bits remaining, the entire byte should be a
|
||||
// zero. This fast-path compares the byte to 0 and if it matches, subtract
|
||||
// 8 bits.
|
||||
if remaining >= 8 {
|
||||
if byte != 0 {
|
||||
return false;
|
||||
}
|
||||
remaining -= 8;
|
||||
} else {
|
||||
// Otherwise mask off individual bits and check against them.
|
||||
let mask = 0xFF << (8 - remaining);
|
||||
if (byte & mask) != 0 {
|
||||
return false;
|
||||
}
|
||||
remaining = 0;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn anubis_work_inner(
|
||||
difficulty: u32,
|
||||
initial_nonce: u32,
|
||||
iterand: u32,
|
||||
) -> Result<u32, hashx::Error> {
|
||||
let mut nonce: u32 = initial_nonce;
|
||||
|
||||
let data = &DATA_BUFFER;
|
||||
let data_len = *DATA_LENGTH.lock().unwrap();
|
||||
let data_slice = &data[..data_len];
|
||||
let mut i = 0;
|
||||
|
||||
let h = HashX::new(data_slice)?;
|
||||
|
||||
loop {
|
||||
i += 1;
|
||||
let hash = h.hash_to_bytes(nonce as u64);
|
||||
|
||||
if validate(&hash, difficulty) {
|
||||
// If the challenge worked, copy the bytes into `RESULT_HASH` so the runtime
|
||||
// can pick it up.
|
||||
let mut challenge = RESULT_HASH.lock().unwrap();
|
||||
challenge.copy_from_slice(&hash);
|
||||
return Ok(nonce);
|
||||
}
|
||||
|
||||
nonce = nonce.wrapping_add(iterand);
|
||||
|
||||
if i == 1024 {
|
||||
update_nonce(nonce);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is the main entrypoint for the Anubis proof of work implementation.
|
||||
///
|
||||
/// This expects `DATA_BUFFER` to be prepopulated with the challenge value as "raw bytes".
|
||||
/// The definition of what goes in the data buffer is an exercise for the implementor, but
|
||||
/// for SHA-256 we store the hash as "raw bytes". The data buffer is intentionally oversized
|
||||
/// so that the challenge value can be expanded in the future.
|
||||
///
|
||||
/// `difficulty` is the number of leading bits that must match `0` in order for the
|
||||
/// challenge to be successfully passed. This will be validated by the server.
|
||||
///
|
||||
/// `initial_nonce` is the initial value of the nonce (number used once). This nonce will be
|
||||
/// appended to the challenge value in order to find a hash matching the specified
|
||||
/// difficulty.
|
||||
///
|
||||
/// `iterand` (noun form of iterate) is the amount that the nonce should be increased by
|
||||
/// every iteration of the proof of work loop. This will vary by how many threads are
|
||||
/// running the proof-of-work check, and also functions as a thread ID. This prevents
|
||||
/// wasting CPU time retrying a hash+nonce pair that likely won't work.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn anubis_work(difficulty: u32, initial_nonce: u32, iterand: u32) -> u32 {
|
||||
anubis_work_inner(difficulty, initial_nonce, iterand).unwrap()
|
||||
}
|
||||
|
||||
/// This function is called by the server in order to validate a proof-of-work challenge.
|
||||
/// This expects `DATA_BUFFER` to be set to the challenge value and `VERIFICATION_HASH` to
|
||||
/// be set to the "raw bytes" of the SHA-256 hash that the client calculated.
|
||||
///
|
||||
/// If everything is good, it returns true. Otherwise, it returns false.
|
||||
///
|
||||
/// XXX(Xe): this could probably return an error code for what step fails, but this is fine
|
||||
/// for now.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn anubis_validate(nonce: u32, difficulty: u32) -> bool {
|
||||
let data = &DATA_BUFFER;
|
||||
let data_len = *DATA_LENGTH.lock().unwrap();
|
||||
let data_slice = &data[..data_len];
|
||||
|
||||
let h: HashX = HashX::new(data_slice).unwrap();
|
||||
let computed = h.hash_to_bytes(nonce as u64);
|
||||
let valid = validate(&computed, difficulty);
|
||||
if !valid {
|
||||
return false;
|
||||
}
|
||||
|
||||
let verification = VERIFICATION_HASH.lock().unwrap();
|
||||
computed == *verification
|
||||
}
|
||||
|
||||
// These functions exist to give pointers and lengths to the runtime around the Anubis
|
||||
// checks, this allows JavaScript and Go to safely manipulate the memory layout that Rust
|
||||
// has statically allocated at compile time without having to assume how the Rust compiler
|
||||
// is going to lay it out.
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn result_hash_ptr() -> *const u8 {
|
||||
let challenge = RESULT_HASH.lock().unwrap();
|
||||
challenge.as_ptr()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn result_hash_size() -> usize {
|
||||
RESULT_HASH.lock().unwrap().len()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn verification_hash_ptr() -> *const u8 {
|
||||
let verification = VERIFICATION_HASH.lock().unwrap();
|
||||
verification.as_ptr()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn verification_hash_size() -> usize {
|
||||
VERIFICATION_HASH.lock().unwrap().len()
|
||||
}
|
||||
21
wasm/pow/sha256/Cargo.toml
Normal file
21
wasm/pow/sha256/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "sha256"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
sha2 = "0.11.0-pre.5"
|
||||
|
||||
anubis = { path = "../../anubis" }
|
||||
|
||||
[lints.clippy]
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
unwrap_used = "warn"
|
||||
uninlined_format_args = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
cognitive_complexity = "allow"
|
||||
1
wasm/pow/sha256/run.html
Normal file
1
wasm/pow/sha256/run.html
Normal file
@@ -0,0 +1 @@
|
||||
<script src="run.js" type="module"></script>
|
||||
105
wasm/pow/sha256/run.js
Normal file
105
wasm/pow/sha256/run.js
Normal file
@@ -0,0 +1,105 @@
|
||||
// Load and instantiate the .wasm file
|
||||
const response = await fetch("sha256.wasm");
|
||||
|
||||
const importObject = {
|
||||
anubis: {
|
||||
anubis_update_nonce: (nonce) => {
|
||||
console.log(`Received nonce update: ${nonce}`);
|
||||
// Your logic here
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const module = await WebAssembly.compileStreaming(response);
|
||||
const instance = await WebAssembly.instantiate(module, importObject);
|
||||
|
||||
// Get exports
|
||||
const {
|
||||
anubis_work,
|
||||
anubis_validate,
|
||||
data_ptr,
|
||||
result_hash_ptr,
|
||||
result_hash_size,
|
||||
verification_hash_ptr,
|
||||
verification_hash_size,
|
||||
set_data_length,
|
||||
memory
|
||||
} = instance.exports;
|
||||
|
||||
console.log(instance.exports);
|
||||
|
||||
function uint8ArrayToHex(arr) {
|
||||
return Array.from(arr)
|
||||
.map((c) => c.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
}
|
||||
|
||||
function hexToUint8Array(hexString) {
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Example usage:
|
||||
const data = hexToUint8Array("98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4");
|
||||
writeToBuffer(data);
|
||||
|
||||
// Call work function
|
||||
const t0 = Date.now();
|
||||
const nonce = anubis_work(16, 0, 1);
|
||||
const t1 = Date.now();
|
||||
|
||||
console.log(`Done! Took ${t1 - t0}ms, ${nonce} iterations`);
|
||||
|
||||
const challengeBuffer = readFromChallenge();
|
||||
|
||||
{
|
||||
const buffer = new Uint8Array(memory.buffer, verification_hash_ptr(), verification_hash_size());
|
||||
buffer.set(challengeBuffer);
|
||||
}
|
||||
|
||||
// Validate
|
||||
const isValid = anubis_validate(nonce, 10) === 1;
|
||||
console.log(isValid);
|
||||
|
||||
console.log(uint8ArrayToHex(readFromChallenge()));
|
||||
166
wasm/pow/sha256/src/lib.rs
Normal file
166
wasm/pow/sha256/src/lib.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
use anubis::{DATA_BUFFER, DATA_LENGTH, update_nonce};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::boxed::Box;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
/// SHA-256 hashes are 32 bytes (256 bits). These are stored in static buffers due to the
|
||||
/// fact that you cannot easily pass data from host space to WebAssembly space.
|
||||
pub static RESULT_HASH: LazyLock<Box<Mutex<[u8; 32]>>> =
|
||||
LazyLock::new(|| Box::new(Mutex::new([0; 32])));
|
||||
|
||||
pub static VERIFICATION_HASH: LazyLock<Box<Mutex<[u8; 32]>>> =
|
||||
LazyLock::new(|| Box::new(Mutex::new([0; 32])));
|
||||
|
||||
/// Core validation function. Compare each bit in the hash by progressively masking bits until
|
||||
/// some are found to not be matching.
|
||||
///
|
||||
/// There are probably more clever ways to do this, likely involving lookup tables or something
|
||||
/// really fun like that. However in my testing this lets us get up to 200 kilohashes per second
|
||||
/// on my Ryzen 7950x3D, up from about 50 kilohashes per second in JavaScript.
|
||||
fn validate(hash: &[u8], difficulty: u32) -> bool {
|
||||
let mut remaining = difficulty;
|
||||
for &byte in hash {
|
||||
// If we're out of bits to check, exit. This is all good.
|
||||
if remaining == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
// If there are more than 8 bits remaining, the entire byte should be a
|
||||
// zero. This fast-path compares the byte to 0 and if it matches, subtract
|
||||
// 8 bits.
|
||||
if remaining >= 8 {
|
||||
if byte != 0 {
|
||||
return false;
|
||||
}
|
||||
remaining -= 8;
|
||||
} else {
|
||||
// Otherwise mask off individual bits and check against them.
|
||||
let mask = 0xFF << (8 - remaining);
|
||||
if (byte & mask) != 0 {
|
||||
return false;
|
||||
}
|
||||
remaining = 0;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Computes hash for given nonce.
|
||||
///
|
||||
/// This differs from the JavaScript implementations by constructing the hash differently. In
|
||||
/// JavaScript implementations, the SHA-256 input is the result of appending the nonce as an
|
||||
/// integer to the hex-formatted challenge, eg:
|
||||
///
|
||||
/// sha256(`${challenge}${nonce}`);
|
||||
///
|
||||
/// This **does work**, however I think that this can be done a bit better by operating on the
|
||||
/// challenge bytes _directly_ and treating the nonce as a salt.
|
||||
///
|
||||
/// The nonce is also randomly encoded in either big or little endian depending on the last
|
||||
/// byte of the data buffer in an effort to make it more annoying to automate with GPUs.
|
||||
fn compute_hash(nonce: u32) -> [u8; 32] {
|
||||
let data = &DATA_BUFFER;
|
||||
let data_len = *DATA_LENGTH.lock().unwrap();
|
||||
let use_le = data[data_len - 1] >= 128;
|
||||
let data_slice = &data[..data_len];
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(data_slice);
|
||||
hasher.update(if use_le {
|
||||
nonce.to_le_bytes()
|
||||
} else {
|
||||
nonce.to_be_bytes()
|
||||
});
|
||||
hasher.finalize().into()
|
||||
}
|
||||
|
||||
/// This function is the main entrypoint for the Anubis proof of work implementation.
|
||||
///
|
||||
/// This expects `DATA_BUFFER` to be prepopulated with the challenge value as "raw bytes".
|
||||
/// The definition of what goes in the data buffer is an exercise for the implementor, but
|
||||
/// for SHA-256 we store the hash as "raw bytes". The data buffer is intentionally oversized
|
||||
/// so that the challenge value can be expanded in the future.
|
||||
///
|
||||
/// `difficulty` is the number of leading bits that must match `0` in order for the
|
||||
/// challenge to be successfully passed. This will be validated by the server.
|
||||
///
|
||||
/// `initial_nonce` is the initial value of the nonce (number used once). This nonce will be
|
||||
/// appended to the challenge value in order to find a hash matching the specified
|
||||
/// difficulty.
|
||||
///
|
||||
/// `iterand` (noun form of iterate) is the amount that the nonce should be increased by
|
||||
/// every iteration of the proof of work loop. This will vary by how many threads are
|
||||
/// running the proof-of-work check, and also functions as a thread ID. This prevents
|
||||
/// wasting CPU time retrying a hash+nonce pair that likely won't work.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn anubis_work(difficulty: u32, initial_nonce: u32, iterand: u32) -> u32 {
|
||||
let mut nonce = initial_nonce;
|
||||
let mut i = 0;
|
||||
|
||||
loop {
|
||||
i += 1;
|
||||
let hash = compute_hash(nonce);
|
||||
|
||||
if validate(&hash, difficulty) {
|
||||
// If the challenge worked, copy the bytes into `RESULT_HASH` so the runtime
|
||||
// can pick it up.
|
||||
let mut challenge = RESULT_HASH.lock().unwrap();
|
||||
challenge.copy_from_slice(&hash);
|
||||
return nonce;
|
||||
}
|
||||
|
||||
nonce = nonce.wrapping_add(iterand);
|
||||
|
||||
if i == 1024 {
|
||||
update_nonce(nonce);
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This function is called by the server in order to validate a proof-of-work challenge.
|
||||
/// This expects `DATA_BUFFER` to be set to the challenge value and `VERIFICATION_HASH` to
|
||||
/// be set to the "raw bytes" of the SHA-256 hash that the client calculated.
|
||||
///
|
||||
/// If everything is good, it returns true. Otherwise, it returns false.
|
||||
///
|
||||
/// XXX(Xe): this could probably return an error code for what step fails, but this is fine
|
||||
/// for now.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn anubis_validate(nonce: u32, difficulty: u32) -> bool {
|
||||
let computed = compute_hash(nonce);
|
||||
let valid = validate(&computed, difficulty);
|
||||
if !valid {
|
||||
return false;
|
||||
}
|
||||
|
||||
let verification = VERIFICATION_HASH.lock().unwrap();
|
||||
computed == *verification
|
||||
}
|
||||
|
||||
// These functions exist to give pointers and lengths to the runtime around the Anubis
|
||||
// checks, this allows JavaScript and Go to safely manipulate the memory layout that Rust
|
||||
// has statically allocated at compile time without having to assume how the Rust compiler
|
||||
// is going to lay it out.
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn result_hash_ptr() -> *const u8 {
|
||||
let challenge = RESULT_HASH.lock().unwrap();
|
||||
challenge.as_ptr()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn result_hash_size() -> usize {
|
||||
RESULT_HASH.lock().unwrap().len()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn verification_hash_ptr() -> *const u8 {
|
||||
let verification = VERIFICATION_HASH.lock().unwrap();
|
||||
verification.as_ptr()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn verification_hash_size() -> usize {
|
||||
VERIFICATION_HASH.lock().unwrap().len()
|
||||
}
|
||||
316
wasm/wasm.go
Normal file
316
wasm/wasm.go
Normal file
@@ -0,0 +1,316 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
func UpdateNonce(uint32) {}
|
||||
|
||||
var (
|
||||
validationTime = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "anubis_wasm_validation_time",
|
||||
Help: "The time taken for the validation function to run per checker (nanoseconds)",
|
||||
Buckets: prometheus.ExponentialBucketsRange(1, math.Pow(2, 31), 32),
|
||||
}, []string{"fname"})
|
||||
|
||||
validationCount = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "anubis_wasm_validation",
|
||||
Help: "The number of times the validation logic has been run and its success rate",
|
||||
}, []string{"fname", "success"})
|
||||
)
|
||||
|
||||
type Runner struct {
|
||||
r wazero.Runtime
|
||||
code wazero.CompiledModule
|
||||
fname string
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
func NewRunner(ctx context.Context, fname string, fin io.ReadCloser) (*Runner, error) {
|
||||
data, err := io.ReadAll(fin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wasm: can't read from fin: %w", err)
|
||||
}
|
||||
|
||||
var cfg wazero.RuntimeConfig
|
||||
|
||||
switch runtime.GOARCH {
|
||||
case "amd64":
|
||||
cfg = wazero.NewRuntimeConfigCompiler()
|
||||
default:
|
||||
cfg = wazero.NewRuntimeConfigInterpreter()
|
||||
}
|
||||
|
||||
cfg = cfg.WithMemoryLimitPages(512)
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, cfg)
|
||||
|
||||
_, err = r.NewHostModuleBuilder("anubis").
|
||||
NewFunctionBuilder().
|
||||
WithFunc(func(context.Context, uint32) {}).
|
||||
Export("anubis_update_nonce").
|
||||
Instantiate(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wasm: can't export anubis_update_nonce: %w", err)
|
||||
}
|
||||
|
||||
code, err := r.CompileModule(ctx, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wasm: can't compile module: %w", err)
|
||||
}
|
||||
|
||||
result := &Runner{
|
||||
r: r,
|
||||
code: code,
|
||||
fname: fname,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Runner) checkExports(module api.Module) error {
|
||||
funcs := []string{
|
||||
"anubis_work",
|
||||
"anubis_validate",
|
||||
"data_ptr",
|
||||
"set_data_length",
|
||||
"result_hash_ptr",
|
||||
"result_hash_size",
|
||||
"verification_hash_ptr",
|
||||
"verification_hash_size",
|
||||
}
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, fun := range funcs {
|
||||
if module.ExportedFunction(fun) == nil {
|
||||
errs = append(errs, fmt.Errorf("function %s is not defined", fun))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) anubisWork(ctx context.Context, module api.Module, difficulty, initialNonce, iterand uint32) (uint32, error) {
|
||||
results, err := module.ExportedFunction("anubis_work").Call(ctx, uint64(difficulty), uint64(initialNonce), uint64(iterand))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint32(results[0]), nil
|
||||
}
|
||||
|
||||
func (r *Runner) anubisValidate(ctx context.Context, module api.Module, nonce, difficulty uint32) (bool, error) {
|
||||
results, err := module.ExportedFunction("anubis_validate").Call(ctx, uint64(nonce), uint64(difficulty))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Rust booleans are 1 if true
|
||||
return results[0] == 1, nil
|
||||
}
|
||||
|
||||
func (r *Runner) dataPtr(ctx context.Context, module api.Module) (uint32, error) {
|
||||
results, err := module.ExportedFunction("data_ptr").Call(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint32(results[0]), nil
|
||||
}
|
||||
|
||||
func (r *Runner) setDataLength(ctx context.Context, module api.Module, length uint32) error {
|
||||
_, err := module.ExportedFunction("set_data_length").Call(ctx, uint64(length))
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Runner) resultHashPtr(ctx context.Context, module api.Module) (uint32, error) {
|
||||
results, err := module.ExportedFunction("result_hash_ptr").Call(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint32(results[0]), nil
|
||||
}
|
||||
|
||||
func (r *Runner) resultHashSize(ctx context.Context, module api.Module) (uint32, error) {
|
||||
results, err := module.ExportedFunction("result_hash_size").Call(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint32(results[0]), nil
|
||||
}
|
||||
|
||||
func (r *Runner) verificationHashPtr(ctx context.Context, module api.Module) (uint32, error) {
|
||||
results, err := module.ExportedFunction("verification_hash_ptr").Call(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint32(results[0]), nil
|
||||
}
|
||||
|
||||
func (r *Runner) verificationHashSize(ctx context.Context, module api.Module) (uint32, error) {
|
||||
results, err := module.ExportedFunction("verification_hash_size").Call(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return uint32(results[0]), nil
|
||||
}
|
||||
|
||||
func (r *Runner) writeData(ctx context.Context, module api.Module, data []byte) error {
|
||||
if len(data) > 4096 {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
length := uint32(len(data))
|
||||
|
||||
dataPtr, err := r.dataPtr(ctx, module)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't read data pointer: %w", err)
|
||||
}
|
||||
|
||||
if !module.Memory().Write(dataPtr, data) {
|
||||
return fmt.Errorf("[unexpected] can't write memory, is data out of range??")
|
||||
}
|
||||
|
||||
if err := r.setDataLength(ctx, module, length); err != nil {
|
||||
return fmt.Errorf("can't set data length: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runner) readResult(ctx context.Context, module api.Module) ([]byte, error) {
|
||||
length, err := r.resultHashSize(ctx, module)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get result hash size: %w", err)
|
||||
}
|
||||
|
||||
ptr, err := r.resultHashPtr(ctx, module)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't get result hash pointer: %w", err)
|
||||
}
|
||||
|
||||
buf, ok := module.Memory().Read(ptr, length)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("[unexpected] can't read from memory, is something out of range??")
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (r *Runner) run(ctx context.Context, data []byte, difficulty, initialNonce, iterand uint32) (uint32, []byte, api.Module, error) {
|
||||
mod, err := r.r.InstantiateModule(ctx, r.code, wazero.NewModuleConfig().WithName(r.fname))
|
||||
if err != nil {
|
||||
return 0, nil, nil, fmt.Errorf("can't instantiate module: %w", err)
|
||||
}
|
||||
|
||||
if err := r.checkExports(mod); err != nil {
|
||||
return 0, nil, nil, err
|
||||
}
|
||||
|
||||
if err := r.writeData(ctx, mod, data); err != nil {
|
||||
return 0, nil, nil, err
|
||||
}
|
||||
|
||||
nonce, err := r.anubisWork(ctx, mod, difficulty, initialNonce, iterand)
|
||||
if err != nil {
|
||||
return 0, nil, nil, fmt.Errorf("can't run work function: %w", err)
|
||||
}
|
||||
|
||||
hash, err := r.readResult(ctx, mod)
|
||||
if err != nil {
|
||||
return 0, nil, nil, fmt.Errorf("can't read result: %w", err)
|
||||
}
|
||||
|
||||
return nonce, hash, mod, nil
|
||||
}
|
||||
|
||||
func (r *Runner) Run(ctx context.Context, data []byte, difficulty, initialNonce, iterand uint32) (uint32, []byte, error) {
|
||||
nonce, hash, _, err := r.run(ctx, data, difficulty, initialNonce, iterand)
|
||||
if err != nil {
|
||||
return 0, nil, fmt.Errorf("can't run %s: %w", r.fname, err)
|
||||
}
|
||||
|
||||
return nonce, hash, nil
|
||||
}
|
||||
|
||||
func (r *Runner) verify(ctx context.Context, data, verify []byte, nonce, difficulty uint32) (bool, api.Module, error) {
|
||||
mod, err := r.r.InstantiateModule(ctx, r.code, wazero.NewModuleConfig().WithName(r.fname))
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("can't instantiate module: %w", err)
|
||||
}
|
||||
|
||||
if err := r.checkExports(mod); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if err := r.writeData(ctx, mod, data); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
if err := r.writeVerification(ctx, mod, verify); err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
ok, err := r.anubisValidate(ctx, mod, nonce, difficulty)
|
||||
if err != nil {
|
||||
return false, nil, fmt.Errorf("can't validate hash %x from challenge %x, nonce %d and difficulty %d: %w", verify, data, nonce, difficulty, err)
|
||||
}
|
||||
|
||||
return ok, mod, nil
|
||||
}
|
||||
|
||||
func (r *Runner) Verify(ctx context.Context, data, verify []byte, nonce, difficulty uint32) (bool, error) {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
t0 := time.Now()
|
||||
ok, _, err := r.verify(ctx, data, verify, nonce, difficulty)
|
||||
validationTime.WithLabelValues(r.fname).Observe(float64(time.Since(t0)))
|
||||
validationCount.WithLabelValues(r.fname, strconv.FormatBool(ok))
|
||||
return ok, err
|
||||
}
|
||||
|
||||
func (r *Runner) writeVerification(ctx context.Context, module api.Module, data []byte) error {
|
||||
length, err := r.verificationHashSize(ctx, module)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get verification hash size: %v", err)
|
||||
}
|
||||
|
||||
if length != uint32(len(data)) {
|
||||
return fmt.Errorf("data is too big, want %d bytes, got: %d", length, len(data))
|
||||
}
|
||||
|
||||
ptr, err := r.verificationHashPtr(ctx, module)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't get verification hash pointer: %v", err)
|
||||
}
|
||||
|
||||
if !module.Memory().Write(ptr, data) {
|
||||
return fmt.Errorf("[unexpected] can't write memory, is data out of range??")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
181
wasm/wasm_test.go
Normal file
181
wasm/wasm_test.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/TecharoHQ/anubis/web"
|
||||
)
|
||||
|
||||
func abiTest(t testing.TB, kind, fname string, difficulty uint32) {
|
||||
fin, err := web.Static.Open("static/wasm/" + kind + "/" + fname)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fin.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
t.Cleanup(cancel)
|
||||
|
||||
runner, err := NewRunner(ctx, fname, fin)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
fmt.Fprint(h, t.Name())
|
||||
data := h.Sum(nil)
|
||||
|
||||
nonce, hash, mod, err := runner.run(ctx, data, difficulty, 0, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := runner.writeVerification(ctx, mod, hash); err != nil {
|
||||
t.Fatalf("can't write verification: %v", err)
|
||||
}
|
||||
|
||||
ok, err := runner.anubisValidate(ctx, mod, nonce, difficulty)
|
||||
if err != nil {
|
||||
t.Fatalf("can't run validation: %v", err)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Error("validation failed")
|
||||
}
|
||||
|
||||
t.Logf("used %d pages of wasm memory (%d bytes)", mod.Memory().Size()/63356, mod.Memory().Size())
|
||||
}
|
||||
|
||||
func TestAlgos(t *testing.T) {
|
||||
fnames, err := fs.ReadDir(web.Static, "static/wasm/baseline")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, kind := range []string{"baseline", "simd128"} {
|
||||
for _, fname := range fnames {
|
||||
if filepath.Ext(fname.Name()) != ".wasm" {
|
||||
continue
|
||||
}
|
||||
t.Run(kind+"/"+fname.Name(), func(t *testing.T) {
|
||||
abiTest(t, kind, fname.Name(), 16)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func bench(b *testing.B, kind, fname string, difficulties []uint32) {
|
||||
b.Helper()
|
||||
|
||||
fin, err := web.Static.Open("static/wasm/" + kind + "/" + fname)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer fin.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
b.Cleanup(cancel)
|
||||
|
||||
runner, err := NewRunner(ctx, fname, fin)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
fmt.Fprint(h, "This is an example value that exists only to test the system.")
|
||||
data := h.Sum(nil)
|
||||
|
||||
_, _, mod, err := runner.run(ctx, data, 0, 0, 1)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
for _, difficulty := range difficulties {
|
||||
b.Run(fmt.Sprintf("difficulty/%d", difficulty), func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
difficulty := difficulty
|
||||
_, err := runner.anubisWork(ctx, mod, difficulty, 0, 1)
|
||||
if err != nil {
|
||||
b.Fatalf("can't do test work run: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSHA256(b *testing.B) {
|
||||
for _, kind := range []string{"baseline", "simd128"} {
|
||||
b.Run(kind, func(b *testing.B) {
|
||||
bench(b, kind, "sha256.wasm", []uint32{4, 6, 8, 10, 12, 14, 16})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHashX(b *testing.B) {
|
||||
for _, kind := range []string{"baseline", "simd128"} {
|
||||
b.Run(kind, func(b *testing.B) {
|
||||
bench(b, kind, "hashx.wasm", []uint32{4, 6, 8, 10, 12, 14, 16})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkValidate(b *testing.B) {
|
||||
fnames, err := fs.ReadDir(web.Static, "static/wasm/simd128")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
fmt.Fprint(h, "This is an example value that exists only to test the system.")
|
||||
data := h.Sum(nil)
|
||||
|
||||
for _, fname := range fnames {
|
||||
fname := fname.Name()
|
||||
|
||||
difficulty := uint32(1)
|
||||
|
||||
switch fname {
|
||||
case "sha256.wasm":
|
||||
difficulty = 16
|
||||
}
|
||||
|
||||
if filepath.Ext(fname) != ".wasm" {
|
||||
continue
|
||||
}
|
||||
|
||||
b.Run(fname, func(b *testing.B) {
|
||||
fin, err := web.Static.Open("static/wasm/simd128/" + fname)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer fin.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
b.Cleanup(cancel)
|
||||
|
||||
runner, err := NewRunner(ctx, fname, fin)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
nonce, hash, mod, err := runner.run(ctx, data, difficulty, 0, 1)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if err := runner.writeVerification(ctx, mod, hash); err != nil {
|
||||
b.Fatalf("can't write verification: %v", err)
|
||||
}
|
||||
|
||||
for b.Loop() {
|
||||
_, err := runner.anubisValidate(ctx, mod, nonce, difficulty)
|
||||
if err != nil {
|
||||
b.Fatalf("can't run validation: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,9 @@ 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.
|
||||
|
||||
Includes code written in Rust and transpiled from WebAssembly to JavaScript
|
||||
which is used under the terms of the licenses that comprise those crates.
|
||||
|
||||
@licend The above is the entire license notice
|
||||
for the JavaScript code in this page.
|
||||
*/'
|
||||
@@ -49,7 +52,7 @@ for file in js/**/*.ts js/**/*.mjs; do
|
||||
|
||||
mkdir -p "$(dirname "$out")"
|
||||
|
||||
esbuild "$file" --sourcemap --bundle --minify --outfile="$out" --banner:js="$LICENSE"
|
||||
esbuild "$file" --sourcemap --minify --bundle --outfile="$out" --banner:js="$LICENSE"
|
||||
gzip -f -k -n "$out"
|
||||
zstd -f -k --ultra -22 "$out"
|
||||
brotli -fZk "$out"
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import fast from "./fast";
|
||||
import wasm from "./wasm";
|
||||
|
||||
export default {
|
||||
fast: fast,
|
||||
slow: fast, // XXX(Xe): slow is deprecated, but keep this around in case anything goes bad
|
||||
|
||||
hashx: wasm,
|
||||
sha256: wasm,
|
||||
}
|
||||
23
web/js/algorithms/wasm/hashx.ts
Normal file
23
web/js/algorithms/wasm/hashx.ts
Normal file
@@ -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
|
||||
};
|
||||
118
web/js/algorithms/wasm/index.ts
Normal file
118
web/js/algorithms/wasm/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { u } from "../../../lib/xeact";
|
||||
import { simd } from "wasm-feature-detect";
|
||||
// import { compile } from '@haribala/wasm2js';
|
||||
|
||||
type ProgressCallback = (nonce: number | string) => void;
|
||||
|
||||
interface ProcessOptions {
|
||||
basePrefix: string;
|
||||
version: string;
|
||||
algorithm: string;
|
||||
}
|
||||
|
||||
const getHardwareConcurrency = () =>
|
||||
navigator.hardwareConcurrency !== undefined ? navigator.hardwareConcurrency : 1;
|
||||
|
||||
// https://stackoverflow.com/questions/47879864/how-can-i-check-if-a-browser-supports-webassembly
|
||||
const isWASMSupported = (() => {
|
||||
try {
|
||||
if (typeof WebAssembly === "object"
|
||||
&& typeof WebAssembly.instantiate === "function") {
|
||||
const module = new WebAssembly.Module(Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00));
|
||||
if (module instanceof WebAssembly.Module)
|
||||
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
})();
|
||||
|
||||
export default function process(
|
||||
options: ProcessOptions,
|
||||
data: string,
|
||||
difficulty: number = 5,
|
||||
signal: AbortSignal | null = null,
|
||||
progressCallback?: ProgressCallback,
|
||||
threads: number = Math.trunc(Math.max(getHardwareConcurrency() / 2, 1)),
|
||||
): Promise<string> {
|
||||
const { basePrefix, version, algorithm } = options;
|
||||
|
||||
let worker = "wasm";
|
||||
|
||||
if (!isWASMSupported) {
|
||||
worker = "wasm2js";
|
||||
}
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let wasmFeatures = "baseline";
|
||||
|
||||
if (await simd()) {
|
||||
wasmFeatures = "simd128";
|
||||
}
|
||||
|
||||
let module = undefined;
|
||||
|
||||
if (isWASMSupported) {
|
||||
module = await fetch(u(`${basePrefix}/.within.website/x/cmd/anubis/static/wasm/${wasmFeatures}/${algorithm}.wasm?cacheBuster=${version}`))
|
||||
.then(x => WebAssembly.compileStreaming(x));
|
||||
}
|
||||
|
||||
const webWorkerURL = `${basePrefix}/.within.website/x/cmd/anubis/static/js/worker/${worker}.mjs?cacheBuster=${version}`;
|
||||
|
||||
const workers: Worker[] = [];
|
||||
let settled = false;
|
||||
|
||||
const onAbort = () => {
|
||||
console.log("PoW aborted");
|
||||
cleanup();
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
workers.forEach((w) => w.terminate());
|
||||
if (signal != null) {
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
algorithm,
|
||||
module,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
23
web/js/algorithms/wasm/sha256.ts
Normal file
23
web/js/algorithms/wasm/sha256.ts
Normal file
@@ -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
|
||||
};
|
||||
@@ -171,7 +171,7 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
||||
try {
|
||||
const t0 = Date.now();
|
||||
const { hash, nonce } = await process(
|
||||
{ basePrefix, version: anubisVersion },
|
||||
{ basePrefix, version: anubisVersion, algorithm: rules.algorithm },
|
||||
challenge.randomData,
|
||||
rules.difficulty,
|
||||
null,
|
||||
|
||||
105
web/js/worker/wasm.ts
Normal file
105
web/js/worker/wasm.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
export interface Args {
|
||||
data: string;
|
||||
difficulty: number;
|
||||
nonce: number;
|
||||
threads: number;
|
||||
module: BufferSource;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
addEventListener("message", async (event: MessageEvent<Args>) => {
|
||||
const { data, difficulty, threads, module } = event.data;
|
||||
let { nonce } = event.data;
|
||||
|
||||
const importObject = {
|
||||
anubis: {
|
||||
anubis_update_nonce: (nonce: number) => postMessage(nonce),
|
||||
}
|
||||
};
|
||||
|
||||
if (nonce !== 0) {
|
||||
importObject.anubis.anubis_update_nonce = (_) => { };
|
||||
}
|
||||
|
||||
const obj = await WebAssembly.instantiate(module, importObject);
|
||||
|
||||
const {
|
||||
anubis_work,
|
||||
data_ptr,
|
||||
result_hash_ptr,
|
||||
result_hash_size,
|
||||
set_data_length,
|
||||
memory
|
||||
} = (obj as unknown as any).exports as unknown as AnubisExports;
|
||||
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,
|
||||
});
|
||||
});
|
||||
106
web/js/worker/wasm2js.ts
Normal file
106
web/js/worker/wasm2js.ts
Normal file
@@ -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,
|
||||
});
|
||||
});
|
||||
2
web/static/wasm/.gitignore
vendored
Normal file
2
web/static/wasm/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
Reference in New Issue
Block a user