Skip to content

Commit

Permalink
Merge pull request #10 from MetaMask/remove-and-forget
Browse files Browse the repository at this point in the history
New features
  • Loading branch information
danfinlay committed Jul 12, 2018
2 parents 8f8e408 + 7e5874a commit 519aec5
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 6 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
eth-trezor-keyring [![CircleCI](https://circleci.com/gh/brunobar79/eth-trezor-keyring.svg?style=svg)](https://circleci.com/gh/brunobar79/eth-trezor-keyring)
eth-trezor-keyring [![CircleCI](https://circleci.com/gh/MetaMask/eth-trezor-keyring.svg?style=svg)](https://circleci.com/gh/MetaMask/eth-trezor-keyring)
==================

An implementation of MetaMask's [Keyring interface](https://github.com/MetaMask/eth-simple-keyring#the-keyring-class-protocol), that uses a TREZOR hardware
Expand All @@ -21,14 +21,21 @@ Using
In addition to all the known methods from the [Keyring class protocol](https://github.com/MetaMask/eth-simple-keyring#the-keyring-class-protocol),
there are a few others:


- **isUnlocked** : Returns true if we have the public key in memory, which allows to generate the list of accounts at any time

- **unlock** : Connects to the TREZOR device and exports the extended public key, which is later used to read the available ethereum addresses inside the trezor account.

- **setAccountToUnlock** : the index of the account that you want to unlock in order to use with the signTransaction and signPersonalMessage methods

- **getFirstPage** : returns the first ordered set of accounts from the TREZOR account

- **getNextPage** : returns the next ordered set of accounts from the TREZOR account based on the current page

- **getPreviousPage** : returns the previous ordered set of accounts from the TREZOR account based on the current page

- **forgetDevice** : removes all the device info from memory so the next interaction with the keyring will prompt the user to connect the TREZOR device and export the account information

Testing
-------
Run the following command:
Expand Down
32 changes: 29 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ class TrezorKeyring extends EventEmitter {
return Promise.resolve()
}

isUnlocked () {
return !!(this.hdk && this.hdk.publicKey)
}

unlock () {

if (this.hdk.publicKey) return Promise.resolve('already unlocked')
if (this.isUnlocked()) return Promise.resolve('already unlocked')

return new Promise((resolve, reject) => {
TrezorConnect.getXPubKey(
Expand Down Expand Up @@ -90,6 +94,11 @@ class TrezorKeyring extends EventEmitter {
})
}

getFirstPage () {
this.page = 0
return this.__getPage(1)
}

getNextPage () {
return this.__getPage(1)
}
Expand All @@ -102,11 +111,13 @@ class TrezorKeyring extends EventEmitter {

this.page += increment

if (this.page <= 0) { this.page = 1 }

return new Promise((resolve, reject) => {
this.unlock()
.then(_ => {

const from = this.page === 0 ? 0 : (this.page - 1) * this.perPage
const from = (this.page - 1) * this.perPage
const to = from + this.perPage

const accounts = []
Expand All @@ -115,7 +126,7 @@ class TrezorKeyring extends EventEmitter {
const address = this._addressFromIndex(pathBase, i)
accounts.push({
address: address,
balance: 0,
balance: null,
index: i,
})
this.paths[ethUtil.toChecksumAddress(address)] = i
Expand All @@ -133,6 +144,13 @@ class TrezorKeyring extends EventEmitter {
return Promise.resolve(this.accounts.slice())
}

removeAccount (address) {
if (!this.accounts.map(a => a.toLowerCase()).includes(address.toLowerCase())) {
throw new Error(`Address ${address} not found in this keyring`)
}
this.accounts = this.accounts.filter(a => a.toLowerCase() !== address.toLowerCase())
}

// tx is an instance of the ethereumjs-transaction class.
signTransaction (address, tx) {

Expand Down Expand Up @@ -224,6 +242,14 @@ class TrezorKeyring extends EventEmitter {
throw new Error('Not supported on this device')
}

forgetDevice () {
this.accounts = []
this.hdk = new HDKey()
this.page = 0
this.unlockedAccount = 0
this.paths = {}
}

/* PRIVATE METHODS */

_padLeftEven (hex) {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "eth-trezor-keyring",
"version": "0.0.1",
"version": "0.1.0",
"description": "A MetaMask compatible keyring, for trezor hardware wallets",
"main": "index.js",
"scripts": {
"test": "mocha",
"lint": "./node_modules/.bin/eslint . --ext .js",
"lint::fix": "./node_modules/.bin/eslint --fix . --ext .js"
"lint:fix": "./node_modules/.bin/eslint --fix . --ext .js"
},
"repository": {
"type": "git",
Expand Down
67 changes: 67 additions & 0 deletions test/test-eth-trezor-keyring.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ describe('TrezorKeyring', function () {
})
})


describe('isUnlocked', function () {
it('should return true if we have a public key', function () {
assert.equal(keyring.isUnlocked(), true)
})
})

describe('unlock', function () {
it('should resolve if we have a public key', function (done) {
keyring.unlock().then(_ => {
Expand Down Expand Up @@ -173,6 +180,50 @@ describe('TrezorKeyring', function () {
})
})

describe('removeAccount', function () {
describe('if the account exists', function () {
it('should remove that account', function (done) {
keyring.setAccountToUnlock(0)
keyring.addAccounts()
.then(async (accounts) => {
assert.equal(accounts.length, 1)
keyring.removeAccount(fakeAccounts[0])
const accountsAfterRemoval = await keyring.getAccounts()
assert.equal(accountsAfterRemoval.length, 0)
done()
})
})
})

describe('if the account does not exist', function () {
it('should throw an error', function () {
const unexistingAccount = '0x0000000000000000000000000000000000000000'
expect(_ => {
keyring.removeAccount(unexistingAccount)
}).to.throw(`Address ${unexistingAccount} not found in this keyring`)
})
})
})

describe('getFirstPage', function () {
it('should set the currentPage to 1', async function () {
await keyring.getFirstPage()
assert.equal(keyring.page, 1)
})

it('should return the list of accounts for current page', async function () {

const accounts = await keyring.getFirstPage()

expect(accounts.length, keyring.perPage)
expect(accounts[0].address, fakeAccounts[0])
expect(accounts[1].address, fakeAccounts[1])
expect(accounts[2].address, fakeAccounts[2])
expect(accounts[3].address, fakeAccounts[3])
expect(accounts[4].address, fakeAccounts[4])
})
})

describe('getNextPage', function () {

it('should return the list of accounts for current page', async function () {
Expand Down Expand Up @@ -300,4 +351,20 @@ describe('TrezorKeyring', function () {
})
})

describe('forgetDevice', function () {
it('should clear the content of the keyring', async function () {
// Add an account
keyring.setAccountToUnlock(0)
await keyring.addAccounts()

// Wipe the keyring
keyring.forgetDevice()

const accounts = await keyring.getAccounts()

assert.equal(keyring.isUnlocked(), false)
assert.equal(accounts.length, 0)
})
})

})

0 comments on commit 519aec5

Please sign in to comment.