diff options
Diffstat (limited to 'web/js')
| -rw-r--r-- | web/js/main.mjs | 57 | ||||
| -rw-r--r-- | web/js/proof-of-work-slow.mjs | 18 | ||||
| -rw-r--r-- | web/js/proof-of-work.mjs | 32 |
3 files changed, 90 insertions, 17 deletions
diff --git a/web/js/main.mjs b/web/js/main.mjs index daaafcf..01f21f0 100644 --- a/web/js/main.mjs +++ b/web/js/main.mjs @@ -37,7 +37,7 @@ const dependencies = [ const status = document.getElementById('status'); const image = document.getElementById('image'); const title = document.getElementById('title'); - const spinner = document.getElementById('spinner'); + const progress = document.getElementById('progress'); const anubisVersion = JSON.parse(document.getElementById('anubis_version').textContent); const ohNoes = ({ @@ -46,8 +46,7 @@ const dependencies = [ title.innerHTML = titleMsg; status.innerHTML = statusMsg; image.src = imageSrc; - spinner.innerHTML = ""; - spinner.style.display = "none"; + progress.style.display = "none"; }; if (!window.isSecureContext) { @@ -68,8 +67,7 @@ const dependencies = [ // title.innerHTML = "Oh no!"; // status.innerHTML = "Checks failed. Please check your browser's settings and try again."; // image.src = imageURL("sad"); - // spinner.innerHTML = ""; - // spinner.style.display = "none"; + // progress.style.display = "none"; // return; // } @@ -112,20 +110,59 @@ const dependencies = [ return; } - status.innerHTML = `Calculating...<br/>Difficulty: ${rules.report_as}`; - spinner.style.display = "block"; + status.innerHTML = `Calculating...<br/>Difficulty: ${rules.report_as}, `; + progress.style.display = "inline-block"; + // the whole text, including "Speed:", as a single node, because some browsers + // (Firefox mobile) present screen readers with each node as a separate piece + // of text. + const rateText = document.createTextNode("Speed: 0kH/s"); + status.appendChild(rateText); + + let lastSpeedUpdate = 0; + let showingApology = false; + const likelihood = Math.pow(16, -rules.report_as); try { const t0 = Date.now(); - const { hash, nonce } = await process(challenge, rules.difficulty); + const { hash, nonce } = await process( + challenge, + rules.difficulty, + (iters) => { + const delta = Date.now() - t0; + // only update the speed every second so it's less visually distracting + if (delta - lastSpeedUpdate > 1000) { + lastSpeedUpdate = delta; + rateText.data = `Speed: ${(iters / delta).toFixed(3)}kH/s`; + } + + // the probability of still being on the page is (1 - likelihood) ^ iters. + // by definition, half of the time the progress bar only gets to half, so + // apply a polynomial ease-out function to move faster in the beginning + // and then slow down as things get increasingly unlikely. quadratic felt + // the best in testing, but this may need adjustment in the future. + const probability = Math.pow(1 - likelihood, iters); + const distance = (1 - Math.pow(probability, 2)) * 100; + progress["aria-valuenow"] = distance; + progress.firstElementChild.style.width = `${distance}%`; + + if (probability < 0.1 && !showingApology) { + status.append( + document.createElement("br"), + document.createTextNode( + "Verification is taking longer than expected. Please do not refresh the page.", + ), + ); + showingApology = true; + } + }, + ); const t1 = Date.now(); console.log({ hash, nonce }); title.innerHTML = "Success!"; status.innerHTML = `Done! Took ${t1 - t0}ms, ${nonce} iterations`; image.src = imageURL("happy", anubisVersion); - spinner.innerHTML = ""; - spinner.style.display = "none"; + progress.style.display = "none"; setTimeout(() => { const redir = window.location.href; diff --git a/web/js/proof-of-work-slow.mjs b/web/js/proof-of-work-slow.mjs index e30dc21..6522c0b 100644 --- a/web/js/proof-of-work-slow.mjs +++ b/web/js/proof-of-work-slow.mjs @@ -1,6 +1,11 @@ // https://dev.to/ratmd/simple-proof-of-work-in-javascript-3kgm -export default function process(data, difficulty = 5, _threads = 1) { +export default function process( + data, + difficulty = 5, + progressCallback = null, + _threads = 1, +) { console.debug("slow algo"); return new Promise((resolve, reject) => { let webWorkerURL = URL.createObjectURL(new Blob([ @@ -10,8 +15,12 @@ export default function process(data, difficulty = 5, _threads = 1) { let worker = new Worker(webWorkerURL); worker.onmessage = (event) => { - worker.terminate(); - resolve(event.data); + if (typeof event.data === "number") { + progressCallback?.(event.data); + } else { + worker.terminate(); + resolve(event.data); + } }; worker.onerror = (event) => { @@ -47,6 +56,9 @@ function processTask() { 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')); diff --git a/web/js/proof-of-work.mjs b/web/js/proof-of-work.mjs index b4f9c53..60d8d61 100644 --- a/web/js/proof-of-work.mjs +++ b/web/js/proof-of-work.mjs @@ -1,4 +1,9 @@ -export default function process(data, difficulty = 5, threads = (navigator.hardwareConcurrency || 1)) { +export default function process( + data, + difficulty = 5, + progressCallback = null, + threads = (navigator.hardwareConcurrency || 1), +) { console.debug("fast algo"); return new Promise((resolve, reject) => { let webWorkerURL = URL.createObjectURL(new Blob([ @@ -11,9 +16,12 @@ export default function process(data, difficulty = 5, threads = (navigator.hardw let worker = new Worker(webWorkerURL); worker.onmessage = (event) => { - workers.forEach(worker => worker.terminate()); - worker.terminate(); - resolve(event.data); + if (typeof event.data === "number") { + progressCallback?.(event.data); + } else { + workers.forEach(worker => worker.terminate()); + resolve(event.data); + } }; worker.onerror = (event) => { @@ -55,6 +63,8 @@ function processTask() { let nonce = event.data.nonce; let threads = event.data.threads; + const threadId = nonce; + while (true) { const currentHash = await sha256(data + nonce); const thisHash = new Uint8Array(currentHash); @@ -78,7 +88,21 @@ function processTask() { break; } + const oldNonce = nonce; nonce += threads; + + // send a progess update every 1024 iterations. since each thread checks + // separate values, one simple way to do this is by bit masking the + // nonce for multiples of 1024. unfortunately, if the number of threads + // is not prime, only some of the threads will be sending the status + // update and they will get behind the others. this is slightly more + // complicated but ensures an even distribution between threads. + if ( + nonce > oldNonce | 1023 && // we've wrapped past 1024 + (nonce >> 10) % threads === threadId // and it's our turn + ) { + postMessage(nonce); + } } postMessage({ |
