Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: cache getSodiumKeysFromSeed for same seed #199

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions features/keychain/module/__tests__/sodium.test.js
Original file line number Diff line number Diff line change
@@ -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'
)
Expand All @@ -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 })
Expand Down
9 changes: 7 additions & 2 deletions features/keychain/module/crypto/sodium.js
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we clear this when the keychain gets locked?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

const getKeysFromSeed = async ({ seedId, keyId, exportPrivate }) => {
Expand Down
Loading