type padding = { padding: number[] seed: number } type verifyOut = { valid: boolean reason: string } function modMul(n: number, x: number, m: number): number { let out = 0 for (let i = 0; i < x; i++) { out = out + n out = out % m } return out } class randomGenerator { state = 0 seen: number[] = [] constructor(seed: number) { this.state = seed } next(): number { let temp = modMul(this.state, 6967, 65536) if (this.seen.length >= 65536) { this.seen = [] } while (this.seen.includes(temp)) { temp++ temp = temp % 65536 } this.seen.push(temp) this.state = temp return this.state } } function generatePadding(len: number, seed?: number): padding { if (seed == undefined) { seed = Math.floor(Math.random() * 65535) } let out: number[] = [] let generator = new randomGenerator(seed) for (let i = 0; i < len; i++) { let n = generator.next() out.push(n) } return { padding: out, seed: seed } } function copy(input: number[]): number[] { let out: number[] = [] for (let i = 0; i < input.length; i++) { out[i] = input[i] } return out } function dec2bin(dec: number): string { return (dec >>> 0).toString(2); } function bin2dec(bin: string): number { return parseInt(bin, 2) } function fixbin(bin: string, len: number): string { let out = bin while (out.length < len) { out = "0" + out } return out.substring(out.length-len, out.length) } function bin2hex(bin: string): string { let chars = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"] let hex = "" while (bin.length > 3) { let n = bin2dec(bin.substring(0, 4)) hex += chars[n] bin = bin.substring(4, bin.length) } return hex } function hex2bin(hex: string): string { let chars = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"] let bin = "" for (let i = 0; i < hex.length; i++) { bin += fixbin(dec2bin(chars.indexOf(hex[i].toUpperCase())), 4) } return bin } function checksum(input: string) { let state: string[] = [] for (let i = 0; i < 16; i++) { state.push("0") } let bin = "" for (let i = 0; i < input.length; i++) { if (input.charCodeAt(i) < 0 || input.charCodeAt(i) > 50000) { throw new Error("Character at index " + i + " is not a valid character") } bin += dec2bin(input.charCodeAt(i)) } while (bin.length > 0) { state.splice(0, 1) state.push(bin[0]) if (state[0] == "1") { state[8] = state[8] == "1" ? "0" : "1" state[6] = state[6] == "1" ? "0" : "1" state[1] = state[1] == "1" ? "0" : "1" } bin = bin.substring(1, bin.length) } return state.join("") } function hash(input: string, salt: number = 0, iterations: number = 32, len: number = 32, seed?: number): string { if (len*4%16 != 0) { throw new Error("Invalid length") } if (len < 16) { throw new Error("Length must be at least 16") } if (seed != undefined) { if (seed < 0 || seed > 65535) { throw new Error("Invalid seed") } } const padding = generatePadding(len, seed); let hash: number[] = [] for (let i = 0; i < padding.padding.length; i++) { hash[i] = padding.padding[i] } let plus = hash.length for (let i = 0; i < input.length; i++) { if (input.charCodeAt(i) < 0 || input.charCodeAt(i) > 50000) { throw new Error("Character at index " + i + " is not a valid character") } hash[plus+i] = input.charCodeAt(i) } for (let i = 0; i < iterations; i++) { let state = copy(hash) for (let ii = 0; ii < hash.length; ii++) { let temp = state[ii] + (state[ii+1] ? state[ii+1] : state[0]) + salt while (temp > 65536) { temp -= i==0 ? 2411 : (state[ii-1] ? state[ii-1] : state[state.length-1]) } hash[ii] = temp } } const pSeed = fixbin(dec2bin(padding.seed), 16) let sumAdd = "" for (let i = 0; sumAdd.length < 16 && i < padding.padding.length; i++) { sumAdd += padding.padding[i].toString(16) } sumAdd = sumAdd.substring(0, 16).toUpperCase() const sum = checksum(input + sumAdd) let bin = "" for (let i = 0; bin.length < len*4-32; i++) { bin += hash[i] ? fixbin(dec2bin(hash[i]), 16) : "0000000000000000" } const outBin = pSeed + bin + sum const out = bin2hex(outBin) return out } function verifyHash(input: string, inHash: string, salt: number = 0, iterations: number = 32, len: number = 32): verifyOut { if (len*4%16 != 0) { throw new Error("Invalid length") } if (len < 16) { throw new Error("Length must be at least 16") } if (inHash.length != len) { throw new Error("The hash is not " + len + " long") } const hBin = hex2bin(inHash) const pSeed = bin2dec(hBin.substring(0, 16)) const inSum = hBin.substring(hBin.length-16, hBin.length) const padding = generatePadding(len, pSeed); let sumAdd = "" for (let i = 0; sumAdd.length < 16 && i < padding.padding.length; i++) { sumAdd += padding.padding[i].toString(16) } sumAdd = sumAdd.substring(0, 16).toUpperCase() const sum = checksum(input + sumAdd) if (inSum != sum) { return { valid: false, reason: "Checksum does not match" } } const verifyHash = hash(input, salt, iterations, len, pSeed) if (inHash == verifyHash) { return { valid: true, reason: "" } } else { return { valid: false, reason: "Hash does not match" } } } export default { hash, verifyHash } export type { verifyOut }