Lightweight build: lazy load tweetnacl dependency module (curve25519 JS fallback)

Since all major browsers have shipped support for the curve
in WebCrypto, we only load the JS fallback if needed.

Also, add native/non-native ECDH test for Curve25519Legacy.
(The more modern X25519/X448 algo implementations cannot be
tested that way since they include an HKDF step for which
we assume native support and do not implement a fallback.)
This commit is contained in:
larabr 2025-07-28 15:12:55 +02:00
parent 721b918296
commit ed5554e114
No known key found for this signature in database
GPG Key ID: 2A4BEC40729185DD
8 changed files with 220 additions and 44 deletions

View File

@ -6,9 +6,8 @@
import enums from '../enums'; import enums from '../enums';
import util from '../util'; import util from '../util';
const webCrypto = util.getWebCrypto();
export default async function computeHKDF(hashAlgo, inputKey, salt, info, outLen) { export default async function computeHKDF(hashAlgo, inputKey, salt, info, outLen) {
const webCrypto = util.getWebCrypto();
const hash = enums.read(enums.webHash, hashAlgo); const hash = enums.read(enums.webHash, hashAlgo);
if (!hash) throw new Error('Hash algo not supported with HKDF'); if (!hash) throw new Error('Hash algo not supported with HKDF');

View File

@ -30,9 +30,6 @@ import * as pkcs5 from '../../pkcs5';
import { getCipherParams } from '../../cipher'; import { getCipherParams } from '../../cipher';
import { generateEphemeralEncryptionMaterial as ecdhXGenerateEphemeralEncryptionMaterial, recomputeSharedSecret as ecdhXRecomputeSharedSecret } from './ecdh_x'; import { generateEphemeralEncryptionMaterial as ecdhXGenerateEphemeralEncryptionMaterial, recomputeSharedSecret as ecdhXRecomputeSharedSecret } from './ecdh_x';
const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
/** /**
* Validate ECDH parameters * Validate ECDH parameters
* @param {module:type/oid} oid - Elliptic curve object identifier * @param {module:type/oid} oid - Elliptic curve object identifier
@ -238,6 +235,7 @@ async function jsPublicEphemeralKey(curve, Q) {
* @async * @async
*/ */
async function webPrivateEphemeralKey(curve, V, Q, d) { async function webPrivateEphemeralKey(curve, V, Q, d) {
const webCrypto = util.getWebCrypto();
const recipient = privateToJWK(curve.payloadSize, curve.web, Q, d); const recipient = privateToJWK(curve.payloadSize, curve.web, Q, d);
let privateKey = webCrypto.importKey( let privateKey = webCrypto.importKey(
'jwk', 'jwk',
@ -289,6 +287,7 @@ async function webPrivateEphemeralKey(curve, V, Q, d) {
* @async * @async
*/ */
async function webPublicEphemeralKey(curve, Q) { async function webPublicEphemeralKey(curve, Q) {
const webCrypto = util.getWebCrypto();
const jwk = rawPublicToJWK(curve.payloadSize, curve.web, Q); const jwk = rawPublicToJWK(curve.payloadSize, curve.web, Q);
let keyPair = webCrypto.generateKey( let keyPair = webCrypto.generateKey(
{ {
@ -338,6 +337,7 @@ async function webPublicEphemeralKey(curve, Q) {
* @async * @async
*/ */
async function nodePrivateEphemeralKey(curve, V, d) { async function nodePrivateEphemeralKey(curve, V, d) {
const nodeCrypto = util.getNodeCrypto();
const recipient = nodeCrypto.createECDH(curve.node); const recipient = nodeCrypto.createECDH(curve.node);
recipient.setPrivateKey(d); recipient.setPrivateKey(d);
const sharedKey = new Uint8Array(recipient.computeSecret(V)); const sharedKey = new Uint8Array(recipient.computeSecret(V));
@ -354,6 +354,7 @@ async function nodePrivateEphemeralKey(curve, V, d) {
* @async * @async
*/ */
async function nodePublicEphemeralKey(curve, Q) { async function nodePublicEphemeralKey(curve, Q) {
const nodeCrypto = util.getNodeCrypto();
const sender = nodeCrypto.createECDH(curve.node); const sender = nodeCrypto.createECDH(curve.node);
sender.generateKeys(); sender.generateKeys();
const sharedKey = new Uint8Array(sender.computeSecret(Q)); const sharedKey = new Uint8Array(sender.computeSecret(Q));

View File

@ -3,9 +3,7 @@
* @module crypto/public_key/elliptic/ecdh * @module crypto/public_key/elliptic/ecdh
*/ */
import x25519 from '@openpgp/tweetnacl';
import * as aesKW from '../../aes_kw'; import * as aesKW from '../../aes_kw';
import { getRandomBytes } from '../../random';
import enums from '../../../enums'; import enums from '../../../enums';
import util from '../../../util'; import util from '../../../util';
@ -55,9 +53,9 @@ export async function generate(algo) {
if (err.name !== 'NotSupportedError') { if (err.name !== 'NotSupportedError') {
throw err; throw err;
} }
const { default: x25519 } = await import('@openpgp/tweetnacl');
// k stays in little-endian, unlike legacy ECDH over curve25519 // k stays in little-endian, unlike legacy ECDH over curve25519
const k = getRandomBytes(32); const { secretKey: k, publicKey: A } = x25519.box.keyPair();
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
return { A, k }; return { A, k };
} }
@ -240,10 +238,10 @@ export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
if (err.name !== 'NotSupportedError') { if (err.name !== 'NotSupportedError') {
throw err; throw err;
} }
const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo)); const { default: x25519 } = await import('@openpgp/tweetnacl');
const { secretKey: ephemeralSecretKey, publicKey: ephemeralPublicKey } = x25519.box.keyPair();
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA); const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
assertNonZeroArray(sharedSecret); assertNonZeroArray(sharedSecret);
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
return { ephemeralPublicKey, sharedSecret }; return { ephemeralPublicKey, sharedSecret };
} }
case enums.publicKey.x448: { case enums.publicKey.x448: {
@ -278,6 +276,7 @@ export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
if (err.name !== 'NotSupportedError') { if (err.name !== 'NotSupportedError') {
throw err; throw err;
} }
const { default: x25519 } = await import('@openpgp/tweetnacl');
const sharedSecret = x25519.scalarMult(k, ephemeralPublicKey); const sharedSecret = x25519.scalarMult(k, ephemeralPublicKey);
assertNonZeroArray(sharedSecret); assertNonZeroArray(sharedSecret);
return sharedSecret; return sharedSecret;

View File

@ -20,10 +20,9 @@
* @module crypto/public_key/elliptic/eddsa * @module crypto/public_key/elliptic/eddsa
*/ */
import ed25519 from '@openpgp/tweetnacl';
import util from '../../../util'; import util from '../../../util';
import enums from '../../../enums'; import enums from '../../../enums';
import { computeDigest, getHashByteLength } from '../../hash'; import { getHashByteLength } from '../../hash';
import { getRandomBytes } from '../../random'; import { getRandomBytes } from '../../random';
import { b64ToUint8Array, uint8ArrayToB64 } from '../../../encoding/base64'; import { b64ToUint8Array, uint8ArrayToB64 } from '../../../encoding/base64';
@ -59,7 +58,9 @@ export async function generate(algo) {
if (err.name !== 'NotSupportedError') { if (err.name !== 'NotSupportedError') {
throw err; throw err;
} }
const { default: ed25519 } = await import('@openpgp/tweetnacl');
const seed = getRandomBytes(getPayloadSize(algo)); const seed = getRandomBytes(getPayloadSize(algo));
// not using `ed25519.sign.keyPair` since it returns the expanded secret, so using `fromSeed` instead is more straightforward
const { publicKey: A } = ed25519.sign.keyPair.fromSeed(seed); const { publicKey: A } = ed25519.sign.keyPair.fromSeed(seed);
return { A, seed }; return { A, seed };
} }
@ -111,6 +112,7 @@ export async function sign(algo, hashAlgo, message, publicKey, privateKey, hashe
if (err.name !== 'NotSupportedError') { if (err.name !== 'NotSupportedError') {
throw err; throw err;
} }
const { default: ed25519 } = await import('@openpgp/tweetnacl');
const secretKey = util.concatUint8Array([privateKey, publicKey]); const secretKey = util.concatUint8Array([privateKey, publicKey]);
const signature = ed25519.sign.detached(hashed, secretKey); const signature = ed25519.sign.detached(hashed, secretKey);
return { RS: signature }; return { RS: signature };
@ -157,6 +159,7 @@ export async function verify(algo, hashAlgo, { RS }, m, publicKey, hashed) {
if (err.name !== 'NotSupportedError') { if (err.name !== 'NotSupportedError') {
throw err; throw err;
} }
const { default: ed25519 } = await import('@openpgp/tweetnacl');
return ed25519.sign.detached.verify(hashed, RS, publicKey); return ed25519.sign.detached.verify(hashed, RS, publicKey);
} }
@ -203,7 +206,7 @@ export async function validateParams(algo, A, seed) {
if (err.name !== 'NotSupportedError') { if (err.name !== 'NotSupportedError') {
return false; return false;
} }
const { default: ed25519 } = await import('@openpgp/tweetnacl');
const { publicKey } = ed25519.sign.keyPair.fromSeed(seed); const { publicKey } = ed25519.sign.keyPair.fromSeed(seed);
return util.equalsUint8Array(A, publicKey); return util.equalsUint8Array(A, publicKey);
} }

View File

@ -19,7 +19,6 @@
* @fileoverview Wrapper of an instance of an Elliptic Curve * @fileoverview Wrapper of an instance of an Elliptic Curve
* @module crypto/public_key/elliptic/curve * @module crypto/public_key/elliptic/curve
*/ */
import nacl from '@openpgp/tweetnacl';
import enums from '../../../enums'; import enums from '../../../enums';
import util from '../../../util'; import util from '../../../util';
import { uint8ArrayToB64, b64ToUint8Array } from '../../../encoding/base64'; import { uint8ArrayToB64, b64ToUint8Array } from '../../../encoding/base64';

View File

@ -323,7 +323,10 @@ export default () => describe('ECDH key exchange @lightweight', function () {
const disableNative = () => { const disableNative = () => {
enableNative(); enableNative();
// stubbed functions return undefined // stubbed functions return undefined
getWebCryptoStub = sinonSandbox.stub(util, 'getWebCrypto'); getWebCryptoStub = sinonSandbox.stub(util, 'getWebCrypto').returns({
generateKey: () => { const e = new Error('getWebCrypto is mocked'); e.name = 'NotSupportedError'; throw e; },
importKey: () => { const e = new Error('getWebCrypto is mocked'); e.name = 'NotSupportedError'; throw e; }
});
getNodeCryptoStub = sinonSandbox.stub(util, 'getNodeCrypto'); getNodeCryptoStub = sinonSandbox.stub(util, 'getNodeCrypto');
}; };
const enableNative = () => { const enableNative = () => {
@ -331,6 +334,46 @@ export default () => describe('ECDH key exchange @lightweight', function () {
getNodeCryptoStub && getNodeCryptoStub.restore(); getNodeCryptoStub && getNodeCryptoStub.restore();
}; };
/**
* Test that the result of `encryptFunction` can be decrypted by `decryptFunction`
* with and without native crypto support.
* @param encryptFunction - `(data: Uint8Array) => encryptFunctionResult`
* @param decryptFunction - `(encryptFunctionResult) => <decryption result>`
* @param expectNative - whether native usage is expected for the algorithm
*/
const testRountripWithAndWithoutNative = async (
encryptFunction,
decryptFunction, // (encryptFunctionResult) => decryption result
expectNative
) => {
const nodeCrypto = util.getNodeCrypto();
const webCrypto = util.getWebCrypto();
const data = random.getRandomBytes(16);
const nativeSpy = webCrypto ? sinonSandbox.spy(webCrypto, 'deriveBits') : sinonSandbox.spy(nodeCrypto, 'createECDH'); // functions used both for encryption and decryption
const nativeResult = await encryptFunction(data);
const expectedNativeEncryptCallCount = nativeSpy.callCount;
disableNative();
const nonNativeResult = await encryptFunction(data);
expect(nativeSpy.callCount).to.equal(expectedNativeEncryptCallCount); // assert that fallback implementation was called
if (expectNative) {
expect(nativeSpy.calledOnce).to.be.true;
}
enableNative();
expect(await decryptFunction(nativeResult)).to.deep.equal(data);
expect(await decryptFunction(nonNativeResult)).to.deep.equal(data);
const expectedNativeCallCount = nativeSpy.callCount;
disableNative();
expect(await decryptFunction(nativeResult)).to.deep.equal(data);
expect(await decryptFunction(nonNativeResult)).to.deep.equal(data);
expect(nativeSpy.callCount).to.equal(expectedNativeCallCount); // assert that fallback implementation was called
if (expectNative) {
expect(nativeSpy.callCount).to.equal(3); // one encryption + two decryptions
}
};
allCurves.forEach(curveName => { allCurves.forEach(curveName => {
it(`${curveName}`, async function () { it(`${curveName}`, async function () {
const nodeCrypto = util.getNodeCrypto(); const nodeCrypto = util.getNodeCrypto();
@ -344,21 +387,27 @@ export default () => describe('ECDH key exchange @lightweight', function () {
const curve = new elliptic_curves.CurveWithOID(curveName); const curve = new elliptic_curves.CurveWithOID(curveName);
const oid = new OID(curve.oid); const oid = new OID(curve.oid);
const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher }); const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher });
const data = random.getRandomBytes(16);
const Q = key_data[curveName].pub; const Q = key_data[curveName].pub;
const d = key_data[curveName].priv; const d = key_data[curveName].priv;
const { publicKey: V, wrappedKey: C } = await ecdh.encrypt(oid, kdfParams, data, Q, fingerprint1);
const nativeDecryptSpy = webCrypto ? sinonSandbox.spy(webCrypto, 'deriveBits') : sinonSandbox.spy(nodeCrypto, 'createECDH'); await testRountripWithAndWithoutNative(
expect(await ecdh.decrypt(oid, kdfParams, V, C, Q, d, fingerprint1)).to.deep.equal(data); data => ecdh.encrypt(oid, kdfParams, data, Q, fingerprint1),
const expectedNativeCallCount = nativeDecryptSpy.callCount; encryptResult => ecdh.decrypt(oid, kdfParams, encryptResult.publicKey, encryptResult.wrappedKey, Q, d, fingerprint1),
disableNative(); expectNativeWeb.has(curveName) // all major browsers implement x25519
expect(await ecdh.decrypt(oid, kdfParams, V, C, Q, d, fingerprint1)).to.deep.equal(data); );
expect(nativeDecryptSpy.callCount).to.equal(expectedNativeCallCount); // assert that fallback implementation was called });
if (expectNativeWeb.has(curveName)) { });
expect(nativeDecryptSpy.calledOnce).to.be.true;
} it('Successful exchange x25519 (legacy)', async function () {
}); const curve = new elliptic_curves.CurveWithOID(openpgp.enums.curve.curve25519Legacy);
const oid = new OID(curve.oid);
const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher });
await testRountripWithAndWithoutNative(
data => ecdh.encrypt(oid, kdfParams, data, Q1, fingerprint1),
encryptResult => ecdh.decrypt(oid, kdfParams, encryptResult.publicKey, encryptResult.wrappedKey, Q1, d1, fingerprint1),
false // all major browsers implement x25519, but webkit linux falls back due to bugs
);
}); });
}); });
}); });

View File

@ -4,17 +4,59 @@ import chaiAsPromised from 'chai-as-promised'; // eslint-disable-line import/new
chaiUse(chaiAsPromised); chaiUse(chaiAsPromised);
import openpgp from '../initOpenpgp.js'; import openpgp from '../initOpenpgp.js';
import * as elliptic_curves from '../../src/crypto/public_key/elliptic'; import * as elliptic_curves from '../../src/crypto/public_key/elliptic/index.js';
import { computeDigest } from '../../src/crypto/hash'; import { computeDigest } from '../../src/crypto/hash/index.js';
import config from '../../src/config'; import config from '../../src/config/index.js';
import util from '../../src/util.js'; import util from '../../src/util.js';
import elliptic_data from './elliptic_data'; import elliptic_data from './elliptic_data.js';
import OID from '../../src/type/oid.js'; import OID from '../../src/type/oid.js';
import { getRandomBytes } from '../../src/crypto/random.js';
/**
* Test that the result of `signFunction` can be verified by `verifyFunction`
* with and without native crypto support.
* @param signFunction - `(data: Uint8Array) => signFunctionResult`
* @param verifyFunction - `(encryptFunctionResult) => <decryption result>`
* @param expectNative - whether native usage is expected for the algorithm
*/
const testRountripWithAndWithoutNative = async (
{ sinonSandbox, enableNative, disableNative },
signFunction,
verifyFunction, // (signFunctionResult) => verification result
expectNative
) => {
const nodeCrypto = util.getNodeCrypto();
const webCrypto = util.getWebCrypto();
const data = getRandomBytes(16);
const dataDigest = await computeDigest(openpgp.enums.hash.sha512, data);
const nativeSpySign = webCrypto ? sinonSandbox.spy(webCrypto, 'sign') : sinonSandbox.spy(nodeCrypto, 'createSign');
const nativeResult = await signFunction(data, dataDigest);
const expectedNativeSignCallCount = nativeSpySign.callCount;
disableNative();
const nonNativeResult = await signFunction(data, dataDigest);
expect(nativeSpySign.callCount).to.equal(expectedNativeSignCallCount); // assert that fallback implementation was called
if (expectNative) {
expect(nativeSpySign.calledOnce).to.be.true;
}
const nativeSpyVerify = webCrypto ? sinonSandbox.spy(webCrypto, 'verify') : sinonSandbox.spy(nodeCrypto, 'createVerify');
enableNative();
expect(await verifyFunction(nativeResult, data, dataDigest)).to.be.true;
expect(await verifyFunction(nonNativeResult, data, dataDigest)).to.be.true;
const expectedNativeVerifyCallCount = nativeSpyVerify.callCount;
disableNative();
expect(await verifyFunction(nativeResult, data, dataDigest)).to.be.true;
expect(await verifyFunction(nonNativeResult, data, dataDigest)).be.true;
expect(nativeSpyVerify.callCount).to.equal(expectedNativeVerifyCallCount); // assert that fallback implementation was called
if (expectNative) {
expect(nativeSpyVerify.callCount).to.equal(2);
}
};
const key_data = elliptic_data.key_data; const key_data = elliptic_data.key_data;
/* eslint-disable no-invalid-this */ export default () => describe('ECC signatures', function () {
export default () => describe('Elliptic Curve Cryptography @lightweight', function () {
const signature_data = { const signature_data = {
priv: new Uint8Array([ priv: new Uint8Array([
0x14, 0x2B, 0xE2, 0xB7, 0x4D, 0xBD, 0x1B, 0x22, 0x14, 0x2B, 0xE2, 0xB7, 0x4D, 0xBD, 0x1B, 0x22,
@ -241,19 +283,103 @@ export default () => describe('Elliptic Curve Cryptography @lightweight', functi
); );
}); });
const curves = ['secp256k1' , 'nistP256', 'nistP384', 'nistP521', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1']; const curves = ['secp256k1' , 'nistP256', 'nistP384', 'nistP521', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
curves.forEach(curveName => it(`${curveName} - Sign and verify message`, async function () { curves.forEach(curveName => it(`${curveName} - Sign and verify message with generated key`, async function () {
const sinonState = { sinonSandbox, enableNative, disableNative };
const curve = new elliptic_curves.CurveWithOID(curveName); const curve = new elliptic_curves.CurveWithOID(curveName);
const oid = new OID(curve.oid); const oid = new OID(curve.oid);
const { Q: keyPublic, secret: keyPrivate } = await elliptic_curves.generate(curveName); const expectNativeWeb = new Set(['nistP256', 'nistP384']); // older versions of safari do not implement nistP521
const message = new Uint8Array([
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, const nativeKey = await elliptic_curves.generate(curveName);
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF await testRountripWithAndWithoutNative(
]); sinonState,
const messageDigest = await computeDigest(openpgp.enums.hash.sha512, message); (data, dataDigest) => elliptic_curves.ecdsa.sign(oid, openpgp.enums.hash.sha512, data, nativeKey.Q, nativeKey.secret, dataDigest),
await testNativeAndFallback(async () => { (signature, data, dataDigest) => elliptic_curves.ecdsa.verify(oid, openpgp.enums.hash.sha512, signature, data, nativeKey.Q, dataDigest),
const signature = await elliptic_curves.ecdsa.sign(oid, openpgp.enums.hash.sha512, message, keyPublic, keyPrivate, messageDigest); expectNativeWeb.has(curveName)
await expect(elliptic_curves.ecdsa.verify(oid, openpgp.enums.hash.sha512, signature, message, keyPublic, messageDigest)).to.eventually.be.true; );
sinonSandbox.restore(); // reset spies
disableNative();
const nonNativeKey = await elliptic_curves.generate(curveName);
enableNative();
await testRountripWithAndWithoutNative(
sinonState,
(data, dataDigest) => elliptic_curves.ecdsa.sign(oid, openpgp.enums.hash.sha512, data, nonNativeKey.Q, nonNativeKey.secret, dataDigest),
(signature, data, dataDigest) => elliptic_curves.ecdsa.verify(oid, openpgp.enums.hash.sha512, signature, data, nonNativeKey.Q, dataDigest),
expectNativeWeb.has(curveName)
);
}));
}); });
describe('EdDSA signature', function () {
let sinonSandbox;
let getWebCryptoStub;
let getNodeCryptoStub;
beforeEach(function () {
sinonSandbox = sinon.createSandbox();
});
afterEach(function () {
sinonSandbox.restore();
});
const disableNative = () => {
enableNative();
// stubbed functions return undefined
getWebCryptoStub = sinonSandbox.stub(util, 'getWebCrypto').returns({
generateKey: () => { const e = new Error('getWebCrypto is mocked'); e.name = 'NotSupportedError'; throw e; },
importKey: () => { const e = new Error('getWebCrypto is mocked'); e.name = 'NotSupportedError'; throw e; }
});
getNodeCryptoStub = sinonSandbox.stub(util, 'getNodeCrypto');
};
const enableNative = () => {
getWebCryptoStub && getWebCryptoStub.restore();
getNodeCryptoStub && getNodeCryptoStub.restore();
};
it('ed25519Legacy - Sign and verify message with generated key', async function () {
const sinonState = { sinonSandbox, enableNative, disableNative };
const curve = new elliptic_curves.CurveWithOID(openpgp.enums.curve.ed25519Legacy);
const oid = new OID(curve.oid);
const nativeKey = await elliptic_curves.generate(openpgp.enums.curve.ed25519Legacy);
await testRountripWithAndWithoutNative(
sinonState,
(data, dataDigest) => elliptic_curves.eddsaLegacy.sign(oid, openpgp.enums.hash.sha512, data, nativeKey.Q, nativeKey.secret, dataDigest),
(signature, data, dataDigest) => elliptic_curves.eddsaLegacy.verify(oid, openpgp.enums.hash.sha512, signature, data, nativeKey.Q, dataDigest)
);
sinonSandbox.restore(); // reset spies
disableNative();
const nonNativeKey = await elliptic_curves.generate(openpgp.enums.curve.ed25519Legacy);
enableNative();
await testRountripWithAndWithoutNative(
sinonState,
(data, dataDigest) => elliptic_curves.eddsaLegacy.sign(oid, openpgp.enums.hash.sha512, data, nonNativeKey.Q, nonNativeKey.secret, dataDigest),
(signature, data, dataDigest) => elliptic_curves.eddsaLegacy.verify(oid, openpgp.enums.hash.sha512, signature, data, nonNativeKey.Q, dataDigest)
);
});
['ed25519', 'ed448'].forEach(algoName => it(`${algoName} - Sign and verify message with native generated key`, async function () {
const sinonState = { sinonSandbox, enableNative, disableNative };
const algo = openpgp.enums.publicKey[algoName];
const nativeKey = await elliptic_curves.eddsa.generate(algo);
await testRountripWithAndWithoutNative(
sinonState,
(data, dataDigest) => elliptic_curves.eddsa.sign(algo, openpgp.enums.hash.sha512, data, nativeKey.A, nativeKey.seed, dataDigest),
(signature, data, dataDigest) => elliptic_curves.eddsa.verify(algo, openpgp.enums.hash.sha512, signature, data, nativeKey.A, dataDigest)
);
sinonSandbox.restore(); // reset spies
disableNative();
const nonNativeKey = await elliptic_curves.eddsa.generate(algo);
enableNative();
await testRountripWithAndWithoutNative(
sinonState,
(data, dataDigest) => elliptic_curves.eddsa.sign(algo, openpgp.enums.hash.sha512, data, nonNativeKey.A, nonNativeKey.seed, dataDigest),
(signature, data, dataDigest) => elliptic_curves.eddsa.verify(algo, openpgp.enums.hash.sha512, signature, data, nonNativeKey.A, dataDigest)
);
})); }));
}); });
}); });

View File

@ -2,7 +2,7 @@ import testBigInteger from './biginteger';
import testCipher from './cipher'; import testCipher from './cipher';
import testHash from './hash'; import testHash from './hash';
import testCrypto from './crypto'; import testCrypto from './crypto';
import testElliptic from './elliptic'; import testElliptic from './ecdsa_eddsa';
import testBrainpoolRFC7027 from './brainpool_rfc7027'; import testBrainpoolRFC7027 from './brainpool_rfc7027';
import testECDH from './ecdh'; import testECDH from './ecdh';
import testPKCS5 from './pkcs5'; import testPKCS5 from './pkcs5';