diff --git a/features/keychain/module/__tests__/sodium.test.js b/features/keychain/module/__tests__/sodium.test.js index 95c98e4..2d2b715 100644 --- a/features/keychain/module/__tests__/sodium.test.js +++ b/features/keychain/module/__tests__/sodium.test.js @@ -1,9 +1,22 @@ -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 createKeychain from './create-keychain.js' +import { getSodiumKeysFromSeed } from '@exodus/crypto/sodium' + import { getSeedId } from '../crypto/seed-id.js' +const getSodiumKeysFromSeedMock = jest.fn(getSodiumKeysFromSeed) + +mock.module('@exodus/crypto/sodium', { + namedExports: { + getSodiumKeysFromSeed: getSodiumKeysFromSeedMock, + }, +}) + +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 +36,25 @@ const BOB_KEY = new KeyIdentifier({ }) describe('libsodium', () => { + it('should cache sodium keys', async () => { + const keychain = createKeychain({ seed }) + 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() + }) + 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 f076183..9bbcff0 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 }) => {