Skip to content

Commit dcf3b02

Browse files
committed
add wasm md4 implementation
expose createHash is loader context
1 parent f0298fe commit dcf3b02

File tree

13 files changed

+556
-212
lines changed

13 files changed

+556
-212
lines changed

assembly/hash/md4.asm.ts

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
** ********************************************************************
3+
** md4.c -- Implementation of MD4 Message Digest Algorithm **
4+
** Updated: 2/16/90 by Ronald L. Rivest **
5+
** (C) 1990 RSA Data Security, Inc. **
6+
** ********************************************************************
7+
*/
8+
9+
// Ported to assemblyscript by Tobias Koppers
10+
11+
let totalLength: u32;
12+
let A: u32;
13+
let B: u32;
14+
let C: u32;
15+
let D: u32;
16+
17+
function F(x: u32, y: u32, z: u32): u32 {
18+
return z ^ (x & (y ^ z));
19+
}
20+
function G(x: u32, y: u32, z: u32): u32 {
21+
return (x & (y | z)) | (y & z);
22+
}
23+
function H(x: u32, y: u32, z: u32): u32 {
24+
return x ^ y ^ z;
25+
}
26+
27+
function roundF(a: u32, b: u32, c: u32, d: u32, i: u32, s: u32): u32 {
28+
return rotl<u32>(a + F(b, c, d) + load<u32>(i), s);
29+
}
30+
function roundG(a: u32, b: u32, c: u32, d: u32, i: u32, s: u32): u32 {
31+
return rotl<u32>(a + G(b, c, d) + load<u32>(i) + 0x5a827999, s);
32+
}
33+
function roundH(a: u32, b: u32, c: u32, d: u32, i: u32, s: u32): u32 {
34+
return rotl<u32>(a + H(b, c, d) + load<u32>(i) + 0x6ed9eba1, s);
35+
}
36+
37+
export function init(): void {
38+
A = 0x67452301;
39+
B = 0xefcdab89;
40+
C = 0x98badcfe;
41+
D = 0x10325476;
42+
totalLength = 0;
43+
}
44+
45+
function body(size: u32): void {
46+
let _A = A;
47+
let _B = B;
48+
let _C = C;
49+
let _D = D;
50+
51+
for (let i: u32 = 0; i < size; i += 64) {
52+
let a = _A;
53+
let b = _B;
54+
let c = _C;
55+
let d = _D;
56+
57+
// Round F
58+
59+
a = roundF(a, b, c, d, i + 4 * 0, 3);
60+
d = roundF(d, a, b, c, i + 4 * 1, 7);
61+
c = roundF(c, d, a, b, i + 4 * 2, 11);
62+
b = roundF(b, c, d, a, i + 4 * 3, 19);
63+
64+
a = roundF(a, b, c, d, i + 4 * 4, 3);
65+
d = roundF(d, a, b, c, i + 4 * 5, 7);
66+
c = roundF(c, d, a, b, i + 4 * 6, 11);
67+
b = roundF(b, c, d, a, i + 4 * 7, 19);
68+
69+
a = roundF(a, b, c, d, i + 4 * 8, 3);
70+
d = roundF(d, a, b, c, i + 4 * 9, 7);
71+
c = roundF(c, d, a, b, i + 4 * 10, 11);
72+
b = roundF(b, c, d, a, i + 4 * 11, 19);
73+
74+
a = roundF(a, b, c, d, i + 4 * 12, 3);
75+
d = roundF(d, a, b, c, i + 4 * 13, 7);
76+
c = roundF(c, d, a, b, i + 4 * 14, 11);
77+
b = roundF(b, c, d, a, i + 4 * 15, 19);
78+
79+
// Round G
80+
81+
a = roundG(a, b, c, d, i + 4 * 0, 3);
82+
d = roundG(d, a, b, c, i + 4 * 4, 5);
83+
c = roundG(c, d, a, b, i + 4 * 8, 9);
84+
b = roundG(b, c, d, a, i + 4 * 12, 13);
85+
86+
a = roundG(a, b, c, d, i + 4 * 1, 3);
87+
d = roundG(d, a, b, c, i + 4 * 5, 5);
88+
c = roundG(c, d, a, b, i + 4 * 9, 9);
89+
b = roundG(b, c, d, a, i + 4 * 13, 13);
90+
91+
a = roundG(a, b, c, d, i + 4 * 2, 3);
92+
d = roundG(d, a, b, c, i + 4 * 6, 5);
93+
c = roundG(c, d, a, b, i + 4 * 10, 9);
94+
b = roundG(b, c, d, a, i + 4 * 14, 13);
95+
96+
a = roundG(a, b, c, d, i + 4 * 3, 3);
97+
d = roundG(d, a, b, c, i + 4 * 7, 5);
98+
c = roundG(c, d, a, b, i + 4 * 11, 9);
99+
b = roundG(b, c, d, a, i + 4 * 15, 13);
100+
101+
// Round H
102+
103+
a = roundH(a, b, c, d, i + 4 * 0, 3);
104+
d = roundH(d, a, b, c, i + 4 * 8, 9);
105+
c = roundH(c, d, a, b, i + 4 * 4, 11);
106+
b = roundH(b, c, d, a, i + 4 * 12, 15);
107+
108+
a = roundH(a, b, c, d, i + 4 * 2, 3);
109+
d = roundH(d, a, b, c, i + 4 * 10, 9);
110+
c = roundH(c, d, a, b, i + 4 * 6, 11);
111+
b = roundH(b, c, d, a, i + 4 * 14, 15);
112+
113+
a = roundH(a, b, c, d, i + 4 * 1, 3);
114+
d = roundH(d, a, b, c, i + 4 * 9, 9);
115+
c = roundH(c, d, a, b, i + 4 * 5, 11);
116+
b = roundH(b, c, d, a, i + 4 * 13, 15);
117+
118+
a = roundH(a, b, c, d, i + 4 * 3, 3);
119+
d = roundH(d, a, b, c, i + 4 * 11, 9);
120+
c = roundH(c, d, a, b, i + 4 * 7, 11);
121+
b = roundH(b, c, d, a, i + 4 * 15, 15);
122+
123+
_A += a;
124+
_B += b;
125+
_C += c;
126+
_D += d;
127+
}
128+
129+
A = _A;
130+
B = _B;
131+
C = _C;
132+
D = _D;
133+
}
134+
135+
export function update(length: u32): void {
136+
body(length);
137+
totalLength += length;
138+
}
139+
140+
export function final(length: u32): void {
141+
const bits: u64 = u64(totalLength + length) << 3;
142+
const finalLength: u32 = (length + 9 + 63) & ~63;
143+
const bitsPosition = finalLength - 8;
144+
145+
// end
146+
store<u8>(length++, 0x80);
147+
148+
// padding
149+
for (; length & 7 && length < finalLength; length++) store<u8>(length, 0);
150+
for (; length < finalLength; length += 8) store<u64>(length, 0);
151+
152+
// bits
153+
store<u64>(bitsPosition, bits);
154+
155+
body(finalLength);
156+
157+
store<u64>(0, u32ToHex(A));
158+
store<u64>(8, u32ToHex(B));
159+
store<u64>(16, u32ToHex(C));
160+
store<u64>(24, u32ToHex(D));
161+
}
162+
163+
function u32ToHex(x: u64): u64 {
164+
// from https://johnnylee-sde.github.io/Fast-unsigned-integer-to-hex-string/
165+
166+
x = ((x & 0xffff0000) << 16) | (x & 0xffff);
167+
x = ((x & 0x0000ff000000ff00) << 8) | (x & 0x000000ff000000ff);
168+
x = ((x & 0x00f000f000f000f0) >> 4) | ((x & 0x000f000f000f000f) << 8);
169+
170+
const mask = ((x + 0x0606060606060606) >> 4) & 0x0101010101010101;
171+
172+
x |= 0x3030303030303030;
173+
174+
x += 0x27 * mask;
175+
176+
return x;
177+
}

assembly/hash/xxhash64.asm.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@ export function final(length: u32): void {
108108
result *= Prime3;
109109
result ^= result >> 32;
110110

111-
store<u64>(0, result);
112-
113111
store<u64>(0, u32ToHex(result >> 32));
114112
store<u64>(8, u32ToHex(result & 0xffffffff));
115113
}

benchmark/md4.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
const crypto = require("crypto");
2+
const createHash = require("../lib/util/createHash");
3+
4+
let result;
5+
6+
const measure = (fn, count) => {
7+
const start = process.hrtime.bigint();
8+
for (let i = 0; i < count; i++) result = fn();
9+
return Number(process.hrtime.bigint() - start);
10+
};
11+
12+
const NS_PER_MS = 1000000; // 1ms
13+
const MIN_DURATION = 100 * NS_PER_MS; // 100ms
14+
const MAX_DURATION = 1000 * NS_PER_MS; // 1000ms
15+
const MAX_WARMUP_DURATION = 1 * NS_PER_MS; // 1ms
16+
17+
const format = (fast, slow, fastName, slowName, count) => {
18+
return `${fastName} is ${
19+
Math.round(((slow - fast) * 1000) / slow) / 10
20+
}% faster than ${slowName} (${Math.round(fast / 100 / count) / 10} µs vs ${
21+
Math.round(slow / 100 / count) / 10
22+
} µs, ${count}x)`;
23+
};
24+
25+
const compare = (n1, f1, n2, f2) => {
26+
let count = 1;
27+
while (true) {
28+
const timings = [f1, f2, f1, f2, f1, f2].map(f => measure(f, count));
29+
const t1 = Math.min(timings[0], timings[2], timings[4]);
30+
const t2 = Math.min(timings[1], timings[3], timings[5]);
31+
if (count === 1 && (t1 > MAX_WARMUP_DURATION || t2 > MAX_WARMUP_DURATION)) {
32+
continue;
33+
}
34+
if (
35+
(t1 > MIN_DURATION && t2 > MIN_DURATION) ||
36+
t1 > MAX_DURATION ||
37+
t2 > MAX_DURATION
38+
) {
39+
return t1 > t2
40+
? format(t2, t1, n2, n1, count)
41+
: format(t1, t2, n1, n2, count);
42+
}
43+
count *= 2;
44+
}
45+
};
46+
47+
for (const size of [
48+
1, 2, 4, 8, 10, 20, 40, 60, 80, 100, 200, 1000, 5000, 10000, 20000, 50000,
49+
100000, 200000, 500000
50+
]) {
51+
const longString = require("crypto").randomBytes(size).toString("hex");
52+
const buffer = require("crypto").randomBytes(size * 2);
53+
console.log(
54+
`string ${longString.length} chars: ` +
55+
compare(
56+
"crypto md4",
57+
() => {
58+
const hash = crypto.createHash("md4");
59+
hash.update(longString);
60+
return hash.digest("hex");
61+
},
62+
"wasm md4",
63+
() => {
64+
const hash = createHash("md4");
65+
hash.update(longString);
66+
return hash.digest("hex");
67+
}
68+
)
69+
);
70+
console.log(
71+
`buffer ${buffer.length} bytes: ` +
72+
compare(
73+
"crypto md4",
74+
() => {
75+
const hash = crypto.createHash("md4");
76+
hash.update(buffer);
77+
return hash.digest("hex");
78+
},
79+
"wasm md4",
80+
() => {
81+
const hash = createHash("md4");
82+
hash.update(buffer);
83+
return hash.digest("hex");
84+
}
85+
)
86+
);
87+
}

declarations/LoaderContext.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { ResolveOptionsWithDependencyType } from "../lib/ResolverFactory";
55
import type Compilation from "../lib/Compilation";
66
import type Compiler from "../lib/Compiler";
77
import type NormalModule from "../lib/NormalModule";
8+
import type Hash from "../lib/util/Hash";
89
import type { InputFileSystem } from "../lib/util/fs";
910
import type { Logger } from "../lib/logging/Logger";
1011
import type {
@@ -39,6 +40,7 @@ export interface NormalModuleLoaderContext<OptionsType> {
3940
utils: {
4041
absolutify: (context: string, request: string) => string;
4142
contextify: (context: string, request: string) => string;
43+
createHash: (algorithm: string) => Hash;
4244
};
4345
rootContext: string;
4446
fs: InputFileSystem;

lib/NormalModule.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,8 @@ class NormalModule extends Module {
538538
return context === this.context
539539
? getContextifyInContext()(request)
540540
: getContextify()(context, request);
541-
}
541+
},
542+
createHash
542543
};
543544
const loaderContext = {
544545
version: 2,

lib/util/createHash.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ class DebugHash extends Hash {
126126

127127
let crypto = undefined;
128128
let createXXHash64 = undefined;
129+
let createMd4 = undefined;
129130
let BatchedHash = undefined;
130131

131132
/**
@@ -149,6 +150,17 @@ module.exports = algorithm => {
149150
}
150151
}
151152
return new BatchedHash(createXXHash64());
153+
case "md4":
154+
if (createMd4 === undefined) {
155+
createMd4 = require("./hash/md4");
156+
if (BatchedHash === undefined) {
157+
BatchedHash = require("./hash/BatchedHash");
158+
}
159+
}
160+
return new BatchedHash(createMd4());
161+
case "native-md4":
162+
if (crypto === undefined) crypto = require("crypto");
163+
return new BulkUpdateDecorator(() => crypto.createHash("md4"), "md4");
152164
default:
153165
if (crypto === undefined) crypto = require("crypto");
154166
return new BulkUpdateDecorator(

lib/util/hash/md4.js

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)