v0.28.0 fca9155

pow-worker.js RawHistoryFolder

1// pow-worker.js: the proof of work of the public issue form, in a dedicated
2// Worker (started by issue-form.js). It finds the smallest decimal counter n
3// such that SHA-256(nonce bytes || ":" || n) has at least `difficulty` leading
4// zero bits: the very check of the server (usecase/issue_submissions.go,
5// powHash). The nonce arrives as base64url without padding, like the server
6// sends it. Messages in: {nonce, difficulty}. Messages out:
7// {progress: tried} now and then, then {solution: "<decimal>"} or {error}.
8//
9// window-less on purpose: ermitePow is also loaded by Node for the tests
10// (jstest/issue-form.test.mjs).
11(() => {
12  const BATCH = 2048;
13  const enc = new TextEncoder();
14
15  // leadingZeroBits counts the zero bits at the start of the bytes.
16  function leadingZeroBits(bytes) {
17    let n = 0;
18    for (const b of bytes) {
19      if (b === 0) { n += 8; continue; }
20      return n + Math.clz32(b) - 24;
21    }
22    return n;
23  }
24
25  // b64urlDecode turns unpadded base64url into bytes.
26  function b64urlDecode(s) {
27    const bin = atob(String(s).replace(/-/g, '+').replace(/_/g, '/'));
28    const out = new Uint8Array(bin.length);
29    for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
30    return out;
31  }
32
33  // input is nonce || ":" || decimal(n), the bytes the server hashes.
34  function input(nonce, n) {
35    const tail = enc.encode(':' + n);
36    const out = new Uint8Array(nonce.length + tail.length);
37    out.set(nonce, 0);
38    out.set(tail, nonce.length);
39    return out;
40  }
41
42  // solve returns the smallest solution as a decimal string. digest is
43  // (Uint8Array) => Promise<ArrayBuffer-like>, crypto.subtle.digest by default.
44  // Hashes are asked a batch at a time, in order, so the first hit of the first
45  // batch that has one is the smallest. onProgress(tried) is called after each
46  // batch; it may return true to stop (solve then returns null).
47  async function solve(nonce, difficulty, { digest, batch = BATCH, onProgress } = {}) {
48    const dig = digest || ((d) => globalThis.crypto.subtle.digest('SHA-256', d));
49    for (let start = 0; ; start += batch) {
50      const sums = await Promise.all(Array.from({ length: batch }, async (_, i) => new Uint8Array(await dig(input(nonce, start + i)))));
51      for (let i = 0; i < batch; i++) {
52        if (leadingZeroBits(sums[i]) >= difficulty) return String(start + i);
53      }
54      if (onProgress && onProgress(start + batch)) return null;
55    }
56  }
57
58  globalThis.ermitePow = { leadingZeroBits, b64urlDecode, input, solve };
59
60  if (typeof WorkerGlobalScope !== 'undefined' && typeof globalThis.postMessage === 'function') {
61    globalThis.onmessage = async (e) => {
62      try {
63        if (!globalThis.crypto || !globalThis.crypto.subtle) { postMessage({ error: 'nocrypto' }); return; }
64        const { nonce, difficulty } = e.data;
65        const solution = await solve(b64urlDecode(nonce), difficulty, { onProgress: (tried) => { postMessage({ progress: tried }); } });
66        postMessage({ solution });
67      } catch (err) {
68        postMessage({ error: String(err && err.message || err) });
69      }
70    };
71  }
72})();

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 45

↑↓ moveenter openesc close