277 lines
No EOL
8.5 KiB
TypeScript
277 lines
No EOL
8.5 KiB
TypeScript
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 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 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) > 50000) {
|
|
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)
|
|
}
|
|
}
|
|
let hashes: string[] = []
|
|
for (let i = 0; i < dataBins.length; i++) {
|
|
let hsh = bin2hex(pBin+dataBins[i])
|
|
const crc = checksum(hsh)
|
|
hsh += bin2hex(crc)
|
|
hashes.push(hsh)
|
|
}
|
|
if (hashes.length == 1) {
|
|
return hashes[0]
|
|
} else {
|
|
return hash(hashes.join(""), salt, len, padding.seed)
|
|
}
|
|
}
|
|
|
|
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
|
|
} |