@@ -11,22 +11,29 @@ function readKey(name) { |
11 | 11 | return fs.readFileSync(`${fixtures_keydir}/${name}.pem`, 'utf8'); |
12 | 12 | } |
13 | 13 |
|
| 14 | +function readKeyPair(publicKeyName, privateKeyName) { |
| 15 | + return { |
| 16 | + publicKey: readKey(publicKeyName), |
| 17 | + privateKey: readKey(privateKeyName), |
| 18 | + }; |
| 19 | +} |
| 20 | + |
14 | 21 | const keyFixtures = {}; |
15 | 22 |
|
16 | 23 | if (hasOpenSSL(3, 5)) { |
17 | | - keyFixtures['ml-kem-512'] = readKey('ml_kem_512_private'); |
18 | | - keyFixtures['ml-kem-768'] = readKey('ml_kem_768_private'); |
19 | | - keyFixtures['ml-kem-1024'] = readKey('ml_kem_1024_private'); |
| 24 | + keyFixtures['ml-kem-512'] = readKeyPair('ml_kem_512_public', 'ml_kem_512_private'); |
| 25 | + keyFixtures['ml-kem-768'] = readKeyPair('ml_kem_768_public', 'ml_kem_768_private'); |
| 26 | + keyFixtures['ml-kem-1024'] = readKeyPair('ml_kem_1024_public', 'ml_kem_1024_private'); |
20 | 27 | } |
21 | 28 | if (hasOpenSSL(3, 2)) { |
22 | | - keyFixtures['p-256'] = readKey('ec_p256_private'); |
23 | | - keyFixtures['p-384'] = readKey('ec_p384_private'); |
24 | | - keyFixtures['p-521'] = readKey('ec_p521_private'); |
25 | | - keyFixtures.x25519 = readKey('x25519_private'); |
26 | | - keyFixtures.x448 = readKey('x448_private'); |
| 29 | + keyFixtures['p-256'] = readKeyPair('ec_p256_public', 'ec_p256_private'); |
| 30 | + keyFixtures['p-384'] = readKeyPair('ec_p384_public', 'ec_p384_private'); |
| 31 | + keyFixtures['p-521'] = readKeyPair('ec_p521_public', 'ec_p521_private'); |
| 32 | + keyFixtures.x25519 = readKeyPair('x25519_public', 'x25519_private'); |
| 33 | + keyFixtures.x448 = readKeyPair('x448_public', 'x448_private'); |
27 | 34 | } |
28 | 35 | if (hasOpenSSL(3, 0)) { |
29 | | - keyFixtures.rsa = readKey('rsa_private_2048'); |
| 36 | + keyFixtures.rsa = readKeyPair('rsa_public_2048', 'rsa_private_2048'); |
30 | 37 | } |
31 | 38 |
|
32 | 39 | if (Object.keys(keyFixtures).length === 0) { |
@@ -37,32 +44,46 @@ if (Object.keys(keyFixtures).length === 0) { |
37 | 44 | const bench = common.createBenchmark(main, { |
38 | 45 | keyType: Object.keys(keyFixtures), |
39 | 46 | mode: ['sync', 'async', 'async-parallel'], |
40 | | - keyFormat: ['keyObject', 'keyObject.unique'], |
| 47 | + keyFormat: ['keyObject', 'keyObject.unique', 'pem', 'der', 'jwk', |
| 48 | + 'raw-public', 'raw-private', 'raw-seed'], |
41 | 49 | op: ['encapsulate', 'decapsulate'], |
42 | 50 | n: [1e3], |
43 | 51 | }, { |
44 | 52 | combinationFilter(p) { |
45 | 53 | // "keyObject.unique" allows to compare the result with "keyObject" to |
46 | 54 | // assess whether mutexes over the key material impact the operation |
47 | | - return p.keyFormat !== 'keyObject.unique' || |
48 | | - (p.keyFormat === 'keyObject.unique' && p.mode === 'async-parallel'); |
| 55 | + if (p.keyFormat === 'keyObject.unique') |
| 56 | + return p.mode === 'async-parallel'; |
| 57 | + // JWK is not supported for ml-kem for now |
| 58 | + if (p.keyFormat === 'jwk') |
| 59 | + return !p.keyType.startsWith('ml-'); |
| 60 | + // raw-public is only supported for encapsulate, not rsa |
| 61 | + if (p.keyFormat === 'raw-public') |
| 62 | + return p.keyType !== 'rsa' && p.op === 'encapsulate'; |
| 63 | + // raw-private is not supported for rsa and ml-kem, only for decapsulate |
| 64 | + if (p.keyFormat === 'raw-private') |
| 65 | + return p.keyType !== 'rsa' && !p.keyType.startsWith('ml-') && p.op === 'decapsulate'; |
| 66 | + // raw-seed is only supported for ml-kem |
| 67 | + if (p.keyFormat === 'raw-seed') |
| 68 | + return p.keyType.startsWith('ml-'); |
| 69 | + return true; |
49 | 70 | }, |
50 | 71 | }); |
51 | 72 |
|
52 | | -function measureSync(n, op, privateKey, keys, ciphertexts) { |
| 73 | +function measureSync(n, op, key, keys, ciphertexts) { |
53 | 74 | bench.start(); |
54 | 75 | for (let i = 0; i < n; ++i) { |
55 | | - const key = privateKey || keys[i]; |
| 76 | + const k = key || keys[i]; |
56 | 77 | if (op === 'encapsulate') { |
57 | | - crypto.encapsulate(key); |
| 78 | + crypto.encapsulate(k); |
58 | 79 | } else { |
59 | | - crypto.decapsulate(key, ciphertexts[i]); |
| 80 | + crypto.decapsulate(k, ciphertexts[i]); |
60 | 81 | } |
61 | 82 | } |
62 | 83 | bench.end(n); |
63 | 84 | } |
64 | 85 |
|
65 | | -function measureAsync(n, op, privateKey, keys, ciphertexts) { |
| 86 | +function measureAsync(n, op, key, keys, ciphertexts) { |
66 | 87 | let remaining = n; |
67 | 88 | function done() { |
68 | 89 | if (--remaining === 0) |
@@ -72,44 +93,98 @@ function measureAsync(n, op, privateKey, keys, ciphertexts) { |
72 | 93 | } |
73 | 94 |
|
74 | 95 | function one() { |
75 | | - const key = privateKey || keys[n - remaining]; |
| 96 | + const k = key || keys[n - remaining]; |
76 | 97 | if (op === 'encapsulate') { |
77 | | - crypto.encapsulate(key, done); |
| 98 | + crypto.encapsulate(k, done); |
78 | 99 | } else { |
79 | | - crypto.decapsulate(key, ciphertexts[n - remaining], done); |
| 100 | + crypto.decapsulate(k, ciphertexts[n - remaining], done); |
80 | 101 | } |
81 | 102 | } |
82 | 103 | bench.start(); |
83 | 104 | one(); |
84 | 105 | } |
85 | 106 |
|
86 | | -function measureAsyncParallel(n, op, privateKey, keys, ciphertexts) { |
| 107 | +function measureAsyncParallel(n, op, key, keys, ciphertexts) { |
87 | 108 | let remaining = n; |
88 | 109 | function done() { |
89 | 110 | if (--remaining === 0) |
90 | 111 | bench.end(n); |
91 | 112 | } |
92 | 113 | bench.start(); |
93 | 114 | for (let i = 0; i < n; ++i) { |
94 | | - const key = privateKey || keys[i]; |
| 115 | + const k = key || keys[i]; |
95 | 116 | if (op === 'encapsulate') { |
96 | | - crypto.encapsulate(key, done); |
| 117 | + crypto.encapsulate(k, done); |
97 | 118 | } else { |
98 | | - crypto.decapsulate(key, ciphertexts[i], done); |
| 119 | + crypto.decapsulate(k, ciphertexts[i], done); |
99 | 120 | } |
100 | 121 | } |
101 | 122 | } |
102 | 123 |
|
103 | 124 | function main({ n, mode, keyFormat, keyType, op }) { |
104 | | - const pems = [...Buffer.alloc(n)].map(() => keyFixtures[keyType]); |
105 | | - const keyObjects = pems.map(crypto.createPrivateKey); |
| 125 | + const isEncapsulate = op === 'encapsulate'; |
| 126 | + const pemSource = isEncapsulate ? |
| 127 | + keyFixtures[keyType].publicKey : |
| 128 | + keyFixtures[keyType].privateKey; |
| 129 | + const createKeyFn = isEncapsulate ? crypto.createPublicKey : crypto.createPrivateKey; |
| 130 | + const pems = [...Buffer.alloc(n)].map(() => pemSource); |
| 131 | + const keyObjects = pems.map(createKeyFn); |
106 | 132 |
|
107 | | - let privateKey, keys, ciphertexts; |
| 133 | + // Warm up OpenSSL's provider operation cache for each key object |
| 134 | + if (isEncapsulate) { |
| 135 | + for (const keyObject of keyObjects) { |
| 136 | + crypto.encapsulate(keyObject); |
| 137 | + } |
| 138 | + } else { |
| 139 | + const warmupCiphertext = crypto.encapsulate(keyObjects[0]).ciphertext; |
| 140 | + for (const keyObject of keyObjects) { |
| 141 | + crypto.decapsulate(keyObject, warmupCiphertext); |
| 142 | + } |
| 143 | + } |
| 144 | + |
| 145 | + const asymmetricKeyType = keyObjects[0].asymmetricKeyType; |
| 146 | + let key, keys, ciphertexts; |
108 | 147 |
|
109 | 148 | switch (keyFormat) { |
110 | 149 | case 'keyObject': |
111 | | - privateKey = keyObjects[0]; |
| 150 | + key = keyObjects[0]; |
| 151 | + break; |
| 152 | + case 'pem': |
| 153 | + key = pems[0]; |
112 | 154 | break; |
| 155 | + case 'jwk': { |
| 156 | + key = { key: keyObjects[0].export({ format: 'jwk' }), format: 'jwk' }; |
| 157 | + break; |
| 158 | + } |
| 159 | + case 'der': { |
| 160 | + const type = isEncapsulate ? 'spki' : 'pkcs8'; |
| 161 | + key = { key: keyObjects[0].export({ format: 'der', type }), format: 'der', type }; |
| 162 | + break; |
| 163 | + } |
| 164 | + case 'raw-public': { |
| 165 | + const exportedKey = keyObjects[0].export({ format: 'raw-public' }); |
| 166 | + const keyOpts = { key: exportedKey, format: 'raw-public', asymmetricKeyType }; |
| 167 | + if (asymmetricKeyType === 'ec') keyOpts.namedCurve = keyObjects[0].asymmetricKeyDetails.namedCurve; |
| 168 | + key = keyOpts; |
| 169 | + break; |
| 170 | + } |
| 171 | + case 'raw-private': { |
| 172 | + const exportedKey = keyObjects[0].export({ format: 'raw-private' }); |
| 173 | + const keyOpts = { key: exportedKey, format: 'raw-private', asymmetricKeyType }; |
| 174 | + if (asymmetricKeyType === 'ec') keyOpts.namedCurve = keyObjects[0].asymmetricKeyDetails.namedCurve; |
| 175 | + key = keyOpts; |
| 176 | + break; |
| 177 | + } |
| 178 | + case 'raw-seed': { |
| 179 | + // raw-seed requires a private key to export from |
| 180 | + const privateKeyObject = crypto.createPrivateKey(keyFixtures[keyType].privateKey); |
| 181 | + key = { |
| 182 | + key: privateKeyObject.export({ format: 'raw-seed' }), |
| 183 | + format: 'raw-seed', |
| 184 | + asymmetricKeyType, |
| 185 | + }; |
| 186 | + break; |
| 187 | + } |
113 | 188 | case 'keyObject.unique': |
114 | 189 | keys = keyObjects; |
115 | 190 | break; |
@@ -118,23 +193,25 @@ function main({ n, mode, keyFormat, keyType, op }) { |
118 | 193 | } |
119 | 194 |
|
120 | 195 | // Pre-generate ciphertexts for decapsulate operations |
121 | | - if (op === 'decapsulate') { |
122 | | - if (privateKey) { |
123 | | - ciphertexts = [...Buffer.alloc(n)].map(() => crypto.encapsulate(privateKey).ciphertext); |
| 196 | + if (!isEncapsulate) { |
| 197 | + const encapKey = crypto.createPublicKey( |
| 198 | + crypto.createPrivateKey(keyFixtures[keyType].privateKey)); |
| 199 | + if (key) { |
| 200 | + ciphertexts = [...Buffer.alloc(n)].map(() => crypto.encapsulate(encapKey).ciphertext); |
124 | 201 | } else { |
125 | | - ciphertexts = keys.map((key) => crypto.encapsulate(key).ciphertext); |
| 202 | + ciphertexts = keys.map(() => crypto.encapsulate(encapKey).ciphertext); |
126 | 203 | } |
127 | 204 | } |
128 | 205 |
|
129 | 206 | switch (mode) { |
130 | 207 | case 'sync': |
131 | | - measureSync(n, op, privateKey, keys, ciphertexts); |
| 208 | + measureSync(n, op, key, keys, ciphertexts); |
132 | 209 | break; |
133 | 210 | case 'async': |
134 | | - measureAsync(n, op, privateKey, keys, ciphertexts); |
| 211 | + measureAsync(n, op, key, keys, ciphertexts); |
135 | 212 | break; |
136 | 213 | case 'async-parallel': |
137 | | - measureAsyncParallel(n, op, privateKey, keys, ciphertexts); |
| 214 | + measureAsyncParallel(n, op, key, keys, ciphertexts); |
138 | 215 | break; |
139 | 216 | } |
140 | 217 | } |