Skip to content

Commit

Permalink
Use noble-ed25519 over tweetnacl for signature verification (#16)
Browse files Browse the repository at this point in the history
Much faster than tweetnacl, and no constant-timeness required.

We are not using v2 for now, despite being smaller, because it relies on
bigint literals, and it requires polyfilling the WebCrypto lib
manually in Node < 19.
  • Loading branch information
larabr committed Sep 11, 2024
1 parent 9baf1c4 commit f443c61
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 7 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"devDependencies": {
"@noble/ciphers": "^0.6.0",
"@noble/curves": "^1.6.0",
"@noble/ed25519": "^1.7.3",
"@noble/hashes": "^1.5.0",
"@openpgp/jsdoc": "^3.6.11",
"@openpgp/seek-bzip": "^1.0.5-git",
Expand Down
21 changes: 21 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ export default Object.assign([
commonjs({
ignore: nodeBuiltinModules.concat(nodeDependencies)
}),
replace({
include: 'node_modules/@noble/ed25519/**',
// Rollup ignores the `browser: { crypto: false }` directive in package.json, since `exports` are present,
// hence we need to manually drop it.
"import * as nodeCrypto from 'crypto'": 'const nodeCrypto = null',
delimiters: ['', '']
}),
replace({
'OpenPGP.js VERSION': `OpenPGP.js ${pkg.version}`,
"import { createRequire } from 'module';": 'const createRequire = () => () => {}',
Expand Down Expand Up @@ -119,6 +126,13 @@ export default Object.assign([
commonjs({
ignore: nodeBuiltinModules.concat(nodeDependencies)
}),
replace({
include: 'node_modules/@noble/ed25519/**',
// Rollup ignores the `browser: { crypto: false }` directive in package.json, since `exports` are present,
// hence we need to manually drop it.
"import * as nodeCrypto from 'crypto'": 'const nodeCrypto = null',
delimiters: ['', '']
}),
replace({
'OpenPGP.js VERSION': `OpenPGP.js ${pkg.version}`,
"import { createRequire } from 'module';": 'const createRequire = () => () => {}',
Expand Down Expand Up @@ -149,6 +163,13 @@ export default Object.assign([
ignore: nodeBuiltinModules.concat(nodeDependencies),
requireReturnsDefault: 'preferred'
}),
replace({
include: 'node_modules/@noble/ed25519/**',
// Rollup ignores the `browser: { crypto: false }` directive in package.json, since `exports` are present,
// hence we need to manually drop it.
"import * as nodeCrypto from 'crypto'": 'const nodeCrypto = null',
delimiters: ['', '']
}),
replace({
"import { createRequire } from 'module';": 'const createRequire = () => () => {}',
delimiters: ['', '']
Expand Down
12 changes: 7 additions & 5 deletions src/crypto/public_key/elliptic/eddsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
* @module crypto/public_key/elliptic/eddsa
*/

import ed25519 from '@openpgp/tweetnacl';
import naclEd25519 from '@openpgp/tweetnacl'; // better constant-timeness as it uses Uint8Arrays over BigInts
import { verify as nobleEd25519Verify } from '@noble/ed25519';

import util from '../../../util';
import enums from '../../../enums';
import hash from '../../hash';
Expand Down Expand Up @@ -52,7 +54,7 @@ export async function generate(algo) {
throw err;
}
const seed = getRandomBytes(getPayloadSize(algo));
const { publicKey: A } = ed25519.sign.keyPair.fromSeed(seed);
const { publicKey: A } = naclEd25519.sign.keyPair.fromSeed(seed);
return { A, seed };
}

Expand Down Expand Up @@ -101,7 +103,7 @@ export async function sign(algo, hashAlgo, message, publicKey, privateKey, hashe
throw err;
}
const secretKey = util.concatUint8Array([privateKey, publicKey]);
const signature = ed25519.sign.detached(hashed, secretKey);
const signature = naclEd25519.sign.detached(hashed, secretKey);
return { RS: signature };
}

Expand Down Expand Up @@ -143,7 +145,7 @@ export async function verify(algo, hashAlgo, { RS }, m, publicKey, hashed) {
if (err.name !== 'NotSupportedError') {
throw err;
}
return ed25519.sign.detached.verify(hashed, RS, publicKey);
return nobleEd25519Verify(RS, hashed, publicKey);
}

case enums.publicKey.ed448: {
Expand Down Expand Up @@ -171,7 +173,7 @@ export async function validateParams(algo, A, seed) {
* and expect A == A'
* TODO: move to sign-verify using WebCrypto (same as ECDSA) when curve is more widely implemented
*/
const { publicKey } = ed25519.sign.keyPair.fromSeed(seed);
const { publicKey } = naclEd25519.sign.keyPair.fromSeed(seed);
return util.equalsUint8Array(A, publicKey);
}

Expand Down
4 changes: 2 additions & 2 deletions src/crypto/public_key/elliptic/eddsa_legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* @module crypto/public_key/elliptic/eddsa_legacy
*/

import nacl from '@openpgp/tweetnacl';
import naclEd25519 from '@openpgp/tweetnacl'; // better constant-timeness as it uses Uint8Arrays over BigInts
import util from '../../../util';
import enums from '../../../enums';
import hash from '../../hash';
Expand Down Expand Up @@ -96,7 +96,7 @@ export async function validateParams(oid, Q, k) {
* Derive public point Q' = dG from private key
* and expect Q == Q'
*/
const { publicKey } = nacl.sign.keyPair.fromSeed(k);
const { publicKey } = naclEd25519.sign.keyPair.fromSeed(k);
const dG = new Uint8Array([0x40, ...publicKey]); // Add public key prefix
return util.equalsUint8Array(Q, dG);

Expand Down

0 comments on commit f443c61

Please sign in to comment.