diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5775-md5-lengths.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5775-md5-lengths.ts.json new file mode 100644 index 000000000..84c394e4a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5775-md5-lengths.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5775-md5-lengths.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index 930d44818..ce7de9654 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -7263,45 +7263,112 @@ static ScrStr *scr_digest_encode(const unsigned char *d, size_t n, int enc) { /* ── MD5 (RFC 1321) ───────────────────────────────────────────────── */ -static const uint32_t scr_md5_k[64] = { - 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, - 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, - 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, - 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, - 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, - 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, - 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, - 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, - 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, - 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, - 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; -static const unsigned char scr_md5_r[64] = { - 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; +/* The 64 steps are unrolled with their message word, constant and + * rotation inlined. a + m + K does not depend on the previous step, so + * each step's critical path is the round function of b, an add, the + * rotate and the add of b. G is written as (b & d) + (c & ~d) (the two + * terms share no bits) so that its c & ~d half joins that early sum. The + * empty asm statement keeps the compiler from moving the constant's add + * after the round function, which puts it back on the critical path. */ +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__wasm__) +#define SCR_MD5_PIN(v) __asm__("" : "+r"(v)) +#else +#define SCR_MD5_PIN(v) ((void)0) +#endif +#define SCR_MD5_F(b, c, d) ((d) ^ ((b) & ((c) ^ (d)))) +#define SCR_MD5_H(b, c, d) ((b) ^ (c) ^ (d)) +#define SCR_MD5_I(b, c, d) ((c) ^ ((b) | ~(d))) +#define SCR_MD5_STEP(f, a, b, c, d, xk, s) \ + do { \ + (a) += (xk); \ + SCR_MD5_PIN(a); \ + (a) += f((b), (c), (d)); \ + (a) = ((a) << (s) | (a) >> (32 - (s))) + (b); \ + } while (0) +#define SCR_MD5_GSTEP(a, b, c, d, xk, s) \ + do { \ + (a) += ((c) & ~(d)) + (xk); \ + SCR_MD5_PIN(a); \ + (a) += (b) & (d); \ + (a) = ((a) << (s) | (a) >> (32 - (s))) + (b); \ + } while (0) -SCR_DIGEST_ALIGNED static void scr_md5_block(uint32_t h[4], const unsigned char *p) { - uint32_t m[16]; - for (int i = 0; i < 16; i++) { - m[i] = (uint32_t)p[i * 4] | ((uint32_t)p[i * 4 + 1] << 8) | - ((uint32_t)p[i * 4 + 2] << 16) | ((uint32_t)p[i * 4 + 3] << 24); - } +SCR_DIGEST_ALIGNED static void scr_md5_blocks(uint32_t h[4], const unsigned char *p, size_t blocks) { uint32_t a = h[0], b = h[1], c = h[2], d = h[3]; - for (int i = 0; i < 64; i++) { - uint32_t f, g; - if (i < 16) { f = (b & c) | (~b & d); g = (uint32_t)i; } - else if (i < 32) { f = (d & b) | (~d & c); g = (5u * i + 1) & 15; } - else if (i < 48) { f = b ^ c ^ d; g = (3u * i + 5) & 15; } - else { f = c ^ (b | ~d); g = (7u * i) & 15; } - uint32_t t = d; - d = c; - c = b; - uint32_t x = a + f + scr_md5_k[i] + m[g]; - b = b + ((x << scr_md5_r[i]) | (x >> (32 - scr_md5_r[i]))); - a = t; + for (; blocks != 0; blocks--, p += 64) { + uint32_t m[16]; + for (int i = 0; i < 16; i++) { + m[i] = (uint32_t)p[i * 4] | ((uint32_t)p[i * 4 + 1] << 8) | + ((uint32_t)p[i * 4 + 2] << 16) | ((uint32_t)p[i * 4 + 3] << 24); + } + uint32_t a0 = a, b0 = b, c0 = c, d0 = d; + SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[0] + 0xd76aa478u, 7); + SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[1] + 0xe8c7b756u, 12); + SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[2] + 0x242070dbu, 17); + SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[3] + 0xc1bdceeeu, 22); + SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[4] + 0xf57c0fafu, 7); + SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[5] + 0x4787c62au, 12); + SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[6] + 0xa8304613u, 17); + SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[7] + 0xfd469501u, 22); + SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[8] + 0x698098d8u, 7); + SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[9] + 0x8b44f7afu, 12); + SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[10] + 0xffff5bb1u, 17); + SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[11] + 0x895cd7beu, 22); + SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[12] + 0x6b901122u, 7); + SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[13] + 0xfd987193u, 12); + SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[14] + 0xa679438eu, 17); + SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[15] + 0x49b40821u, 22); + SCR_MD5_GSTEP(a, b, c, d, m[1] + 0xf61e2562u, 5); + SCR_MD5_GSTEP(d, a, b, c, m[6] + 0xc040b340u, 9); + SCR_MD5_GSTEP(c, d, a, b, m[11] + 0x265e5a51u, 14); + SCR_MD5_GSTEP(b, c, d, a, m[0] + 0xe9b6c7aau, 20); + SCR_MD5_GSTEP(a, b, c, d, m[5] + 0xd62f105du, 5); + SCR_MD5_GSTEP(d, a, b, c, m[10] + 0x02441453u, 9); + SCR_MD5_GSTEP(c, d, a, b, m[15] + 0xd8a1e681u, 14); + SCR_MD5_GSTEP(b, c, d, a, m[4] + 0xe7d3fbc8u, 20); + SCR_MD5_GSTEP(a, b, c, d, m[9] + 0x21e1cde6u, 5); + SCR_MD5_GSTEP(d, a, b, c, m[14] + 0xc33707d6u, 9); + SCR_MD5_GSTEP(c, d, a, b, m[3] + 0xf4d50d87u, 14); + SCR_MD5_GSTEP(b, c, d, a, m[8] + 0x455a14edu, 20); + SCR_MD5_GSTEP(a, b, c, d, m[13] + 0xa9e3e905u, 5); + SCR_MD5_GSTEP(d, a, b, c, m[2] + 0xfcefa3f8u, 9); + SCR_MD5_GSTEP(c, d, a, b, m[7] + 0x676f02d9u, 14); + SCR_MD5_GSTEP(b, c, d, a, m[12] + 0x8d2a4c8au, 20); + SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[5] + 0xfffa3942u, 4); + SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[8] + 0x8771f681u, 11); + SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[11] + 0x6d9d6122u, 16); + SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[14] + 0xfde5380cu, 23); + SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[1] + 0xa4beea44u, 4); + SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[4] + 0x4bdecfa9u, 11); + SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[7] + 0xf6bb4b60u, 16); + SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[10] + 0xbebfbc70u, 23); + SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[13] + 0x289b7ec6u, 4); + SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[0] + 0xeaa127fau, 11); + SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[3] + 0xd4ef3085u, 16); + SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[6] + 0x04881d05u, 23); + SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[9] + 0xd9d4d039u, 4); + SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[12] + 0xe6db99e5u, 11); + SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[15] + 0x1fa27cf8u, 16); + SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[2] + 0xc4ac5665u, 23); + SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[0] + 0xf4292244u, 6); + SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[7] + 0x432aff97u, 10); + SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[14] + 0xab9423a7u, 15); + SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[5] + 0xfc93a039u, 21); + SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[12] + 0x655b59c3u, 6); + SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[3] + 0x8f0ccc92u, 10); + SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[10] + 0xffeff47du, 15); + SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[1] + 0x85845dd1u, 21); + SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[8] + 0x6fa87e4fu, 6); + SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[15] + 0xfe2ce6e0u, 10); + SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[6] + 0xa3014314u, 15); + SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[13] + 0x4e0811a1u, 21); + SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[4] + 0xf7537e82u, 6); + SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[11] + 0xbd3af235u, 10); + SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[2] + 0x2ad7d2bbu, 15); + SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[9] + 0xeb86d391u, 21); + a += a0; b += b0; c += c0; d += d0; } - h[0] += a; h[1] += b; h[2] += c; h[3] += d; + h[0] = a; h[1] = b; h[2] = c; h[3] = d; } typedef struct ScrMd5Ctx { @@ -7329,14 +7396,15 @@ static void scr_md5_update(ScrMd5Ctx *ctx, const unsigned char *data, size_t len data += take; len -= take; if (ctx->tail_len == 64) { - scr_md5_block(ctx->h, ctx->tail); + scr_md5_blocks(ctx->h, ctx->tail, 1); ctx->tail_len = 0; } } - while (len >= 64) { - scr_md5_block(ctx->h, data); - data += 64; - len -= 64; + if (len >= 64) { + size_t blocks = len / 64; + scr_md5_blocks(ctx->h, data, blocks); + data += blocks * 64; + len -= blocks * 64; } if (len != 0) { memcpy(ctx->tail, data, len); @@ -7354,8 +7422,7 @@ static size_t scr_md5_final(const ScrMd5Ctx *source, unsigned char out[32]) { memset(tail + rem + 1, 0, pad - rem - 1 - 8); uint64_t bits = ctx.bytes * 8; for (int b = 0; b < 8; b++) tail[pad - 8 + b] = (unsigned char)(bits >> (8 * b)); - scr_md5_block(ctx.h, tail); - if (pad == 128) scr_md5_block(ctx.h, tail + 64); + scr_md5_blocks(ctx.h, tail, pad / 64); for (int j = 0; j < 4; j++) { for (int b = 0; b < 4; b++) out[j * 4 + b] = (unsigned char)(ctx.h[j] >> (8 * b)); } diff --git a/tests/corpus/5775-md5-lengths.ts b/tests/corpus/5775-md5-lengths.ts new file mode 100644 index 000000000..957491568 --- /dev/null +++ b/tests/corpus/5775-md5-lengths.ts @@ -0,0 +1,34 @@ +// MD5 and md5-sha1 over every length from 0 to 520 bytes, fed whole and +// in 1-, 7-, 64- and 100-byte updates, then over 1 MiB, and HMAC-MD5 +// over a few lengths. Each length's digests are folded into one running +// SHA-512 so the output stays short. +import { createHash, createHmac } from "node:crypto"; + +const data = Buffer.alloc(1 << 20); +let x = 0x9e3779b9; +for (let i = 0; i < data.length; i++) { + x ^= x << 13; + x ^= x >>> 17; + x ^= x << 5; + data[i] = x & 0xff; +} + +function digest(algorithm: string, input: Buffer, chunk: number): Buffer { + const hash = createHash(algorithm); + if (chunk === 0) hash.update(input); + else for (let at = 0; at < input.length; at += chunk) hash.update(input.subarray(at, at + chunk)); + return hash.digest(); +} + +for (const algorithm of ["md5", "md5-sha1"]) { + for (const chunk of [0, 1, 7, 64, 100]) { + const fold = createHash("sha512"); + for (let length = 0; length <= 520; length++) fold.update(digest(algorithm, data.subarray(0, length), chunk)); + console.log(algorithm, "chunk", chunk, fold.digest("hex").slice(0, 32)); + } + console.log(algorithm, "1 MiB", digest(algorithm, data, 0).toString("hex").slice(0, 32), digest(algorithm, data, 4096).toString("hex").slice(0, 32)); +} +console.log("abc", createHash("md5").update("abc").digest("hex")); +for (const length of [0, 1, 55, 56, 63, 64, 65, 119, 120, 1000]) { + console.log("hmac", length, createHmac("md5", "key").update(data.subarray(0, length)).digest("hex").slice(0, 32)); +} diff --git a/tests/harness/corpus-costs/5775-md5-lengths.ts.json b/tests/harness/corpus-costs/5775-md5-lengths.ts.json new file mode 100644 index 000000000..70f936cec --- /dev/null +++ b/tests/harness/corpus-costs/5775-md5-lengths.ts.json @@ -0,0 +1 @@ +{"plain":2850,"san":3660}