/** * SPDX-License-Identifier: CC0-1.0 * * Copyright (C) 2025 Woj. Kosior */ /* #+begin_src manifest-jq .matches = [""] #+end_src */ if (false) { /* #+begin_src background-js */ blockJsOnCookie(/^techaro[.]lol-anubis-auth=/); } /* #+end_src */ /* Solve Anubis challenge. */ function byteArray2Hex(arrayLike) { const byte2Hex = byte => ("0" + byte.toString(16)).slice(-2); return [...arrayLike].map(byte2Hex).join(""); } const countingSchemes = { up: { startNum: "1000000000", delta: 1 }, down: { /* Server parses nonce as Int which might be a signed 32-bit type. */ startNum: "2147483647", delta: -1 } }; const ascii0 = 48; const ascii9 = 57; let finished = false; // let hashCount = 0; // var start_time = Date.now(); async function powLoop(randomData, hash0sNeeded, counting) { const {startNum, delta} = counting; const inputArray = new TextEncoder().encode(randomData + startNum); const lastIdx = inputArray.length - 1; const halfedByteIdx = Math.floor(hash0sNeeded / 2); const bytesChecked = Math.ceil(hash0sNeeded / 2); while (!finished) { const digest = new Uint8Array( await crypto.subtle.digest("SHA-256", inputArray) ); const savedByte = digest[halfedByteIdx]; digest[halfedByteIdx] >>= 4; let success = true; for (let idx= 0; idx < bytesChecked; idx++) success = success && digest[idx] === 0; if (!success) { let idx = lastIdx; let add = delta; while (true) { const byte = inputArray[idx] += add; if (byte > ascii9) { inputArray[idx] = ascii0; add = 1; } else if (byte < ascii0) { inputArray[idx] = ascii9; add = -1; } else { break; } idx--; } continue; } digest[halfedByteIdx] = savedByte; const suffixArray = inputArray.slice(-startNum.length); const suffix = parseInt(new TextDecoder().decode(suffixArray)); finished = true; return [digest, suffix]; } } async function solvePow(randomData, hash0sNeeded) { const startMillis = Date.now(); /* * Under both IceCat 140.3.1-gnu1 and ungoogled-chromium 140.0.7339.207-1 * (both from Guix) on a Core 2 CPU the best hash/s result was observed when * running 2 async loops. TODO: use workers to leverage hw concurrency. */ const [digest, suffix] = await Promise.any([ powLoop(randomData, hash0sNeeded, countingSchemes.up), powLoop(randomData, hash0sNeeded, countingSchemes.down) ]); return { response: byteArray2Hex(digest), nonce: suffix, elapsedTime: Date.now() - startMillis }; } // (async () => { // for (let i = 0; i < 15; i++) { // await new Promise(resolve => setTimeout(resolve, 1000)); // if (i < 5) { // start_time = Date.now(); // hashCount = 0; // continue; // } // const ms = Date.now() - start_time; // const hps = Math.round(hashCount / ms * 1000); // console.log(`${hashCount} hashes (${hps} hash/s)`); // } // stop = true; // })() async function solvePreact(randomData, waitTime) { const inputArray = new TextEncoder().encode(randomData); /* * The server checks if enough time has passed. Challenge's difficulty * corresponds to a unit of 80 or 95 milliseconds (depending on Anubis * version). */ const [digestBuffer, _] = await Promise.all([ crypto.subtle.digest("SHA-256", inputArray), new Promise(resolve => setTimeout(resolve, 95 * waitTime)) ]); return {result: byteArray2Hex(new Uint8Array(digestBuffer))}; } function err(what) { console.error(what); /* TODO: Display message on the page. */ } const challengeScript = document.getElementById("anubis_challenge"); const anubisPrefixScript = document.getElementById("anubis_base_prefix"); const anubisUrlScript = document.getElementById("anubis_public_url"); async function solve() { const unsupportedAnubisErr = what => err(`${what} Maybe the extension needs tweaking to ` + "support this version of Anubis?"); const badDataFormatErr = () => unsupportedAnubisErr("Challenge data format not understood."); let anubisPrefix = "", anubisUrl = null, challengeData; try { challengeData = JSON.parse(challengeScript.textContent); if (!anubisPrefixScript) { console.warn("No Anubis base prefix found in page, trying empty " + "string."); } else { anubisPrefix = JSON.parse(anubisPrefixScript.textContent); } if (anubisUrlScript) { const anubisUrlString = JSON.parse(anubisUrlScript.textContent); if (anubisUrlString) anubisUrl = new URL(anubisUrlString); } } catch(ex) { console.error(ex); return badDataFormatErr(); } challengeData = new Object(challengeData); if (challengeData.rules?.algorithm === "metarefresh") return; const randomData = (challengeData.challenge?.randomData || challengeData.challenge); const challengeId = challengeData.challenge?.id; const difficulty = challengeData.rules?.difficulty; if (typeof randomData !== "string" || typeof difficulty !== "number" || (challengeId && typeof challengeId !== "string") || typeof anubisPrefix !== "string") return badDataFormatErr(); if (!["fast", "preact", "slow"].includes(challengeData.rules.algorithm)) return unsupportedAnubisErr("Unsupported challenge algorithm."); const anubisizedLocation = () => "" + new URL( /[^:]+:[/]*[^/]+(.*)/.exec(window.location.href + "")[1], anubisUrl ); const isAnubisLocation = (anubisUrl && anubisizedLocation() === window.location.href); const redirectTarget = isAnubisLocation ? new URLSearchParams(window.location.search).get("redir") : window.location.href; if (!redirectTarget) return unsupportedAnubisErr("Failed to extract redirect target."); const solver = challengeData.rules.algorithm === "preact" ? solvePreact : solvePow; const solutionUrlParams = await solver(randomData, difficulty); const destination = new URL( anubisPrefix + "/.within.website/x/cmd/anubis/api/pass-challenge?", window.location.href ); destination.search = new URLSearchParams({ ...solutionUrlParams, ...(challengeId && {id: challengeId}), redir: redirectTarget }); window.location.href = destination; } if (challengeScript && !window.location.href.startsWith("file://")) solve();