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: 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 xor(a: string, b: string): string { let out = "" for (let i = 0; i < a.length; i++) { if (a[i] == b[i]) { out += "0" } else { out += "1" } } return out } 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 hashBlock(block: number[][], salt: number, iterations: number): number[][] { for (let it = 0; it < iterations; it++) { let state: number[][] = copy(block, true) //Add columns for (let i = 0; i < block.length; i++) { for (let ii = 0; ii < block[i].length; ii++) { let temp = state[i][ii]+(state[i][ii+1] ? state[i][ii+1] : (state[i+1] ? state[i+1][0] : state[0][0])) while (temp > 65536) { temp -= it==0 ? 2411 : state[i][ii-1] ? state[i][ii-1] : (state[i-1] ? state[i-1][state[i-1].length-1] : state[state.length-1][state[state.length-1].length-1]) } block[i][ii] = temp } } state = copy(block, true) //Add rows for (let i = 0; i < block.length; i++) { for (let ii = 0; ii < block[i].length; ii++) { let temp = state[i][ii]+(state[i+1] ? state[i+1][ii] : (state[0][ii+1] ? state[0][ii+1] : state[0][0])) while (temp > 65536) { temp -= it==0 ? 2411 : state[i-1] ? state[i-1][ii] : (state[state.length-1][ii-1] ? state[state.length-1][ii-1] : state[state.length-1][state[state.length-1].length-1]) } block[i][ii] = temp } } } return block } 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 must be at most 512") } if (seed != undefined) { if (seed < 0 || seed > 65535) { throw new Error("Invalid seed") } } const iterations = len*2 const padding = generatePadding(len, seed); let blocks: number[][][] = [[[]]] // len x 16 for (let ip = 0; ip < padding.padding.length; ip++) { if (blocks[blocks.length-1][blocks[blocks.length-1].length-1].length == len) { blocks[blocks.length-1].push([]) } blocks[blocks.length-1][blocks[blocks.length-1].length-1].push(padding.padding[ip]) } 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 (blocks[blocks.length-1].length == 16 && blocks[blocks.length-1][blocks[blocks.length-1].length-1].length == len) { blocks.push([[]]) for (let ip = 0; ip < padding.padding.length; ip++) { if (blocks[blocks.length-1][blocks[blocks.length-1].length-1].length == len) { blocks[blocks.length-1].push([]) } blocks[blocks.length-1][blocks[blocks.length-1].length-1].push(padding.padding[ip]) } } if (blocks[blocks.length-1][blocks[blocks.length-1].length-1].length == len) { blocks[blocks.length-1].push([]) } blocks[blocks.length-1][blocks[blocks.length-1].length-1].push(input.charCodeAt(i)) } let pPos = 0 while (blocks[blocks.length-1].length != 16 || blocks[blocks.length-1][blocks[blocks.length-1].length-1].length != len) { if (blocks[blocks.length-1][blocks[blocks.length-1].length-1].length == len) { blocks[blocks.length-1].push([]) } blocks[blocks.length-1][blocks[blocks.length-1].length-1].push(padding.padding[pPos]) pPos++ if (pPos >= padding.padding.length) { pPos = 0 } } for (let i = 0; i < blocks.length; i++) { blocks[i] = hashBlock(blocks[i], salt, iterations) } const pBin = fixbin(dec2bin(padding.seed), 16) let dataBins: string[] = [] for (let i = 0; i < blocks.length; i++) { dataBins[i] = "" for (let ii = 0; dataBins[i].length < len*4-32; ii++) { dataBins[i] += fixbin(dec2bin(blocks[i][0][ii]), 16) } } while (dataBins.length > 1) { dataBins[0] = xor(dataBins[0], dataBins[1]) dataBins.splice(1, 1) } let hash = bin2hex(pBin + dataBins[0]) 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 must be at most 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 }