From 8d64bd4f1f2b5bf4110927aedcf9e163d12c98b0 Mon Sep 17 00:00:00 2001 From: Jan W Date: Thu, 23 Jan 2025 15:56:57 +0100 Subject: [PATCH 1/4] perf: cache getSodiumKeysFromSeed for same seed --- .../keychain/module/__tests__/sodium.test.js | 32 ++++++++++++++++++- features/keychain/module/crypto/sodium.js | 9 ++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/features/keychain/module/__tests__/sodium.test.js b/features/keychain/module/__tests__/sodium.test.js index 95c98e45..9ea6d7a1 100644 --- a/features/keychain/module/__tests__/sodium.test.js +++ b/features/keychain/module/__tests__/sodium.test.js @@ -1,8 +1,17 @@ import { mnemonicToSeed } from 'bip39' import KeyIdentifier from '@exodus/key-identifier' -import createKeychain from './create-keychain.js' import { getSeedId } from '../crypto/seed-id.js' +import { getSodiumKeysFromSeed } from '@exodus/crypto/sodium' + +const sodiumKeysFromSeedMock = jest.fn(getSodiumKeysFromSeed) + +jest.mock('@exodus/crypto/sodium', () => ({ + __esModule: true, + getSodiumKeysFromSeed: sodiumKeysFromSeedMock, +})) + +const { default: createKeychain } = await import('./create-keychain.js') const seed = mnemonicToSeed( 'menu memory fury language physical wonder dog valid smart edge decrease worth' @@ -23,6 +32,27 @@ const BOB_KEY = new KeyIdentifier({ }) describe('libsodium', () => { + it('should cache sodium keys', async () => { + const keychain = createKeychain({ seed }) + + await keychain.sodium.getSodiumKeysFromSeed({ + seedId, + keyId: BOB_KEY, + }) + + await keychain.sodium.getSodiumKeysFromSeed({ + seedId, + keyId: BOB_KEY, + }) + + await keychain.sodium.getSodiumKeysFromSeed({ + seedId, + keyId: BOB_KEY, + }) + + expect(sodiumKeysFromSeedMock).toHaveBeenCalledOnce() + }) + it('should have sign keys compatibility with SLIP10', async () => { const keychain = createKeychain({ seed }) const exportedKeys = await keychain.exportKey({ seedId, keyId: ALICE_KEY }) diff --git a/features/keychain/module/crypto/sodium.js b/features/keychain/module/crypto/sodium.js index f0761836..9bbcff08 100644 --- a/features/keychain/module/crypto/sodium.js +++ b/features/keychain/module/crypto/sodium.js @@ -1,5 +1,5 @@ import sodium from '@exodus/sodium-crypto' -import { mapValues } from '@exodus/basic-utils' +import { mapValues, memoize } from '@exodus/basic-utils' import * as curve25519 from '@exodus/crypto/curve25519' import { getSodiumKeysFromSeed } from '@exodus/crypto/sodium' @@ -16,13 +16,18 @@ const cloneKeypair = ({ keys, exportPrivate }) => { } } +export const getMemoizedSodiumKeysFromSeed = memoize(getSodiumKeysFromSeed, (seed) => + seed.toString('hex') +) + export const create = ({ getPrivateHDKey }) => { // Sodium encryptor caches the private key and the return value holds // not refences to keychain internals, allowing the seed to be safely // garbage collected, clearing it from memory. + const getSodiumKeysFromIdentifier = async ({ seedId, keyId }) => { const { privateKey: sodiumSeed } = getPrivateHDKey({ seedId, keyId }) - return getSodiumKeysFromSeed(sodiumSeed) + return getMemoizedSodiumKeysFromSeed(sodiumSeed) } const getKeysFromSeed = async ({ seedId, keyId, exportPrivate }) => { From 790e0075f4fcffb5d1a6bbc8da65f6da54ddf73b Mon Sep 17 00:00:00 2001 From: Jan W Date: Thu, 23 Jan 2025 16:02:13 +0100 Subject: [PATCH 2/4] refactor: mock name --- features/keychain/module/__tests__/sodium.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/features/keychain/module/__tests__/sodium.test.js b/features/keychain/module/__tests__/sodium.test.js index 9ea6d7a1..4b168cdf 100644 --- a/features/keychain/module/__tests__/sodium.test.js +++ b/features/keychain/module/__tests__/sodium.test.js @@ -4,11 +4,11 @@ import KeyIdentifier from '@exodus/key-identifier' import { getSeedId } from '../crypto/seed-id.js' import { getSodiumKeysFromSeed } from '@exodus/crypto/sodium' -const sodiumKeysFromSeedMock = jest.fn(getSodiumKeysFromSeed) +const getSodiumKeysFromSeedMock = jest.fn(getSodiumKeysFromSeed) jest.mock('@exodus/crypto/sodium', () => ({ __esModule: true, - getSodiumKeysFromSeed: sodiumKeysFromSeedMock, + getSodiumKeysFromSeed: getSodiumKeysFromSeedMock, })) const { default: createKeychain } = await import('./create-keychain.js') @@ -50,7 +50,7 @@ describe('libsodium', () => { keyId: BOB_KEY, }) - expect(sodiumKeysFromSeedMock).toHaveBeenCalledOnce() + expect(getSodiumKeysFromSeedMock).toHaveBeenCalledOnce() }) it('should have sign keys compatibility with SLIP10', async () => { From efac0f55832bb3b2c6c55020301b2ff08e0c5e83 Mon Sep 17 00:00:00 2001 From: Jan W Date: Thu, 23 Jan 2025 16:07:17 +0100 Subject: [PATCH 3/4] test: mock with node --- features/keychain/module/__tests__/sodium.test.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/features/keychain/module/__tests__/sodium.test.js b/features/keychain/module/__tests__/sodium.test.js index 4b168cdf..27bc2dbe 100644 --- a/features/keychain/module/__tests__/sodium.test.js +++ b/features/keychain/module/__tests__/sodium.test.js @@ -1,4 +1,5 @@ import { mnemonicToSeed } from 'bip39' +import { mock } from 'node:test' import KeyIdentifier from '@exodus/key-identifier' import { getSeedId } from '../crypto/seed-id.js' @@ -6,10 +7,11 @@ import { getSodiumKeysFromSeed } from '@exodus/crypto/sodium' const getSodiumKeysFromSeedMock = jest.fn(getSodiumKeysFromSeed) -jest.mock('@exodus/crypto/sodium', () => ({ - __esModule: true, - getSodiumKeysFromSeed: getSodiumKeysFromSeedMock, -})) +mock.module('@exodus/crypto/sodium', { + namedExports: { + getSodiumKeysFromSeed: getSodiumKeysFromSeedMock, + }, +}) const { default: createKeychain } = await import('./create-keychain.js') From 1f82441bc2e9c7b1ccb09536449881df71b0df31 Mon Sep 17 00:00:00 2001 From: Jan W Date: Thu, 23 Jan 2025 16:11:39 +0100 Subject: [PATCH 4/4] test: use encryption as example --- .../keychain/module/__tests__/sodium.test.js | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/features/keychain/module/__tests__/sodium.test.js b/features/keychain/module/__tests__/sodium.test.js index 27bc2dbe..2d2b7158 100644 --- a/features/keychain/module/__tests__/sodium.test.js +++ b/features/keychain/module/__tests__/sodium.test.js @@ -1,10 +1,12 @@ -import { mnemonicToSeed } from 'bip39' import { mock } from 'node:test' +import crypto from 'node:crypto' +import { mnemonicToSeed } from 'bip39' import KeyIdentifier from '@exodus/key-identifier' -import { getSeedId } from '../crypto/seed-id.js' import { getSodiumKeysFromSeed } from '@exodus/crypto/sodium' +import { getSeedId } from '../crypto/seed-id.js' + const getSodiumKeysFromSeedMock = jest.fn(getSodiumKeysFromSeed) mock.module('@exodus/crypto/sodium', { @@ -36,21 +38,19 @@ const BOB_KEY = new KeyIdentifier({ describe('libsodium', () => { it('should cache sodium keys', async () => { const keychain = createKeychain({ seed }) - - await keychain.sodium.getSodiumKeysFromSeed({ - seedId, - keyId: BOB_KEY, - }) - - await keychain.sodium.getSodiumKeysFromSeed({ - seedId, - keyId: BOB_KEY, - }) - - await keychain.sodium.getSodiumKeysFromSeed({ - seedId, - keyId: BOB_KEY, - }) + const toPublicKey = crypto.randomBytes(32) + + const encrypt = () => + keychain.sodium.encryptBox({ + seedId, + keyId: BOB_KEY, + data: Buffer.from('Batman is Bruce Wayne - or is he Harvey Dent?', 'utf8'), + toPublicKey, + }) + + await encrypt() + await encrypt() + await encrypt() expect(getSodiumKeysFromSeedMock).toHaveBeenCalledOnce() })