This repository has been archived by the owner on Jul 21, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: add Ed25519 benchmark suite (#213)
Pulls the benchmark suite out of #211 so it can be merged independently.
- Loading branch information
1 parent
f80946c
commit 9088fd8
Showing
4 changed files
with
338 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
/* eslint-disable no-console */ | ||
'use strict' | ||
|
||
/* | ||
* Make sure that every Ed25519 implementation can use keys generated | ||
* by every other implementation to sign and verify messages signed | ||
* by themselves and by every other implementation using those keys. | ||
* | ||
* Nb. some modules return different structures from their key generation | ||
* routine - we normalise to `{ privateKey: seed, publicKey }`. | ||
* | ||
* Most implementations return the seed as the private key but supercop.wasm | ||
* returns a hash of the seed. We ignore supercop's private key in favour | ||
* of the seed here, since we can re-create it using the createKeyPair | ||
* function because key generation is deterministic for a given seed. | ||
*/ | ||
|
||
const randomBytes = require('iso-random-stream/src/random') | ||
const { concat } = require('uint8arrays/concat') | ||
const { fromString } = require('uint8arrays/from-string') | ||
|
||
const native = require('ed25519') | ||
const noble = require('@noble/ed25519') | ||
const { subtle } = require('crypto').webcrypto | ||
require('node-forge/lib/ed25519') | ||
const forge = require('node-forge/lib/forge') | ||
const stable = require('@stablelib/ed25519') | ||
const supercopWasm = require('supercop.wasm') | ||
|
||
const ALGORITHM = 'NODE-ED25519' | ||
const ED25519_PKCS8_PREFIX = fromString('302e020100300506032b657004220420', 'hex') | ||
|
||
const implementations = [{ | ||
name: '@noble/ed25519', | ||
before: () => {}, | ||
generateKeyPair: async () => { | ||
const privateKey = noble.utils.randomPrivateKey() | ||
const publicKey = await noble.getPublicKey(privateKey) | ||
|
||
return { | ||
privateKey, | ||
publicKey | ||
} | ||
}, | ||
sign: (message, keyPair) => noble.sign(message, keyPair.privateKey), | ||
verify: (message, signature, keyPair) => noble.verify(signature, message, keyPair.publicKey) | ||
}, { | ||
name: '@stablelib/ed25519', | ||
before: () => {}, | ||
generateKeyPair: async () => { | ||
const key = stable.generateKeyPair() | ||
|
||
return { | ||
privateKey: key.secretKey.subarray(0, 32), | ||
publicKey: key.publicKey | ||
} | ||
}, | ||
sign: (message, keyPair) => stable.sign(concat([keyPair.privateKey, keyPair.publicKey]), message), | ||
verify: (message, signature, keyPair) => stable.verify(keyPair.publicKey, message, signature) | ||
}, { | ||
name: 'node-forge/ed25519', | ||
before: () => {}, | ||
generateKeyPair: async () => { | ||
const seed = randomBytes(32) | ||
const key = await forge.pki.ed25519.generateKeyPair({ seed }) | ||
|
||
return { | ||
privateKey: key.privateKey.subarray(0, 32), | ||
publicKey: key.publicKey | ||
} | ||
}, | ||
sign: (message, keyPair) => forge.pki.ed25519.sign({ message, privateKey: keyPair.privateKey }), | ||
verify: (message, signature, keyPair) => forge.pki.ed25519.verify({ signature, message, publicKey: keyPair.publicKey }) | ||
}, { | ||
name: 'supercop.wasm', | ||
before: () => { | ||
return new Promise(resolve => { | ||
supercopWasm.ready(() => { | ||
resolve() | ||
}) | ||
}) | ||
}, | ||
generateKeyPair: async () => { | ||
const seed = supercopWasm.createSeed() | ||
const key = supercopWasm.createKeyPair(seed) | ||
|
||
return { | ||
privateKey: seed, | ||
publicKey: key.publicKey | ||
} | ||
}, | ||
sign: (message, keyPair) => { | ||
const key = supercopWasm.createKeyPair(keyPair.privateKey) | ||
|
||
return supercopWasm.sign(message, key.publicKey, key.secretKey) | ||
}, | ||
verify: (message, signature, keyPair) => { | ||
return supercopWasm.verify(signature, message, keyPair.publicKey) | ||
} | ||
}, { | ||
name: 'native Ed25519', | ||
generateKeyPair: async () => { | ||
const seed = randomBytes(32) | ||
const key = native.MakeKeypair(seed) | ||
|
||
return { | ||
privateKey: key.privateKey.subarray(0, 32), | ||
publicKey: key.publicKey | ||
} | ||
}, | ||
sign: (message, keyPair) => native.Sign(message, keyPair.privateKey), | ||
verify: (message, signature, keyPair) => native.Verify(message, signature, keyPair.publicKey) | ||
}, { | ||
name: 'node.js web crypto', | ||
generateKeyPair: async () => { | ||
const key = await subtle.generateKey({ | ||
name: 'NODE-ED25519', | ||
namedCurve: 'NODE-ED25519' | ||
}, true, ['sign', 'verify']) | ||
const jwk = await subtle.exportKey('jwk', key.privateKey) | ||
|
||
return { | ||
privateKey: fromString(jwk.d, 'base64url'), | ||
publicKey: fromString(jwk.x, 'base64url') | ||
} | ||
}, | ||
sign: async (message, keyPair) => { | ||
const pkcs8 = concat([ | ||
ED25519_PKCS8_PREFIX, | ||
keyPair.privateKey | ||
], ED25519_PKCS8_PREFIX.length + 32) | ||
const cryptoKey = await subtle.importKey('pkcs8', pkcs8, { | ||
name: ALGORITHM, | ||
namedCurve: ALGORITHM | ||
}, true, ['sign']) | ||
|
||
const signature = await subtle.sign(ALGORITHM, cryptoKey, message) | ||
|
||
return new Uint8Array(signature) | ||
}, | ||
verify: async (message, signature, keyPair) => { | ||
const cryptoKey = await subtle.importKey('raw', keyPair.publicKey, { | ||
name: ALGORITHM, | ||
namedCurve: ALGORITHM, | ||
public: true | ||
}, true, ['verify']) | ||
|
||
return subtle.verify(ALGORITHM, cryptoKey, signature, message) | ||
} | ||
}] | ||
|
||
async function test (a, b) { | ||
console.info(`test ${a.name} against ${b.name}`) | ||
const message = Buffer.from('hello world ' + Math.random()) | ||
|
||
const keyPair = await a.generateKeyPair() | ||
|
||
if (keyPair.privateKey.length !== 32) { | ||
throw new Error('Private key not 32 bytes') | ||
} | ||
|
||
if (keyPair.publicKey.length !== 32) { | ||
throw new Error('Public key not 32 bytes') | ||
} | ||
|
||
// make sure we can sign and verify with keys created by the other implementation | ||
const pairs = [[a, a], [a, b], [b, a], [b, b]] | ||
|
||
for (const [a, b] of pairs) { | ||
console.info('test', a.name, 'against', b.name) | ||
const signature = await a.sign(message, keyPair) | ||
const isSigned = await b.verify(message, signature, keyPair) | ||
|
||
if (!isSigned) { | ||
console.error(`${b.name} could not verify signature created by ${a.name}`) | ||
} | ||
} | ||
} | ||
|
||
async function main () { | ||
for (const a of implementations) { | ||
if (a.before) { | ||
await a.before() | ||
} | ||
|
||
for (const b of implementations) { | ||
if (b.before) { | ||
await b.before() | ||
} | ||
|
||
await test(a, b) | ||
await test(b, a) | ||
} | ||
} | ||
} | ||
|
||
main() | ||
.catch(err => { | ||
console.error(err) | ||
process.exit(1) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* eslint-disable no-console */ | ||
'use strict' | ||
|
||
const Benchmark = require('benchmark') | ||
const randomBytes = require('iso-random-stream/src/random') | ||
const native = require('ed25519') | ||
const noble = require('@noble/ed25519') | ||
const { subtle } = require('crypto').webcrypto | ||
require('node-forge/lib/ed25519') | ||
const forge = require('node-forge/lib/forge') | ||
const stable = require('@stablelib/ed25519') | ||
const supercopWasm = require('supercop.wasm') | ||
|
||
const suite = new Benchmark.Suite('ed25519 implementations') | ||
|
||
suite.add('@noble/ed25519', async (d) => { | ||
const message = Buffer.from('hello world ' + Math.random()) | ||
const privateKey = noble.utils.randomPrivateKey() | ||
const publicKey = await noble.getPublicKey(privateKey) | ||
const signature = await noble.sign(message, privateKey) | ||
const isSigned = await noble.verify(signature, message, publicKey) | ||
|
||
if (!isSigned) { | ||
throw new Error('could not verify noble signature') | ||
} | ||
|
||
d.resolve() | ||
}, { defer: true }) | ||
|
||
suite.add('@stablelib/ed25519', async (d) => { | ||
const message = Buffer.from('hello world ' + Math.random()) | ||
const key = stable.generateKeyPair() | ||
const signature = await stable.sign(key.secretKey, message) | ||
const isSigned = await stable.verify(key.publicKey, message, signature) | ||
|
||
if (!isSigned) { | ||
throw new Error('could not verify stablelib signature') | ||
} | ||
|
||
d.resolve() | ||
}, { defer: true }) | ||
|
||
suite.add('node-forge/ed25519', async (d) => { | ||
const message = Buffer.from('hello world ' + Math.random()) | ||
const seed = randomBytes(32) | ||
const key = await forge.pki.ed25519.generateKeyPair({ seed }) | ||
const signature = await forge.pki.ed25519.sign({ message, privateKey: key.privateKey }) | ||
const res = await forge.pki.ed25519.verify({ signature, message, publicKey: key.publicKey }) | ||
|
||
if (!res) { | ||
throw new Error('could not verify node-forge signature') | ||
} | ||
|
||
d.resolve() | ||
}, { defer: true }) | ||
|
||
suite.add('supercop.wasm', async (d) => { | ||
const message = Buffer.from('hello world ' + Math.random()) | ||
const seed = supercopWasm.createSeed() | ||
const keys = supercopWasm.createKeyPair(seed) | ||
const signature = supercopWasm.sign(message, keys.publicKey, keys.secretKey) | ||
const isSigned = await supercopWasm.verify(signature, message, keys.publicKey) | ||
|
||
if (!isSigned) { | ||
throw new Error('could not verify noble signature') | ||
} | ||
|
||
d.resolve() | ||
}, { defer: true }) | ||
|
||
suite.add('ed25519 (native module)', async (d) => { | ||
const message = Buffer.from('hello world ' + Math.random()) | ||
const seed = randomBytes(32) | ||
const key = native.MakeKeypair(seed) | ||
const signature = native.Sign(message, key) | ||
const res = native.Verify(message, signature, key.publicKey) | ||
|
||
if (!res) { | ||
throw new Error('could not verify native signature') | ||
} | ||
|
||
d.resolve() | ||
}, { defer: true }) | ||
|
||
suite.add('node.js web-crypto', async (d) => { | ||
const message = Buffer.from('hello world ' + Math.random()) | ||
|
||
const key = await subtle.generateKey({ | ||
name: 'NODE-ED25519', | ||
namedCurve: 'NODE-ED25519' | ||
}, true, ['sign', 'verify']) | ||
const signature = await subtle.sign('NODE-ED25519', key.privateKey, message) | ||
const res = await subtle.verify('NODE-ED25519', key.publicKey, signature, message) | ||
|
||
if (!res) { | ||
throw new Error('could not verify node.js signature') | ||
} | ||
|
||
d.resolve() | ||
}, { defer: true }) | ||
|
||
async function main () { | ||
supercopWasm.ready(() => { | ||
suite | ||
.on('cycle', (event) => console.log(String(event.target))) | ||
.on('complete', function () { | ||
console.log('fastest is ' + this.filter('fastest').map('name')) | ||
}) | ||
.run({ async: true }) | ||
}) | ||
} | ||
|
||
main() | ||
.catch(err => { | ||
console.error(err) | ||
process.exit(1) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"name": "libp2p-crypto-ed25519-benchmarks", | ||
"version": "0.0.0", | ||
"private": true, | ||
"scripts": { | ||
"start": "node .", | ||
"compat": "node compat.js" | ||
}, | ||
"license": "MIT", | ||
"dependencies": { | ||
"@noble/ed25519": "^1.3.0", | ||
"@stablelib/ed25519": "^1.0.2", | ||
"benchmark": "^2.1.4", | ||
"ed25519": "^0.0.5", | ||
"iso-random-stream": "^2.0.0", | ||
"node-forge": "^0.10.0", | ||
"supercop.wasm": "^5.0.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters