carlos / movement Public

Clone
git clone https://amadan.net/carlos/movement

Plain git — no account needed to clone.

Download

Download this file

1// SHA-256, synchronously, over a Uint8Array.
2//
3// The browser already has SHA-256 in crypto.subtle, and it is not usable
4// here: subtle.digest is asynchronous, returning a promise per call, and this
5// runs a few hundred thousand times to solve one challenge. A promise per
6// attempt is not an inner loop.
7//
8// So it is transcribed rather than invented — FIPS 180-4, the ordinary
9// reference implementation. Its test is comparison against Go's
10// crypto/sha256 over a corpus, not cleverness. Nothing in here should ever
11// be optimised in a way that makes it disagree with the Go verifier by even
12// one byte: a disagreement is a signup that silently fails for a real person
13// with no error anyone can read.
14
15const K = new Uint32Array([
16 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
17 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
18 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
19 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
20 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
21 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
22 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
23 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
24 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
25 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
26 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
27]);
28
29// Reused across calls. The solver runs this hundreds of thousands of times
30// in a tight loop, and allocating two arrays per attempt is most of the cost.
31const W = new Uint32Array(64);
32const H = new Uint32Array(8);
33const OUT = new Uint8Array(32);
34
35function rotr(x, n) {
36 return (x >>> n) | (x << (32 - n));
37}
38
39// digest hashes len bytes of buf and writes 32 bytes into OUT, which it
40// returns. The result is only valid until the next call — the caller reads it
41// immediately, which is all this solver ever does.
42export function sha256(buf, len) {
43 H[0] = 0x6a09e667; H[1] = 0xbb67ae85; H[2] = 0x3c6ef372; H[3] = 0xa54ff53a;
44 H[4] = 0x510e527f; H[5] = 0x9b05688c; H[6] = 0x1f83d9ab; H[7] = 0x5be0cd19;
45
46 // One padded copy: the message, a 0x80 byte, zeroes, then the length in
47 // bits as a big-endian 64-bit integer.
48 const withLen = len + 9;
49 const total = withLen + ((64 - (withLen % 64)) % 64);
50 const m = new Uint8Array(total);
51 m.set(buf.subarray(0, len));
52 m[len] = 0x80;
53
54 const bits = len * 8;
55 // Lengths here are far below 2^32 bits, so the high word is always zero.
56 m[total - 4] = (bits >>> 24) & 0xff;
57 m[total - 3] = (bits >>> 16) & 0xff;
58 m[total - 2] = (bits >>> 8) & 0xff;
59 m[total - 1] = bits & 0xff;
60
61 for (let off = 0; off < total; off += 64) {
62 for (let i = 0; i < 16; i++) {
63 const j = off + i * 4;
64 W[i] = (m[j] << 24) | (m[j + 1] << 16) | (m[j + 2] << 8) | m[j + 3];
65 }
66 for (let i = 16; i < 64; i++) {
67 const s0 = rotr(W[i - 15], 7) ^ rotr(W[i - 15], 18) ^ (W[i - 15] >>> 3);
68 const s1 = rotr(W[i - 2], 17) ^ rotr(W[i - 2], 19) ^ (W[i - 2] >>> 10);
69 W[i] = (W[i - 16] + s0 + W[i - 7] + s1) | 0;
70 }
71
72 let a = H[0], b = H[1], c = H[2], d = H[3];
73 let e = H[4], f = H[5], g = H[6], h = H[7];
74
75 for (let i = 0; i < 64; i++) {
76 const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
77 const ch = (e & f) ^ (~e & g);
78 const t1 = (h + S1 + ch + K[i] + W[i]) | 0;
79 const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
80 const maj = (a & b) ^ (a & c) ^ (b & c);
81 const t2 = (S0 + maj) | 0;
82
83 h = g; g = f; f = e; e = (d + t1) | 0;
84 d = c; c = b; b = a; a = (t1 + t2) | 0;
85 }
86
87 H[0] = (H[0] + a) | 0; H[1] = (H[1] + b) | 0;
88 H[2] = (H[2] + c) | 0; H[3] = (H[3] + d) | 0;
89 H[4] = (H[4] + e) | 0; H[5] = (H[5] + f) | 0;
90 H[6] = (H[6] + g) | 0; H[7] = (H[7] + h) | 0;
91 }
92
93 for (let i = 0; i < 8; i++) {
94 OUT[i * 4] = (H[i] >>> 24) & 0xff;
95 OUT[i * 4 + 1] = (H[i] >>> 16) & 0xff;
96 OUT[i * 4 + 2] = (H[i] >>> 8) & 0xff;
97 OUT[i * 4 + 3] = H[i] & 0xff;
98 }
99 return OUT;
100}
101
102// leadingZeroBits counts zero bits from the most significant end, the same
103// way the Go verifier does.
104export function leadingZeroBits(sum) {
105 let n = 0;
106 for (let i = 0; i < sum.length; i++) {
107 const b = sum[i];
108 if (b === 0) {
109 n += 8;
110 continue;
111 }
112 return n + Math.clz32(b) - 24;
113 }
114 return n;
115}
116
117// hex is used only by tests, to compare a digest against Go's.
118export function hex(bytes) {
119 let s = "";
120 for (let i = 0; i < bytes.length; i++) {
121 s += bytes[i].toString(16).padStart(2, "0");
122 }
123 return s;
124}
125