Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions benchmark/crypto/webcrypto-sign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
'use strict';

const common = require('../common.js');
const { hasOpenSSL } = require('../../test/common/crypto.js');
const { subtle } = globalThis.crypto;

const kAlgorithms = {
'ec': { name: 'ECDSA', namedCurve: 'P-256' },
'rsassa-pkcs1-v1_5': {
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
'rsa-pss': {
name: 'RSA-PSS',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
'ed25519': { name: 'Ed25519' },
};

if (hasOpenSSL(3, 5)) {
kAlgorithms['ml-dsa-44'] = { name: 'ML-DSA-44' };
}

const kSignParams = {
'ec': { name: 'ECDSA', hash: 'SHA-256' },
'rsassa-pkcs1-v1_5': { name: 'RSASSA-PKCS1-v1_5' },
'rsa-pss': { name: 'RSA-PSS', saltLength: 32 },
'ed25519': { name: 'Ed25519' },
'ml-dsa-44': { name: 'ML-DSA-44' },
};

const data = globalThis.crypto.getRandomValues(new Uint8Array(256));

let keys;

const bench = common.createBenchmark(main, {
keyType: Object.keys(kAlgorithms),
mode: ['serial', 'parallel'],
keyReuse: ['shared', 'unique'],
n: [1e3],
}, {
combinationFilter(p) {
// Unique only differs from shared when operations overlap (parallel);
// sequential calls have no contention so unique+serial adds no value.
if (p.keyReuse === 'unique') return p.mode === 'parallel';
return true;
},
});

async function measureSerial(n, signParams, sharedKey) {
bench.start();
for (let i = 0; i < n; ++i) {
await subtle.sign(signParams, sharedKey || keys[i], data);
}
bench.end(n);
}

async function measureParallel(n, signParams, sharedKey) {
const promises = new Array(n);
bench.start();
for (let i = 0; i < n; ++i) {
promises[i] = subtle.sign(signParams, sharedKey || keys[i], data);
}
await Promise.all(promises);
bench.end(n);
}

async function main({ n, mode, keyReuse, keyType }) {
const algorithm = kAlgorithms[keyType];
const signParams = kSignParams[keyType];

if (!keys || keys.length !== n || keys[0].algorithm.name !== signParams.name) {
keys = new Array(n);
// Generate one key pair, then import its pkcs8 bytes n times to get
// distinct CryptoKey instances.
const kp = await subtle.generateKey(algorithm, true, ['sign', 'verify']);
const pkcs8 = await subtle.exportKey('pkcs8', kp.privateKey);
for (let i = 0; i < n; ++i) {
keys[i] = await subtle.importKey('pkcs8', pkcs8, algorithm, false, ['sign']);
}
}

const sharedKey = keyReuse === 'shared' ? keys[0] : undefined;

switch (mode) {
case 'serial':
await measureSerial(n, signParams, sharedKey);
break;
case 'parallel':
await measureParallel(n, signParams, sharedKey);
break;
}
}
100 changes: 100 additions & 0 deletions benchmark/crypto/webcrypto-verify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
'use strict';

const common = require('../common.js');
const { hasOpenSSL } = require('../../test/common/crypto.js');
const { subtle } = globalThis.crypto;

const kAlgorithms = {
'ec': { name: 'ECDSA', namedCurve: 'P-256' },
'rsassa-pkcs1-v1_5': {
name: 'RSASSA-PKCS1-v1_5',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
'rsa-pss': {
name: 'RSA-PSS',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
'ed25519': { name: 'Ed25519' },
};

if (hasOpenSSL(3, 5)) {
kAlgorithms['ml-dsa-44'] = { name: 'ML-DSA-44' };
}

const kSignParams = {
'ec': { name: 'ECDSA', hash: 'SHA-256' },
'rsassa-pkcs1-v1_5': { name: 'RSASSA-PKCS1-v1_5' },
'rsa-pss': { name: 'RSA-PSS', saltLength: 32 },
'ed25519': { name: 'Ed25519' },
'ml-dsa-44': { name: 'ML-DSA-44' },
};

const data = globalThis.crypto.getRandomValues(new Uint8Array(256));

let publicKeys;
let signature;

const bench = common.createBenchmark(main, {
keyType: Object.keys(kAlgorithms),
mode: ['serial', 'parallel'],
keyReuse: ['shared', 'unique'],
n: [1e3],
}, {
combinationFilter(p) {
// Unique only differs from shared when operations overlap (parallel);
// sequential calls have no contention so unique+serial adds no value.
if (p.keyReuse === 'unique') return p.mode === 'parallel';
return true;
},
});

async function measureSerial(n, verifyParams, sharedKey) {
bench.start();
for (let i = 0; i < n; ++i) {
await subtle.verify(verifyParams, sharedKey || publicKeys[i], signature, data);
}
bench.end(n);
}

async function measureParallel(n, verifyParams, sharedKey) {
const promises = new Array(n);
bench.start();
for (let i = 0; i < n; ++i) {
promises[i] = subtle.verify(verifyParams, sharedKey || publicKeys[i], signature, data);
}
await Promise.all(promises);
bench.end(n);
}

async function main({ n, mode, keyReuse, keyType }) {
const algorithm = kAlgorithms[keyType];
const verifyParams = kSignParams[keyType];

if (!publicKeys || publicKeys.length !== n ||
publicKeys[0].algorithm.name !== verifyParams.name) {
publicKeys = new Array(n);
// Generate one key pair, then import its spki bytes n times to get
// distinct CryptoKey instances.
const kp = await subtle.generateKey(algorithm, true, ['sign', 'verify']);
const spki = await subtle.exportKey('spki', kp.publicKey);
for (let i = 0; i < n; ++i) {
publicKeys[i] = await subtle.importKey('spki', spki, algorithm, false, ['verify']);
}
signature = await subtle.sign(verifyParams, kp.privateKey, data);
}

const sharedKey = keyReuse === 'shared' ? publicKeys[0] : undefined;

switch (mode) {
case 'serial':
await measureSerial(n, verifyParams, sharedKey);
break;
case 'parallel':
await measureParallel(n, verifyParams, sharedKey);
break;
}
}
4 changes: 4 additions & 0 deletions lib/eslint.config_partial.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export default [
selector: "CallExpression[callee.type='Identifier'][callee.name='ReflectApply'][arguments.2.type='ArrayExpression']",
message: 'Use `FunctionPrototypeCall` to avoid creating an ad-hoc array',
},
{
selector: "VariableDeclarator[init.type='CallExpression'][init.callee.name='internalBinding'][init.arguments.0.value='crypto'] > ObjectPattern > Property[key.name='getCryptoKeySlots']",
message: "Use `const { getCryptoKeySlots } = require('internal/crypto/keys');` instead of destructuring it from `internalBinding('crypto')`.",
},
],
'no-restricted-globals': [
'error',
Expand Down
Loading
Loading