mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-05 16:28:17 +00:00
Compare commits
7 Commits
Xe/fix-pur
...
Xe/allow-o
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d38767c50d | ||
|
|
f948406d2a | ||
|
|
8d1c80c244 | ||
|
|
feba16b0bb | ||
|
|
71b2561279 | ||
|
|
87b0ed9b97 | ||
|
|
d67f728a9b |
3
.github/actions/spelling/expect.txt
vendored
3
.github/actions/spelling/expect.txt
vendored
@@ -141,7 +141,6 @@ httpdebug
|
||||
Huawei
|
||||
hypertext
|
||||
iaskspider
|
||||
iaso
|
||||
iat
|
||||
ifm
|
||||
Imagesift
|
||||
@@ -233,7 +232,6 @@ promauto
|
||||
promhttp
|
||||
proofofwork
|
||||
publicsuffix
|
||||
purejs
|
||||
pwcmd
|
||||
pwuser
|
||||
qualys
|
||||
@@ -311,6 +309,7 @@ Varis
|
||||
Velen
|
||||
vendored
|
||||
vhosts
|
||||
videotest
|
||||
VKE
|
||||
Vultr
|
||||
waitloop
|
||||
|
||||
@@ -13,24 +13,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
<!-- This changes the project to: -->
|
||||
|
||||
- Downstream consumers can change the default [log/slog#Logger](https://pkg.go.dev/log/slog#Logger) instance that Anubis uses by setting `opts.Logger` to your slog instance of choice ([#864](https://github.com/TecharoHQ/anubis/issues/864)).
|
||||
- The [Thoth client](https://anubis.techaro.lol/docs/admin/thoth) is now public in the repo instead of being an internal package.
|
||||
- [Custom-AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client)'s default User-Agent has an increased weight by default ([#852](https://github.com/TecharoHQ/anubis/issues/852)).
|
||||
- The [`segments`](./admin/configuration/expressions.mdx#segments) function was added for splitting a path into its slash-separated segments.
|
||||
- When issuing a challenge, Anubis stores information about that challenge into the store. That stored information is later used to validate challenge responses. This works around nondeterminism in bot rules. ([#917](https://github.com/TecharoHQ/anubis/issues/917))
|
||||
- When parsing [Open Graph tags](./admin/configuration/open-graph.mdx), add any URLs found in the responses to a temporary "allow cache" so that social preview images work.
|
||||
- Proof of work solving has had a complete overhaul and rethink based on feedback from browser engine developers, frontend experts, and overall performance profiling.
|
||||
- One of the biggest sources of lag in Firefox has been eliminated: the use of WebCrypto. Now whenever Anubis detects the client is using Firefox (or Pale Moon), it will swap over to a pure-JS implementation of SHA-256 for speed.
|
||||
- Optimize the performance of the pure-JS Anubis solver.
|
||||
- Web Workers are stored as dedicated JavaScript files in `static/js/workers/*.mjs`.
|
||||
- Pave the way for non-SHA256 solver methods and eventually one that uses WebAssembly (or WebAssembly code compiled to JS for those that disable WebAssembly).
|
||||
- Legacy JavaScript code has been eliminated.
|
||||
- The contact email in the LibreJS header has been changed.
|
||||
- The hard dependency on WebCrypto has been removed, allowing a proof of work challenge to work over plain (unencrypted) HTTP.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- The "slow" frontend solver has been removed in order to reduce maintenance burden. Any existing uses of it will still work, but issue a warning upon startup asking administrators to upgrade to the "fast" frontend solver.
|
||||
|
||||
## v1.21.3: Minfilia Warde - Echo 3
|
||||
|
||||
|
||||
@@ -112,8 +112,4 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
||||
- https://bbs.archlinux32.org/
|
||||
- https://bugs.archlinux32.org/
|
||||
</details>
|
||||
- <details>
|
||||
<summary>HackLab.TO</summary>
|
||||
- https://hacklab.to/
|
||||
- https://knowledge.hacklab.to/
|
||||
</details>
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@ func InitSlog(level string) {
|
||||
slog.SetDefault(slog.New(h))
|
||||
}
|
||||
|
||||
func GetRequestLogger(base *slog.Logger, r *http.Request) *slog.Logger {
|
||||
return base.With(
|
||||
func GetRequestLogger(r *http.Request) *slog.Logger {
|
||||
return slog.With(
|
||||
"host", r.Host,
|
||||
"method", r.Method,
|
||||
"path", r.URL.Path,
|
||||
|
||||
@@ -75,7 +75,6 @@ type Server struct {
|
||||
hs512Secret []byte
|
||||
opts Options
|
||||
store store.Interface
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
|
||||
@@ -151,7 +150,7 @@ func (s *Server) maybeReverseProxyOrPage(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpStatusOnly bool) {
|
||||
lg := internal.GetRequestLogger(s.logger, r)
|
||||
lg := internal.GetRequestLogger(r)
|
||||
|
||||
if val, _ := s.store.Get(r.Context(), fmt.Sprintf("ogtags:allow:%s%s", r.Host, r.URL.String())); val != nil {
|
||||
lg.Debug("serving opengraph tag asset")
|
||||
@@ -165,7 +164,7 @@ func (s *Server) maybeReverseProxy(w http.ResponseWriter, r *http.Request, httpS
|
||||
cookiePath = strings.TrimSuffix(anubis.BasePrefix, "/") + "/"
|
||||
}
|
||||
|
||||
cr, rule, err := s.check(r, lg)
|
||||
cr, rule, err := s.check(r)
|
||||
if err != nil {
|
||||
lg.Error("check failed", "err", err)
|
||||
localizer := localization.GetLocalizer(r)
|
||||
@@ -281,7 +280,7 @@ func (s *Server) checkRules(w http.ResponseWriter, r *http.Request, cr policy.Ch
|
||||
return true
|
||||
default:
|
||||
s.ClearCookie(w, CookieOpts{Path: cookiePath, Host: r.Host})
|
||||
lg.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule)
|
||||
slog.Error("CONFIG ERROR: unknown rule", "rule", cr.Rule)
|
||||
s.respondWithError(w, r, fmt.Sprintf("%s \"maybeReverseProxy.Rules\"", localizer.T("internal_server_error")))
|
||||
return true
|
||||
}
|
||||
@@ -317,7 +316,7 @@ func (s *Server) handleDNSBL(w http.ResponseWriter, r *http.Request, ip string,
|
||||
}
|
||||
|
||||
func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
lg := internal.GetRequestLogger(s.logger, r)
|
||||
lg := internal.GetRequestLogger(r)
|
||||
localizer := localization.GetLocalizer(r)
|
||||
|
||||
redir := r.FormValue("redir")
|
||||
@@ -336,7 +335,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
r.URL.Path = redir
|
||||
|
||||
encoder := json.NewEncoder(w)
|
||||
cr, rule, err := s.check(r, lg)
|
||||
cr, rule, err := s.check(r)
|
||||
if err != nil {
|
||||
lg.Error("check failed", "err", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -388,7 +387,7 @@ func (s *Server) MakeChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
lg := internal.GetRequestLogger(s.logger, r)
|
||||
lg := internal.GetRequestLogger(r)
|
||||
localizer := localization.GetLocalizer(r)
|
||||
|
||||
redir := r.FormValue("redir")
|
||||
@@ -435,7 +434,7 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
cr, rule, err := s.check(r, lg)
|
||||
cr, rule, err := s.check(r)
|
||||
if err != nil {
|
||||
lg.Error("check failed", "err", err)
|
||||
s.respondWithError(w, r, fmt.Sprintf("%s \"passChallenge\"", localizer.T("internal_server_error")))
|
||||
@@ -516,7 +515,7 @@ func cr(name string, rule config.Rule, weight int) policy.CheckResult {
|
||||
}
|
||||
|
||||
// Check evaluates the list of rules, and returns the result
|
||||
func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *policy.Bot, error) {
|
||||
func (s *Server) check(r *http.Request) (policy.CheckResult, *policy.Bot, error) {
|
||||
host := r.Header.Get("X-Real-Ip")
|
||||
if host == "" {
|
||||
return decaymap.Zilch[policy.CheckResult](), nil, fmt.Errorf("[misconfiguration] X-Real-Ip header is not set")
|
||||
@@ -540,7 +539,7 @@ func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *p
|
||||
case config.RuleDeny, config.RuleAllow, config.RuleBenchmark, config.RuleChallenge:
|
||||
return cr("bot/"+b.Name, b.Action, weight), &b, nil
|
||||
case config.RuleWeigh:
|
||||
lg.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust)
|
||||
slog.Debug("adjusting weight", "name", b.Name, "delta", b.Weight.Adjust)
|
||||
weight += b.Weight.Adjust
|
||||
}
|
||||
}
|
||||
@@ -549,7 +548,7 @@ func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *p
|
||||
for _, t := range s.policy.Thresholds {
|
||||
result, _, err := t.Program.ContextEval(r.Context(), &policy.ThresholdRequest{Weight: weight})
|
||||
if err != nil {
|
||||
lg.Error("error when evaluating threshold expression", "expression", t.Expression.String(), "err", err)
|
||||
slog.Error("error when evaluating threshold expression", "expression", t.Expression.String(), "err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -343,7 +343,7 @@ func TestCheckDefaultDifficultyMatchesPolicy(t *testing.T) {
|
||||
|
||||
req.Header.Add("X-Real-Ip", "127.0.0.1")
|
||||
|
||||
cr, bot, err := s.check(req, s.logger)
|
||||
cr, bot, err := s.check(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -583,7 +583,7 @@ func TestCloudflareWorkersRule(t *testing.T) {
|
||||
req.Header.Add("X-Real-Ip", "127.0.0.1")
|
||||
req.Header.Add("Cf-Worker", "true")
|
||||
|
||||
cr, _, err := s.check(req, s.logger)
|
||||
cr, _, err := s.check(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -601,7 +601,7 @@ func TestCloudflareWorkersRule(t *testing.T) {
|
||||
|
||||
req.Header.Add("X-Real-Ip", "127.0.0.1")
|
||||
|
||||
cr, _, err := s.check(req, s.logger)
|
||||
cr, _, err := s.check(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ type Options struct {
|
||||
OpenGraph config.OpenGraph
|
||||
ServeRobotsTXT bool
|
||||
CookieSecure bool
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty int) (*policy.ParsedConfig, error) {
|
||||
@@ -90,12 +89,8 @@ func LoadPoliciesOrDefault(ctx context.Context, fname string, defaultDifficulty
|
||||
}
|
||||
|
||||
func New(opts Options) (*Server, error) {
|
||||
if opts.Logger == nil {
|
||||
opts.Logger = slog.With("subsystem", "anubis")
|
||||
}
|
||||
|
||||
if opts.ED25519PrivateKey == nil && opts.HS512Secret == nil {
|
||||
opts.Logger.Debug("opts.PrivateKey not set, generating a new one")
|
||||
slog.Debug("opts.PrivateKey not set, generating a new one")
|
||||
_, priv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("lib: can't generate private key: %v", err)
|
||||
@@ -113,7 +108,6 @@ func New(opts Options) (*Server, error) {
|
||||
opts: opts,
|
||||
OGTags: ogtags.NewOGTagCache(opts.Target, opts.Policy.OpenGraph, opts.Policy.Store),
|
||||
store: opts.Policy.Store,
|
||||
logger: opts.Logger,
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
@@ -120,7 +120,7 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
||||
return
|
||||
}
|
||||
|
||||
lg := internal.GetRequestLogger(s.logger, r)
|
||||
lg := internal.GetRequestLogger(r)
|
||||
|
||||
if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") && randomChance(64) {
|
||||
lg.Error("client was given a challenge but does not in fact support gzip compression")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"loading": "Hleður...",
|
||||
"why_am_i_seeing": "Af hverju er ég að sjá þetta?",
|
||||
"protected_by": "Verndað með",
|
||||
"protected_from": "Frá",
|
||||
"protected_from": "From",
|
||||
"made_with": "Gert í 🇨🇦 með ❤️",
|
||||
"mascot_design": "Lukkudýrið hannað af",
|
||||
"ai_companies_explanation": "Þú ert að sjá þetta vegna þess að kerfisstjóri þessa vefsvæðis hefur sett upp Anubis til að vernda vefþjóninn fyrir holskeflu beiðna frá svokölluðum gervigreindarfyrirtækjum sem samviskulaust eru að skrapa upplýsingar af vefsvæðum annarra. Þetta getur valdið og veldur töfum og truflunum á þessum vefsvæðum, sem aftur veldur því að efni þeirra verður öllum óaðgengilegt.",
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
"loading": "Yükleniyor...",
|
||||
"why_am_i_seeing": "Bunu neden görüyorum?",
|
||||
"protected_by": "Koruma sağlayan:",
|
||||
"protected_from": "Yapan:",
|
||||
"made_with": "🇨🇦’da ❤️ ile yapıldı",
|
||||
"protected_from": "From",
|
||||
"made_with": "🇨🇦'da ❤️ ile yapıldı",
|
||||
"mascot_design": "Maskot tasarımı:",
|
||||
"ai_companies_explanation": "Bunu görüyorsunuz; çünkü bu web sitesinin yöneticisi, yapay zekâ şirketlerinin web sitelerini agresif şekilde kazımasına karşı sunucuyu korumak için Anubis’i kurdu. Bu tarz kazımalar sitelerin erişilemez olmasına ve kesintilere neden olabiliyor.",
|
||||
"anubis_compromise": "Anubis bir uzlaşmadır. Anubis, istenmeyen e-postaları azaltmak için önerilen bir iş kanıtı sistemi olan Hashcash benzeri bir sistemi kullanır. Bireysel kullanımda bu ek yük göz ardı edilebilir olsa da, büyük ölçekli kazıyıcılarda birikerek kazımayı oldukça maliyetli hale getirir.",
|
||||
"hack_purpose": "Bu geçici bir çözümdür. Esas amacı, başsız tarayıcıları parmak iziyle tanımlamak için daha fazla zaman kazandırmak ve bu sayede meşru kullanıcıların bu zorluk sayfasını görmesini önlemektir.",
|
||||
"ai_companies_explanation": "Bunu görüyorsunuz çünkü bu web sitesinin yöneticisi, yapay zeka şirketlerinin web sitelerini agresif şekilde kazımasına karşı sunucuyu korumak için Anubis'i kurdu. Bu tarz kazımalar sitelerin erişilemez olmasına ve kesintilere neden olabiliyor.",
|
||||
"anubis_compromise": "Anubis bir uzlaşmadır. Anubis, spam e-postaları azaltmak için önerilen bir iş kanıtı sistemi olan Hashcash benzeri bir sistemi kullanır. Bireysel kullanımda bu ek yük göz ardı edilebilir olsa da, büyük ölçekli kazıyıcılarda birikerek kazımayı oldukça maliyetli hale getirir.",
|
||||
"hack_purpose": "Bu geçici bir çözümdür. Esas amacı, başsız tarayıcıları parmak iziyle tanımlamak için daha fazla zaman kazandırmak, ve bu sayede meşru kullanıcıların bu zorluk sayfasını görmesini önlemektir.",
|
||||
"jshelter_note": "Lütfen dikkat: Anubis, JShelter gibi eklentilerin devre dışı bıraktığı modern JavaScript özelliklerini gerektirir. Lütfen bu alan adı için JShelter veya benzeri eklentileri devre dışı bırakın.",
|
||||
"version_info": "Bu web sitesi şu Anubis sürümünü çalıştırıyor:",
|
||||
"try_again": "Tekrar dene",
|
||||
"go_home": "Ana sayfaya dön",
|
||||
"contact_webmaster": "veya engellenmemeniz gerektiğini düşünüyorsanız lütfen şu adrese e-posta gönderin:",
|
||||
"contact_webmaster": "ya da engellenmemeniz gerektiğini düşünüyorsanız, lütfen şu adrese e-posta gönderin:",
|
||||
"connection_security": "Bağlantınızın güvenliği sağlanırken lütfen bekleyin.",
|
||||
"javascript_required": "Ne yazık ki bu aşamayı geçebilmek için JavaScript’i etkinleştirmeniz gerekiyor. Bunun nedeni, yapay zekâ şirketlerinin web barındırma konusundaki sosyal sözleşmeyi değiştirmiş olmasıdır. JavaScript’siz bir çözüm geliştirilmektedir.",
|
||||
"javascript_required": "Ne yazık ki bu aşamayı geçebilmek için JavaScript’i etkinleştirmeniz gerekiyor. Bunun nedeni, yapay zeka şirketlerinin web barındırma konusundaki sosyal sözleşmeyi değiştirmiş olmasıdır. JavaScript’siz bir çözüm geliştirilmektedir.",
|
||||
"benchmark_requires_js": "Kıyaslama aracının çalıştırılması için JavaScript’in etkin olması gereklidir.",
|
||||
"difficulty": "Zorluk:",
|
||||
"algorithm": "Algoritma:",
|
||||
@@ -25,19 +25,19 @@
|
||||
"iters_a": "Tekrar A",
|
||||
"time_b": "Süre B",
|
||||
"iters_b": "Tekrar B",
|
||||
"static_check_endpoint": "Bu sadece, ters vekil sunucunuzun kullanması için bir kontrol adresidir.",
|
||||
"static_check_endpoint": "Bu sadece ters proxy'nizin kullanması için bir kontrol adresidir.",
|
||||
"authorization_required": "Yetkilendirme gerekli",
|
||||
"cookies_disabled": "Tarayıcınız çerezleri devre dışı bırakacak şekilde yapılandırılmış. Anubis, gerçek bir kullanıcı olduğunuzu doğrulamak için çerezlere ihtiyaç duyar. Lütfen bu alan adı için çerezleri etkinleştirin.",
|
||||
"access_denied": "Erişim reddedildi: Hata kodu",
|
||||
"access_denied": "Erişim Reddedildi: hata kodu",
|
||||
"dronebl_entry": "DroneBL bir giriş bildirdi",
|
||||
"see_dronebl_lookup": "bakınız",
|
||||
"internal_server_error": "Sunucu Hatası: Yönetici Anubis’i yanlış yapılandırmış. Lütfen yöneticinizle iletişime geçin ve şu civardaki kayıtlara bakmasını isteyin:",
|
||||
"internal_server_error": "Sunucu Hatası: Yönetici Anubis'i yanlış yapılandırmış. Lütfen yöneticinizle iletişime geçin ve şunun civarındaki kayıtlara bakmasını isteyin:",
|
||||
"invalid_redirect": "Geçersiz yönlendirme",
|
||||
"redirect_not_parseable": "Yönlendirme URL’si çözümlenemiyor",
|
||||
"redirect_not_parseable": "Yönlendirme URL'si çözümlenemiyor",
|
||||
"redirect_domain_not_allowed": "Yönlendirme alan adına izin verilmiyor",
|
||||
"failed_to_sign_jwt": "JWT imzalanamadı",
|
||||
"invalid_invocation": "Geçersiz MakeChallenge çağrısı",
|
||||
"client_error_browser": "İstemci hatası: Lütfen tarayıcınızın güncel olduğundan emin olun ve daha sonra tekrar deneyin.",
|
||||
"client_error_browser": "İstemci Hatası: Lütfen tarayıcınızın güncel olduğundan emin olun ve daha sonra tekrar deneyin.",
|
||||
"oh_noes": "Ah hayır!",
|
||||
"benchmarking_anubis": "Anubis kıyaslanıyor!",
|
||||
"you_are_not_a_bot": "Bot değilsiniz!",
|
||||
@@ -61,4 +61,4 @@
|
||||
"js_finished_reading": "Okumayı bitirdim, devam et →",
|
||||
"js_calculation_error": "Hesaplama hatası!",
|
||||
"js_calculation_error_msg": "Zorluk hesaplaması başarısız oldu:"
|
||||
}
|
||||
}
|
||||
@@ -149,10 +149,6 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
|
||||
if parsedBot.Challenge.Algorithm == "" {
|
||||
parsedBot.Challenge.Algorithm = config.DefaultAlgorithm
|
||||
}
|
||||
|
||||
if parsedBot.Challenge.Algorithm == "slow" {
|
||||
slog.Warn("use of deprecated algorithm \"slow\" detected, please update this to \"fast\" when possible", "name", parsedBot.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if b.Weight != nil {
|
||||
@@ -167,10 +163,6 @@ func ParseConfig(ctx context.Context, fin io.Reader, fname string, defaultDiffic
|
||||
}
|
||||
|
||||
for _, t := range c.Thresholds {
|
||||
if t.Challenge != nil && t.Challenge.Algorithm == "slow" {
|
||||
slog.Warn("use of deprecated algorithm \"slow\" detected, please update this to \"fast\" when possible", "name", t.Name)
|
||||
}
|
||||
|
||||
if t.Name == "legacy-anubis-behaviour" && t.Expression.String() == "true" {
|
||||
if !warnedAboutThresholds.Load() {
|
||||
slog.Warn("configuration file does not contain thresholds, see docs for details on how to upgrade", "fname", fname, "docs_url", "https://anubis.techaro.lol/docs/admin/configuration/thresholds/")
|
||||
|
||||
97
package-lock.json
generated
97
package-lock.json
generated
@@ -8,9 +8,6 @@
|
||||
"name": "@techaro/anubis",
|
||||
"version": "1.21.3",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-js": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cssnano": "^7.1.0",
|
||||
"cssnano-preset-advanced": "^7.0.8",
|
||||
@@ -22,44 +19,6 @@
|
||||
"postcss-url": "^10.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-crypto/sha256-js": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz",
|
||||
"integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/util": "^5.2.0",
|
||||
"@aws-sdk/types": "^3.222.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-crypto/util": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz",
|
||||
"integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/types": "^3.222.0",
|
||||
"@smithy/util-utf8": "^2.0.0",
|
||||
"tslib": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/types": {
|
||||
"version": "3.840.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz",
|
||||
"integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/types": "^4.3.1",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.8",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
|
||||
@@ -502,56 +461,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/is-array-buffer": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
|
||||
"integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/types": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz",
|
||||
"integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/util-buffer-from": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
|
||||
"integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/is-array-buffer": "^2.2.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@smithy/util-utf8": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
|
||||
"integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@smithy/util-buffer-from": "^2.2.0",
|
||||
"tslib": "^2.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
@@ -2606,12 +2515,6 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
|
||||
@@ -26,8 +26,5 @@
|
||||
"postcss-import": "^16.1.1",
|
||||
"postcss-import-url": "^7.2.0",
|
||||
"postcss-url": "^10.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-js": "^5.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
17
web/build.sh
17
web/build.sh
@@ -8,7 +8,7 @@ LICENSE='/*
|
||||
@licstart The following is the entire license notice for the
|
||||
JavaScript code in this page.
|
||||
|
||||
Copyright (c) 2025 Xe Iaso <xe.iaso@techaro.lol>
|
||||
Copyright (c) 2025 Xe Iaso <me@xeiaso.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -28,9 +28,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Includes code from https://github.com/aws/aws-sdk-js-crypto-helpers which is
|
||||
used under the terms of the Apache 2 license.
|
||||
|
||||
@licend The above is the entire license notice
|
||||
for the JavaScript code in this page.
|
||||
*/'
|
||||
@@ -39,9 +36,9 @@ for the JavaScript code in this page.
|
||||
mkdir -p static/locales
|
||||
cp ../lib/localization/locales/*.json static/locales/
|
||||
|
||||
for file in js/*.mjs js/worker/*.mjs; do
|
||||
esbuild "${file}" --sourcemap --bundle --minify --outfile=static/"${file}" --banner:js="${LICENSE}"
|
||||
gzip -f -k -n static/${file}
|
||||
zstd -f -k --ultra -22 static/${file}
|
||||
brotli -fZk static/${file}
|
||||
done
|
||||
esbuild js/main.mjs --sourcemap --bundle --minify --outfile=static/js/main.mjs "--banner:js=${LICENSE}"
|
||||
gzip -f -k -n static/js/main.mjs
|
||||
zstd -f -k --ultra -22 static/js/main.mjs
|
||||
brotli -fZk static/js/main.mjs
|
||||
|
||||
esbuild js/bench.mjs --sourcemap --bundle --minify --outfile=static/js/bench.mjs
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
export default function process(
|
||||
{ basePrefix, version },
|
||||
data,
|
||||
difficulty = 5,
|
||||
signal = null,
|
||||
progressCallback = null,
|
||||
threads = Math.max(navigator.hardwareConcurrency / 2, 1),
|
||||
) {
|
||||
console.debug("fast algo");
|
||||
|
||||
let workerMethod = window.crypto !== undefined ? "webcrypto" : "purejs";
|
||||
|
||||
if (navigator.userAgent.includes("Firefox") || navigator.userAgent.includes("Goanna")) {
|
||||
console.log("Firefox detected, using pure-JS fallback");
|
||||
workerMethod = "purejs";
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let webWorkerURL = `${basePrefix}/.within.website/x/cmd/anubis/static/js/worker/sha256-${workerMethod}.mjs?cacheBuster=${version}`;
|
||||
|
||||
console.log(webWorkerURL);
|
||||
|
||||
const workers = [];
|
||||
let settled = false;
|
||||
|
||||
const cleanup = () => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
workers.forEach((w) => w.terminate());
|
||||
if (signal != null) {
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
}
|
||||
};
|
||||
|
||||
const onAbort = () => {
|
||||
console.log("PoW aborted");
|
||||
cleanup();
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
};
|
||||
|
||||
if (signal != null) {
|
||||
if (signal.aborted) {
|
||||
return onAbort();
|
||||
}
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
}
|
||||
|
||||
for (let i = 0; i < threads; i++) {
|
||||
let worker = new Worker(webWorkerURL);
|
||||
|
||||
worker.onmessage = (event) => {
|
||||
if (typeof event.data === "number") {
|
||||
progressCallback?.(event.data);
|
||||
} else {
|
||||
cleanup();
|
||||
resolve(event.data);
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (event) => {
|
||||
cleanup();
|
||||
reject(event);
|
||||
};
|
||||
|
||||
worker.postMessage({
|
||||
data,
|
||||
difficulty,
|
||||
nonce: i,
|
||||
threads,
|
||||
});
|
||||
|
||||
workers.push(worker);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import fast from "./fast.mjs";
|
||||
|
||||
export default {
|
||||
fast: fast,
|
||||
slow: fast, // XXX(Xe): slow is deprecated, but keep this around in case anything goes bad
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import algorithms from "./algorithms/index.mjs";
|
||||
import processFast from "./proof-of-work.mjs";
|
||||
import processSlow from "./proof-of-work-slow.mjs";
|
||||
|
||||
const defaultDifficulty = 4;
|
||||
const algorithms = {
|
||||
fast: processFast,
|
||||
slow: processSlow,
|
||||
};
|
||||
|
||||
const status = document.getElementById("status");
|
||||
const difficultyInput = document.getElementById("difficulty-input");
|
||||
@@ -37,7 +42,7 @@ const benchmarkTrial = async (stats, difficulty, algorithm, signal) => {
|
||||
.join("");
|
||||
|
||||
const t0 = performance.now();
|
||||
const { hash, nonce } = await process({ basePrefix: "/", version: "devel" }, challenge, Number(difficulty), signal);
|
||||
const { hash, nonce } = await process(challenge, Number(difficulty), signal);
|
||||
const t1 = performance.now();
|
||||
console.log({ hash, nonce });
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import algorithms from "./algorithms/index.mjs";
|
||||
import processFast from "./proof-of-work.mjs";
|
||||
import processSlow from "./proof-of-work-slow.mjs";
|
||||
|
||||
const algorithms = {
|
||||
fast: processFast,
|
||||
slow: processSlow,
|
||||
};
|
||||
|
||||
// from Xeact
|
||||
const u = (url = "", params = {}) => {
|
||||
@@ -69,6 +75,11 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
||||
await initTranslations();
|
||||
|
||||
const dependencies = [
|
||||
{
|
||||
name: "WebCrypto",
|
||||
msg: t('web_crypto_error'),
|
||||
value: window.crypto,
|
||||
},
|
||||
{
|
||||
name: "Web Workers",
|
||||
msg: t('web_workers_error'),
|
||||
@@ -108,6 +119,15 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
||||
progress.style.display = "none";
|
||||
};
|
||||
|
||||
if (!window.isSecureContext) {
|
||||
ohNoes({
|
||||
titleMsg: t('context_not_secure'),
|
||||
statusMsg: t('context_not_secure_msg'),
|
||||
imageSrc: imageURL("reject", anubisVersion, basePrefix),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
status.innerHTML = t('calculating');
|
||||
|
||||
for (const { value, name, msg } of dependencies) {
|
||||
@@ -151,7 +171,6 @@ const t = (key) => translations[`js_${key}`] || translations[key] || key;
|
||||
try {
|
||||
const t0 = Date.now();
|
||||
const { hash, nonce } = await process(
|
||||
{ basePrefix, version: anubisVersion },
|
||||
challenge,
|
||||
rules.difficulty,
|
||||
null,
|
||||
|
||||
99
web/js/proof-of-work-slow.mjs
Normal file
99
web/js/proof-of-work-slow.mjs
Normal file
@@ -0,0 +1,99 @@
|
||||
// https://dev.to/ratmd/simple-proof-of-work-in-javascript-3kgm
|
||||
|
||||
export default function process(
|
||||
data,
|
||||
difficulty = 5,
|
||||
signal = null,
|
||||
progressCallback = null,
|
||||
_threads = 1,
|
||||
) {
|
||||
console.debug("slow algo");
|
||||
return new Promise((resolve, reject) => {
|
||||
let webWorkerURL = URL.createObjectURL(
|
||||
new Blob(["(", processTask(), ")()"], { type: "application/javascript" }),
|
||||
);
|
||||
|
||||
let worker = new Worker(webWorkerURL);
|
||||
let settled = false;
|
||||
|
||||
const cleanup = () => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
worker.terminate();
|
||||
if (signal != null) {
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
}
|
||||
URL.revokeObjectURL(webWorkerURL);
|
||||
};
|
||||
|
||||
const onAbort = () => {
|
||||
console.log("PoW aborted");
|
||||
cleanup();
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
};
|
||||
|
||||
if (signal != null) {
|
||||
if (signal.aborted) {
|
||||
return onAbort();
|
||||
}
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
}
|
||||
|
||||
worker.onmessage = (event) => {
|
||||
if (typeof event.data === "number") {
|
||||
progressCallback?.(event.data);
|
||||
} else {
|
||||
cleanup();
|
||||
resolve(event.data);
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (event) => {
|
||||
cleanup();
|
||||
reject(event);
|
||||
};
|
||||
|
||||
worker.postMessage({
|
||||
data,
|
||||
difficulty,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function processTask() {
|
||||
return function () {
|
||||
const sha256 = (text) => {
|
||||
const encoded = new TextEncoder().encode(text);
|
||||
return crypto.subtle.digest("SHA-256", encoded.buffer).then((result) =>
|
||||
Array.from(new Uint8Array(result))
|
||||
.map((c) => c.toString(16).padStart(2, "0"))
|
||||
.join(""),
|
||||
);
|
||||
};
|
||||
|
||||
addEventListener("message", async (event) => {
|
||||
let data = event.data.data;
|
||||
let difficulty = event.data.difficulty;
|
||||
|
||||
let hash;
|
||||
let nonce = 0;
|
||||
do {
|
||||
if ((nonce & 1023) === 0) {
|
||||
postMessage(nonce);
|
||||
}
|
||||
hash = await sha256(data + nonce++);
|
||||
} while (
|
||||
hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")
|
||||
);
|
||||
|
||||
nonce -= 1; // last nonce was post-incremented
|
||||
|
||||
postMessage({
|
||||
hash,
|
||||
data,
|
||||
difficulty,
|
||||
nonce,
|
||||
});
|
||||
});
|
||||
}.toString();
|
||||
}
|
||||
137
web/js/proof-of-work.mjs
Normal file
137
web/js/proof-of-work.mjs
Normal file
@@ -0,0 +1,137 @@
|
||||
export default function process(
|
||||
data,
|
||||
difficulty = 5,
|
||||
signal = null,
|
||||
progressCallback = null,
|
||||
threads = Math.max(navigator.hardwareConcurrency / 2, 1),
|
||||
) {
|
||||
console.debug("fast algo");
|
||||
return new Promise((resolve, reject) => {
|
||||
let webWorkerURL = URL.createObjectURL(
|
||||
new Blob(["(", processTask(), ")()"], { type: "application/javascript" }),
|
||||
);
|
||||
|
||||
const workers = [];
|
||||
let settled = false;
|
||||
|
||||
const cleanup = () => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
workers.forEach((w) => w.terminate());
|
||||
if (signal != null) {
|
||||
signal.removeEventListener("abort", onAbort);
|
||||
}
|
||||
URL.revokeObjectURL(webWorkerURL);
|
||||
};
|
||||
|
||||
const onAbort = () => {
|
||||
console.log("PoW aborted");
|
||||
cleanup();
|
||||
reject(new DOMException("Aborted", "AbortError"));
|
||||
};
|
||||
|
||||
if (signal != null) {
|
||||
if (signal.aborted) {
|
||||
return onAbort();
|
||||
}
|
||||
signal.addEventListener("abort", onAbort, { once: true });
|
||||
}
|
||||
|
||||
for (let i = 0; i < threads; i++) {
|
||||
let worker = new Worker(webWorkerURL);
|
||||
|
||||
worker.onmessage = (event) => {
|
||||
if (typeof event.data === "number") {
|
||||
progressCallback?.(event.data);
|
||||
} else {
|
||||
cleanup();
|
||||
resolve(event.data);
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (event) => {
|
||||
cleanup();
|
||||
reject(event);
|
||||
};
|
||||
|
||||
worker.postMessage({
|
||||
data,
|
||||
difficulty,
|
||||
nonce: i,
|
||||
threads,
|
||||
});
|
||||
|
||||
workers.push(worker);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function processTask() {
|
||||
return function () {
|
||||
const sha256 = (text) => {
|
||||
const encoded = new TextEncoder().encode(text);
|
||||
return crypto.subtle.digest("SHA-256", encoded.buffer);
|
||||
};
|
||||
|
||||
function uint8ArrayToHexString(arr) {
|
||||
return Array.from(arr)
|
||||
.map((c) => c.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
}
|
||||
|
||||
addEventListener("message", async (event) => {
|
||||
let data = event.data.data;
|
||||
let difficulty = event.data.difficulty;
|
||||
let hash;
|
||||
let nonce = event.data.nonce;
|
||||
let threads = event.data.threads;
|
||||
|
||||
const threadId = nonce;
|
||||
let localIterationCount = 0;
|
||||
|
||||
while (true) {
|
||||
const currentHash = await sha256(data + nonce);
|
||||
const thisHash = new Uint8Array(currentHash);
|
||||
let valid = true;
|
||||
|
||||
for (let j = 0; j < difficulty; j++) {
|
||||
const byteIndex = Math.floor(j / 2); // which byte we are looking at
|
||||
const nibbleIndex = j % 2; // which nibble in the byte we are looking at (0 is high, 1 is low)
|
||||
|
||||
let nibble =
|
||||
(thisHash[byteIndex] >> (nibbleIndex === 0 ? 4 : 0)) & 0x0f; // Get the nibble
|
||||
|
||||
if (nibble !== 0) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid) {
|
||||
hash = uint8ArrayToHexString(thisHash);
|
||||
console.log(hash);
|
||||
break;
|
||||
}
|
||||
|
||||
nonce += threads;
|
||||
|
||||
// send a progress update every 1024 iterations so that the user can be informed of
|
||||
// the state of the challenge.
|
||||
if (threadId == 0 && localIterationCount === 1024) {
|
||||
postMessage(nonce);
|
||||
localIterationCount = 0;
|
||||
}
|
||||
localIterationCount++;
|
||||
}
|
||||
|
||||
postMessage({
|
||||
hash,
|
||||
data,
|
||||
difficulty,
|
||||
nonce,
|
||||
});
|
||||
});
|
||||
}.toString();
|
||||
}
|
||||
16
web/js/video.mjs
Normal file
16
web/js/video.mjs
Normal file
@@ -0,0 +1,16 @@
|
||||
const videoElement = `<video id="videotest" width="0" height="0" src="/.within.website/x/cmd/anubis/static/testdata/black.mp4"></video>`;
|
||||
|
||||
export const testVideo = async (testarea) => {
|
||||
testarea.innerHTML = videoElement;
|
||||
return await new Promise((resolve) => {
|
||||
const video = document.getElementById("videotest");
|
||||
video.oncanplay = () => {
|
||||
testarea.style.display = "none";
|
||||
resolve(true);
|
||||
};
|
||||
video.onerror = () => {
|
||||
testarea.style.display = "none";
|
||||
resolve(false);
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Sha256 } from '@aws-crypto/sha256-js';
|
||||
|
||||
const calculateSHA256 = (text) => {
|
||||
const hash = new Sha256();
|
||||
hash.update(text);
|
||||
return hash.digest();
|
||||
};
|
||||
|
||||
function toHexString(arr) {
|
||||
return Array.from(arr)
|
||||
.map((c) => c.toString(16).padStart(2, "0"))
|
||||
.join("");
|
||||
}
|
||||
|
||||
addEventListener('message', async ({ data: eventData }) => {
|
||||
const { data, difficulty, threads } = eventData;
|
||||
let nonce = eventData.nonce;
|
||||
const isMainThread = nonce === 0;
|
||||
let iterations = 0;
|
||||
|
||||
const requiredZeroBytes = Math.floor(difficulty / 2);
|
||||
const isDifficultyOdd = difficulty % 2 !== 0;
|
||||
|
||||
for (; ;) {
|
||||
const hashBuffer = await calculateSHA256(data + nonce);
|
||||
const hashArray = new Uint8Array(hashBuffer);
|
||||
|
||||
let isValid = true;
|
||||
for (let i = 0; i < requiredZeroBytes; i++) {
|
||||
if (hashArray[i] !== 0) {
|
||||
isValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isValid && isDifficultyOdd) {
|
||||
if ((hashArray[requiredZeroBytes] >> 4) !== 0) {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
const finalHash = toHexString(hashArray);
|
||||
postMessage({
|
||||
hash: finalHash,
|
||||
data,
|
||||
difficulty,
|
||||
nonce,
|
||||
});
|
||||
return; // Exit worker
|
||||
}
|
||||
|
||||
nonce += threads;
|
||||
iterations++;
|
||||
|
||||
// Send a progress update from the main thread every 1024 iterations.
|
||||
if (isMainThread && (iterations & 1023) === 0) {
|
||||
postMessage(nonce);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,57 +0,0 @@
|
||||
const encoder = new TextEncoder();
|
||||
const calculateSHA256 = async (input) => {
|
||||
const data = encoder.encode(input);
|
||||
return await crypto.subtle.digest("SHA-256", data);
|
||||
};
|
||||
|
||||
const toHexString = (byteArray) => {
|
||||
return byteArray.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
|
||||
};
|
||||
|
||||
addEventListener("message", async ({ data: eventData }) => {
|
||||
const { data, difficulty, threads } = eventData;
|
||||
let nonce = eventData.nonce;
|
||||
const isMainThread = nonce === 0;
|
||||
let iterations = 0;
|
||||
|
||||
const requiredZeroBytes = Math.floor(difficulty / 2);
|
||||
const isDifficultyOdd = difficulty % 2 !== 0;
|
||||
|
||||
for (; ;) {
|
||||
const hashBuffer = await calculateSHA256(data + nonce);
|
||||
const hashArray = new Uint8Array(hashBuffer);
|
||||
|
||||
let isValid = true;
|
||||
for (let i = 0; i < requiredZeroBytes; i++) {
|
||||
if (hashArray[i] !== 0) {
|
||||
isValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isValid && isDifficultyOdd) {
|
||||
if ((hashArray[requiredZeroBytes] >> 4) !== 0) {
|
||||
isValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
const finalHash = toHexString(hashArray);
|
||||
postMessage({
|
||||
hash: finalHash,
|
||||
data,
|
||||
difficulty,
|
||||
nonce,
|
||||
});
|
||||
return; // Exit worker
|
||||
}
|
||||
|
||||
nonce += threads;
|
||||
iterations++;
|
||||
|
||||
// Send a progress update from the main thread every 1024 iterations.
|
||||
if (isMainThread && (iterations & 1023) === 0) {
|
||||
postMessage(nonce);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user