Spaces:
Paused
Paused
| // From https://gist.github.com/guillermodlpa/f6d955f838e9b10d1ef95b8e259b2c58 | |
| // From https://gist.github.com/stevendesu/2d52f7b5e1f1184af3b667c0b5e054b8 | |
| // To ensure cross-browser support even without a proper SubtleCrypto | |
| // impelmentation (or without access to the impelmentation, as is the case with | |
| // Chrome loaded over HTTP instead of HTTPS), this library can create SHA-256 | |
| // HMAC signatures using nothing but raw JavaScript | |
| /* eslint-disable no-magic-numbers, id-length, no-param-reassign, new-cap */ | |
| // By giving internal functions names that we can mangle, future calls to | |
| // them are reduced to a single byte (minor space savings in minified file) | |
| const uint8Array = Uint8Array; | |
| const uint32Array = Uint32Array; | |
| const pow = Math.pow; | |
| // Will be initialized below | |
| // Using a Uint32Array instead of a simple array makes the minified code | |
| // a bit bigger (we lose our `unshift()` hack), but comes with huge | |
| // performance gains | |
| const DEFAULT_STATE = new uint32Array(8); | |
| const ROUND_CONSTANTS: number[] = []; | |
| // Reusable object for expanded message | |
| // Using a Uint32Array instead of a simple array makes the minified code | |
| // 7 bytes larger, but comes with huge performance gains | |
| const M = new uint32Array(64); | |
| // After minification the code to compute the default state and round | |
| // constants is smaller than the output. More importantly, this serves as a | |
| // good educational aide for anyone wondering where the magic numbers come | |
| // from. No magic numbers FTW! | |
| function getFractionalBits(n: number) { | |
| return ((n - (n | 0)) * pow(2, 32)) | 0; | |
| } | |
| let n = 2; | |
| let nPrime = 0; | |
| while (nPrime < 64) { | |
| // isPrime() was in-lined from its original function form to save | |
| // a few bytes | |
| let isPrime = true; | |
| // Math.sqrt() was replaced with pow(n, 1/2) to save a few bytes | |
| // var sqrtN = pow(n, 1 / 2); | |
| // So technically to determine if a number is prime you only need to | |
| // check numbers up to the square root. However this function only runs | |
| // once and we're only computing the first 64 primes (up to 311), so on | |
| // any modern CPU this whole function runs in a couple milliseconds. | |
| // By going to n / 2 instead of sqrt(n) we net 8 byte savings and no | |
| // scaling performance cost | |
| for (let factor = 2; factor <= n / 2; factor++) { | |
| if (n % factor === 0) { | |
| isPrime = false; | |
| } | |
| } | |
| if (isPrime) { | |
| if (nPrime < 8) { | |
| DEFAULT_STATE[nPrime] = getFractionalBits(pow(n, 1 / 2)); | |
| } | |
| ROUND_CONSTANTS[nPrime] = getFractionalBits(pow(n, 1 / 3)); | |
| nPrime++; | |
| } | |
| n++; | |
| } | |
| // For cross-platform support we need to ensure that all 32-bit words are | |
| // in the same endianness. A UTF-8 TextEncoder will return BigEndian data, | |
| // so upon reading or writing to our ArrayBuffer we'll only swap the bytes | |
| // if our system is LittleEndian (which is about 99% of CPUs) | |
| const LittleEndian = !!new uint8Array(new uint32Array([1]).buffer)[0]; | |
| function convertEndian(word: number) { | |
| if (LittleEndian) { | |
| return ( | |
| // byte 1 -> byte 4 | |
| (word >>> 24) | | |
| // byte 2 -> byte 3 | |
| (((word >>> 16) & 0xff) << 8) | | |
| // byte 3 -> byte 2 | |
| ((word & 0xff00) << 8) | | |
| // byte 4 -> byte 1 | |
| (word << 24) | |
| ); | |
| } else { | |
| return word; | |
| } | |
| } | |
| function rightRotate(word: number, bits: number) { | |
| return (word >>> bits) | (word << (32 - bits)); | |
| } | |
| function sha256(data: Uint8Array) { | |
| // Copy default state | |
| const STATE = DEFAULT_STATE.slice(); | |
| // Caching this reduces occurrences of ".length" in minified JavaScript | |
| // 3 more byte savings! :D | |
| const legth = data.length; | |
| // Pad data | |
| const bitLength = legth * 8; | |
| const newBitLength = 512 - ((bitLength + 64) % 512) - 1 + bitLength + 65; | |
| // "bytes" and "words" are stored BigEndian | |
| const bytes = new uint8Array(newBitLength / 8); | |
| const words = new uint32Array(bytes.buffer); | |
| bytes.set(data, 0); | |
| // Append a 1 | |
| bytes[legth] = 0b10000000; | |
| // Store length in BigEndian | |
| words[words.length - 1] = convertEndian(bitLength); | |
| // Loop iterator (avoid two instances of "var") -- saves 2 bytes | |
| let round; | |
| // Process blocks (512 bits / 64 bytes / 16 words at a time) | |
| for (let block = 0; block < newBitLength / 32; block += 16) { | |
| const workingState = STATE.slice(); | |
| // Rounds | |
| for (round = 0; round < 64; round++) { | |
| let MRound; | |
| // Expand message | |
| if (round < 16) { | |
| // Convert to platform Endianness for later math | |
| MRound = convertEndian(words[block + round]); | |
| } else { | |
| const gamma0x = M[round - 15]; | |
| const gamma1x = M[round - 2]; | |
| MRound = | |
| M[round - 7] + | |
| M[round - 16] + | |
| (rightRotate(gamma0x, 7) ^ | |
| rightRotate(gamma0x, 18) ^ | |
| (gamma0x >>> 3)) + | |
| (rightRotate(gamma1x, 17) ^ | |
| rightRotate(gamma1x, 19) ^ | |
| (gamma1x >>> 10)); | |
| } | |
| // M array matches platform endianness | |
| M[round] = MRound |= 0; | |
| // Computation | |
| const t1 = | |
| (rightRotate(workingState[4], 6) ^ | |
| rightRotate(workingState[4], 11) ^ | |
| rightRotate(workingState[4], 25)) + | |
| ((workingState[4] & workingState[5]) ^ | |
| (~workingState[4] & workingState[6])) + | |
| workingState[7] + | |
| MRound + | |
| ROUND_CONSTANTS[round]; | |
| const t2 = | |
| (rightRotate(workingState[0], 2) ^ | |
| rightRotate(workingState[0], 13) ^ | |
| rightRotate(workingState[0], 22)) + | |
| ((workingState[0] & workingState[1]) ^ | |
| (workingState[2] & (workingState[0] ^ workingState[1]))); | |
| for (let i = 7; i > 0; i--) { | |
| workingState[i] = workingState[i - 1]; | |
| } | |
| workingState[0] = (t1 + t2) | 0; | |
| workingState[4] = (workingState[4] + t1) | 0; | |
| } | |
| // Update state | |
| for (round = 0; round < 8; round++) { | |
| STATE[round] = (STATE[round] + workingState[round]) | 0; | |
| } | |
| } | |
| // Finally the state needs to be converted to BigEndian for output | |
| // And we want to return a Uint8Array, not a Uint32Array | |
| return new uint8Array( | |
| new uint32Array( | |
| STATE.map(function (val) { | |
| return convertEndian(val); | |
| }), | |
| ).buffer, | |
| ); | |
| } | |
| function hmac(key: Uint8Array, data: ArrayLike<number>) { | |
| if (key.length > 64) key = sha256(key); | |
| if (key.length < 64) { | |
| const tmp = new Uint8Array(64); | |
| tmp.set(key, 0); | |
| key = tmp; | |
| } | |
| // Generate inner and outer keys | |
| const innerKey = new Uint8Array(64); | |
| const outerKey = new Uint8Array(64); | |
| for (let i = 0; i < 64; i++) { | |
| innerKey[i] = 0x36 ^ key[i]; | |
| outerKey[i] = 0x5c ^ key[i]; | |
| } | |
| // Append the innerKey | |
| const msg = new Uint8Array(data.length + 64); | |
| msg.set(innerKey, 0); | |
| msg.set(data, 64); | |
| // Has the previous message and append the outerKey | |
| const result = new Uint8Array(64 + 32); | |
| result.set(outerKey, 0); | |
| result.set(sha256(msg), 64); | |
| // Hash the previous message | |
| return sha256(result); | |
| } | |
| // Convert a string to a Uint8Array, SHA-256 it, and convert back to string | |
| const encoder = new TextEncoder(); | |
| export function sign( | |
| inputKey: string | Uint8Array, | |
| inputData: string | Uint8Array, | |
| ) { | |
| const key = | |
| typeof inputKey === "string" ? encoder.encode(inputKey) : inputKey; | |
| const data = | |
| typeof inputData === "string" ? encoder.encode(inputData) : inputData; | |
| return hmac(key, data); | |
| } | |
| export function hex(bin: Uint8Array) { | |
| return bin.reduce((acc, val) => { | |
| const hexVal = "00" + val.toString(16); | |
| return acc + hexVal.substring(hexVal.length - 2); | |
| }, ""); | |
| } | |
| export function hash(str: string) { | |
| return hex(sha256(encoder.encode(str))); | |
| } | |
| export function hashWithSecret(str: string, secret: string) { | |
| return hex(sign(secret, str)).toString(); | |
| } | |