240 lines
6.1 KiB
JavaScript
240 lines
6.1 KiB
JavaScript
const encoder = new TextEncoder();
|
|
|
|
/**
|
|
* SHA-256 hashing function adapted from https://bitwiseshiftleft.github.io/sjcl
|
|
* modified and redistributed under BSD license
|
|
* @param {string} data
|
|
*/
|
|
export function sha256(data) {
|
|
if (!key[0]) precompute();
|
|
|
|
const out = init.slice(0);
|
|
const array = encode(data);
|
|
|
|
for (let i = 0; i < array.length; i += 16) {
|
|
const w = array.subarray(i, i + 16);
|
|
|
|
let tmp;
|
|
let a;
|
|
let b;
|
|
|
|
let out0 = out[0];
|
|
let out1 = out[1];
|
|
let out2 = out[2];
|
|
let out3 = out[3];
|
|
let out4 = out[4];
|
|
let out5 = out[5];
|
|
let out6 = out[6];
|
|
let out7 = out[7];
|
|
|
|
/* Rationale for placement of |0 :
|
|
* If a value can overflow is original 32 bits by a factor of more than a few
|
|
* million (2^23 ish), there is a possibility that it might overflow the
|
|
* 53-bit mantissa and lose precision.
|
|
*
|
|
* To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that
|
|
* propagates around the loop, and on the hash state out[]. I don't believe
|
|
* that the clamps on out4 and on out0 are strictly necessary, but it's close
|
|
* (for out4 anyway), and better safe than sorry.
|
|
*
|
|
* The clamps on out[] are necessary for the output to be correct even in the
|
|
* common case and for short inputs.
|
|
*/
|
|
|
|
for (let i = 0; i < 64; i++) {
|
|
// load up the input word for this round
|
|
|
|
if (i < 16) {
|
|
tmp = w[i];
|
|
} else {
|
|
a = w[(i + 1) & 15];
|
|
|
|
b = w[(i + 14) & 15];
|
|
|
|
tmp = w[i & 15] =
|
|
(((a >>> 7) ^ (a >>> 18) ^ (a >>> 3) ^ (a << 25) ^ (a << 14)) +
|
|
((b >>> 17) ^ (b >>> 19) ^ (b >>> 10) ^ (b << 15) ^ (b << 13)) +
|
|
w[i & 15] +
|
|
w[(i + 9) & 15]) |
|
|
0;
|
|
}
|
|
|
|
tmp =
|
|
tmp +
|
|
out7 +
|
|
((out4 >>> 6) ^ (out4 >>> 11) ^ (out4 >>> 25) ^ (out4 << 26) ^ (out4 << 21) ^ (out4 << 7)) +
|
|
(out6 ^ (out4 & (out5 ^ out6))) +
|
|
key[i]; // | 0;
|
|
|
|
// shift register
|
|
out7 = out6;
|
|
out6 = out5;
|
|
out5 = out4;
|
|
|
|
out4 = (out3 + tmp) | 0;
|
|
|
|
out3 = out2;
|
|
out2 = out1;
|
|
out1 = out0;
|
|
|
|
out0 =
|
|
(tmp +
|
|
((out1 & out2) ^ (out3 & (out1 ^ out2))) +
|
|
((out1 >>> 2) ^
|
|
(out1 >>> 13) ^
|
|
(out1 >>> 22) ^
|
|
(out1 << 30) ^
|
|
(out1 << 19) ^
|
|
(out1 << 10))) |
|
|
0;
|
|
}
|
|
|
|
out[0] = (out[0] + out0) | 0;
|
|
out[1] = (out[1] + out1) | 0;
|
|
out[2] = (out[2] + out2) | 0;
|
|
out[3] = (out[3] + out3) | 0;
|
|
out[4] = (out[4] + out4) | 0;
|
|
out[5] = (out[5] + out5) | 0;
|
|
out[6] = (out[6] + out6) | 0;
|
|
out[7] = (out[7] + out7) | 0;
|
|
}
|
|
|
|
const bytes = new Uint8Array(out.buffer);
|
|
reverse_endianness(bytes);
|
|
|
|
return base64(bytes);
|
|
}
|
|
|
|
/** The SHA-256 initialization vector */
|
|
const init = new Uint32Array(8);
|
|
|
|
/** The SHA-256 hash key */
|
|
const key = new Uint32Array(64);
|
|
|
|
/** Function to precompute init and key. */
|
|
function precompute() {
|
|
/** @param {number} x */
|
|
function frac(x) {
|
|
return (x - Math.floor(x)) * 0x100000000;
|
|
}
|
|
|
|
let prime = 2;
|
|
|
|
for (let i = 0; i < 64; prime++) {
|
|
let is_prime = true;
|
|
|
|
for (let factor = 2; factor * factor <= prime; factor++) {
|
|
if (prime % factor === 0) {
|
|
is_prime = false;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_prime) {
|
|
if (i < 8) {
|
|
init[i] = frac(prime ** (1 / 2));
|
|
}
|
|
|
|
key[i] = frac(prime ** (1 / 3));
|
|
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @param {Uint8Array} bytes */
|
|
function reverse_endianness(bytes) {
|
|
for (let i = 0; i < bytes.length; i += 4) {
|
|
const a = bytes[i + 0];
|
|
const b = bytes[i + 1];
|
|
const c = bytes[i + 2];
|
|
const d = bytes[i + 3];
|
|
|
|
bytes[i + 0] = d;
|
|
bytes[i + 1] = c;
|
|
bytes[i + 2] = b;
|
|
bytes[i + 3] = a;
|
|
}
|
|
}
|
|
|
|
/** @param {string} str */
|
|
function encode(str) {
|
|
const encoded = encoder.encode(str);
|
|
const length = encoded.length * 8;
|
|
|
|
// result should be a multiple of 512 bits in length,
|
|
// with room for a 1 (after the data) and two 32-bit
|
|
// words containing the original input bit length
|
|
const size = 512 * Math.ceil((length + 65) / 512);
|
|
const bytes = new Uint8Array(size / 8);
|
|
bytes.set(encoded);
|
|
|
|
// append a 1
|
|
bytes[encoded.length] = 0b10000000;
|
|
|
|
reverse_endianness(bytes);
|
|
|
|
// add the input bit length
|
|
const words = new Uint32Array(bytes.buffer);
|
|
words[words.length - 2] = Math.floor(length / 0x100000000); // this will always be zero for us
|
|
words[words.length - 1] = length;
|
|
|
|
return words;
|
|
}
|
|
|
|
/*
|
|
Based on https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
|
|
|
MIT License
|
|
Copyright (c) 2020 Egor Nepomnyaschih
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
|
|
|
|
/** @param {Uint8Array} bytes */
|
|
export function base64(bytes) {
|
|
const l = bytes.length;
|
|
|
|
let result = '';
|
|
let i;
|
|
|
|
for (i = 2; i < l; i += 3) {
|
|
result += chars[bytes[i - 2] >> 2];
|
|
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
|
result += chars[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
|
|
result += chars[bytes[i] & 0x3f];
|
|
}
|
|
|
|
if (i === l + 1) {
|
|
// 1 octet yet to write
|
|
result += chars[bytes[i - 2] >> 2];
|
|
result += chars[(bytes[i - 2] & 0x03) << 4];
|
|
result += '==';
|
|
}
|
|
|
|
if (i === l) {
|
|
// 2 octets yet to write
|
|
result += chars[bytes[i - 2] >> 2];
|
|
result += chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
|
result += chars[(bytes[i - 1] & 0x0f) << 2];
|
|
result += '=';
|
|
}
|
|
|
|
return result;
|
|
}
|