From de7677e40b54e6d4dec50addc596335f563c5ad7 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 19 Nov 2024 07:39:02 +0000 Subject: [PATCH] test: Add misc directory with md5, unrolled hashes --- test/misc/README.md | 5 + test/misc/md5.ts | 107 ++++++++++++++ test/misc/unrolled-argon2.js | 262 +++++++++++++++++++++++++++++++++++ test/misc/unrolled-sha3.ts | 50 +++++++ 4 files changed, 424 insertions(+) create mode 100644 test/misc/README.md create mode 100644 test/misc/md5.ts create mode 100644 test/misc/unrolled-argon2.js create mode 100644 test/misc/unrolled-sha3.ts diff --git a/test/misc/README.md b/test/misc/README.md new file mode 100644 index 0000000..61a3083 --- /dev/null +++ b/test/misc/README.md @@ -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. \ No newline at end of file diff --git a/test/misc/md5.ts b/test/misc/md5.ts new file mode 100644 index 0000000..191b543 --- /dev/null +++ b/test/misc/md5.ts @@ -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 { + 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()); diff --git a/test/misc/unrolled-argon2.js b/test/misc/unrolled-argon2.js new file mode 100644 index 0000000..6f80be4 --- /dev/null +++ b/test/misc/unrolled-argon2.js @@ -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()); diff --git a/test/misc/unrolled-sha3.ts b/test/misc/unrolled-sha3.ts new file mode 100644 index 0000000..d730dd2 --- /dev/null +++ b/test/misc/unrolled-sha3.ts @@ -0,0 +1,50 @@ +const rotlHs = (h: string, l: string, s: number) => + s > 32 ? `(${l} << ${s - 32}) | (${h} >>> ${64 - s})` : `(${h} << ${s}) | (${l} >>> ${32 - s})`; +const rotlLs = (h: string, l: string, s: number) => + s > 32 ? `(${h} << ${s - 32}) | (${l} >>> ${64 - s})` : `(${l} << ${s}) | (${h} >>> ${32 - s})`; + +export const keccakP = (() => { + let out = 'let h, l, s = state;\n'; + const vars = []; + for (let i = 0; i < 200 / 4; i++) vars.push(`s${i} = s[${i}]`); + out += `let ${vars.join(', ')};\n`; + out += `for (let round = 24 - rounds; round < 24; round++) {\n`; + // Theta θ + out += '\n// Theta θ\n'; + for (let x = 0; x < 10; x++) + out += `let B${x} = s${x} ^ s${x + 10} ^ s${x + 20} ^ s${x + 30} ^ s${x + 40};\n`; + for (let x = 0; x < 10; x += 2) { + const B0 = `B${(x + 2) % 10}`; + const B1 = `B${((x + 2) % 10) + 1}`; + out += `h = (${rotlHs(B0, B1, 1)}) ^ B${(x + 8) % 10}; `; + out += `l = (${rotlLs(B0, B1, 1)}) ^ B${((x + 8) % 10) + 1};\n`; + for (let y = 0; y < 50; y += 10) out += `s${x + y} ^= h; s${x + y + 1} ^= l; `; + out += '\n'; + } + // Rho (ρ) and Pi (π) + out += '\n// Rho (ρ) and Pi (π)\n'; + out += `let sh = s${2}, sl = s${3}; `; + for (let t = 0; t < 24; t++) { + const shift = SHA3_ROTL[t]; + out += `h = ${rotlHs('sh', 'sl', shift)}; `; + out += `l = ${rotlLs('sh', 'sl', shift)};\n`; + const PI = SHA3_PI[t]; + out += `sh = s${PI}; sl = s${PI + 1}; `; + out += `s${PI} = h; s${PI + 1} = l;\n`; + } + // Chi (χ) + out += '\n// Chi (χ)\n'; + for (let y = 0; y < 50; y += 10) { + for (let x = 0; x < 10; x++) out += `B${x} = s${y + x}; `; + out += '\n'; + for (let x = 0; x < 10; x++) out += `s${y + x} ^= ~B${(x + 2) % 10} & B${(x + 4) % 10}; `; + out += '\n'; + } + // Iota (ι) + out += `\n// Iota (ι)\n`; + out += `s0 ^= SHA3_IOTA_H[round];\n`; + out += `s1 ^= SHA3_IOTA_L[round];\n`; + out += '}\n'; + for (let i = 0; i < 200 / 4; i++) out += `s[${i}] = s${i}; `; + return new Function('state', 'rounds', 'SHA3_IOTA_H', 'SHA3_IOTA_L', out); +})();