All files / crates/monokulo/static challenge.js

91.66% Statements 55/60
84.21% Branches 16/19
84.61% Functions 11/13
93.87% Lines 46/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88                  2x   2x 2x 2x 2x 2x 2x 2x 2x   4x     512x 512x 513x 512x 517x 512x           1x         2x 1x 1x 1x     1x 1x 1x 1x     2x 2x 512x   2x 2x 512x 512x     2x 383x   1x         2x 2x 1x 1x 1x 1x   1x 1x 1x           1x 1x    
/* Solves the "Checking your connection" proof-of-work and continues by
 * itself (abuse protection, crate::abuse::challenge). The page works without
 * this script: a noscript meta refresh continues after a 10-second wait.
 *
 * The answer is a nonce such that SHA-256(challenge + nonce) starts with
 * `difficulty` zero bits. Hashes are computed with Web Crypto in batches so
 * the page stays responsive; where Web Crypto isn't available (a plain-HTTP
 * page outside a secure context) it falls back to the same 10-second wait
 * the no-JavaScript path uses. */
(function () {
  'use strict';
  var root = document.getElementById('challenge');
  Iif (!root) return;
  var challenge = root.getAttribute('data-challenge');
  var difficulty = parseInt(root.getAttribute('data-difficulty'), 10);
  var continueUrl = root.getAttribute('data-continue');
  var waitUrl = root.getAttribute('data-wait');
  var progress = document.getElementById('challenge-progress');
  progress.hidden = false;
 
  function say(text) { progress.textContent = text; }
 
  function leadingZeroBits(bytes) {
    var bits = 0;
    for (var i = 0; i < bytes.length; i++) {
      if (bytes[i] === 0) { bits += 8; continue; }
      var b = bytes[i];
      while ((b & 0x80) === 0) { bits++; b <<= 1; }
      return bits;
    }
    return bits;
  }
 
  function withParam(url, name, value) {
    return url + (url.indexOf('?') === -1 ? '?' : '&') + name + '=' + encodeURIComponent(value);
  }
 
  // The heading above already says "Checking your connection"; this line
  // says how it's going.
  if (!window.crypto || !window.crypto.subtle || !window.TextEncoder) {
    say('This page continues in 10 seconds.');
    setTimeout(function () { location.replace(waitUrl); }, 10500);
    return;
  }
 
  var encoder = new TextEncoder();
  var BATCH = 256;
  var nonce = 0;
  var started = Date.now();
 
  function batch() {
    var attempts = [];
    for (var i = 0; i < BATCH; i++) {
      attempts.push(String(nonce + i));
    }
    nonce += BATCH;
    return Promise.all(attempts.map(function (n) {
      return crypto.subtle.digest('SHA-256', encoder.encode(challenge + n)).then(function (hash) {
        return leadingZeroBits(new Uint8Array(hash)) >= difficulty ? n : null;
      });
    })).then(function (results) {
      for (var j = 0; j < results.length; j++) {
        if (results[j] !== null) return results[j];
      }
      return null;
    });
  }
 
  function step() {
    batch().then(function (found) {
      if (found !== null) {
        say('Done, continuing…');
        root.setAttribute('aria-busy', 'false');
        location.replace(withParam(continueUrl, 'monokulo_proof', challenge + '.' + found));
        return;
      }
      var seconds = Math.floor((Date.now() - started) / 1000);
      say(seconds < 2 ? 'Working…' : 'Working… (' + seconds + 's)');
      setTimeout(step, 0);
    }).catch(function () {
      say('This page continues in 10 seconds.');
      setTimeout(function () { location.replace(waitUrl); }, 10500);
    });
  }
  say('Working…');
  step();
})();