Commit e0748dfd9d7 for nodejs
commit e0748dfd9d7d54f4e93d21754744cbe116046c98
Author: Filip Skokan <panva.ip@gmail.com>
Date: Wed Sep 23 11:11:50 2026 +0200
benchmark: cover Web Crypto conversion costs
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
Assisted-by: Codex
PR-URL: https://github.com/nodejs/node/pull/66237
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Aviv Keller <me@aviv.sh>
diff --git a/benchmark/crypto/webcrypto-export.js b/benchmark/crypto/webcrypto-export.js
new file mode 100644
index 00000000000..4d96b54ec74
--- /dev/null
+++ b/benchmark/crypto/webcrypto-export.js
@@ -0,0 +1,47 @@
+'use strict';
+
+const common = require('../common.js');
+const fixtures = require('../../test/common/fixtures.js');
+const { createPrivateKey, createPublicKey, subtle } = require('node:crypto');
+
+const bench = common.createBenchmark(main, {
+ keyType: ['hmac', 'aes-gcm', 'ecdsa-private', 'rsa-private', 'rsa-public'],
+ n: [1e5],
+});
+
+async function createKey(keyType) {
+ switch (keyType) {
+ case 'hmac':
+ return subtle.importKey(
+ 'raw', new Uint8Array(32), { name: 'HMAC', hash: 'SHA-256' },
+ true, ['sign', 'verify']);
+ case 'aes-gcm':
+ return subtle.importKey(
+ 'raw', new Uint8Array(32), 'AES-GCM',
+ true, ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey']);
+ case 'ecdsa-private':
+ return createPrivateKey(fixtures.readKey('ec_p256_private.pem'))
+ .toCryptoKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign']);
+ case 'rsa-private':
+ return createPrivateKey(fixtures.readKey('rsa_private_2048.pem'))
+ .toCryptoKey({ name: 'RSA-PSS', hash: 'SHA-256' }, true, ['sign']);
+ case 'rsa-public':
+ return createPublicKey(fixtures.readKey('rsa_private_2048.pem'))
+ .toCryptoKey({ name: 'RSA-PSS', hash: 'SHA-256' }, true, ['verify']);
+ default:
+ throw new Error(`Unsupported key type: ${keyType}`);
+ }
+}
+
+async function main({ n, keyType }) {
+ const key = await createKey(keyType);
+ let result;
+
+ bench.start();
+ for (let i = 0; i < n; i++)
+ result = await subtle.exportKey('jwk', key);
+ bench.end(n);
+
+ if (result.kty === undefined)
+ throw new Error('Missing key type');
+}
diff --git a/benchmark/misc/webcrypto-util.js b/benchmark/misc/webcrypto-util.js
new file mode 100644
index 00000000000..581602ab347
--- /dev/null
+++ b/benchmark/misc/webcrypto-util.js
@@ -0,0 +1,117 @@
+'use strict';
+
+const common = require('../common.js');
+
+const inputs = [
+ 'arraybuffer',
+ 'uint8array',
+ 'dataview',
+ 'buffer',
+ 'sharedview',
+ 'empty-arraybuffer',
+ 'empty-uint8array',
+ 'empty-dataview',
+ 'detached-arraybuffer',
+ 'detached-uint8array',
+ 'detached-dataview',
+];
+
+const bench = common.createBenchmark(main, {
+ op: [
+ ...inputs.flatMap((input) => [`byteLength:${input}`, `bytes:${input}`]),
+ 'truncate:arraybuffer:100',
+ 'truncate:arraybuffer:128',
+ 'truncate:uint8array:100',
+ 'truncate:uint8array:128',
+ 'usages:0',
+ 'usages:1',
+ 'usages:2',
+ 'usages:4',
+ ],
+ n: [1e6],
+}, { flags: ['--expose-internals'] });
+
+function createInput(input) {
+ const empty = input.startsWith('empty-');
+ const detached = input.startsWith('detached-');
+ const type = input.replace(/^(empty|detached)-/, '');
+ const size = empty ? 0 : 32;
+ const buffer = new ArrayBuffer(size + 16);
+ let value;
+ switch (type) {
+ case 'arraybuffer':
+ value = new ArrayBuffer(size);
+ break;
+ case 'uint8array':
+ value = new Uint8Array(buffer, 8, size);
+ break;
+ case 'dataview':
+ value = new DataView(buffer, 8, size);
+ break;
+ case 'buffer':
+ value = Buffer.from(buffer, 8, size);
+ break;
+ case 'sharedview':
+ value = new Uint8Array(new SharedArrayBuffer(size));
+ break;
+ default:
+ throw new Error(`Unsupported input: ${input}`);
+ }
+ if (detached) {
+ const backing = type === 'arraybuffer' ? value : buffer;
+ structuredClone(backing, { transfer: [backing] });
+ }
+ return value;
+}
+
+function main({ n, op }) {
+ const {
+ getBufferSourceByteLength,
+ getBufferSourceBytes,
+ getUsagesFromMask,
+ getUsagesMask,
+ truncateToBitLength,
+ } = require('internal/crypto/util');
+ const [operation, input, length] = op.split(':');
+ let run;
+ switch (operation) {
+ case 'byteLength': {
+ const value = createInput(input);
+ run = () => getBufferSourceByteLength(value);
+ break;
+ }
+ case 'bytes': {
+ const value = createInput(input);
+ run = () => getBufferSourceBytes(value);
+ break;
+ }
+ case 'truncate': {
+ const value = createInput(input);
+ const bits = Number(length);
+ run = () => truncateToBitLength(bits, value);
+ break;
+ }
+ case 'usages': {
+ const usages = {
+ 0: [],
+ 1: ['sign'],
+ 2: ['sign', 'verify'],
+ 4: ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'],
+ };
+ const mask = getUsagesMask(usages[input]);
+ run = () => getUsagesFromMask(mask);
+ break;
+ }
+ default:
+ throw new Error(`Unsupported operation: ${operation}`);
+ }
+
+ let result;
+ bench.start();
+ for (let i = 0; i < n; i++)
+ result = run();
+ bench.end(n);
+
+ if (result === undefined)
+ throw new Error('Missing benchmark result');
+}
diff --git a/benchmark/misc/webcrypto-webidl.js b/benchmark/misc/webcrypto-webidl.js
index 0f6275ed095..318ea798207 100644
--- a/benchmark/misc/webcrypto-webidl.js
+++ b/benchmark/misc/webcrypto-webidl.js
@@ -6,6 +6,13 @@ const bench = common.createBenchmark(main, {
op: [
'normalizeAlgorithm-string',
'normalizeAlgorithm-dict',
+ 'normalizeAlgorithm-validate-aes-gcm',
+ 'normalizeAlgorithm-validate-aes-cbc',
+ 'normalizeAlgorithm-validate-aes-ctr',
+ 'normalizeAlgorithm-validate-aes-generate',
+ 'normalizeAlgorithm-validate-hkdf',
+ 'normalizeAlgorithm-validate-hmac',
+ 'normalizeAlgorithm-validate-rsa',
'webidl-dict',
'webidl-algorithm-identifier-string',
'webidl-algorithm-identifier-object',
@@ -17,7 +24,7 @@ const bench = common.createBenchmark(main, {
}, { flags: ['--expose-internals'] });
function main({ n, op }) {
- const { normalizeAlgorithm } = require('internal/crypto/util');
+ const { normalizeAlgorithm, validateAlgorithm } = require('internal/crypto/util');
switch (op) {
case 'normalizeAlgorithm-string': {
@@ -37,6 +44,54 @@ function main({ n, op }) {
bench.end(n);
break;
}
+ case 'normalizeAlgorithm-validate-aes-gcm':
+ case 'normalizeAlgorithm-validate-aes-cbc':
+ case 'normalizeAlgorithm-validate-aes-ctr':
+ case 'normalizeAlgorithm-validate-aes-generate':
+ case 'normalizeAlgorithm-validate-hkdf':
+ case 'normalizeAlgorithm-validate-hmac':
+ case 'normalizeAlgorithm-validate-rsa': {
+ const cases = {
+ 'aes-gcm': [
+ { name: 'AES-GCM', iv: new Uint8Array(12), tagLength: 128 },
+ 'encrypt',
+ ],
+ 'aes-cbc': [
+ { name: 'AES-CBC', iv: new Uint8Array(16) },
+ 'encrypt',
+ ],
+ 'aes-ctr': [
+ { name: 'AES-CTR', counter: new Uint8Array(16), length: 64 },
+ 'encrypt',
+ ],
+ 'aes-generate': [{ name: 'AES-GCM', length: 256 }, 'generateKey'],
+ 'hkdf': [
+ {
+ name: 'HKDF', hash: 'SHA-256',
+ salt: new Uint8Array(32), info: new Uint8Array(32),
+ },
+ 'deriveBits',
+ ],
+ 'hmac': [{ name: 'HMAC', hash: 'SHA-256', length: 256 }, 'importKey'],
+ 'rsa': [
+ {
+ name: 'RSA-PSS', hash: 'SHA-256', modulusLength: 2048,
+ publicExponent: new Uint8Array([1, 0, 1]),
+ },
+ 'generateKey',
+ ],
+ };
+ const name = op.slice('normalizeAlgorithm-validate-'.length);
+ const [input, operation] = cases[name];
+ bench.start();
+ for (let i = 0; i < n; i++) {
+ const normalized = normalizeAlgorithm(input, operation);
+ // Older revisions validate inside normalizeAlgorithm.
+ validateAlgorithm?.(normalized, operation);
+ }
+ bench.end(n);
+ break;
+ }
case 'webidl-dict': {
// WebIDL dictionary converter in isolation.
const webidl = require('internal/crypto/webidl');
@@ -85,7 +140,7 @@ function main({ n, op }) {
break;
}
case 'webidl-dict-ensure-sha': {
- // Exercises ensureSHA on a hash member.
+ // Converts a dictionary containing a hash identifier.
const webidl = require('internal/crypto/webidl');
const input = { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' };
const opts = { prefix: 'test', context: 'test' };