Skip to content

Commit

Permalink
Merge pull request #14584 from webpack/hash/md4
Browse files Browse the repository at this point in the history
add wasm md4 implementation
  • Loading branch information
sokra committed Oct 28, 2021
2 parents f0298fe + a6bb3e5 commit 0f6c78c
Show file tree
Hide file tree
Showing 18 changed files with 709 additions and 215 deletions.
177 changes: 177 additions & 0 deletions assembly/hash/md4.asm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
** ********************************************************************
** md4.c -- Implementation of MD4 Message Digest Algorithm **
** Updated: 2/16/90 by Ronald L. Rivest **
** (C) 1990 RSA Data Security, Inc. **
** ********************************************************************
*/

// Ported to assemblyscript by Tobias Koppers

let totalLength: u32;
let A: u32;
let B: u32;
let C: u32;
let D: u32;

function F(x: u32, y: u32, z: u32): u32 {
return z ^ (x & (y ^ z));
}
function G(x: u32, y: u32, z: u32): u32 {
return (x & (y | z)) | (y & z);
}
function H(x: u32, y: u32, z: u32): u32 {
return x ^ y ^ z;
}

function roundF(a: u32, b: u32, c: u32, d: u32, i: u32, s: u32): u32 {
return rotl<u32>(a + F(b, c, d) + load<u32>(i), s);
}
function roundG(a: u32, b: u32, c: u32, d: u32, i: u32, s: u32): u32 {
return rotl<u32>(a + G(b, c, d) + load<u32>(i) + 0x5a827999, s);
}
function roundH(a: u32, b: u32, c: u32, d: u32, i: u32, s: u32): u32 {
return rotl<u32>(a + H(b, c, d) + load<u32>(i) + 0x6ed9eba1, s);
}

export function init(): void {
A = 0x67452301;
B = 0xefcdab89;
C = 0x98badcfe;
D = 0x10325476;
totalLength = 0;
}

function body(size: u32): void {
let _A = A;
let _B = B;
let _C = C;
let _D = D;

for (let i: u32 = 0; i < size; i += 64) {
let a = _A;
let b = _B;
let c = _C;
let d = _D;

// Round F

a = roundF(a, b, c, d, i + 4 * 0, 3);
d = roundF(d, a, b, c, i + 4 * 1, 7);
c = roundF(c, d, a, b, i + 4 * 2, 11);
b = roundF(b, c, d, a, i + 4 * 3, 19);

a = roundF(a, b, c, d, i + 4 * 4, 3);
d = roundF(d, a, b, c, i + 4 * 5, 7);
c = roundF(c, d, a, b, i + 4 * 6, 11);
b = roundF(b, c, d, a, i + 4 * 7, 19);

a = roundF(a, b, c, d, i + 4 * 8, 3);
d = roundF(d, a, b, c, i + 4 * 9, 7);
c = roundF(c, d, a, b, i + 4 * 10, 11);
b = roundF(b, c, d, a, i + 4 * 11, 19);

a = roundF(a, b, c, d, i + 4 * 12, 3);
d = roundF(d, a, b, c, i + 4 * 13, 7);
c = roundF(c, d, a, b, i + 4 * 14, 11);
b = roundF(b, c, d, a, i + 4 * 15, 19);

// Round G

a = roundG(a, b, c, d, i + 4 * 0, 3);
d = roundG(d, a, b, c, i + 4 * 4, 5);
c = roundG(c, d, a, b, i + 4 * 8, 9);
b = roundG(b, c, d, a, i + 4 * 12, 13);

a = roundG(a, b, c, d, i + 4 * 1, 3);
d = roundG(d, a, b, c, i + 4 * 5, 5);
c = roundG(c, d, a, b, i + 4 * 9, 9);
b = roundG(b, c, d, a, i + 4 * 13, 13);

a = roundG(a, b, c, d, i + 4 * 2, 3);
d = roundG(d, a, b, c, i + 4 * 6, 5);
c = roundG(c, d, a, b, i + 4 * 10, 9);
b = roundG(b, c, d, a, i + 4 * 14, 13);

a = roundG(a, b, c, d, i + 4 * 3, 3);
d = roundG(d, a, b, c, i + 4 * 7, 5);
c = roundG(c, d, a, b, i + 4 * 11, 9);
b = roundG(b, c, d, a, i + 4 * 15, 13);

// Round H

a = roundH(a, b, c, d, i + 4 * 0, 3);
d = roundH(d, a, b, c, i + 4 * 8, 9);
c = roundH(c, d, a, b, i + 4 * 4, 11);
b = roundH(b, c, d, a, i + 4 * 12, 15);

a = roundH(a, b, c, d, i + 4 * 2, 3);
d = roundH(d, a, b, c, i + 4 * 10, 9);
c = roundH(c, d, a, b, i + 4 * 6, 11);
b = roundH(b, c, d, a, i + 4 * 14, 15);

a = roundH(a, b, c, d, i + 4 * 1, 3);
d = roundH(d, a, b, c, i + 4 * 9, 9);
c = roundH(c, d, a, b, i + 4 * 5, 11);
b = roundH(b, c, d, a, i + 4 * 13, 15);

a = roundH(a, b, c, d, i + 4 * 3, 3);
d = roundH(d, a, b, c, i + 4 * 11, 9);
c = roundH(c, d, a, b, i + 4 * 7, 11);
b = roundH(b, c, d, a, i + 4 * 15, 15);

_A += a;
_B += b;
_C += c;
_D += d;
}

A = _A;
B = _B;
C = _C;
D = _D;
}

export function update(length: u32): void {
body(length);
totalLength += length;
}

export function final(length: u32): void {
const bits: u64 = u64(totalLength + length) << 3;
const finalLength: u32 = (length + 9 + 63) & ~63;
const bitsPosition = finalLength - 8;

// end
store<u8>(length++, 0x80);

// padding
for (; length & 7 && length < finalLength; length++) store<u8>(length, 0);
for (; length < finalLength; length += 8) store<u64>(length, 0);

// bits
store<u64>(bitsPosition, bits);

body(finalLength);

store<u64>(0, u32ToHex(A));
store<u64>(8, u32ToHex(B));
store<u64>(16, u32ToHex(C));
store<u64>(24, u32ToHex(D));
}

function u32ToHex(x: u64): u64 {
// from https://johnnylee-sde.github.io/Fast-unsigned-integer-to-hex-string/

x = ((x & 0xffff0000) << 16) | (x & 0xffff);
x = ((x & 0x0000ff000000ff00) << 8) | (x & 0x000000ff000000ff);
x = ((x & 0x00f000f000f000f0) >> 4) | ((x & 0x000f000f000f000f) << 8);

const mask = ((x + 0x0606060606060606) >> 4) & 0x0101010101010101;

x |= 0x3030303030303030;

x += 0x27 * mask;

return x;
}
2 changes: 0 additions & 2 deletions assembly/hash/xxhash64.asm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,6 @@ export function final(length: u32): void {
result *= Prime3;
result ^= result >> 32;

store<u64>(0, result);

store<u64>(0, u32ToHex(result >> 32));
store<u64>(8, u32ToHex(result & 0xffffffff));
}
Expand Down
39 changes: 39 additions & 0 deletions benchmark/md4-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const createHash = require("../lib/util/createHash");

const compare = require("./micro-compare");

const size = 50;

const strings = [];
for (let count = 1; ; count *= 10) {
while (strings.length < count) {
const s = require("crypto").randomBytes(size).toString("hex");
strings.push(s);
const hash = createHash("native-md4");
hash.update(s);
hash.update(s);
hash.digest("hex");
}
let i = 0;
console.log(
`${count} different 200 char strings: ` +
compare(
"native md4",
() => {
const hash = createHash("native-md4");
const s = strings[(i = (i + 1) % strings.length)];
hash.update(s);
hash.update(s);
return hash.digest("hex");
},
"wasm md4",
() => {
const hash = createHash("md4");
const s = strings[(i = (i + 1) % strings.length)];
hash.update(s);
hash.update(s);
return hash.digest("hex");
}
)
);
}
49 changes: 49 additions & 0 deletions benchmark/md4.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const createHash = require("../lib/util/createHash");

const compare = require("./micro-compare");

for (const size of [
1, 10, 20, 40, 60, 80, 100, 200, 400, 1000, 1001, 5000, 8183, 8184, 8185,
10000, 20000, 32768, 32769, 50000, 100000, 200000
]) {
const longString = require("crypto").randomBytes(size).toString("hex");
const buffer = require("crypto").randomBytes(size * 2);
console.log(
`string ${longString.length} chars: ` +
compare(
"native md4",
() => {
const hash = createHash("native-md4");
hash.update(longString);
hash.update(longString);
return hash.digest("hex");
},
"wasm md4",
() => {
const hash = createHash("md4");
hash.update(longString);
hash.update(longString);
return hash.digest("hex");
}
)
);
console.log(
`buffer ${buffer.length} bytes: ` +
compare(
"native md4",
() => {
const hash = createHash("native-md4");
hash.update(buffer);
hash.update(buffer);
return hash.digest("hex");
},
"wasm md4",
() => {
const hash = createHash("md4");
hash.update(buffer);
hash.update(buffer);
return hash.digest("hex");
}
)
);
}
44 changes: 44 additions & 0 deletions benchmark/micro-compare.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
let result;

const measure = (fn, count) => {
const start = process.hrtime.bigint();
for (let i = 0; i < count; i++) result = fn();
return Number(process.hrtime.bigint() - start);
};

const NS_PER_MS = 1000000; // 1ms
const MIN_DURATION = 100 * NS_PER_MS; // 100ms
const MAX_DURATION = 1000 * NS_PER_MS; // 1000ms
const MAX_WARMUP_DURATION = 1 * NS_PER_MS; // 1ms

const format = (fast, slow, fastName, slowName, count) => {
return `${fastName} is ${
Math.round(((slow - fast) * 1000) / slow) / 10
}% faster than ${slowName} (${Math.round(fast / 100 / count) / 10} µs vs ${
Math.round(slow / 100 / count) / 10
} µs, ${count}x)`;
};

const compare = (n1, f1, n2, f2) => {
let count = 1;
while (true) {
const timings = [f1, f2, f1, f2, f1, f2].map(f => measure(f, count));
const t1 = Math.min(timings[0], timings[2], timings[4]);
const t2 = Math.min(timings[1], timings[3], timings[5]);
if (count === 1 && (t1 > MAX_WARMUP_DURATION || t2 > MAX_WARMUP_DURATION)) {
continue;
}
if (
(t1 > MIN_DURATION && t2 > MIN_DURATION) ||
t1 > MAX_DURATION ||
t2 > MAX_DURATION
) {
return t1 > t2
? format(t2, t1, n2, n1, count)
: format(t1, t2, n1, n2, count);
}
count *= 2;
}
};

module.exports = compare;
45 changes: 45 additions & 0 deletions benchmark/xxhash64-vs-md4.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const createHash = require("../lib/util/createHash");

const compare = require("./micro-compare");

for (const size of [
1, 10, 20, 40, 60, 80, 100, 200, 400, 1000, 1001, 5000, 8183, 8184, 8185,
10000, 20000, 32768, 32769, 50000, 100000, 200000
]) {
const longString = require("crypto").randomBytes(size).toString("hex");
const buffer = require("crypto").randomBytes(size * 2);
console.log(
`string ${longString.length} chars: ` +
compare(
"wasm xxhash64",
() => {
const hash = createHash("xxhash64");
hash.update(longString);
return hash.digest("hex");
},
"wasm md4",
() => {
const hash = createHash("md4");
hash.update(longString);
return hash.digest("hex");
}
)
);
console.log(
`buffer ${buffer.length} bytes: ` +
compare(
"wasm xxhash64",
() => {
const hash = createHash("xxhash64");
hash.update(buffer);
return hash.digest("hex");
},
"wasm md4",
() => {
const hash = createHash("md4");
hash.update(buffer);
return hash.digest("hex");
}
)
);
}
Loading

0 comments on commit 0f6c78c

Please sign in to comment.