From 59a5600e21ef5a0caba10dfaf7414bd65310315b Mon Sep 17 00:00:00 2001 From: Lizard Date: Mon, 4 Aug 2025 01:03:33 +0600 Subject: [PATCH] feat: add 48 bit and 304 bit modes, tests for 384 bit --- README.md | 16 +- jsr.json | 2 +- package.json | 2 +- src/const.ts | 49 +++++ src/index.ts | 396 +------------------------------------ src/kmac/index.ts | 32 ++- src/modes/kupyna256_48.ts | 201 +++++++++++++++++++ src/modes/kupyna3xx_512.ts | 238 ++++++++++++++++++++++ tests/304.test.ts | 25 +++ tests/384.test.ts | 22 ++- tests/48.test.ts | 19 ++ tests/index.test.ts | 32 ++- 12 files changed, 617 insertions(+), 417 deletions(-) create mode 100644 src/modes/kupyna256_48.ts create mode 100644 src/modes/kupyna3xx_512.ts create mode 100644 tests/304.test.ts create mode 100644 tests/48.test.ts diff --git a/README.md b/README.md index 505ca65..f175e11 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,14 @@ bunx jsr i @li0ard/kupyna ``` ## Supported modes -- [x] Hash function -- [x] KMAC (MAC) +- [x] Kupyna 48 bit +- [x] Kupyna 256 bit +- [x] Kupyna 304 bit +- [x] Kupyna 384 bit +- [x] Kupyna 512 bit +- [x] Kupyna KMAC 256 bit +- [x] Kupyna KMAC 384 bit +- [x] Kupyna KMAC 512 bit ## Features - Provides simple and modern API @@ -41,6 +47,12 @@ import { Kupyna256 } from "@li0ard/kupyna" let hash = new Kupyna256() hash.update(new TextEncoder().encode("hello world")) console.log(hash.digest()) + +// OR + +import { kupyna256 } from "@li0ard/kupyna" + +console.log(kupyna256(new TextEncoder().encode("hello world"))) ``` ### Compute KMAC diff --git a/jsr.json b/jsr.json index eaa88c9..9dfa782 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@li0ard/kupyna", - "version": "0.1.0", + "version": "0.1.1", "exports": "./src/index.ts", "publish": { "include": [ diff --git a/package.json b/package.json index 9b811b9..f60c6b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@li0ard/kupyna", - "version": "0.1.0", + "version": "0.1.1", "main": "dist/index.js", "types": "dist/index.d.ts", "type": "module", diff --git a/src/const.ts b/src/const.ts index 9d271a2..0970ef2 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,3 +1,45 @@ +/** + * Kupyna and KMAC abstract class (`@noble/hashes`-like methods) + * @abstract + */ +export abstract class KupynaAndKMAC { + /** Output length */ + abstract outputLen: number; + /** Block length */ + abstract blockLen: number; + + /** Clone hash instance */ + abstract clone(): T; + + /** Update hash buffer */ + abstract update(p: Uint8Array): T + + /** Finalize hash computation and return result as Uint8Array */ + abstract digest(): Uint8Array; +} + +/** + * Kupyna abstract class + * @abstract + */ +export abstract class Kupyna extends KupynaAndKMAC { + abstract s: BigUint64Array; + abstract x: Uint8Array; + abstract nx: number; + abstract len: bigint; +} + +/** + * Kupyna KMAC abstact + * @abstract + */ +export abstract class KupynaKMAC extends KupynaAndKMAC { + abstract h: H; + abstract ik: Uint8Array; + abstract len: bigint; +} + +/** Replacement table No. 1 for nonlinear bi-objective reflection */ export const T0: Readonly = new BigUint64Array([ 0xa832a829d77f9aa8n, 0x4352432297d41143n, 0x5f3e5fc2df80615fn, 0x061e063014121806n, 0x6bda6b7f670cb16bn, 0x75bc758f2356c975n, 0x6cc16c477519ad6cn, 0x592059f2cb927959n, @@ -65,6 +107,7 @@ export const T0: Readonly = new BigUint64Array([ 0x7c917cc71569ed7cn, 0x8b9d8b2c1d96168bn, 0x5613568ae9bf4556n, 0x80ba807427a73a80n, ]); +/** Replacement table No. 2 for nonlinear bi-objective reflection */ export const T1: Readonly = new BigUint64Array([ 0xd1ce3e9e501fcecen, 0x6dbbb1bd06d6bbbbn, 0x60eb0b40ab8bebebn, 0xe092e44bd9729292n, 0x65ea0346ac8feaean, 0xc0cb16804b0bcbcbn, 0x5f13986a794c1313n, 0xe2c146bc7d23c1c1n, @@ -132,6 +175,7 @@ export const T1: Readonly = new BigUint64Array([ 0xfcc776a86f3bc7c7n, 0xe7c04eba7a27c0c0n, 0x8d2955f6dfa42929n, 0xacd7f6c81f7bd7d7n, ]); +/** Replacement table No. 3 for nonlinear bi-objective reflection */ export const T2: Readonly = new BigUint64Array([ 0x93ec4dde769393e5n, 0xd986ec3543d9d99an, 0x9aa47be1529a9ac8n, 0xb5c1992ceeb5b55bn, 0x98b477ef5a9898c2n, 0x220dccee882222aan, 0x451283c60945454cn, 0xfcb332ced7fcfc2bn, @@ -199,6 +243,7 @@ export const T2: Readonly = new BigUint64Array([ 0xf8932ad2c7f8f83fn, 0x0c602824300c0c3cn, 0x74872551cd7474b9n, 0x671f4f28816767e6n, ]); +/** Replacement table No. 4 for nonlinear bi-objective reflection */ export const T3: Readonly = new BigUint64Array([ 0x676d05bd6868d568n, 0x1c09840e8d8d838dn, 0x1e864c0fcacac5can, 0x52b3fe294d4d644dn, 0xbf3744d17373a273n, 0x62a7ec314b4b7a4bn, 0x4ab9f7254e4e6b4en, 0x4dfcd6a82a2a822an, @@ -266,6 +311,7 @@ export const T3: Readonly = new BigUint64Array([ 0x82efb84157571657n, 0xd85a416c1b1b771bn, 0x537a9aa7e0e047e0n, 0x2f5b3a996161f861n, ]); +/** Replacement table No. 5 for nonlinear bi-objective reflection */ export const T4: Readonly = new BigUint64Array([ 0xd77f9aa8a832a829n, 0x97d4114343524322n, 0xdf80615f5f3e5fc2n, 0x14121806061e0630n, 0x670cb16b6bda6b7fn, 0x2356c97575bc758fn, 0x7519ad6c6cc16c47n, 0xcb927959592059f2n, @@ -333,6 +379,7 @@ export const T4: Readonly = new BigUint64Array([ 0x1569ed7c7c917cc7n, 0x1d96168b8b9d8b2cn, 0xe9bf45565613568an, 0x27a73a8080ba8074n, ]); +/** Replacement table No. 6 for nonlinear bi-objective reflection */ export const T5: Readonly = new BigUint64Array([ 0x501fceced1ce3e9en, 0x06d6bbbb6dbbb1bdn, 0xab8bebeb60eb0b40n, 0xd9729292e092e44bn, 0xac8feaea65ea0346n, 0x4b0bcbcbc0cb1680n, 0x794c13135f13986an, 0x7d23c1c1e2c146bcn, @@ -400,6 +447,7 @@ export const T5: Readonly = new BigUint64Array([ 0x6f3bc7c7fcc776a8n, 0x7a27c0c0e7c04eban, 0xdfa429298d2955f6n, 0x1f7bd7d7acd7f6c8n, ]); +/** Replacement table No. 7 for nonlinear bi-objective reflection */ export const T6: Readonly = new BigUint64Array([ 0x769393e593ec4dden, 0x43d9d99ad986ec35n, 0x529a9ac89aa47be1n, 0xeeb5b55bb5c1992cn, 0x5a9898c298b477efn, 0x882222aa220dcceen, 0x0945454c451283c6n, 0xd7fcfc2bfcb332cen, @@ -467,6 +515,7 @@ export const T6: Readonly = new BigUint64Array([ 0xc7f8f83ff8932ad2n, 0x300c0c3c0c602824n, 0xcd7474b974872551n, 0x816767e6671f4f28n, ]); +/** Replacement table No. 8 for nonlinear bi-objective reflection */ export const T7: Readonly = new BigUint64Array([ 0x6868d568676d05bdn, 0x8d8d838d1c09840en, 0xcacac5ca1e864c0fn, 0x4d4d644d52b3fe29n, 0x7373a273bf3744d1n, 0x4b4b7a4b62a7ec31n, 0x4e4e6b4e4ab9f725n, 0x2a2a822a4dfcd6a8n, diff --git a/src/index.ts b/src/index.ts index 4ab02d4..e34b2ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,392 +1,4 @@ -import { T0, T1, T2, T3, T4, T5, T6, T7 } from "./const"; -import { bytesToUint64s, uint64sToBytes, bytesToNumberLE, concatBytes, numberToBytesLE } from "./utils"; - -/** - * Kupyna abstract class (short implementation for 384 bit) - * @abstract - */ -export abstract class KupynaShort { - /** Output length */ - abstract outputLen: number; - /** Block length */ - abstract blockLen: number; - - /** Clone hash instance */ - abstract clone(): T; - - /** Update hash buffer */ - abstract update(p: Uint8Array): T - - /** Finalize hash computation and return result as Uint8Array */ - abstract digest(): Uint8Array; -} - -/** - * Kupyna abstract class - * @abstract - */ -export abstract class Kupyna extends KupynaShort { - abstract s: BigUint64Array; - abstract x: Uint8Array; - abstract nx: number; - abstract len: bigint; -} - -/** Kupyna 256 bit */ -export class Kupyna256 implements Kupyna { - outputLen = 32; - blockLen = 64; - s: BigUint64Array; - x: Uint8Array; - nx: number; - len: bigint; - - /** Kupyna 256 bit */ - constructor() { - this.s = new BigUint64Array(8); - this.x = new Uint8Array(this.blockLen); - this.nx = 0; - this.len = 0n; - - let s1 = new Uint8Array(8); - s1[0] = this.blockLen; - this.s[0] = bytesToNumberLE(s1); - } - /** Create hash instance */ - public static create(): Kupyna256 { return new Kupyna256(); } - - private byte(a: bigint): number { return Number(a & 0xFFn); } - - _cloneInto(to?: Kupyna256): Kupyna256 { - to ||= new Kupyna256(); - to.s = new BigUint64Array(this.s); - to.x = new Uint8Array(this.x); - to.nx = this.nx; - to.len = this.len; - - return to; - } - clone(): Kupyna256 { return this._cloneInto(); } - - update(data: Uint8Array): Kupyna256 { - 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(): Uint8Array { return this.clone().final() } - private final(): Uint8Array { - this.x[this.nx] = 0x80; - this.nx++; - - const fillBytes = (start: number) => { - const available = this.x.length - start; - if (available > 0) this.x.fill(0, start, start + available); - } - - if (this.nx > 52) { - fillBytes(this.nx); - this.block(this.x); - this.nx = 0; - } - - fillBytes(this.nx); - this.x.set(numberToBytesLE(this.len * 8n, 8), 52); - this.block(this.x); - this.outputTransform(); - - return uint64sToBytes(this.s).slice(this.outputLen); - } - - private G(x: BigUint64Array, y: BigUint64Array) { - const T = [T0, T1, T2, T3, T4, T5, T6, T7]; - for (let i = 0; i < 8; i++) { - let result = 0n; - for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - j + 8) % 8] >> (BigInt(j) * 8n))]; - y[i] = result; - } - } - - private G1(x: BigUint64Array, y: BigUint64Array, round: bigint) { - const T = [T0, T1, T2, T3, T4, T5, T6, T7]; - for (let i = 0; i < 8; i++) { - let result = 0n; - for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - j + 8) % 8] >> (BigInt(j) * 8n))]; - y[i] = result ^ BigInt(i << 4) ^ round; - } - } - - private G2(x: BigUint64Array, y: BigUint64Array, round: bigint) { - let r = 0x00F0F0F0F0F0F0F3n; - const T = [T0, T1, T2, T3, T4, T5, T6, T7]; - for (let i = 0; i < 8; i++) { - let result = 0n; - for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - j + 8) % 8] >> (BigInt(j) * 8n))]; - y[i] = result + (r ^ ((BigInt((7 - i) * 16) ^ round) << 56n)); - } - } - - private P(x: BigUint64Array, y: BigUint64Array, round: bigint) { - for(let idx = 0n; idx < 8n; idx++) x[Number(idx)] ^= (idx << 4n) ^ round; - this.G1(x, y, round+1n); - this.G(y, x); - } - - private Q(x: BigUint64Array, y: BigUint64Array, round: bigint) { - let r = 0x00F0F0F0F0F0F0F3n; - for(let j = 0n; j < 8n; j++) x[Number(j)] += (r ^ ((((7n - j) * 0x10n) ^ round) << 56n)); - this.G2(x, y, round+1n); - this.G(y, x); - } - - private outputTransform() { - let t1 = new BigUint64Array(this.s), - t2 = new BigUint64Array(8); - - for(let r = 0n; r < 10n; r += 2n) this.P(t1, t2, r); - for(let column = 0; column < 8; column++) this.s[column] ^= t1[column]; - } - - private transform(b: BigUint64Array) { - let AQ1 = new BigUint64Array(8); - let AP1 = new BigUint64Array(8); - let tmp = new BigUint64Array(8); - - for(let column = 0; column < 8; column++) { - AP1[column] = this.s[column] ^ b[column]; - AQ1[column] = b[column]; - } - - for(let r = 0n; r < 10n; r += 2n) { - this.P(AP1, tmp, r); - this.Q(AQ1, tmp, r); - } - - for(let column = 0; column < 8; column++) this.s[column] ^= AP1[column] ^ AQ1[column]; - } - private block(b: Uint8Array): void { return this.transform(bytesToUint64s(b)); } -} - -/** Kupyna 512 bit */ -export class Kupyna512 implements Kupyna { - outputLen = 64; - blockLen = 128; - s: BigUint64Array; - x: Uint8Array; - nx: number; - len: bigint; - - /** Kupyna 512 bit */ - constructor() { - this.s = new BigUint64Array(16); - this.x = new Uint8Array(this.blockLen); - this.nx = 0; - this.len = 0n; - - let s1 = new Uint8Array(8); - s1[0] = this.blockLen; - this.s[0] = bytesToNumberLE(s1); - } - /** Create hash instance */ - public static create(): Kupyna512 { return new Kupyna512(); } - - private byte(a: bigint) { return Number(a & 0xFFn); } - - _cloneInto(to?: Kupyna512): Kupyna512 { - to ||= new Kupyna512(); - to.s = new BigUint64Array(this.s); - to.x = new Uint8Array(this.x); - to.nx = this.nx; - to.len = this.len; - - return to; - } - clone(): Kupyna512 { return this._cloneInto(); } - - update(data: Uint8Array): Kupyna512 { - 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(): Uint8Array { return this.clone().final(); } - private final(): Uint8Array { - this.x[this.nx] = 0x80; - this.nx++; - - const fillBytes = (start: number) => { - const available = this.x.length - start; - if (available > 0) this.x.fill(0, start, start + available); - }; - - if (this.nx > 116) { - fillBytes(this.nx); - this.block(this.x); - this.nx = 0; - } - - fillBytes(this.nx); - this.x.set(numberToBytesLE(this.len * 8n, 8), 116); - this.block(this.x); - this.outputTransform(); - - return uint64sToBytes(this.s).slice(this.outputLen); - } - - private G(x: BigUint64Array, y: BigUint64Array) { - const T = [T0, T1, T2, T3, T4, T5, T6, T7]; - const offset = [0, 1, 2, 3, 4, 5, 6, 11]; - - for (let i = 0; i < 16; i++) { - let result = 0n; - for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - offset[j] + 16) % 16] >> (BigInt(j) * 8n))]; - y[i] = result; - } - } - - private G1(x: BigUint64Array, y: BigUint64Array, round: bigint) { - const T = [T0, T1, T2, T3, T4, T5, T6, T7]; - const offset = [0, 1, 2, 3, 4, 5, 6, 11]; - - for (let i = 0; i < 16; i++) { - let result = 0n; - for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - offset[j] + 16) % 16] >> (BigInt(j) * 8n))]; - y[i] = result ^ BigInt(i << 4) ^ round; - } - } - - private G2(x: BigUint64Array, y: BigUint64Array, round: bigint) { - let r = 0x00F0F0F0F0F0F0F3n; - const T = [T0, T1, T2, T3, T4, T5, T6, T7]; - const offset = [0, 1, 2, 3, 4, 5, 6, 11]; - for (let i = 0; i < 16; i++) { - let result = 0n; - for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - offset[j] + 16) % 16] >> (BigInt(j) * 8n))]; - y[i] = result + (r ^ ((BigInt((15 - i) * 16) ^ round) << 56n)); - } - } - - private P(x: BigUint64Array, y: BigUint64Array, round: bigint) { - for(let idx = 0n; idx < 16n; idx++) x[Number(idx)] ^= (idx << 4n) ^ round; - this.G1(x, y, round+1n); - this.G(y, x); - } - - private Q(x: BigUint64Array, y: BigUint64Array, round: bigint) { - let r = 0x00F0F0F0F0F0F0F3n; - for(let j = 0n; j < 16n; j++) x[Number(j)] += (r ^ ((((15n - j) * 0x10n) ^ round) << 56n)); - this.G2(x, y, round+1n); - this.G(y, x); - } - - private outputTransform() { - let t1 = new BigUint64Array(this.s), - t2 = new BigUint64Array(16); - - for(let r = 0n; r < 14n; r += 2n) this.P(t1, t2, r); - for(let column = 0; column < 16; column++) this.s[column] ^= t1[column]; - } - - private transform(b: BigUint64Array) { - let AQ1 = new BigUint64Array(16); - let AP1 = new BigUint64Array(16); - let tmp = new BigUint64Array(16); - - for(let column = 0; column < 16; column++) { - AP1[column] = this.s[column] ^ b[column]; - AQ1[column] = b[column]; - } - - for(let r = 0n; r < 14n; r += 2n) { - this.P(AP1, tmp, r); - this.Q(AQ1, tmp, r); - } - - for(let column = 0; column < 16; column++) this.s[column] ^= AP1[column] ^ AQ1[column]; - } - private block(b: Uint8Array) { return this.transform(bytesToUint64s(b)); } -} - -/** Kupyna 384 bit version, potentially broken */ -export class Kupyna384 implements KupynaShort { - outputLen: number = 48; - blockLen: number = 128; - buffer: Uint8Array = new Uint8Array(); - - /** Kupyna 384 bit version, potentially broken */ - constructor() {} - /** Create hash instance */ - public static create(): Kupyna384 { return new Kupyna384(); } - - _cloneInto(to?: Kupyna384): Kupyna384 { - to ||= new Kupyna384(); - to.buffer = new Uint8Array(this.buffer); - return to; - } - clone(): Kupyna384 { return this._cloneInto(); } - - update(data: Uint8Array): this { - this.buffer = concatBytes(this.buffer, data); - return this; - } - - digest(): Uint8Array { return this.clone().final(); } - private final(): Uint8Array { return new Kupyna512().update(this.buffer).digest().slice(16); } -} - -export const kupyna256 = (data: Uint8Array): Uint8Array => new Kupyna256().update(data).digest(); -export const kupyna384 = (data: Uint8Array): Uint8Array => new Kupyna384().update(data).digest(); -export const kupyna512 = (data: Uint8Array): Uint8Array => new Kupyna512().update(data).digest(); -export * from "./kmac/index"; \ No newline at end of file +export * from "./const"; +export * from "./kmac/index"; +export * from "./modes/kupyna256_48"; +export * from "./modes/kupyna3xx_512"; \ No newline at end of file diff --git a/src/kmac/index.ts b/src/kmac/index.ts index 90d899f..fed1465 100644 --- a/src/kmac/index.ts +++ b/src/kmac/index.ts @@ -1,26 +1,12 @@ +import { type KupynaKMAC } from "../const"; import { Kupyna256, Kupyna384, Kupyna512 } from "../index"; import { uint64sToBytes } from "../utils"; import { dpad, kpad32, kpad48, kpad64 } from "./pad"; -/** - * Kupyna KMAC abstact - * @abstract - */ -export abstract class KupynaKMAC { - abstract h: H; - abstract ik: Uint8Array; - abstract len: bigint; - abstract _cloneInto(to?: T): T; - /** Clone KMAC instance */ - abstract clone(): T; - /** Update KMAC buffer */ - abstract update(data: Uint8Array): T; - /** Finalize KMAC computation and return result as Uint8Array */ - abstract digest(): Uint8Array; -} - /** Kupyna KMAC (256 bit version) */ export class KupynaKMAC256 implements KupynaKMAC { + outputLen: number; + blockLen: number; h: Kupyna256; ik: Uint8Array; len: bigint; @@ -31,6 +17,8 @@ export class KupynaKMAC256 implements KupynaKMAC { constructor(public key: Uint8Array) { this.len = 0n; this.h = Kupyna256.create(); + this.outputLen = this.h.outputLen + this.blockLen = this.h.blockLen if(key.length != this.h.outputLen) throw new Error("Invalid key length"); this.h.update(key); @@ -71,7 +59,9 @@ export class KupynaKMAC256 implements KupynaKMAC { } /** Kupyna KMAC (512 bit version) */ -export class KupynaKMAC512 implements KupynaKMAC { +export class KupynaKMAC512 implements KupynaKMAC { + outputLen: number; + blockLen: number; h: Kupyna512; ik: Uint8Array; len: bigint; @@ -82,6 +72,8 @@ export class KupynaKMAC512 implements KupynaKMAC { constructor(public key: Uint8Array) { this.len = 0n; this.h = Kupyna512.create(); + this.outputLen = this.h.outputLen + this.blockLen = this.h.blockLen if(key.length != this.h.outputLen) throw new Error("Invalid key length") this.h.update(key); @@ -123,6 +115,8 @@ export class KupynaKMAC512 implements KupynaKMAC { /** Kupyna KMAC (384 bit version) */ export class KupynaKMAC384 implements KupynaKMAC { + outputLen: number; + blockLen: number; h: Kupyna384; ik: Uint8Array; len: bigint; @@ -133,6 +127,8 @@ export class KupynaKMAC384 implements KupynaKMAC { constructor(public key: Uint8Array) { this.len = 0n; this.h = Kupyna384.create(); + this.outputLen = this.h.outputLen + this.blockLen = this.h.blockLen if(key.length != this.h.outputLen) throw new Error("Invalid key length") this.h.update(key); diff --git a/src/modes/kupyna256_48.ts b/src/modes/kupyna256_48.ts new file mode 100644 index 0000000..3dfcce1 --- /dev/null +++ b/src/modes/kupyna256_48.ts @@ -0,0 +1,201 @@ +import { T0, T1, T2, T3, T4, T5, T6, T7, type Kupyna, type KupynaAndKMAC } from "../const"; +import { bytesToNumberLE, numberToBytesLE, uint64sToBytes, bytesToUint64s, concatBytes } from "../utils"; + +/** Kupyna 256 bit */ +export class Kupyna256 implements Kupyna { + outputLen = 32; + blockLen = 64; + s: BigUint64Array; + x: Uint8Array; + nx: number; + len: bigint; + + /** Kupyna 256 bit */ + constructor() { + this.s = new BigUint64Array(8); + this.x = new Uint8Array(this.blockLen); + this.nx = 0; + this.len = 0n; + + let s1 = new Uint8Array(8); + s1[0] = this.blockLen; + this.s[0] = bytesToNumberLE(s1); + } + /** Create hash instance */ + public static create(): Kupyna256 { return new Kupyna256(); } + + private byte(a: bigint): number { return Number(a & 0xFFn); } + + _cloneInto(to?: Kupyna256): Kupyna256 { + to ||= new Kupyna256(); + to.s = new BigUint64Array(this.s); + to.x = new Uint8Array(this.x); + to.nx = this.nx; + to.len = this.len; + + return to; + } + clone(): Kupyna256 { return this._cloneInto(); } + + update(data: Uint8Array): Kupyna256 { + 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(): Uint8Array { return this.clone().final() } + private final(): Uint8Array { + this.x[this.nx] = 0x80; + this.nx++; + + const fillBytes = (start: number) => { + const available = this.x.length - start; + if (available > 0) this.x.fill(0, start, start + available); + } + + if (this.nx > 52) { + fillBytes(this.nx); + this.block(this.x); + this.nx = 0; + } + + fillBytes(this.nx); + this.x.set(numberToBytesLE(this.len * 8n, 8), 52); + this.block(this.x); + this.outputTransform(); + + return uint64sToBytes(this.s).slice(this.outputLen); + } + + private G(x: BigUint64Array, y: BigUint64Array) { + const T = [T0, T1, T2, T3, T4, T5, T6, T7]; + for (let i = 0; i < 8; i++) { + let result = 0n; + for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - j + 8) % 8] >> (BigInt(j) * 8n))]; + y[i] = result; + } + } + + private G1(x: BigUint64Array, y: BigUint64Array, round: bigint) { + const T = [T0, T1, T2, T3, T4, T5, T6, T7]; + for (let i = 0; i < 8; i++) { + let result = 0n; + for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - j + 8) % 8] >> (BigInt(j) * 8n))]; + y[i] = result ^ BigInt(i << 4) ^ round; + } + } + + private G2(x: BigUint64Array, y: BigUint64Array, round: bigint) { + let r = 0x00F0F0F0F0F0F0F3n; + const T = [T0, T1, T2, T3, T4, T5, T6, T7]; + for (let i = 0; i < 8; i++) { + let result = 0n; + for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - j + 8) % 8] >> (BigInt(j) * 8n))]; + y[i] = result + (r ^ ((BigInt((7 - i) * 16) ^ round) << 56n)); + } + } + + private P(x: BigUint64Array, y: BigUint64Array, round: bigint) { + for(let idx = 0n; idx < 8n; idx++) x[Number(idx)] ^= (idx << 4n) ^ round; + this.G1(x, y, round+1n); + this.G(y, x); + } + + private Q(x: BigUint64Array, y: BigUint64Array, round: bigint) { + let r = 0x00F0F0F0F0F0F0F3n; + for(let j = 0n; j < 8n; j++) x[Number(j)] += (r ^ ((((7n - j) * 0x10n) ^ round) << 56n)); + this.G2(x, y, round+1n); + this.G(y, x); + } + + private outputTransform() { + let t1 = new BigUint64Array(this.s), + t2 = new BigUint64Array(8); + + for(let r = 0n; r < 10n; r += 2n) this.P(t1, t2, r); + for(let column = 0; column < 8; column++) this.s[column] ^= t1[column]; + } + + private transform(b: BigUint64Array) { + let AQ1 = new BigUint64Array(8); + let AP1 = new BigUint64Array(8); + let tmp = new BigUint64Array(8); + + for(let column = 0; column < 8; column++) { + AP1[column] = this.s[column] ^ b[column]; + AQ1[column] = b[column]; + } + + for(let r = 0n; r < 10n; r += 2n) { + this.P(AP1, tmp, r); + this.Q(AQ1, tmp, r); + } + + for(let column = 0; column < 8; column++) this.s[column] ^= AP1[column] ^ AQ1[column]; + } + private block(b: Uint8Array): void { return this.transform(bytesToUint64s(b)); } +} + +/** Kupyna 48 bit version */ +export class Kupyna48 implements KupynaAndKMAC { + outputLen: number = 6; + blockLen: number = 64; + buffer: Uint8Array = new Uint8Array(); + + /** Kupyna 48 bit version */ + constructor() {} + /** Create hash instance */ + public static create(): Kupyna48 { return new Kupyna48(); } + + _cloneInto(to?: Kupyna48): Kupyna48 { + to ||= new Kupyna48(); + to.buffer = new Uint8Array(this.buffer); + return to; + } + clone(): Kupyna48 { return this._cloneInto(); } + + update(data: Uint8Array): this { + this.buffer = concatBytes(this.buffer, data); + return this; + } + + digest(): Uint8Array { return this.clone().final(); } + private final(): Uint8Array { return new Kupyna256().update(this.buffer).digest().slice(-6); } +} + +/** + * Compute hash with Kupyna 48 bit + * @param data Input data + */ +export const kupyna48 = (data: Uint8Array): Uint8Array => new Kupyna48().update(data).digest(); +/** + * Compute hash with Kupyna 256 bit + * @param data Input data + */ +export const kupyna256 = (data: Uint8Array): Uint8Array => new Kupyna256().update(data).digest(); \ No newline at end of file diff --git a/src/modes/kupyna3xx_512.ts b/src/modes/kupyna3xx_512.ts new file mode 100644 index 0000000..e1fbecf --- /dev/null +++ b/src/modes/kupyna3xx_512.ts @@ -0,0 +1,238 @@ +import { T0, T1, T2, T3, T4, T5, T6, T7, type Kupyna, type KupynaAndKMAC } from "../const"; +import { bytesToNumberLE, numberToBytesLE, uint64sToBytes, bytesToUint64s, concatBytes } from "../utils"; + +/** Kupyna 512 bit */ +export class Kupyna512 implements Kupyna { + outputLen = 64; + blockLen = 128; + s: BigUint64Array; + x: Uint8Array; + nx: number; + len: bigint; + + /** Kupyna 512 bit */ + constructor() { + this.s = new BigUint64Array(16); + this.x = new Uint8Array(this.blockLen); + this.nx = 0; + this.len = 0n; + + let s1 = new Uint8Array(8); + s1[0] = this.blockLen; + this.s[0] = bytesToNumberLE(s1); + } + /** Create hash instance */ + public static create(): Kupyna512 { return new Kupyna512(); } + + private byte(a: bigint) { return Number(a & 0xFFn); } + + _cloneInto(to?: Kupyna512): Kupyna512 { + to ||= new Kupyna512(); + to.s = new BigUint64Array(this.s); + to.x = new Uint8Array(this.x); + to.nx = this.nx; + to.len = this.len; + + return to; + } + clone(): Kupyna512 { return this._cloneInto(); } + + update(data: Uint8Array): Kupyna512 { + 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(): Uint8Array { return this.clone().final(); } + private final(): Uint8Array { + this.x[this.nx] = 0x80; + this.nx++; + + const fillBytes = (start: number) => { + const available = this.x.length - start; + if (available > 0) this.x.fill(0, start, start + available); + }; + + if (this.nx > 116) { + fillBytes(this.nx); + this.block(this.x); + this.nx = 0; + } + + fillBytes(this.nx); + this.x.set(numberToBytesLE(this.len * 8n, 8), 116); + this.block(this.x); + this.outputTransform(); + + return uint64sToBytes(this.s).slice(this.outputLen); + } + + private G(x: BigUint64Array, y: BigUint64Array) { + const T = [T0, T1, T2, T3, T4, T5, T6, T7]; + const offset = [0, 1, 2, 3, 4, 5, 6, 11]; + + for (let i = 0; i < 16; i++) { + let result = 0n; + for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - offset[j] + 16) % 16] >> (BigInt(j) * 8n))]; + y[i] = result; + } + } + + private G1(x: BigUint64Array, y: BigUint64Array, round: bigint) { + const T = [T0, T1, T2, T3, T4, T5, T6, T7]; + const offset = [0, 1, 2, 3, 4, 5, 6, 11]; + + for (let i = 0; i < 16; i++) { + let result = 0n; + for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - offset[j] + 16) % 16] >> (BigInt(j) * 8n))]; + y[i] = result ^ BigInt(i << 4) ^ round; + } + } + + private G2(x: BigUint64Array, y: BigUint64Array, round: bigint) { + let r = 0x00F0F0F0F0F0F0F3n; + const T = [T0, T1, T2, T3, T4, T5, T6, T7]; + const offset = [0, 1, 2, 3, 4, 5, 6, 11]; + for (let i = 0; i < 16; i++) { + let result = 0n; + for (let j = 0; j < 8; j++) result ^= T[j][this.byte(x[(i - offset[j] + 16) % 16] >> (BigInt(j) * 8n))]; + y[i] = result + (r ^ ((BigInt((15 - i) * 16) ^ round) << 56n)); + } + } + + private P(x: BigUint64Array, y: BigUint64Array, round: bigint) { + for(let idx = 0n; idx < 16n; idx++) x[Number(idx)] ^= (idx << 4n) ^ round; + this.G1(x, y, round+1n); + this.G(y, x); + } + + private Q(x: BigUint64Array, y: BigUint64Array, round: bigint) { + let r = 0x00F0F0F0F0F0F0F3n; + for(let j = 0n; j < 16n; j++) x[Number(j)] += (r ^ ((((15n - j) * 0x10n) ^ round) << 56n)); + this.G2(x, y, round+1n); + this.G(y, x); + } + + private outputTransform() { + let t1 = new BigUint64Array(this.s), + t2 = new BigUint64Array(16); + + for(let r = 0n; r < 14n; r += 2n) this.P(t1, t2, r); + for(let column = 0; column < 16; column++) this.s[column] ^= t1[column]; + } + + private transform(b: BigUint64Array) { + let AQ1 = new BigUint64Array(16); + let AP1 = new BigUint64Array(16); + let tmp = new BigUint64Array(16); + + for(let column = 0; column < 16; column++) { + AP1[column] = this.s[column] ^ b[column]; + AQ1[column] = b[column]; + } + + for(let r = 0n; r < 14n; r += 2n) { + this.P(AP1, tmp, r); + this.Q(AQ1, tmp, r); + } + + for(let column = 0; column < 16; column++) this.s[column] ^= AP1[column] ^ AQ1[column]; + } + private block(b: Uint8Array) { return this.transform(bytesToUint64s(b)); } +} + +/** Kupyna 304 bit version */ +export class Kupyna304 implements KupynaAndKMAC { + outputLen: number = 6; + blockLen: number = 128; + buffer: Uint8Array = new Uint8Array(); + + /** Kupyna 304 bit version */ + constructor() {} + /** Create hash instance */ + public static create(): Kupyna304 { return new Kupyna304(); } + + _cloneInto(to?: Kupyna304): Kupyna304 { + to ||= new Kupyna304(); + to.buffer = new Uint8Array(this.buffer); + return to; + } + clone(): Kupyna304 { return this._cloneInto(); } + + update(data: Uint8Array): this { + this.buffer = concatBytes(this.buffer, data); + return this; + } + + digest(): Uint8Array { return this.clone().final(); } + private final(): Uint8Array { return new Kupyna512().update(this.buffer).digest().slice(-38); } +} + +/** Kupyna 384 bit version */ +export class Kupyna384 implements KupynaAndKMAC { + outputLen: number = 48; + blockLen: number = 128; + buffer: Uint8Array = new Uint8Array(); + + /** Kupyna 384 bit version */ + constructor() {} + /** Create hash instance */ + public static create(): Kupyna384 { return new Kupyna384(); } + + _cloneInto(to?: Kupyna384): Kupyna384 { + to ||= new Kupyna384(); + to.buffer = new Uint8Array(this.buffer); + return to; + } + clone(): Kupyna384 { return this._cloneInto(); } + + update(data: Uint8Array): this { + this.buffer = concatBytes(this.buffer, data); + return this; + } + + digest(): Uint8Array { return this.clone().final(); } + private final(): Uint8Array { return new Kupyna512().update(this.buffer).digest().slice(16); } +} + +/** + * Compute hash with Kupyna 304 bit + * @param data Input data + */ +export const kupyna304 = (data: Uint8Array): Uint8Array => new Kupyna304().update(data).digest(); +/** + * Compute hash with Kupyna 384 bit + * @param data Input data + */ +export const kupyna384 = (data: Uint8Array): Uint8Array => new Kupyna384().update(data).digest(); +/** + * Compute hash with Kupyna 512 bit + * @param data Input data + */ +export const kupyna512 = (data: Uint8Array): Uint8Array => new Kupyna512().update(data).digest(); \ No newline at end of file diff --git a/tests/304.test.ts b/tests/304.test.ts new file mode 100644 index 0000000..6912233 --- /dev/null +++ b/tests/304.test.ts @@ -0,0 +1,25 @@ +import { hexToBytes } from "../src/utils"; +import { describe, test, expect } from "bun:test"; +import { Kupyna304 } from "../src"; + +const prepareInput = (input: string) => hexToBytes(input.split("\n").join("").replaceAll(" ", "")) + +describe("304 bit", () => { + test("N = 1024", () => { + let input = prepareInput(` +000102030405060708090A0B0C0D0E0F +101112131415161718191A1B1C1D1E1F +202122232425262728292A2B2C2D2E2F +303132333435363738393A3B3C3D3E3F +404142434445464748494A4B4C4D4E4F +505152535455565758595A5B5C5D5E5F +606162636465666768696A6B6C6D6E6F +707172737475767778797A7B7C7D7E7F + `) + + let expected = prepareInput(`0A8CADA32B979635657F256B15D5FCA4A174DE029F0B1B4387C878FCC1C00E8705D783FD7FFE`) + + let a = new Kupyna304() + expect(a.update(input).digest()).toStrictEqual(expected) + }) +}) \ No newline at end of file diff --git a/tests/384.test.ts b/tests/384.test.ts index f1ec193..0461ded 100644 --- a/tests/384.test.ts +++ b/tests/384.test.ts @@ -1,5 +1,23 @@ +import { hexToBytes } from "../src/utils"; import { describe, test, expect } from "bun:test"; +import { Kupyna384 } from "../src"; -describe.todo("384", () => { - +const prepareInput = (input: string) => hexToBytes(input.split("\n").join("").replaceAll(" ", "")) + +describe("384", () => { + test("N = 760", () => { + let input = prepareInput(` +000102030405060708090A0B0C0D0E0F +101112131415161718191A1B1C1D1E1F +202122232425262728292A2B2C2D2E2F +303132333435363738393A3B3C3D3E3F +404142434445464748494A4B4C4D4E4F +505152535455565758595A5B5C5D5E + `) + + let expected = prepareInput(`D9021692D84E5175735654846BA751E6D0ED0FAC36DFBC0841287DCB0B5584C75016C3DECC2A6E47C50B2F3811E351B8`) + + let a = new Kupyna384() + expect(a.update(input).digest()).toStrictEqual(expected) + }) }) \ No newline at end of file diff --git a/tests/48.test.ts b/tests/48.test.ts new file mode 100644 index 0000000..eab7d55 --- /dev/null +++ b/tests/48.test.ts @@ -0,0 +1,19 @@ +import { hexToBytes } from "../src/utils"; +import { describe, test, expect } from "bun:test"; +import { Kupyna48 } from "../src"; + +const prepareInput = (input: string) => hexToBytes(input.split("\n").join("").replaceAll(" ", "")) + +describe("48 bit", () => { + test("N = 512", () => { + let input = prepareInput(` +000102030405060708090A0B0C0D0E0F +101112131415161718191A1B1C1D1E1F +202122232425262728292A2B2C2D2E2F +303132333435363738393A3B3C3D3E3F + `) + let expected = prepareInput(`2F6631239875`) + let a = new Kupyna48() + expect(a.update(input).digest()).toStrictEqual(expected) + }) +}) \ No newline at end of file diff --git a/tests/index.test.ts b/tests/index.test.ts index b01de50..d19f26e 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,17 +1,29 @@ import { describe, test, expect } from "bun:test" import { randomBytes } from "crypto" -import { kupyna256, Kupyna256, kupyna384, Kupyna384, kupyna512, Kupyna512 } from "../src" +import { kupyna256, Kupyna256, kupyna304, Kupyna304, kupyna384, Kupyna384, kupyna48, Kupyna48, kupyna512, Kupyna512 } from "../src" describe("Test symmetric", () => { let chunks: Buffer[] = []; for(let i = 0; i < 10; i++) chunks.push(randomBytes(10)); + test("48", () => { + let m = new Kupyna48() + for(let i of chunks) m.update(i); + expect(m.digest()).toStrictEqual(kupyna48(Buffer.concat(chunks))) + }) + test("256", () => { let m = new Kupyna256() for(let i of chunks) m.update(i); expect(m.digest()).toStrictEqual(kupyna256(Buffer.concat(chunks))) }) + test("304", () => { + let m = new Kupyna304() + for(let i of chunks) m.update(i); + expect(m.digest()).toStrictEqual(kupyna304(Buffer.concat(chunks))) + }) + test("384", () => { let m = new Kupyna384() for(let i of chunks) m.update(i); @@ -26,6 +38,15 @@ describe("Test symmetric", () => { }) describe("Clone", () => { + test("48", () => { + let m = new Kupyna48().update(new TextEncoder().encode("foo")) + let c = m.clone() + c.update(new TextEncoder().encode("bar")) + m.update(new TextEncoder().encode("bar")) + + expect(c.digest()).toStrictEqual(m.digest()) + }) + test("256", () => { let m = new Kupyna256().update(new TextEncoder().encode("foo")) let c = m.clone() @@ -35,6 +56,15 @@ describe("Clone", () => { expect(c.digest()).toStrictEqual(m.digest()) }) + test("304", () => { + let m = new Kupyna304().update(new TextEncoder().encode("foo")) + let c = m.clone() + c.update(new TextEncoder().encode("bar")) + m.update(new TextEncoder().encode("bar")) + + expect(c.digest()).toStrictEqual(m.digest()) + }) + test("384", () => { let m = new Kupyna384().update(new TextEncoder().encode("foo")) let c = m.clone()