diff --git a/index.js b/index.js index 9b6f141..4c289b9 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,13 @@ const TrezorConnect = require('trezor-connect').default; const { TransactionFactory } = require('@ethereumjs/tx'); const hdPathString = `m/44'/60'/0'/0`; +const SLIP0044TestnetPath = `m/44'/1'/0'/0`; + +const ALLOWED_HD_PATHS = { + [hdPathString]: true, + [SLIP0044TestnetPath]: true, +}; + const keyringType = 'Trezor Hardware'; const pathBase = 'm'; const MAX_INDEX = 1000; @@ -338,6 +345,35 @@ class TrezorKeyring extends EventEmitter { this.paths = {}; } + /** + * Set the HD path to be used by the keyring. Only known supported HD paths are allowed. + * + * If the given HD path is already the current HD path, nothing happens. Otherwise the new HD + * path is set, and the wallet state is completely reset. + * + * @throws {Error] Throws if the HD path is not supported. + * + * @param {string} hdPath - The HD path to set. + */ + setHdPath(hdPath) { + if (!ALLOWED_HD_PATHS[hdPath]) { + throw new Error( + `The setHdPath method does not support setting HD Path to ${hdPath}`, + ); + } + + // Reset HDKey if the path changes + if (this.hdPath !== hdPath) { + this.hdk = new HDKey(); + this.accounts = []; + this.page = 0; + this.perPage = 5; + this.unlockedAccount = 0; + this.paths = {}; + } + this.hdPath = hdPath; + } + /* PRIVATE METHODS */ _normalize(buf) { diff --git a/test/test-eth-trezor-keyring.js b/test/test-eth-trezor-keyring.js index d6b9aa0..964f33d 100644 --- a/test/test-eth-trezor-keyring.js +++ b/test/test-eth-trezor-keyring.js @@ -476,4 +476,61 @@ describe('TrezorKeyring', function () { assert.equal(accounts.length, 0); }); }); + + describe('setHdPath', function () { + const initialProperties = { + hdPath: `m/44'/60'/0'/0`, + accounts: ['Account 1'], + page: 2, + perPage: 10, + }; + const accountToUnlock = 1; + const mockPaths = { '0x123': 1 }; + + beforeEach(function () { + keyring.deserialize(initialProperties); + keyring.paths = mockPaths; + keyring.setAccountToUnlock(accountToUnlock.toString(16)); + }); + + it('should do nothing if passed an hdPath equal to the current hdPath', async function () { + keyring.setHdPath(initialProperties.hdPath); + assert.equal(keyring.hdPath, initialProperties.hdPath); + assert.deepEqual(keyring.accounts, initialProperties.accounts); + assert.equal(keyring.page, initialProperties.page); + assert.equal(keyring.perPage, initialProperties.perPage); + assert.equal( + keyring.hdk._publicKey.toString('hex'), + fakeHdKey._publicKey.toString('hex'), + ); + assert.equal(keyring.unlockedAccount, accountToUnlock); + assert.deepEqual(keyring.paths, mockPaths); + }); + + it('should update the hdPath and reset account and page properties if passed a new hdPath', async function () { + const SLIP0044TestnetPath = `m/44'/1'/0'/0`; + + keyring.setHdPath(SLIP0044TestnetPath); + + assert.equal(keyring.hdPath, SLIP0044TestnetPath); + assert.deepEqual(keyring.accounts, []); + assert.equal(keyring.page, 0); + assert.equal(keyring.perPage, 5); + assert.equal(keyring.hdk._publicKey, null); + assert.equal(keyring.unlockedAccount, 0); + assert.deepEqual(keyring.paths, {}); + }); + + it('should throw an error if passed an unsupported hdPath', async function () { + const unsupportedPath = 'unsupported hdPath'; + try { + keyring.setHdPath(unsupportedPath); + } catch (error) { + assert.equal( + error.message, + `The setHdPath method does not support setting HD Path to ${unsupportedPath}`, + ); + } + }); + }); });