Skip to content

Commit d3272c5

Browse files
committed
benchmark: add Web Crypto sign/verify benchmarks
Adds two benchmarks exercising `subtle.sign` / `subtle.verify` across the main algorithm families: - ECDSA P-256 - RSASSA-PKCS1-v1_5 - RSA-PSS - Ed25519 - ML-DSA-44 (gated on OpenSSL >= 3.5) Each benchmark runs two modes (serial / parallel) × two key-reuse patterns (shared / unique per call) so regressions in slot-access hot paths (getCryptoKey* accessors) and in per-call key wrapping surface on both dimensions. Signed-off-by: Filip Skokan <[email protected]>
1 parent fdef77d commit d3272c5

2 files changed

Lines changed: 197 additions & 0 deletions

File tree

benchmark/crypto/webcrypto-sign.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const { hasOpenSSL } = require('../../test/common/crypto.js');
5+
const { subtle } = globalThis.crypto;
6+
7+
const kAlgorithms = {
8+
'ec': { name: 'ECDSA', namedCurve: 'P-256' },
9+
'rsassa-pkcs1-v1_5': {
10+
name: 'RSASSA-PKCS1-v1_5',
11+
modulusLength: 2048,
12+
publicExponent: new Uint8Array([1, 0, 1]),
13+
hash: 'SHA-256',
14+
},
15+
'rsa-pss': {
16+
name: 'RSA-PSS',
17+
modulusLength: 2048,
18+
publicExponent: new Uint8Array([1, 0, 1]),
19+
hash: 'SHA-256',
20+
},
21+
'ed25519': { name: 'Ed25519' },
22+
};
23+
24+
if (hasOpenSSL(3, 5)) {
25+
kAlgorithms['ml-dsa-44'] = { name: 'ML-DSA-44' };
26+
}
27+
28+
const kSignParams = {
29+
'ec': { name: 'ECDSA', hash: 'SHA-256' },
30+
'rsassa-pkcs1-v1_5': { name: 'RSASSA-PKCS1-v1_5' },
31+
'rsa-pss': { name: 'RSA-PSS', saltLength: 32 },
32+
'ed25519': { name: 'Ed25519' },
33+
'ml-dsa-44': { name: 'ML-DSA-44' },
34+
};
35+
36+
const data = globalThis.crypto.getRandomValues(new Uint8Array(256));
37+
38+
let keys;
39+
40+
const bench = common.createBenchmark(main, {
41+
keyType: Object.keys(kAlgorithms),
42+
mode: ['serial', 'parallel'],
43+
keyReuse: ['shared', 'unique'],
44+
n: [1e3],
45+
}, {
46+
combinationFilter(p) {
47+
// Unique only differs from shared when operations overlap (parallel);
48+
// sequential calls have no contention so unique+serial adds no value.
49+
if (p.keyReuse === 'unique') return p.mode === 'parallel';
50+
return true;
51+
},
52+
});
53+
54+
async function measureSerial(n, signParams, sharedKey) {
55+
bench.start();
56+
for (let i = 0; i < n; ++i) {
57+
await subtle.sign(signParams, sharedKey || keys[i], data);
58+
}
59+
bench.end(n);
60+
}
61+
62+
async function measureParallel(n, signParams, sharedKey) {
63+
const promises = new Array(n);
64+
bench.start();
65+
for (let i = 0; i < n; ++i) {
66+
promises[i] = subtle.sign(signParams, sharedKey || keys[i], data);
67+
}
68+
await Promise.all(promises);
69+
bench.end(n);
70+
}
71+
72+
async function main({ n, mode, keyReuse, keyType }) {
73+
const algorithm = kAlgorithms[keyType];
74+
const signParams = kSignParams[keyType];
75+
76+
if (!keys || keys.length !== n || keys[0].algorithm.name !== signParams.name) {
77+
keys = new Array(n);
78+
// Generate one key pair, then import its pkcs8 bytes n times to get
79+
// distinct CryptoKey instances.
80+
const kp = await subtle.generateKey(algorithm, true, ['sign', 'verify']);
81+
const pkcs8 = await subtle.exportKey('pkcs8', kp.privateKey);
82+
for (let i = 0; i < n; ++i) {
83+
keys[i] = await subtle.importKey('pkcs8', pkcs8, algorithm, false, ['sign']);
84+
}
85+
}
86+
87+
const sharedKey = keyReuse === 'shared' ? keys[0] : undefined;
88+
89+
switch (mode) {
90+
case 'serial':
91+
await measureSerial(n, signParams, sharedKey);
92+
break;
93+
case 'parallel':
94+
await measureParallel(n, signParams, sharedKey);
95+
break;
96+
}
97+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const { hasOpenSSL } = require('../../test/common/crypto.js');
5+
const { subtle } = globalThis.crypto;
6+
7+
const kAlgorithms = {
8+
'ec': { name: 'ECDSA', namedCurve: 'P-256' },
9+
'rsassa-pkcs1-v1_5': {
10+
name: 'RSASSA-PKCS1-v1_5',
11+
modulusLength: 2048,
12+
publicExponent: new Uint8Array([1, 0, 1]),
13+
hash: 'SHA-256',
14+
},
15+
'rsa-pss': {
16+
name: 'RSA-PSS',
17+
modulusLength: 2048,
18+
publicExponent: new Uint8Array([1, 0, 1]),
19+
hash: 'SHA-256',
20+
},
21+
'ed25519': { name: 'Ed25519' },
22+
};
23+
24+
if (hasOpenSSL(3, 5)) {
25+
kAlgorithms['ml-dsa-44'] = { name: 'ML-DSA-44' };
26+
}
27+
28+
const kSignParams = {
29+
'ec': { name: 'ECDSA', hash: 'SHA-256' },
30+
'rsassa-pkcs1-v1_5': { name: 'RSASSA-PKCS1-v1_5' },
31+
'rsa-pss': { name: 'RSA-PSS', saltLength: 32 },
32+
'ed25519': { name: 'Ed25519' },
33+
'ml-dsa-44': { name: 'ML-DSA-44' },
34+
};
35+
36+
const data = globalThis.crypto.getRandomValues(new Uint8Array(256));
37+
38+
let publicKeys;
39+
let signature;
40+
41+
const bench = common.createBenchmark(main, {
42+
keyType: Object.keys(kAlgorithms),
43+
mode: ['serial', 'parallel'],
44+
keyReuse: ['shared', 'unique'],
45+
n: [1e3],
46+
}, {
47+
combinationFilter(p) {
48+
// Unique only differs from shared when operations overlap (parallel);
49+
// sequential calls have no contention so unique+serial adds no value.
50+
if (p.keyReuse === 'unique') return p.mode === 'parallel';
51+
return true;
52+
},
53+
});
54+
55+
async function measureSerial(n, verifyParams, sharedKey) {
56+
bench.start();
57+
for (let i = 0; i < n; ++i) {
58+
await subtle.verify(verifyParams, sharedKey || publicKeys[i], signature, data);
59+
}
60+
bench.end(n);
61+
}
62+
63+
async function measureParallel(n, verifyParams, sharedKey) {
64+
const promises = new Array(n);
65+
bench.start();
66+
for (let i = 0; i < n; ++i) {
67+
promises[i] = subtle.verify(verifyParams, sharedKey || publicKeys[i], signature, data);
68+
}
69+
await Promise.all(promises);
70+
bench.end(n);
71+
}
72+
73+
async function main({ n, mode, keyReuse, keyType }) {
74+
const algorithm = kAlgorithms[keyType];
75+
const verifyParams = kSignParams[keyType];
76+
77+
if (!publicKeys || publicKeys.length !== n ||
78+
publicKeys[0].algorithm.name !== verifyParams.name) {
79+
publicKeys = new Array(n);
80+
// Generate one key pair, then import its spki bytes n times to get
81+
// distinct CryptoKey instances.
82+
const kp = await subtle.generateKey(algorithm, true, ['sign', 'verify']);
83+
const spki = await subtle.exportKey('spki', kp.publicKey);
84+
for (let i = 0; i < n; ++i) {
85+
publicKeys[i] = await subtle.importKey('spki', spki, algorithm, false, ['verify']);
86+
}
87+
signature = await subtle.sign(verifyParams, kp.privateKey, data);
88+
}
89+
90+
const sharedKey = keyReuse === 'shared' ? publicKeys[0] : undefined;
91+
92+
switch (mode) {
93+
case 'serial':
94+
await measureSerial(n, verifyParams, sharedKey);
95+
break;
96+
case 'parallel':
97+
await measureParallel(n, verifyParams, sharedKey);
98+
break;
99+
}
100+
}

0 commit comments

Comments
 (0)