IGA/IGA.ts

289 lines
No EOL
7.7 KiB
TypeScript

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
}