diff --git a/jest.config.js b/jest.config.js index 64cb1194..00c847e6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -41,10 +41,10 @@ module.exports = { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 80.35, - functions: 93.65, - lines: 91.9, - statements: 92.07, + branches: 80.18, + functions: 93.54, + lines: 91.69, + statements: 91.87, }, }, preset: 'ts-jest', diff --git a/src/KeyringController.test.ts b/src/KeyringController.test.ts index 8cdc119a..22430b7c 100644 --- a/src/KeyringController.test.ts +++ b/src/KeyringController.test.ts @@ -67,12 +67,17 @@ async function initializeKeyringController({ if (seedPhrase && !password) { throw new Error('Password required to restore vault'); } else if (seedPhrase && password) { - await keyringController.createNewVaultAndRestore( - PASSWORD, - walletOneSeedWords, - ); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + opts: { + mnemonic: walletOneSeedWords, + numberOfAccounts: 1, + }, + }); } else if (password) { - await keyringController.createNewVaultAndKeychain(PASSWORD); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + }); } return keyringController; @@ -162,7 +167,9 @@ describe('KeyringController', () => { const keyringController = await initializeKeyringController({ password: PASSWORD, }); - await keyringController.createNewVaultAndKeychain(PASSWORD); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + }); await keyringController.persistAllKeyrings(); expect(keyringController.keyrings).toHaveLength(1); @@ -257,7 +264,9 @@ describe('KeyringController', () => { cacheEncryptionKey: true, }, }); - await keyringController.createNewVaultAndKeychain(PASSWORD); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + }); deleteEncryptionKeyAndSalt(keyringController); const response = await keyringController.persistAllKeyrings(); @@ -293,24 +302,71 @@ describe('KeyringController', () => { }); }); }); + + it('should add an `encryptionSalt` to the `memStore` when a vault is restored', async () => { + const keyringController = await initializeKeyringController({ + password: PASSWORD, + constructorOptions: { + cacheEncryptionKey: true, + }, + }); + + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + opts: { + mnemonic: walletOneSeedWords, + numberOfAccounts: 1, + }, + }); + + const finalMemStore = keyringController.memStore.getState(); + expect(finalMemStore.encryptionKey).toBe(MOCK_HARDCODED_KEY); + expect(finalMemStore.encryptionSalt).toBe(MOCK_ENCRYPTION_SALT); + }); }); - describe('createNewVaultAndKeychain', () => { - it('should create a new vault', async () => { + describe('createNewVaultWithKeyring', () => { + it('should create a new vault with a HD keyring', async () => { const keyringController = await initializeKeyringController({ password: PASSWORD, }); keyringController.store.putState({}); assert(!keyringController.store.getState().vault, 'no previous vault'); - const newVault = await keyringController.createNewVaultAndKeychain( + const newVault = await keyringController.createNewVaultWithKeyring( PASSWORD, + { + type: KeyringType.HD, + }, ); const { vault } = keyringController.store.getState(); expect(vault).toStrictEqual(expect.stringMatching('.+')); expect(typeof newVault).toBe('object'); }); + it('should create a new vault with a simple keyring', async () => { + const keyringController = await initializeKeyringController({ + password: PASSWORD, + }); + keyringController.store.putState({}); + assert(!keyringController.store.getState().vault, 'no previous vault'); + + const newVault = await keyringController.createNewVaultWithKeyring( + PASSWORD, + { + type: KeyringType.Simple, + opts: walletOnePrivateKey, + }, + ); + const { vault } = keyringController.store.getState(); + expect(vault).toStrictEqual(expect.stringMatching('.+')); + expect(typeof newVault).toBe('object'); + + const accounts = await keyringController.getAccounts(); + expect(accounts).toHaveLength(1); + expect(accounts[0]).toBe(walletOneAddresses[0]); + }); + it('should unlock the vault', async () => { const keyringController = await initializeKeyringController({ password: PASSWORD, @@ -318,7 +374,9 @@ describe('KeyringController', () => { keyringController.store.putState({}); assert(!keyringController.store.getState().vault, 'no previous vault'); - await keyringController.createNewVaultAndKeychain(PASSWORD); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + }); const { isUnlocked } = keyringController.memStore.getState(); expect(isUnlocked).toBe(true); }); @@ -335,7 +393,9 @@ describe('KeyringController', () => { keyringController.store.putState({}); assert(!keyringController.store.getState().vault, 'no previous vault'); - await keyringController.createNewVaultAndKeychain(PASSWORD); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + }); const { vault } = keyringController.store.getState(); // eslint-disable-next-line jest/no-restricted-matchers expect(vault).toBeTruthy(); @@ -353,8 +413,10 @@ describe('KeyringController', () => { .mockImplementation(async () => Promise.resolve([])); await expect(async () => - keyringController.createNewVaultAndKeychain(PASSWORD), - ).rejects.toThrow(KeyringControllerError.NoAccountOnKeychain); + keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + }), + ).rejects.toThrow(KeyringControllerError.NoFirstAccount); }); describe('when `cacheEncryptionKey` is enabled', () => { @@ -366,16 +428,16 @@ describe('KeyringController', () => { }, }); - await keyringController.createNewVaultAndKeychain(PASSWORD); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + }); const finalMemStore = keyringController.memStore.getState(); expect(finalMemStore.encryptionKey).toBe(MOCK_HARDCODED_KEY); expect(finalMemStore.encryptionSalt).toBe(MOCK_ENCRYPTION_SALT); }); }); - }); - describe('createNewVaultAndRestore', () => { it('clears old keyrings and creates a one', async () => { const keyringController = await initializeKeyringController({ password: PASSWORD, @@ -387,10 +449,13 @@ describe('KeyringController', () => { const allAccounts = await keyringController.getAccounts(); expect(allAccounts).toHaveLength(2); - await keyringController.createNewVaultAndRestore( - PASSWORD, - walletOneSeedWords, - ); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + opts: { + mnemonic: walletOneSeedWords, + numberOfAccounts: 1, + }, + }); const allAccountsAfter = await keyringController.getAccounts(); expect(allAccountsAfter).toHaveLength(1); @@ -403,7 +468,13 @@ describe('KeyringController', () => { }); await expect(async () => // @ts-expect-error Missing other required permission types. - keyringController.createNewVaultAndRestore(12, walletTwoSeedWords), + keyringController.createNewVaultWithKeyring(12, { + type: KeyringType.HD, + opts: { + mnemonic: walletTwoSeedWords, + numberOfAccounts: 1, + }, + }), ).rejects.toThrow('KeyringController - Password must be of type string.'); }); @@ -412,16 +483,26 @@ describe('KeyringController', () => { password: PASSWORD, }); await expect(async () => - keyringController.createNewVaultAndRestore( - PASSWORD, - 'test test test palace city barely security section midnight wealth south deer', - ), + keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + opts: { + mnemonic: + 'test test test palace city barely security section midnight wealth south deer', + numberOfAccounts: 1, + }, + }), ).rejects.toThrow( 'Eth-Hd-Keyring: Invalid secret recovery phrase provided', ); await expect(async () => - keyringController.createNewVaultAndRestore(PASSWORD, '1234'), + keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + opts: { + mnemonic: '1234', + numberOfAccounts: 1, + }, + }), ).rejects.toThrow( 'Eth-Hd-Keyring: Invalid secret recovery phrase provided', ); @@ -437,10 +518,13 @@ describe('KeyringController', () => { Buffer.from(walletTwoSeedWords).values(), ); - await keyringController.createNewVaultAndRestore( - PASSWORD, - mnemonicAsArrayOfNumbers, - ); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + opts: { + mnemonic: mnemonicAsArrayOfNumbers, + numberOfAccounts: 1, + }, + }); const allAccountsAfter = await keyringController.getAccounts(); expect(allAccountsAfter).toHaveLength(1); @@ -456,31 +540,14 @@ describe('KeyringController', () => { .mockImplementation(async () => Promise.resolve([])); await expect(async () => - keyringController.createNewVaultAndRestore( - PASSWORD, - walletTwoSeedWords, - ), - ).rejects.toThrow('KeyringController - First Account not found.'); - }); - - describe('when `cacheEncryptionKey` is enabled', () => { - it('should add an `encryptionSalt` to the `memStore` when a vault is restored', async () => { - const keyringController = await initializeKeyringController({ - password: PASSWORD, - constructorOptions: { - cacheEncryptionKey: true, + keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + opts: { + mnemonic: walletTwoSeedWords, + numberOfAccounts: 1, }, - }); - - await keyringController.createNewVaultAndRestore( - PASSWORD, - walletOneSeedWords, - ); - - const finalMemStore = keyringController.memStore.getState(); - expect(finalMemStore.encryptionKey).toBe(MOCK_HARDCODED_KEY); - expect(finalMemStore.encryptionSalt).toBe(MOCK_ENCRYPTION_SALT); - }); + }), + ).rejects.toThrow('KeyringController - First Account not found.'); }); }); @@ -943,10 +1010,13 @@ describe('KeyringController', () => { it('does not throw if a vault exists in state', async () => { const keyringController = await initializeKeyringController(); - await keyringController.createNewVaultAndRestore( - PASSWORD, - walletOneSeedWords, - ); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + opts: { + mnemonic: walletOneSeedWords, + numberOfAccounts: 1, + }, + }); expect(async () => keyringController.verifyPassword(PASSWORD), @@ -1273,10 +1343,13 @@ describe('KeyringController', () => { const keyringController = await initializeKeyringController({ password: PASSWORD, }); - await keyringController.createNewVaultAndRestore( - PASSWORD, - walletOneSeedWords, - ); + await keyringController.createNewVaultWithKeyring(PASSWORD, { + type: KeyringType.HD, + opts: { + mnemonic: walletOneSeedWords, + numberOfAccounts: 1, + }, + }); const privateKey = await keyringController.exportAccount( // @ts-expect-error this value should never be undefined in this specific context. walletOneAddresses[0], diff --git a/src/KeyringController.ts b/src/KeyringController.ts index 6d89f38b..42eecb1a 100644 --- a/src/KeyringController.ts +++ b/src/KeyringController.ts @@ -115,43 +115,25 @@ class KeyringController extends EventEmitter { */ /** - * Create New Vault And Keychain + * Create new vault And with a specific keyring * * Destroys any old encrypted storage, * creates a new encrypted store with the given password, - * randomly creates a new HD wallet with 1 account, - * faucets that account on the testnet. + * creates a new wallet with 1 account. * * @fires KeyringController#unlock * @param password - The password to encrypt the vault with. + * @param keyring - A object containing the params to instantiate a new keyring. + * @param keyring.type - The keyring type. + * @param keyring.opts - Optional parameters required to instantiate the keyring. * @returns A promise that resolves to the state. */ - async createNewVaultAndKeychain( + async createNewVaultWithKeyring( password: string, - ): Promise { - this.password = password; - - await this.#createFirstKeyTree(); - this.#setUnlocked(); - return this.fullUpdate(); - } - - /** - * CreateNewVaultAndRestore - * - * Destroys any old encrypted storage, - * creates a new encrypted store with the given password, - * creates a new HD wallet from the given seed with 1 account. - * - * @fires KeyringController#unlock - * @param password - The password to encrypt the vault with. - * @param seedPhrase - The BIP39-compliant seed phrase, - * either as a string or Uint8Array. - * @returns A promise that resolves to the state. - */ - async createNewVaultAndRestore( - password: string, - seedPhrase: Uint8Array | string | number[], + keyring: { + type: string; + opts?: unknown; + }, ): Promise { if (typeof password !== 'string') { throw new TypeError(KeyringControllerError.WrongPasswordType); @@ -159,16 +141,7 @@ class KeyringController extends EventEmitter { this.password = password; await this.#clearKeyrings(); - const keyring = await this.addNewKeyring(KeyringType.HD, { - mnemonic: seedPhrase, - numberOfAccounts: 1, - }); - - const [firstAccount] = await keyring.getAccounts(); - - if (!firstAccount) { - throw new Error(KeyringControllerError.NoFirstAccount); - } + await this.#createKeyring(keyring.type, keyring.opts); this.#setUnlocked(); return this.fullUpdate(); } @@ -1011,28 +984,24 @@ class KeyringController extends EventEmitter { // ======================= /** - * Create First Key Tree. + * Create keyring. * - * - Clears the existing vault. * - Creates a new vault. - * - Creates a random new HD Keyring with 1 account. - * - Makes that account the selected account. - * - Faucets that account on testnet. - * - Puts the current seed words into the state tree. - * + * - Creates a new keyring with at least one account. + * - Makes the first account the selected account. + * @param type - Keyring type to instantiate. + * @param opts - Optional parameters required to instantiate the keyring. * @returns A promise that resolves if the operation was successful. */ - async #createFirstKeyTree(): Promise { - await this.#clearKeyrings(); - - const keyring = await this.addNewKeyring(KeyringType.HD); + async #createKeyring(type: string, opts?: unknown): Promise { + const keyring = await this.addNewKeyring(type, opts); if (!keyring) { throw new Error(KeyringControllerError.NoKeyring); } const [firstAccount] = await keyring.getAccounts(); if (!firstAccount) { - throw new Error(KeyringControllerError.NoAccountOnKeychain); + throw new Error(KeyringControllerError.NoFirstAccount); } const hexAccount = normalizeToHex(firstAccount);