Skip to content
This repository was archived by the owner on Jul 21, 2023. It is now read-only.

Commit 9088fd8

Browse files
authoredNov 29, 2021
chore: add Ed25519 benchmark suite (#213)
Pulls the benchmark suite out of #211 so it can be merged independently.
1 parent f80946c commit 9088fd8

File tree

4 files changed

+338
-1
lines changed

4 files changed

+338
-1
lines changed
 

‎benchmarks/ed25519/compat.js

+201
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/* eslint-disable no-console */
2+
'use strict'
3+
4+
/*
5+
* Make sure that every Ed25519 implementation can use keys generated
6+
* by every other implementation to sign and verify messages signed
7+
* by themselves and by every other implementation using those keys.
8+
*
9+
* Nb. some modules return different structures from their key generation
10+
* routine - we normalise to `{ privateKey: seed, publicKey }`.
11+
*
12+
* Most implementations return the seed as the private key but supercop.wasm
13+
* returns a hash of the seed. We ignore supercop's private key in favour
14+
* of the seed here, since we can re-create it using the createKeyPair
15+
* function because key generation is deterministic for a given seed.
16+
*/
17+
18+
const randomBytes = require('iso-random-stream/src/random')
19+
const { concat } = require('uint8arrays/concat')
20+
const { fromString } = require('uint8arrays/from-string')
21+
22+
const native = require('ed25519')
23+
const noble = require('@noble/ed25519')
24+
const { subtle } = require('crypto').webcrypto
25+
require('node-forge/lib/ed25519')
26+
const forge = require('node-forge/lib/forge')
27+
const stable = require('@stablelib/ed25519')
28+
const supercopWasm = require('supercop.wasm')
29+
30+
const ALGORITHM = 'NODE-ED25519'
31+
const ED25519_PKCS8_PREFIX = fromString('302e020100300506032b657004220420', 'hex')
32+
33+
const implementations = [{
34+
name: '@noble/ed25519',
35+
before: () => {},
36+
generateKeyPair: async () => {
37+
const privateKey = noble.utils.randomPrivateKey()
38+
const publicKey = await noble.getPublicKey(privateKey)
39+
40+
return {
41+
privateKey,
42+
publicKey
43+
}
44+
},
45+
sign: (message, keyPair) => noble.sign(message, keyPair.privateKey),
46+
verify: (message, signature, keyPair) => noble.verify(signature, message, keyPair.publicKey)
47+
}, {
48+
name: '@stablelib/ed25519',
49+
before: () => {},
50+
generateKeyPair: async () => {
51+
const key = stable.generateKeyPair()
52+
53+
return {
54+
privateKey: key.secretKey.subarray(0, 32),
55+
publicKey: key.publicKey
56+
}
57+
},
58+
sign: (message, keyPair) => stable.sign(concat([keyPair.privateKey, keyPair.publicKey]), message),
59+
verify: (message, signature, keyPair) => stable.verify(keyPair.publicKey, message, signature)
60+
}, {
61+
name: 'node-forge/ed25519',
62+
before: () => {},
63+
generateKeyPair: async () => {
64+
const seed = randomBytes(32)
65+
const key = await forge.pki.ed25519.generateKeyPair({ seed })
66+
67+
return {
68+
privateKey: key.privateKey.subarray(0, 32),
69+
publicKey: key.publicKey
70+
}
71+
},
72+
sign: (message, keyPair) => forge.pki.ed25519.sign({ message, privateKey: keyPair.privateKey }),
73+
verify: (message, signature, keyPair) => forge.pki.ed25519.verify({ signature, message, publicKey: keyPair.publicKey })
74+
}, {
75+
name: 'supercop.wasm',
76+
before: () => {
77+
return new Promise(resolve => {
78+
supercopWasm.ready(() => {
79+
resolve()
80+
})
81+
})
82+
},
83+
generateKeyPair: async () => {
84+
const seed = supercopWasm.createSeed()
85+
const key = supercopWasm.createKeyPair(seed)
86+
87+
return {
88+
privateKey: seed,
89+
publicKey: key.publicKey
90+
}
91+
},
92+
sign: (message, keyPair) => {
93+
const key = supercopWasm.createKeyPair(keyPair.privateKey)
94+
95+
return supercopWasm.sign(message, key.publicKey, key.secretKey)
96+
},
97+
verify: (message, signature, keyPair) => {
98+
return supercopWasm.verify(signature, message, keyPair.publicKey)
99+
}
100+
}, {
101+
name: 'native Ed25519',
102+
generateKeyPair: async () => {
103+
const seed = randomBytes(32)
104+
const key = native.MakeKeypair(seed)
105+
106+
return {
107+
privateKey: key.privateKey.subarray(0, 32),
108+
publicKey: key.publicKey
109+
}
110+
},
111+
sign: (message, keyPair) => native.Sign(message, keyPair.privateKey),
112+
verify: (message, signature, keyPair) => native.Verify(message, signature, keyPair.publicKey)
113+
}, {
114+
name: 'node.js web crypto',
115+
generateKeyPair: async () => {
116+
const key = await subtle.generateKey({
117+
name: 'NODE-ED25519',
118+
namedCurve: 'NODE-ED25519'
119+
}, true, ['sign', 'verify'])
120+
const jwk = await subtle.exportKey('jwk', key.privateKey)
121+
122+
return {
123+
privateKey: fromString(jwk.d, 'base64url'),
124+
publicKey: fromString(jwk.x, 'base64url')
125+
}
126+
},
127+
sign: async (message, keyPair) => {
128+
const pkcs8 = concat([
129+
ED25519_PKCS8_PREFIX,
130+
keyPair.privateKey
131+
], ED25519_PKCS8_PREFIX.length + 32)
132+
const cryptoKey = await subtle.importKey('pkcs8', pkcs8, {
133+
name: ALGORITHM,
134+
namedCurve: ALGORITHM
135+
}, true, ['sign'])
136+
137+
const signature = await subtle.sign(ALGORITHM, cryptoKey, message)
138+
139+
return new Uint8Array(signature)
140+
},
141+
verify: async (message, signature, keyPair) => {
142+
const cryptoKey = await subtle.importKey('raw', keyPair.publicKey, {
143+
name: ALGORITHM,
144+
namedCurve: ALGORITHM,
145+
public: true
146+
}, true, ['verify'])
147+
148+
return subtle.verify(ALGORITHM, cryptoKey, signature, message)
149+
}
150+
}]
151+
152+
async function test (a, b) {
153+
console.info(`test ${a.name} against ${b.name}`)
154+
const message = Buffer.from('hello world ' + Math.random())
155+
156+
const keyPair = await a.generateKeyPair()
157+
158+
if (keyPair.privateKey.length !== 32) {
159+
throw new Error('Private key not 32 bytes')
160+
}
161+
162+
if (keyPair.publicKey.length !== 32) {
163+
throw new Error('Public key not 32 bytes')
164+
}
165+
166+
// make sure we can sign and verify with keys created by the other implementation
167+
const pairs = [[a, a], [a, b], [b, a], [b, b]]
168+
169+
for (const [a, b] of pairs) {
170+
console.info('test', a.name, 'against', b.name)
171+
const signature = await a.sign(message, keyPair)
172+
const isSigned = await b.verify(message, signature, keyPair)
173+
174+
if (!isSigned) {
175+
console.error(`${b.name} could not verify signature created by ${a.name}`)
176+
}
177+
}
178+
}
179+
180+
async function main () {
181+
for (const a of implementations) {
182+
if (a.before) {
183+
await a.before()
184+
}
185+
186+
for (const b of implementations) {
187+
if (b.before) {
188+
await b.before()
189+
}
190+
191+
await test(a, b)
192+
await test(b, a)
193+
}
194+
}
195+
}
196+
197+
main()
198+
.catch(err => {
199+
console.error(err)
200+
process.exit(1)
201+
})

‎benchmarks/ed25519/index.js

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/* eslint-disable no-console */
2+
'use strict'
3+
4+
const Benchmark = require('benchmark')
5+
const randomBytes = require('iso-random-stream/src/random')
6+
const native = require('ed25519')
7+
const noble = require('@noble/ed25519')
8+
const { subtle } = require('crypto').webcrypto
9+
require('node-forge/lib/ed25519')
10+
const forge = require('node-forge/lib/forge')
11+
const stable = require('@stablelib/ed25519')
12+
const supercopWasm = require('supercop.wasm')
13+
14+
const suite = new Benchmark.Suite('ed25519 implementations')
15+
16+
suite.add('@noble/ed25519', async (d) => {
17+
const message = Buffer.from('hello world ' + Math.random())
18+
const privateKey = noble.utils.randomPrivateKey()
19+
const publicKey = await noble.getPublicKey(privateKey)
20+
const signature = await noble.sign(message, privateKey)
21+
const isSigned = await noble.verify(signature, message, publicKey)
22+
23+
if (!isSigned) {
24+
throw new Error('could not verify noble signature')
25+
}
26+
27+
d.resolve()
28+
}, { defer: true })
29+
30+
suite.add('@stablelib/ed25519', async (d) => {
31+
const message = Buffer.from('hello world ' + Math.random())
32+
const key = stable.generateKeyPair()
33+
const signature = await stable.sign(key.secretKey, message)
34+
const isSigned = await stable.verify(key.publicKey, message, signature)
35+
36+
if (!isSigned) {
37+
throw new Error('could not verify stablelib signature')
38+
}
39+
40+
d.resolve()
41+
}, { defer: true })
42+
43+
suite.add('node-forge/ed25519', async (d) => {
44+
const message = Buffer.from('hello world ' + Math.random())
45+
const seed = randomBytes(32)
46+
const key = await forge.pki.ed25519.generateKeyPair({ seed })
47+
const signature = await forge.pki.ed25519.sign({ message, privateKey: key.privateKey })
48+
const res = await forge.pki.ed25519.verify({ signature, message, publicKey: key.publicKey })
49+
50+
if (!res) {
51+
throw new Error('could not verify node-forge signature')
52+
}
53+
54+
d.resolve()
55+
}, { defer: true })
56+
57+
suite.add('supercop.wasm', async (d) => {
58+
const message = Buffer.from('hello world ' + Math.random())
59+
const seed = supercopWasm.createSeed()
60+
const keys = supercopWasm.createKeyPair(seed)
61+
const signature = supercopWasm.sign(message, keys.publicKey, keys.secretKey)
62+
const isSigned = await supercopWasm.verify(signature, message, keys.publicKey)
63+
64+
if (!isSigned) {
65+
throw new Error('could not verify noble signature')
66+
}
67+
68+
d.resolve()
69+
}, { defer: true })
70+
71+
suite.add('ed25519 (native module)', async (d) => {
72+
const message = Buffer.from('hello world ' + Math.random())
73+
const seed = randomBytes(32)
74+
const key = native.MakeKeypair(seed)
75+
const signature = native.Sign(message, key)
76+
const res = native.Verify(message, signature, key.publicKey)
77+
78+
if (!res) {
79+
throw new Error('could not verify native signature')
80+
}
81+
82+
d.resolve()
83+
}, { defer: true })
84+
85+
suite.add('node.js web-crypto', async (d) => {
86+
const message = Buffer.from('hello world ' + Math.random())
87+
88+
const key = await subtle.generateKey({
89+
name: 'NODE-ED25519',
90+
namedCurve: 'NODE-ED25519'
91+
}, true, ['sign', 'verify'])
92+
const signature = await subtle.sign('NODE-ED25519', key.privateKey, message)
93+
const res = await subtle.verify('NODE-ED25519', key.publicKey, signature, message)
94+
95+
if (!res) {
96+
throw new Error('could not verify node.js signature')
97+
}
98+
99+
d.resolve()
100+
}, { defer: true })
101+
102+
async function main () {
103+
supercopWasm.ready(() => {
104+
suite
105+
.on('cycle', (event) => console.log(String(event.target)))
106+
.on('complete', function () {
107+
console.log('fastest is ' + this.filter('fastest').map('name'))
108+
})
109+
.run({ async: true })
110+
})
111+
}
112+
113+
main()
114+
.catch(err => {
115+
console.error(err)
116+
process.exit(1)
117+
})

‎benchmarks/ed25519/package.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "libp2p-crypto-ed25519-benchmarks",
3+
"version": "0.0.0",
4+
"private": true,
5+
"scripts": {
6+
"start": "node .",
7+
"compat": "node compat.js"
8+
},
9+
"license": "MIT",
10+
"dependencies": {
11+
"@noble/ed25519": "^1.3.0",
12+
"@stablelib/ed25519": "^1.0.2",
13+
"benchmark": "^2.1.4",
14+
"ed25519": "^0.0.5",
15+
"iso-random-stream": "^2.0.0",
16+
"node-forge": "^0.10.0",
17+
"supercop.wasm": "^5.0.1"
18+
}
19+
}

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
],
1919
"scripts": {
2020
"lint": "aegir lint",
21-
"dep-check": "aegir dep-check",
21+
"dep-check": "aegir dep-check package.json dep-check src/**/*.js test/**/*.js",
2222
"build": "npm run build:proto && aegir build --no-types",
2323
"build:proto": "pbjs -t static-module -w commonjs -r libp2p-crypto-keys --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/keys/keys.js ./src/keys/keys.proto",
2424
"test": "aegir test",

0 commit comments

Comments
 (0)
This repository has been archived.