Skip to content

Commit

Permalink
test: Add misc directory with md5, unrolled hashes
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmillr committed Nov 19, 2024
1 parent 009ab25 commit de7677e
Show file tree
Hide file tree
Showing 4 changed files with 424 additions and 0 deletions.
5 changes: 5 additions & 0 deletions test/misc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# misc

Miscelanous items that could be useful when developing / testing.

`unrolled-` files contain hash implementations with unrolled loops, which contribute to speed-up, but are hard to audit.
107 changes: 107 additions & 0 deletions test/misc/md5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { HashMD } from './_md.js';
import { rotl, wrapConstructor } from './utils.js';

// MD5 (RFC 1321) was cryptographically broken.
// It is still widely used in legacy apps. Don't use it for a new protocol.
// - Collisions: 2**18 (vs 2**60 for SHA1)
// - No practical pre-image attacks (only theoretical, 2**123.4)
// - HMAC seems kinda ok: https://datatracker.ietf.org/doc/html/rfc6151
// Architecture is similar to SHA1. Differences:
// - reduced output length: 16 bytes (128 bit) instead of 20
// - 64 rounds, instead of 80
// - little-endian: could be faster, but will require more code
// - non-linear index selection: huge speed-up for unroll
// - per round constants: more memory accesses, additional speed-up for unroll

// tests
// MD5: {
// fn: md5,
// obj: md5.create,
// node: (buf) => Uint8Array.from(createHash('md5').update(buf).digest()),
// node_obj: () => createHash('md5'),
// nist: [
// '90015098 3cd24fb0d 6963f7d2 8e17f72',
// 'd41d8cd9 8f00b204e 9800998e cf8427e',
// '8215ef07 96a20bcaa ae116d38 76c664a',
// '03dd8807 a93175fb0 62dfb55d c7d359c',
// '7707d6ae 4e027c70e ea2a935c 2296f21',
// ],
// },

// Per-round constants
const K = Array.from({ length: 64 }, (_, i) => Math.floor(2 ** 32 * Math.abs(Math.sin(i + 1))));

// Choice: a ? b : c
const Chi = (a: number, b: number, c: number) => (a & b) ^ (~a & c);

// Initial state (same as sha1, but 4 u32 instead of 5)
const IV = /* @__PURE__ */ new Uint32Array([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]);

// Temporary buffer, not used to store anything between runs
// Named this way for SHA1 compat
const MD5_W = /* @__PURE__ */ new Uint32Array(16);
class MD5 extends HashMD<MD5> {
private A = IV[0] | 0;
private B = IV[1] | 0;
private C = IV[2] | 0;
private D = IV[3] | 0;

constructor() {
super(64, 16, 8, true);
}
protected get(): [number, number, number, number] {
const { A, B, C, D } = this;
return [A, B, C, D];
}
protected set(A: number, B: number, C: number, D: number) {
this.A = A | 0;
this.B = B | 0;
this.C = C | 0;
this.D = D | 0;
}
protected process(view: DataView, offset: number): void {
for (let i = 0; i < 16; i++, offset += 4) MD5_W[i] = view.getUint32(offset, true);
// Compression function main loop, 64 rounds
let { A, B, C, D } = this;
for (let i = 0; i < 64; i++) {
let F, g, s;
if (i < 16) {
F = Chi(B, C, D);
g = i;
s = [7, 12, 17, 22];
} else if (i < 32) {
F = Chi(D, B, C);
g = (5 * i + 1) % 16;
s = [5, 9, 14, 20];
} else if (i < 48) {
F = B ^ C ^ D;
g = (3 * i + 5) % 16;
s = [4, 11, 16, 23];
} else {
F = C ^ (B | ~D);
g = (7 * i) % 16;
s = [6, 10, 15, 21];
}
F = F + A + K[i] + MD5_W[g];
A = D;
D = C;
C = B;
B = B + rotl(F, s[i % 4]);
}
// Add the compressed chunk to the current hash value
A = (A + this.A) | 0;
B = (B + this.B) | 0;
C = (C + this.C) | 0;
D = (D + this.D) | 0;
this.set(A, B, C, D);
}
protected roundClean() {
MD5_W.fill(0);
}
destroy() {
this.set(0, 0, 0, 0);
this.buffer.fill(0);
}
}

export const md5 = /* @__PURE__ */ wrapConstructor(() => new MD5());
262 changes: 262 additions & 0 deletions test/misc/unrolled-argon2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
// // u32 * u32 = u64
// function mul(a, b) {
// const aL = a & 0xffff;
// const aH = a >>> 16;
// const bL = b & 0xffff;
// const bH = b >>> 16;
// const ll = Math.imul(aL, bL);
// const hl = Math.imul(aH, bL);
// const lh = Math.imul(aL, bH);
// const hh = Math.imul(aH, bH);
// const carry = (ll >>> 16) + (hl & 0xffff) + lh;
// const high = (hh + (hl >>> 16) + (carry >>> 16)) | 0;
// const low = (carry << 16) | (ll & 0xffff);
// return { h: high, l: low };
// }

// function mul2(a, b) {
// // 2 * a * b (via shifts)
// const { h, l } = mul(a, b);
// return { h: ((h << 1) | (l >>> 31)) & 0xffff_ffff, l: (l << 1) & 0xffff_ffff };
// }

// // A + B + (2 * u32(A) * u32(B))
// function blamka(Ah, Al, Bh, Bl) {
// const { h: Ch, l: Cl } = mul2(Al, Bl);
// // A + B + (2 * A * B)
// const Rll = add3L(Al, Bl, Cl);
// return { h: add3H(Rll, Ah, Bh, Ch), l: Rll | 0 };
// }

// // Temporary block buffer
// const A2_BUF = new Uint32Array(256); // 1024

// function G(a, b, c, d) {
// let Al = A2_BUF[2*a], Ah = A2_BUF[2*a + 1]; // prettier-ignore
// let Bl = A2_BUF[2*b], Bh = A2_BUF[2*b + 1]; // prettier-ignore
// let Cl = A2_BUF[2*c], Ch = A2_BUF[2*c + 1]; // prettier-ignore
// let Dl = A2_BUF[2*d], Dh = A2_BUF[2*d + 1]; // prettier-ignore

// ({ h: Ah, l: Al } = blamka(Ah, Al, Bh, Bl));
// ({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
// ({ Dh, Dl } = { Dh: rotr32H(Dh, Dl), Dl: rotr32L(Dh, Dl) });

// ({ h: Ch, l: Cl } = blamka(Ch, Cl, Dh, Dl));
// ({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
// ({ Bh, Bl } = { Bh: rotrSH(Bh, Bl, 24), Bl: rotrSL(Bh, Bl, 24) });

// ({ h: Ah, l: Al } = blamka(Ah, Al, Bh, Bl));
// ({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
// ({ Dh, Dl } = { Dh: rotrSH(Dh, Dl, 16), Dl: rotrSL(Dh, Dl, 16) });

// ({ h: Ch, l: Cl } = blamka(Ch, Cl, Dh, Dl));
// ({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
// ({ Bh, Bl } = { Bh: rotrBH(Bh, Bl, 63), Bl: rotrBL(Bh, Bl, 63) });

// (A2_BUF[2 * a] = Al), (A2_BUF[2 * a + 1] = Ah);
// (A2_BUF[2 * b] = Bl), (A2_BUF[2 * b + 1] = Bh);
// (A2_BUF[2 * c] = Cl), (A2_BUF[2 * c + 1] = Ch);
// (A2_BUF[2 * d] = Dl), (A2_BUF[2 * d + 1] = Dh);
// }

// // prettier-ignore
// function P(
// v00, v01, v02, v03, v04, v05, v06, v07,
// v08, v09, v10, v11, v12, v13, v14, v15,
// ) {
// G(v00, v04, v08, v12);
// G(v01, v05, v09, v13);
// G(v02, v06, v10, v14);
// G(v03, v07, v11, v15);
// G(v00, v05, v10, v15);
// G(v01, v06, v11, v12);
// G(v02, v07, v08, v13);
// G(v03, v04, v09, v14);
// }

// function block(x, xPos, yPos, outPos, needXor) {
// for (let i = 0; i < 256; i++) A2_BUF[i] = x[xPos + i] ^ x[yPos + i];
// // columns
// for (let i = 0; i < 128; i += 16) {
// // prettier-ignore
// P(
// i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7,
// i + 8, i + 9, i + 10, i + 11, i + 12, i + 13, i + 14, i + 15
// );
// }
// // rows
// for (let i = 0; i < 16; i += 2) {
// // prettier-ignore
// P(
// i, i + 1, i + 16, i + 17, i + 32, i + 33, i + 48, i + 49,
// i + 64, i + 65, i + 80, i + 81, i + 96, i + 97, i + 112, i + 113
// );
// }
// if (needXor) for (let i = 0; i < 256; i++) x[outPos + i] ^= A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
// else for (let i = 0; i < 256; i++) x[outPos + i] = A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
// A2_BUF.fill(0);
// }

const num = (i) => `${i}`.padStart(2, '0');

function genP() {
let res = `function P(
v00: number, v01: number, v02: number, v03: number, v04: number, v05: number, v06: number, v07: number,
v08: number, v09: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number,
) {
`;
for (let i = 0; i < 16; i++) {
res += `let V${num(i)}l = A2_BUF[2*v${num(i)}], V${num(i)}h = A2_BUF[2*v${num(i)} + 1]; // prettier-ignore\n`;
}
const G = (a, b, c, d) => {
res += `// A: ${a} B: ${b} C: ${c} D: ${d}\n`;
const Ah = `V${num(a)}h`, Al = `V${num(a)}l`; // prettier-ignore
const Bh = `V${num(b)}h`, Bl = `V${num(b)}l`; // prettier-ignore
const Ch = `V${num(c)}h`, Cl = `V${num(c)}l`; // prettier-ignore
const Dh = `V${num(d)}h`, Dl = `V${num(d)}l`; // prettier-ignore

res += `
({ h: ${Ah}, l: ${Al} } = blamka(${Ah}, ${Al}, ${Bh}, ${Bl}));
({ ${Dh}, ${Dl} } = { ${Dh}: ${Dh} ^ ${Ah}, ${Dl}: ${Dl} ^ ${Al} });
({ ${Dh}, ${Dl} } = { ${Dh}: rotr32H(${Dh}, ${Dl}), ${Dl}: rotr32L(${Dh}, ${Dl}) });
({ h: ${Ch}, l: ${Cl} } = blamka(${Ch}, ${Cl}, ${Dh}, ${Dl}));
({ ${Bh}, ${Bl} } = { ${Bh}: ${Bh} ^ ${Ch}, ${Bl}: ${Bl} ^ ${Cl} });
({ ${Bh}, ${Bl} } = { ${Bh}: rotrSH(${Bh}, ${Bl}, 24), ${Bl}: rotrSL(${Bh}, ${Bl}, 24) });
({ h: ${Ah}, l: ${Al} } = blamka(${Ah}, ${Al}, ${Bh}, ${Bl}));
({ ${Dh}, ${Dl} } = { ${Dh}: ${Dh} ^ ${Ah}, ${Dl}: ${Dl} ^ ${Al} });
({ ${Dh}, ${Dl} } = { ${Dh}: rotrSH(${Dh}, ${Dl}, 16), ${Dl}: rotrSL(${Dh}, ${Dl}, 16) });
({ h: ${Ch}, l: ${Cl} } = blamka(${Ch}, ${Cl}, ${Dh}, ${Dl}));
({ ${Bh}, ${Bl} } = { ${Bh}: ${Bh} ^ ${Ch}, ${Bl}: ${Bl} ^ ${Cl} });
({ ${Bh}, ${Bl} } = { ${Bh}: rotrBH(${Bh}, ${Bl}, 63), ${Bl}: rotrBL(${Bh}, ${Bl}, 63) });
`;
};

G(0, 4, 8, 12);
G(1, 5, 9, 13);
G(2, 6, 10, 14);
G(3, 7, 11, 15);
G(0, 5, 10, 15);
G(1, 6, 11, 12);
G(2, 7, 8, 13);
G(3, 4, 9, 14);

for (let i = 0; i < 16; i++) {
res += `(A2_BUF[2 * v${num(i)}] = V${num(i)}l), (A2_BUF[2 * v${num(i)} + 1] = V${num(i)}h);\n`;
}
res += `
}`;
return res;
}

function genBlock() {
let res = `function block(x: Uint32Array, xPos: number, yPos: number, outPos: number, needXor: boolean) {
`;
for (let i = 0; i < 256; i++) res += `let A2_BUF${num(i)} = x[xPos + ${i}] ^ x[yPos + ${i}];\n`;

function G(a, b, c, d) {
res += `
{
let Al = A2_BUF${num(2 * a)}, Ah = A2_BUF${num(2 * a + 1)}; // prettier-ignore
let Bl = A2_BUF${num(2 * b)}, Bh = A2_BUF${num(2 * b + 1)}; // prettier-ignore
let Cl = A2_BUF${num(2 * c)}, Ch = A2_BUF${num(2 * c + 1)}; // prettier-ignore
let Dl = A2_BUF${num(2 * d)}, Dh = A2_BUF${num(2 * d + 1)}; // prettier-ignore
({ h: Ah, l: Al } = blamka(Ah, Al, Bh, Bl));
({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
({ Dh, Dl } = { Dh: rotr32H(Dh, Dl), Dl: rotr32L(Dh, Dl) });
({ h: Ch, l: Cl } = blamka(Ch, Cl, Dh, Dl));
({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
({ Bh, Bl } = { Bh: rotrSH(Bh, Bl, 24), Bl: rotrSL(Bh, Bl, 24) });
({ h: Ah, l: Al } = blamka(Ah, Al, Bh, Bl));
({ Dh, Dl } = { Dh: Dh ^ Ah, Dl: Dl ^ Al });
({ Dh, Dl } = { Dh: rotrSH(Dh, Dl, 16), Dl: rotrSL(Dh, Dl, 16) });
({ h: Ch, l: Cl } = blamka(Ch, Cl, Dh, Dl));
({ Bh, Bl } = { Bh: Bh ^ Ch, Bl: Bl ^ Cl });
({ Bh, Bl } = { Bh: rotrBH(Bh, Bl, 63), Bl: rotrBL(Bh, Bl, 63) });
(A2_BUF${num(2 * a)} = Al), (A2_BUF${num(2 * a + 1)} = Ah);
(A2_BUF${num(2 * b)} = Bl), (A2_BUF${num(2 * b + 1)} = Bh);
(A2_BUF${num(2 * c)} = Cl), (A2_BUF${num(2 * c + 1)} = Ch);
(A2_BUF${num(2 * d)} = Dl), (A2_BUF${num(2 * d + 1)} = Dh);
}
`;
}

function P(v00, v01, v02, v03, v04, v05, v06, v07, v08, v09, v10, v11, v12, v13, v14, v15) {
G(v00, v04, v08, v12);
G(v01, v05, v09, v13);
G(v02, v06, v10, v14);
G(v03, v07, v11, v15);
G(v00, v05, v10, v15);
G(v01, v06, v11, v12);
G(v02, v07, v08, v13);
G(v03, v04, v09, v14);
}
// columns (8)
for (let i = 0; i < 128; i += 16) {
// prettier-ignore
P(
i, i + 1, i + 2, i + 3, i + 4, i + 5, i + 6, i + 7,
i + 8, i + 9, i + 10, i + 11, i + 12, i + 13, i + 14, i + 15
);
}
// rows (8)
for (let i = 0; i < 16; i += 2) {
// prettier-ignore
P(
i, i + 1, i + 16, i + 17, i + 32, i + 33, i + 48, i + 49,
i + 64, i + 65, i + 80, i + 81, i + 96, i + 97, i + 112, i + 113
);
}

res += ` if (needXor) {\n`;
for (let i = 0; i < 256; i++)
res += ` x[outPos + ${i}] ^= A2_BUF${num(i)} ^ x[xPos + ${i}] ^ x[yPos + ${i}];\n`;
res += ` } else {\n`;
for (let i = 0; i < 256; i++)
res += ` x[outPos + ${i}] = A2_BUF${num(i)} ^ x[xPos + ${i}] ^ x[yPos + ${i}];\n`;
res += ' }';
res += `
}`;
return res;
}

// console.log(genBlock());

function genBlock2() {
let res = `function block(x: Uint32Array, xPos: number, yPos: number, outPos: number, needXor: boolean) {
for (let i = 0; i < 256; i++) A2_BUF[i] = x[xPos + i] ^ x[yPos + i];
`;
// columns (8)
for (let i = 0; i < 128; i += 16) {
res += ` // prettier-ignore
P(
${i}, ${i + 1}, ${i + 2}, ${i + 3}, ${i + 4}, ${i + 5}, ${i + 6}, ${i + 7},
${i + 8}, ${i + 9}, ${i + 10}, ${i + 11}, ${i + 12}, ${i + 13}, ${i + 14}, ${i + 15}
);
`;
}
// rows (8)
for (let i = 0; i < 16; i += 2) {
res += ` // prettier-ignore
P(
${i}, ${i + 1}, ${i + 16}, ${i + 17}, ${i + 32}, ${i + 33}, ${i + 48}, ${i + 49},
${i + 64}, ${i + 65}, ${i + 80}, ${i + 81}, ${i + 96}, ${i + 97}, ${i + 112}, ${i + 113}
);
`;
}
res += `
if (needXor) for (let i = 0; i < 256; i++) x[outPos + i] ^= A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
else for (let i = 0; i < 256; i++) x[outPos + i] = A2_BUF[i] ^ x[xPos + i] ^ x[yPos + i];
A2_BUF.fill(0);`;
res += `
}`;
return res;
}

console.log(genBlock2());
Loading

0 comments on commit de7677e

Please sign in to comment.