forked from ChatGPTNextWeb/NextChat
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
251 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
// 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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters