PageSourceSearch

https://www.list-org.com/assets/captcha_pow_worker.js

js list-org.com collected 2026-10-02 04:37:12 UTC 7,560 bytes, 175 lines download raw bytes

1/* CaptchaCheckbox / OSv2 — proof-of-work worker.
2 *
3 * Майнит pow_nonce такой, что
4 *     sha256("captcha-pow-v1|" || init_nonce_16 || pow_nonce_4LE)
5 * имеет не меньше `difficulty` ведущих нулевых бит.
6 *
7 * Сообщения:
8 *   in:  { type: 'mine', challengeHex: '<32 hex>', difficulty: K }
9 *   out: { type: 'progress', tried: N }                — каждые ~64k попыток
10 *        { type: 'solved',   nonceB64: '...'         } — нашли решение
11 *        { type: 'error',    code: 'too_hard'|'bad_input' }
12 */
13
14'use strict';
15
16// SHA-256 (FIPS 180-4). Реализация рассчитана на одноблочный вход
17// (msgLen ≤ 55 байт): мы знаем, что наш препейл-нонс-payload — 35 байт.
18var K256 = new Uint32Array([
19    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
20    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
21    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
22    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
23    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
24    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
25    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
26    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
27]);
28
29var H0 = new Uint32Array([
30    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
31    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
32]);
33
34function rotr(x, n) { return ((x >>> n) | (x << (32 - n))) >>> 0; }
35
36/**
37 * Хеширует один 64-байтный блок (16 uint32 big-endian слов).
38 * Возвращает первые 4 байта результата как uint32 — для проверки leading zeros.
39 * Для разных задач можно вернуть полный массив, но нам нужны только первые биты.
40 */
41function sha256FirstWord(W) {
42    // W: Uint32Array(64); первые 16 слов = блок, остальные пересчитываем здесь.
43    for (var i = 16; i < 64; i++) {
44        var x15 = W[i - 15];
45        var s0 = (rotr(x15, 7) ^ rotr(x15, 18) ^ (x15 >>> 3)) >>> 0;
46        var x2 = W[i - 2];
47        var s1 = (rotr(x2, 17) ^ rotr(x2, 19) ^ (x2 >>> 10)) >>> 0;
48        W[i] = (W[i - 16] + s0 + W[i - 7] + s1) | 0;
49    }
50
51    var a = H0[0], b = H0[1], c = H0[2], d = H0[3];
52    var e = H0[4], f = H0[5], g = H0[6], h = H0[7];
53
54    for (var i = 0; i < 64; i++) {
55        var S1 = (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) >>> 0;
56        var ch = ((e & f) ^ (~e & g)) >>> 0;
57        var temp1 = (h + S1 + ch + K256[i] + W[i]) | 0;
58        var S0 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) >>> 0;
59        var mj = ((a & b) ^ (a & c) ^ (b & c)) >>> 0;
60        var temp2 = (S0 + mj) | 0;
61        h = g; g = f; f = e;
62        e = (d + temp1) | 0;
63        d = c; c = b; b = a;
64        a = (temp1 + temp2) | 0;
65    }
66    // Возвращаем первое слово итогового хеша (a + H0[0]).
67    return (a + H0[0]) >>> 0;
68}
69
70function leadingZeroBitsU32(w) {
71    if (w === 0) return 32;
72    var n = 0;
73    for (var mask = 0x80000000; mask > 0; mask = (mask >>> 1)) {
74        if ((w & mask) === 0) n++;
75        else break;
76    }
77    return n;
78}
79
80function hexToBytes(hex) {
81    if (hex.length % 2 !== 0) return null;
82    var out = new Uint8Array(hex.length / 2);
83    for (var i = 0; i < out.length; i++) {
84        var b = parseInt(hex.substr(i * 2, 2), 16);
85        if (isNaN(b)) return null;
86        out[i] = b;
87    }
88    return out;
89}
90
91function bytesToB64(bytes) {
92    var s = '';
93    for (var i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
94    return btoa(s).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
95}
96
97self.onmessage = function (ev) {
98    var msg = ev.data || {};
99    if (msg.type !== 'mine') return;
100
101    var difficulty = msg.difficulty | 0;
102    var nonceBytes = hexToBytes(String(msg.challengeHex || ''));
103    if (!nonceBytes || nonceBytes.length !== 16 || difficulty <= 0 || difficulty > 32) {
104        self.postMessage({ type: 'error', code: 'bad_input' });
105        return;
106    }
107
108    // Препейл: "captcha-pow-v1|" (15 байт) + nonce (16 байт) + pow_nonce (4 байта LE) = 35 байт.
109    // Это вмещается в один блок sha256. Готовим блок-словарь однажды и
110    // апдейтим только то, что меняется по итерациям.
111    var prefix = 'captcha-pow-v1|';
112    var msgLen = prefix.length + 16 + 4; // 35
113    var bits = msgLen * 8;               // 280
114
115    // 64-байтный блок = 16 uint32 big-endian.
116    // Заранее вычисляем «постоянную часть» блока (всё кроме pow_nonce).
117    var msgBytes = new Uint8Array(64);
118    for (var i = 0; i < prefix.length; i++) msgBytes[i] = prefix.charCodeAt(i);
119    for (var i = 0; i < 16; i++)            msgBytes[prefix.length + i] = nonceBytes[i];
120    // bytes 31..34 — место под pow_nonce, заполним в цикле.
121    msgBytes[35] = 0x80;
122    // bytes 36..55 — нули (Uint8Array уже занулён).
123    // bytes 56..63 — длина в битах (uint64 BE).
124    msgBytes[56] = 0; msgBytes[57] = 0; msgBytes[58] = 0; msgBytes[59] = 0;
125    msgBytes[60] = (bits >>> 24) & 0xff;
126    msgBytes[61] = (bits >>> 16) & 0xff;
127    msgBytes[62] = (bits >>> 8)  & 0xff;
128    msgBytes[63] = bits & 0xff;
129
130    var W = new Uint32Array(64);
131    function loadFixed() {
132        for (var i = 0; i < 16; i++) {
133            var b = i * 4;
134            W[i] = ((msgBytes[b]     << 24) |
135                    (msgBytes[b + 1] << 16) |
136                    (msgBytes[b + 2] <<  8) |
137                    (msgBytes[b + 3]      )) >>> 0;
138        }
139    }
140    loadFixed();
141
142    var MAX_ITER = 0xFFFFFFFF;
143    var batch = 65536;
144    var tried = 0;
145
146    for (var n = 0; n <= MAX_ITER; n++) {
147        // pow_nonce_4LE → bytes 31..34. Только эти байты меняются;
148        // обновляем W[7] (low byte) и W[8] (high 3 bytes).
149        msgBytes[31] = n         & 0xff;
150        msgBytes[32] = (n >>> 8) & 0xff;
151        msgBytes[33] = (n >>> 16) & 0xff;
152        msgBytes[34] = (n >>> 24) & 0xff;
153        // W[7] = bytes 28..31 — байт 31 в low.
154        W[7] = ((msgBytes[28] << 24) | (msgBytes[29] << 16) | (msgBytes[30] << 8) | msgBytes[31]) >>> 0;
155        // W[8] = bytes 32..35 — байты 32,33,34 + 0x80.
156        W[8] = ((msgBytes[32] << 24) | (msgBytes[33] << 16) | (msgBytes[34] << 8) | 0x80) >>> 0;
157
158        var firstWord = sha256FirstWord(W);
159        if (leadingZeroBitsU32(firstWord) >= difficulty) {
160            var nonceLE = new Uint8Array(4);
161            nonceLE[0] = n         & 0xff;
162            nonceLE[1] = (n >>> 8) & 0xff;
163            nonceLE[2] = (n >>> 16) & 0xff;
164            nonceLE[3] = (n >>> 24) & 0xff;
165            self.postMessage({ type: 'solved', nonceB64: bytesToB64(nonceLE), tried: n + 1 });
166            return;
167        }
168
169        tried++;
170        if ((tried % batch) === 0) {
171            self.postMessage({ type: 'progress', tried: tried });
172        }
173    }
174    self.postMessage({ type: 'error', code: 'too_hard' });
175};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.