Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(crypto): move FNV hashes from TypeScript to Rust/Wasm and implement iteration functionality #4515

Merged
merged 11 commits into from
Mar 26, 2024
Merged
2 changes: 1 addition & 1 deletion _tools/check_deprecation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const EXCLUDED_PATHS = [
"fs/testdata",
"http/testdata",
"http/_negotiation",
"crypto/_benches",
"crypto/_wasm",
"crypto/_fnv",
"encoding/_yaml",
"encoding/_toml",
"_tools",
Expand Down
133 changes: 83 additions & 50 deletions crypto/_benches/bench.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,98 @@
#!/usr/bin/env -S deno run
#!/usr/bin/env -S deno bench
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assert, assertEquals } from "../../assert/mod.ts";
import {
crypto as stdCrypto,
wasmDigestAlgorithms as DIGEST_ALGORITHM_NAMES,
} from "../mod.ts";

import { crypto as stdCrypto } from "../mod.ts";
import nodeCrypto from "node:crypto";

import { crypto as oldCrypto } from "https://deno.land/std@0.220.1/crypto/mod.ts";

const webCrypto = globalThis.crypto;

// Wasm is limited to 32-bit operations, which SHA-256 is optimized for, while
// SHA-512 is optimized for 64-bit operations and may be slower.
for (const algorithm of ["SHA-256", "SHA-512"] as const) {
for (
const length of [
64,
262_144,
4_194_304,
67_108_864,
524_291_328,
] as const
) {
// Create a test input buffer and do some operations to hopefully ensure
// it's fully initialized and not optimized away.
const buffer = new Uint8Array(length);
for (let i = 0; i < length; i++) {
buffer[i] = (i + (i % 13) + (i % 31)) % 255;
}
let sum = 0;
for (const byte of buffer) {
sum += byte;
const BENCHMARKED_DIGEST_ALGORITHM_NAMES = [
"SHA-256",
"SHA-512",
"BLAKE3",
"FNV32A",
"FNV64A",
] satisfies (typeof DIGEST_ALGORITHM_NAMES[number])[];

const WEB_CRYPTO_DIGEST_ALGORITHM_NAMES = [
"SHA-1",
"SHA-256",
"SHA-384",
"SHA-512",
] satisfies (typeof DIGEST_ALGORITHM_NAMES[number])[];

const NODE_CRYPTO_DIGEST_ALGORITHM_NAMES = [
"MD4",
"MD5",
"RIPEMD-160",
"SHA-1",
"SHA-224",
"SHA-256",
"SHA-384",
"SHA-512",
] satisfies (typeof DIGEST_ALGORITHM_NAMES[number])[];

for (
const [length, humanLength] of [
[64, "64 B"],
[262_144, "256 KiB"],
[4_194_304, "4 MiB"],
[67_108_864, "64 MiB"],
[524_291_328, "512 MiB"],
] as const
) {
const buffer = new Uint8Array(length);
for (let i = 0; i < length; i++) {
buffer[i] = (i + (i % 13) + (i % 31)) % 256;
}

for (const name of BENCHMARKED_DIGEST_ALGORITHM_NAMES) {
Deno.bench({
group: `digesting ${humanLength}`,
name: `${name} from std/crypto Wasm digesting ${humanLength}`,
async fn() {
await stdCrypto.subtle.digest(name, [buffer]);
},
});

if (name.startsWith("FNV")) {
Deno.bench({
group: `digesting ${humanLength}`,
name:
`${name} from std/crypto (v0.220.1) TypeScript digesting ${humanLength}`,
async fn() {
await oldCrypto.subtle.digest(name, buffer);
},
});
}
assert(sum > 0);

for (
const implementation of [
"runtime WebCrypto (target)",
"std/crypto Wasm (you are here)",
] as const
if (
(WEB_CRYPTO_DIGEST_ALGORITHM_NAMES as readonly string[]).includes(name)
) {
let lastDigest: ArrayBuffer | undefined;

Deno.bench({
name: `${algorithm.padEnd(12)} ${
length
.toString()
.padStart(12)
}B ${implementation}`,
group: `digesting ${humanLength}`,
name: `${name} from runtime WebCrypto digesting ${humanLength}`,
async fn() {
let digest;
if (implementation === "std/crypto Wasm (you are here)") {
digest = stdCrypto.subtle.digestSync(algorithm, buffer);
} else if (implementation === "runtime WebCrypto (target)") {
digest = await webCrypto.subtle.digest(algorithm, buffer);
} else {
throw new Error(`Unknown implementation ${implementation}`);
}
await webCrypto.subtle.digest(name, buffer);
},
});
}

assert(digest.byteLength > 0);
if (
(NODE_CRYPTO_DIGEST_ALGORITHM_NAMES as readonly string[]).includes(name)
) {
const nodeName = name.replace("-", "").toLowerCase();

if (lastDigest) {
assertEquals(lastDigest, digest);
}
lastDigest = digest;
Deno.bench({
group: `digesting ${humanLength}`,
name: `${name} from runtime node:crypto digesting ${humanLength}`,
fn() {
nodeCrypto.createHash(nodeName).update(buffer).digest();
},
});
}
Expand Down
Loading
Loading