Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
162 changes: 162 additions & 0 deletions benchmark/crypto/_webcrypto_sync_fast_path_common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
'use strict';

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const fixturesKeyDir = path.resolve(__dirname, '../../test/fixtures/keys/');

const kWebCryptoSyncFastPathThreshold = 64 * 1024;

const kThresholdSizeLabels = [
'minimal',
'middle',
'at-threshold',
'after-threshold',
];

const kParallelism = 4;

function ptn(size) {
const buffer = Buffer.allocUnsafe(size);
for (let i = 0; i < size; i++) {
buffer[i] = i % 0xfb;
}
return buffer;
}

function thresholdSize(label, {
minimum = 1,
multiple = 1,
threshold = kWebCryptoSyncFastPathThreshold,
} = {}) {
switch (label) {
case 'minimal':
return minimum;
case 'middle':
return roundDown(Math.max(minimum, threshold / 2), multiple);
case 'at-threshold':
return roundDown(Math.max(minimum, threshold), multiple);
case 'after-threshold':
return roundUp(Math.max(minimum, threshold + 1), multiple);
}
throw new Error(`Unknown threshold size label: ${label}`);
}

function roundDown(value, multiple) {
return Math.max(multiple, Math.floor(value / multiple) * multiple);
}

function roundUp(value, multiple) {
return Math.ceil(value / multiple) * multiple;
}

function readKey(name) {
return fs.readFileSync(path.resolve(fixturesKeyDir, `${name}.pem`), 'utf8');
}

function fixtureKeyPair(publicKeyName, privateKeyName) {
return {
publicKey: readKey(publicKeyName),
privateKey: readKey(privateKeyName),
};
}

function fixtureCryptoKeyPair(
publicKeyName,
privateKeyName,
algorithm,
publicUsages,
privateUsages,
) {
return {
publicKey: crypto.createPublicKey(readKey(publicKeyName))
.toCryptoKey(algorithm, false, publicUsages),
privateKey: crypto.createPrivateKey(readKey(privateKeyName))
.toCryptoKey(algorithm, false, privateUsages),
};
}

async function importSecretKey({
algorithm,
usages,
length = 32,
format = 'raw-secret',
extractable = false,
}) {
return globalThis.crypto.subtle.importKey(
format,
ptn(length),
algorithm,
extractable,
usages);
}

function isSupported(operation, algorithm) {
if (typeof SubtleCrypto.supports !== 'function')
return true;
return SubtleCrypto.supports(operation, algorithm);
}

async function measureAsync(bench, n, mode, fn) {
if (mode === 'parallel') {
const parallelism = Math.min(kParallelism, n);
let started = 0;
let completed = 0;
bench.start();
await new Promise((resolve, reject) => {
let failed = false;
function fail(err) {
failed = true;
reject(err);
}

function launch() {
if (failed)
return;

const i = started++;
let promise;
try {
promise = fn(i);
} catch (err) {
fail(err);
return;
}

Promise.resolve(promise).then(() => {
if (failed)
return;
if (++completed === n) {
resolve();
} else if (started < n) {
launch();
}
}, fail);
}

for (let i = 0; i < parallelism; i++)
launch();
});
bench.end(n);
return;
}

bench.start();
for (let i = 0; i < n; i++)
await fn(i);
bench.end(n);
}

module.exports = {
fixtureCryptoKeyPair,
fixtureKeyPair,
importSecretKey,
isSupported,
kThresholdSizeLabels,
kWebCryptoSyncFastPathThreshold,
measureAsync,
ptn,
readKey,
thresholdSize,
};
148 changes: 148 additions & 0 deletions benchmark/crypto/webcrypto-sync-fast-path-cipher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
'use strict';

const common = require('../common.js');
const {
importSecretKey,
isSupported,
kThresholdSizeLabels,
kWebCryptoSyncFastPathThreshold,
measureAsync,
ptn,
thresholdSize,
} = require('./_webcrypto_sync_fast_path_common.js');

const { subtle } = globalThis.crypto;

const keyAlgorithms = {
'AES-CBC': { name: 'AES-CBC', length: 128 },
'AES-CTR': { name: 'AES-CTR', length: 128 },
'AES-GCM': { name: 'AES-GCM', length: 128 },
'AES-OCB': { name: 'AES-OCB', length: 128 },
'ChaCha20-Poly1305': { name: 'ChaCha20-Poly1305' },
};

const kAesCbcCtrEncryptSyncFastPathThreshold = 32 * 1024;

function supportParams(algorithm) {
switch (algorithm) {
case 'AES-CBC':
return { name: algorithm, iv: new Uint8Array(16) };
case 'AES-CTR':
return { name: algorithm, counter: new Uint8Array(16), length: 64 };
case 'AES-GCM':
return { name: algorithm, iv: new Uint8Array(12) };
case 'AES-OCB':
return { name: algorithm, iv: new Uint8Array(15), tagLength: 128 };
case 'ChaCha20-Poly1305':
return { name: algorithm, iv: new Uint8Array(12), tagLength: 128 };
}
throw new Error(`Unknown cipher algorithm: ${algorithm}`);
}

const cipherAlgorithms = Object.keys(keyAlgorithms)
.filter((name) => isSupported('encrypt', supportParams(name)));

if (cipherAlgorithms.length === 0) {
console.log('no supported WebCrypto cipher algorithms available');
process.exit(0);
}

const bench = common.createBenchmark(main, {
algorithm: cipherAlgorithms,
operation: ['encrypt', 'decrypt'],
size: kThresholdSizeLabels,
mode: ['serial', 'parallel'],
n: [1e3],
});

function cipherParams(algorithm, size) {
switch (algorithm) {
case 'AES-CBC':
return {
name: algorithm,
iv: ptn(16),
};
case 'AES-CTR':
return {
name: algorithm,
counter: ptn(16),
length: 64,
};
case 'AES-GCM':
return {
name: algorithm,
iv: ptn(12),
additionalData: ptn(16),
tagLength: 128,
};
case 'AES-OCB':
return {
name: algorithm,
iv: ptn(15),
additionalData: ptn(16),
tagLength: 128,
};
case 'ChaCha20-Poly1305':
return {
name: algorithm,
iv: ptn(12),
additionalData: ptn(16),
tagLength: 128,
};
}
throw new Error(`Unknown cipher algorithm: ${algorithm}`);
}

function syncFastPathThreshold(algorithm, operation) {
return operation === 'encrypt' &&
(algorithm === 'AES-CBC' || algorithm === 'AES-CTR') ?
kAesCbcCtrEncryptSyncFastPathThreshold :
kWebCryptoSyncFastPathThreshold;
}

function measuredInputOverhead(algorithm, operation) {
switch (algorithm) {
case 'AES-GCM':
case 'AES-OCB':
case 'ChaCha20-Poly1305':
return operation === 'decrypt' ? 32 : 16;
default:
return 0;
}
}

function dataSize(algorithm, operation, size) {
const minimum = algorithm === 'AES-CBC' ? 16 : 1;
if (size === 'minimal')
return minimum;

const overhead = measuredInputOverhead(algorithm, operation);
const multiple = algorithm === 'AES-CBC' ? 16 : 1;
return thresholdSize(size, {
minimum: minimum + overhead,
multiple,
threshold: syncFastPathThreshold(algorithm, operation),
}) - overhead;
}

async function setupCipherOperation(algorithm, operation, size) {
const key = await importSecretKey({
algorithm: keyAlgorithms[algorithm],
usages: ['encrypt', 'decrypt'],
length: algorithm === 'ChaCha20-Poly1305' ? 32 : 16,
});
const data = ptn(dataSize(algorithm, operation, size));

const params = cipherParams(algorithm, size);
if (operation === 'encrypt') {
return () => subtle.encrypt(params, key, data);
}

const ciphertext = await subtle.encrypt(params, key, data);
return () => subtle.decrypt(params, key, ciphertext);
}

async function main({ n, algorithm, operation, size, mode }) {
const run = await setupCipherOperation(algorithm, operation, size);
await measureAsync(bench, n, mode, run);
}
65 changes: 65 additions & 0 deletions benchmark/crypto/webcrypto-sync-fast-path-derive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use strict';

const common = require('../common.js');
const {
fixtureCryptoKeyPair,
measureAsync,
} = require('./_webcrypto_sync_fast_path_common.js');

const { subtle } = globalThis.crypto;

const keyFixtures = {
'ecdh-p256': {
publicKeyName: 'ec_p256_public',
privateKeyName: 'ec_p256_private',
keyAlgorithm: { name: 'ECDH', namedCurve: 'P-256' },
deriveAlgorithmName: 'ECDH',
length: 256,
},
'x25519': {
publicKeyName: 'x25519_public',
privateKeyName: 'x25519_private',
keyAlgorithm: { name: 'X25519' },
deriveAlgorithmName: 'X25519',
length: 256,
},
};

if (!process.features.openssl_is_boringssl) {
keyFixtures.x448 = {
publicKeyName: 'x448_public',
privateKeyName: 'x448_private',
keyAlgorithm: { name: 'X448' },
deriveAlgorithmName: 'X448',
length: 448,
};
}

const algorithms = Object.keys(keyFixtures);

const bench = common.createBenchmark(main, {
algorithm: algorithms,
mode: ['serial', 'parallel'],
n: [1e3],
});

async function setupDeriveOperation(algorithm) {
const fixture = keyFixtures[algorithm];
const pair = fixtureCryptoKeyPair(
fixture.publicKeyName,
fixture.privateKeyName,
fixture.keyAlgorithm,
[],
['deriveBits']);
const params = {
name: fixture.deriveAlgorithmName,
public: pair.publicKey,
};

return () => subtle.deriveBits(params, pair.privateKey, fixture.length);
}

async function main({ n, algorithm, mode }) {
const run = await setupDeriveOperation(algorithm);
await measureAsync(bench, n, mode, run);
}
Loading