type padding = { padding: number[] seed: number generator: randomGenerator } 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, generator: generator } } function copy(input: any[], deep: boolean): any[] { let out: any[] = [] for (let i = 0; i < input.length; i++) { if (deep && typeof input[i] == "object") { out[i] = copy(input[i], deep) } else { 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) > 65535) { 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 hashInternal(toHash: number[], len: number, salt: number, first: boolean): number[] { let complete: number[] = [] let count = 0 while (toHash.length > 0) { let inp = copy(toHash, true) for (let i = 0; i < complete.length; i++) { inp.push(complete[i]) } let selected: number[] = [] for (let i = 0; i < len; i++) { selected.push(inp[i]) } inp = inp.slice(len, inp.length) toHash = toHash.slice(len, toHash.length) let curr = 0 for (let i = 0; i < inp.length; i++) { let temp = selected[curr] + inp[i] + salt while (temp >= 65536) { let sub = selected[curr-1] ? selected[curr-1] : selected[selected.length-1] if (first || sub == 0) { sub = 2411 } temp -= sub } selected[curr] = temp curr++ if (curr >= len) { curr = 0 } } for (let i = 0; i < selected.length; i++) { complete.push(selected[i]) } if (!first) { break } else { count++ if (count*len >= 1024) { break } } } if (!first) { for (let i = 0; i < toHash.length; i++) { complete.push(toHash[i]) } } return complete } function hashInternal2(toHash: number[], salt: number, first: boolean): number[] { let state = copy(toHash, true) for (let i = 0; i < toHash.length; i++) { let temp = state[i] + (state[i+1] ? state[i+1] : state[0]) + salt while (temp >= 65536) { let sub = state[i-1] ? state[i-1] : state[state.length-1] if (first || sub == 0) { sub = 2411 } temp -= sub } toHash[i] = temp } return toHash } function hash(input: string, salt: number = 0, 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 (len > 512) { throw new Error("Length can't be more than 512") } if (seed != undefined) { if (seed < 0 || seed > 65535) { throw new Error("Invalid seed") } } const iterations = len*2 const padding = generatePadding(len, seed); let toHash: number[] = [] for (let i = 0; i < padding.padding.length; i++) { toHash.push(padding.padding[i]) } for (let i = 0; i < input.length; i++) { if (input.charCodeAt(i) < 0 || input.charCodeAt(i) > 65535) { throw new Error("Character at index " + i + " is not a valid character") } if (i % len == 0) { toHash.push(padding.generator.next()) } toHash.push(input.charCodeAt(i)) } while ((toHash.length % len != 0) || (toHash.length < iterations)) { toHash.push(padding.generator.next()) } for (let it = 0; it < iterations; it++) { toHash = hashInternal(toHash, len, salt, it == 0) toHash = hashInternal2(toHash, salt, it == 0) } const pBin = fixbin(dec2bin(padding.seed), 16) let dataBin = "" for (let i = 0; dataBin.length < len*4-32; i++) { dataBin += fixbin(dec2bin(toHash[i]), 16) } let hash = bin2hex(pBin + dataBin) const crc = checksum(hash) hash += bin2hex(crc) return hash } function verifyHash(input: string, inHash: string, salt: number = 0, 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 (len > 512) { throw new Error("Length can't be more than 512") } 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 inCRC = hBin.substring(hBin.length-16, hBin.length) const crc = checksum(inHash.substring(0, inHash.length-4)) if (inCRC != crc) { return { valid: false, reason: "Checksum does not match" } } const verifyHash = hash(input, salt, 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 }