@li0ard/kupyna
Version:
Kupyna (DSTU 7564:2014) hash function in pure TypeScript
161 lines (160 loc) • 5.45 kB
JavaScript
import { T0, T1, T2, T3, T4, T5, T6, T7 } from "../const";
import { bytesToNumberLE, bytesToUint64s, concatBytes, numberToBytesLE, uint64sToBytes } from "../utils";
const T = [T0, T1, T2, T3, T4, T5, T6, T7];
const r = 0x00f0f0f0f0f0f0f3n;
/** Abstract class for 256/512 bit */
export class KupynaBase {
blockLen;
/** State size */
stSize;
/** Threshold for padding */
threshold;
s;
x;
nx;
len;
constructor(blockLen) {
this.blockLen = blockLen;
this.stSize = (blockLen / 2) / 4;
this.threshold = blockLen - 12;
this.s = new BigUint64Array(this.stSize);
this.x = new Uint8Array(blockLen);
this.nx = 0;
this.len = 0n;
let s1 = new Uint8Array(8);
s1[0] = blockLen;
this.s[0] = bytesToNumberLE(s1);
}
/** Update hash buffer */
update(data) {
const nn = data.length;
this.len += BigInt(nn);
if (this.nx > 0) {
const available = this.blockLen - this.nx;
const n = Math.min(available, data.length);
this.x.set(data.slice(0, n), this.nx);
this.nx += n;
if (this.nx === this.blockLen) {
this.block(this.x);
this.nx = 0;
}
data = data.slice(n);
}
while (data.length >= this.blockLen) {
this.block(data.slice(0, this.blockLen));
this.nx = 0;
data = data.slice(this.blockLen);
}
if (data.length > 0) {
this.x.set(data, 0);
this.nx = data.length;
}
return this;
}
digest() { return this.clone().final(); }
final() {
this.x[this.nx] = 0x80;
this.nx++;
const fillBytes = (start) => {
const available = this.x.length - start;
if (available > 0)
this.x.fill(0, start, start + available);
};
if (this.nx > this.threshold) {
fillBytes(this.nx);
this.block(this.x);
this.nx = 0;
}
fillBytes(this.nx);
this.x.set(numberToBytesLE(this.len * 8n, 12), this.threshold);
this.block(this.x);
this.outputTransform();
return uint64sToBytes(this.s).slice(this.outputLen);
}
byte(a) { return Number(a & 0xffn); }
G(x, y) {
for (let i = 0; i < this.stSize; i++) {
let result = 0n;
for (let j = 0; j < 8; j++) {
const index = (i - this.offsets[j] + this.stSize) % this.stSize;
result ^= T[j][this.byte(x[index] >> (BigInt(j) * 8n))];
}
y[i] = result;
}
}
G1(x, y, round) {
for (let i = 0; i < this.stSize; i++) {
let result = 0n;
for (let j = 0; j < 8; j++) {
const index = (i - this.offsets[j] + this.stSize) % this.stSize;
result ^= T[j][this.byte(x[index] >> (BigInt(j) * 8n))];
}
y[i] = result ^ BigInt(i << 4) ^ round;
}
}
G2(x, y, round) {
for (let i = 0; i < this.stSize; i++) {
let result = 0n;
for (let j = 0; j < 8; j++) {
const index = (i - this.offsets[j] + this.stSize) % this.stSize;
result ^= T[j][this.byte(x[index] >> (BigInt(j) * 8n))];
}
y[i] = result + (r ^ ((BigInt((this.stSize - 1 - i) * 16) ^ round) << 56n));
}
}
P(x, y, round) {
for (let idx = 0n; idx < BigInt(this.stSize); idx++)
x[Number(idx)] ^= (idx << 4n) ^ round;
this.G1(x, y, round + 1n);
this.G(y, x);
}
Q(x, y, round) {
for (let j = 0n; j < BigInt(this.stSize); j++)
x[Number(j)] += (r ^ ((((BigInt(this.stSize - 1) - j) * 0x10n) ^ round) << 56n));
this.G2(x, y, round + 1n);
this.G(y, x);
}
outputTransform() {
let t1 = new BigUint64Array(this.s), t2 = new BigUint64Array(this.stSize);
for (let r = 0n; r < BigInt(this.rounds); r += 2n)
this.P(t1, t2, r);
for (let column = 0; column < this.stSize; column++)
this.s[column] ^= t1[column];
}
transform(b) {
let AQ1 = new BigUint64Array(this.stSize);
let AP1 = new BigUint64Array(this.stSize);
let tmp = new BigUint64Array(this.stSize);
for (let column = 0; column < this.stSize; column++) {
AP1[column] = this.s[column] ^ b[column];
AQ1[column] = b[column];
}
for (let r = 0n; r < BigInt(this.rounds); r += 2n) {
this.P(AP1, tmp, r);
this.Q(AQ1, tmp, r);
}
for (let column = 0; column < this.stSize; column++)
this.s[column] ^= AP1[column] ^ AQ1[column];
}
block(b) { return this.transform(bytesToUint64s(b)); }
}
/** Abstract class for derived versions (48/304/384 bit) */
export class KupynaDerived {
hash;
slice;
outputLen;
blockLen;
buffer = new Uint8Array();
constructor(hash, slice) {
this.hash = hash;
this.slice = slice;
this.outputLen = Math.abs(slice);
this.blockLen = hash().blockLen;
}
update(data) {
this.buffer = concatBytes(this.buffer, data);
return this;
}
digest() { return this.clone().final(); }
final() { return this.hash().update(this.buffer).digest().slice(this.slice); }
}