[chore] more NoLLaMas proof-of-work tweaking (#4096)

- replaces the sha256 calculation with an alternative implementation that seems to use more uniform time-taken across different platforms
- goes back to the simpler difficulty calculation without a "partial" difficulty level

Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4096
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
This commit is contained in:
kim
2025-05-03 16:45:25 +00:00
committed by kim
parent c15002d76e
commit 211192c482
7 changed files with 184 additions and 113 deletions

View File

@@ -1,3 +1,4 @@
node_modules
prism.js
prism.css
prism.css
nollamasworker/sha256.js

View File

@@ -19,7 +19,7 @@
const explanation = "Your browser is currently solving a proof-of-work challenge designed to deter \"ai\" scrapers. This should take no more than a few seconds...";
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener("DOMContentLoaded", function() {
// Get the nollamas section container.
const nollamas = document.querySelector(".nollamas");
@@ -44,20 +44,17 @@ document.addEventListener('DOMContentLoaded', function() {
// Read the challenge and difficulty from
// data attributes on the nollamas section.
const challenge = nollamas.dataset.nollamasChallenge;
const difficulty1 = nollamas.dataset.nollamasDifficulty1;
const difficulty2 = nollamas.dataset.nollamasDifficulty2;
const difficulty = nollamas.dataset.nollamasDifficulty;
console.log('challenge:', challenge); // eslint-disable-line no-console
console.log('difficulty1:', difficulty1); // eslint-disable-line no-console
console.log('difficulty2:', difficulty2); // eslint-disable-line no-console
console.log("challenge:", challenge); // eslint-disable-line no-console
console.log("difficulty:", difficulty); // eslint-disable-line no-console
// Prepare the worker with task function.
const worker = new Worker("/assets/dist/nollamasworker.js");
const startTime = performance.now();
worker.postMessage({
challenge: challenge,
difficulty1: difficulty1,
difficulty2: difficulty2,
difficulty: difficulty,
});
// Set the main worker function.
@@ -65,13 +62,13 @@ document.addEventListener('DOMContentLoaded', function() {
if (e.data.done) {
const endTime = performance.now();
const duration = endTime - startTime;
// Remove spinner and replace it with a tick
// and info about how long it took to solve.
nollamas.removeChild(spinner);
const solutionWrapper = document.createElement("div");
solutionWrapper.className = "nollamas-status";
const tick = document.createElement("i");
tick.className = "fa fa-check fa-2x fa-fw";
tick.setAttribute("title","Solved!");
@@ -79,17 +76,18 @@ document.addEventListener('DOMContentLoaded', function() {
solutionWrapper.appendChild(tick);
const took = document.createElement("span");
took.appendChild(document.createTextNode(`Solved in ${duration.toString()}ms!`));
const solvedText = `Solved after ${e.data.nonce} iterations, in ${duration.toString()}ms!`;
took.appendChild(document.createTextNode(solvedText));
solutionWrapper.appendChild(took);
nollamas.appendChild(solutionWrapper);
// Wait for 500ms before redirecting, to give
// visitors a shot at reading the message, but
// not so long that they have to wait unduly.
setTimeout(() => {
let url = new URL(window.location.href);
url.searchParams.set('nollamas_solution', e.data.nonce);
url.searchParams.set("nollamas_solution", e.data.nonce);
window.location.replace(url.toString());
}, 500);
}

View File

@@ -17,43 +17,51 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
onmessage = async function(e) {
console.log('worker started'); // eslint-disable-line no-console
import sha256 from "./sha256";
const challenge = e.data.challenge;
let compute = async function(challengeStr, diffStr) {
const textEncoder = new TextEncoder();
// Get difficulty and generate the expected
// zero ASCII prefix to check for in hashes.
const difficulty1Str = e.data.difficulty1;
const difficulty2Str = e.data.difficulty2;
const difficulty1 = parseInt(difficulty1Str, 10);
const zeroPrefix = '0'.repeat(difficulty1);
// Get difficulty1 as number and generate
// expected zero ASCII prefix to check for.
const diff1 = parseInt(diffStr, 10);
const zeros = "0".repeat(diff1);
// Calculate hex encoded prefix required to check solution, where we
// need diff1 no. chars in hex, and hex encoding doubles input length.
const prefixLen = diff1 / 2 + (diff1 % 2 != 0 ? 2 : 0);
let nonce = 0;
while (true) { // eslint-disable-line no-constant-condition
// Create possible solution string from challenge + nonce.
const solution = textEncoder.encode(challenge + nonce.toString());
// Create possible solution string from challenge string + nonce.
const solution = textEncoder.encode(challengeStr + nonce.toString());
// Generate SHA256 hashsum of solution string and hex encode the result.
const hashBuffer = await crypto.subtle.digest('SHA-256', solution);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
// Generate SHA256 hashsum of solution string, and hex encode the
// necessary prefix length we need to check for a valid solution.
const prefixArray = Array.from(sha256(solution).slice(0, prefixLen));
const prefixHex = prefixArray.map(b => b.toString(16).padStart(2, "0")).join("");
// Check if the hex encoded hash has
// difficulty defined zeroes prefix.
if (hashHex.startsWith(zeroPrefix)) {
// Check if the next char after zero prefix
// is specifically less than difficulty2 char.
if (hashHex.charAt(difficulty1) < difficulty2Str) {
postMessage({ nonce: nonce, done: true });
break;
}
if (prefixHex.startsWith(zeros)) {
return nonce;
}
// Iter.
nonce++;
}
};
onmessage = async function(e) {
console.log('worker started'); // eslint-disable-line no-console
const challenge = e.data.challenge;
const difficulty = e.data.difficulty;
// Compute the nonce that produces solution with args.
let nonce = await compute(challenge, difficulty);
// Post the solution nonce back to caller.
postMessage({ nonce: nonce, done: true });
};

View File

@@ -0,0 +1,113 @@
/*
Copyright 2022 Andrea Griffini
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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.
*/
// sha256(data) returns the digest of an input piece of data.
// sha256(none) returns an object you can call .add(data), and .digest() at the end.
// the returned digest is a 32-byte Uint8Array instance with an added .hex() function.
// input should be string (that will be encoded as UTF-8) or an array-like with values 0..255.
// source: https://github.com/6502/sha256
export default function sha256(data) {
let h0 = 0x6a09e667, h1 = 0xbb67ae85, h2 = 0x3c6ef372, h3 = 0xa54ff53a,
h4 = 0x510e527f, h5 = 0x9b05688c, h6 = 0x1f83d9ab, h7 = 0x5be0cd19,
tsz = 0, bp = 0;
const k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
rrot = (x, n) => (x >>> n) | (x << (32-n)),
w = new Uint32Array(64),
buf = new Uint8Array(64),
process = () => {
for (let j=0,r=0; j<16; j++,r+=4) {
w[j] = (buf[r]<<24) | (buf[r+1]<<16) | (buf[r+2]<<8) | buf[r+3];
}
for (let j=16; j<64; j++) {
let s0 = rrot(w[j-15], 7) ^ rrot(w[j-15], 18) ^ (w[j-15] >>> 3);
let s1 = rrot(w[j-2], 17) ^ rrot(w[j-2], 19) ^ (w[j-2] >>> 10);
w[j] = (w[j-16] + s0 + w[j-7] + s1) | 0;
}
let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
for (let j=0; j<64; j++) {
let S1 = rrot(e, 6) ^ rrot(e, 11) ^ rrot(e, 25),
ch = (e & f) ^ ((~e) & g),
t1 = (h + S1 + ch + k[j] + w[j]) | 0,
S0 = rrot(a, 2) ^ rrot(a, 13) ^ rrot(a, 22),
maj = (a & b) ^ (a & c) ^ (b & c),
t2 = (S0 + maj) | 0;
h = g; g = f; f = e; e = (d + t1)|0; d = c; c = b; b = a; a = (t1 + t2)|0;
}
h0 = (h0 + a)|0; h1 = (h1 + b)|0; h2 = (h2 + c)|0; h3 = (h3 + d)|0;
h4 = (h4 + e)|0; h5 = (h5 + f)|0; h6 = (h6 + g)|0; h7 = (h7 + h)|0;
bp = 0;
},
add = data => {
if (typeof data === "string") {
data = typeof TextEncoder === "undefined" ? Buffer.from(data) : (new TextEncoder).encode(data);
}
for (let i=0; i<data.length; i++) {
buf[bp++] = data[i];
if (bp === 64) {process();}
}
tsz += data.length;
},
digest = () => {
buf[bp++] = 0x80; if (bp == 64) {process();}
if (bp + 8 > 64) {
while (bp < 64) {buf[bp++] = 0x00;}
process();
}
while (bp < 58) {buf[bp++] = 0x00;}
// Max number of bytes is 35,184,372,088,831
let L = tsz * 8;
buf[bp++] = (L / 1099511627776.) & 255;
buf[bp++] = (L / 4294967296.) & 255;
buf[bp++] = L >>> 24;
buf[bp++] = (L >>> 16) & 255;
buf[bp++] = (L >>> 8) & 255;
buf[bp++] = L & 255;
process();
let reply = new Uint8Array(32);
reply[ 0] = h0 >>> 24; reply[ 1] = (h0 >>> 16) & 255; reply[ 2] = (h0 >>> 8) & 255; reply[ 3] = h0 & 255;
reply[ 4] = h1 >>> 24; reply[ 5] = (h1 >>> 16) & 255; reply[ 6] = (h1 >>> 8) & 255; reply[ 7] = h1 & 255;
reply[ 8] = h2 >>> 24; reply[ 9] = (h2 >>> 16) & 255; reply[10] = (h2 >>> 8) & 255; reply[11] = h2 & 255;
reply[12] = h3 >>> 24; reply[13] = (h3 >>> 16) & 255; reply[14] = (h3 >>> 8) & 255; reply[15] = h3 & 255;
reply[16] = h4 >>> 24; reply[17] = (h4 >>> 16) & 255; reply[18] = (h4 >>> 8) & 255; reply[19] = h4 & 255;
reply[20] = h5 >>> 24; reply[21] = (h5 >>> 16) & 255; reply[22] = (h5 >>> 8) & 255; reply[23] = h5 & 255;
reply[24] = h6 >>> 24; reply[25] = (h6 >>> 16) & 255; reply[26] = (h6 >>> 8) & 255; reply[27] = h6 & 255;
reply[28] = h7 >>> 24; reply[29] = (h7 >>> 16) & 255; reply[30] = (h7 >>> 8) & 255; reply[31] = h7 & 255;
reply.hex = () => {
let res = "";
reply.forEach(x => res += ("0" + x.toString(16)).slice(-2)); // eslint-disable-line no-return-assign
return res;
};
return reply;
};
if (data === undefined) {return {add, digest};}
add(data);
return digest();
}