aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/lib/trezorKeyring.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts/lib/trezorKeyring.js')
-rw-r--r--app/scripts/lib/trezorKeyring.js255
1 files changed, 255 insertions, 0 deletions
diff --git a/app/scripts/lib/trezorKeyring.js b/app/scripts/lib/trezorKeyring.js
new file mode 100644
index 000000000..d99f384f2
--- /dev/null
+++ b/app/scripts/lib/trezorKeyring.js
@@ -0,0 +1,255 @@
+const { EventEmitter } = require('events')
+const ethUtil = require('ethereumjs-util')
+// const sigUtil = require('eth-sig-util')
+//const { Lock } = require('semaphore-async-await')
+
+const hdPathString = `m/44'/60'/0'/0`
+const keyringType = 'Trezor Hardware Keyring'
+
+const TrezorConnect = require('./trezor-connect.js')
+const HDKey = require('hdkey')
+const TREZOR_FIRMWARE_VERSION = '1.4.0'
+const log = require('loglevel')
+
+class TrezorKeyring extends EventEmitter {
+ constructor (opts = {}) {
+ super()
+ this.type = keyringType
+ //this.lock = new Lock()
+ this.accounts = []
+ this.hdk = new HDKey()
+ this.deserialize(opts)
+ this.page = 0
+ this.perPage = 5
+ }
+
+ serialize () {
+ return Promise.resolve({ hdPath: this.hdPath, accounts: this.accounts })
+ }
+
+ deserialize (opts = {}) {
+ this.hdPath = opts.hdPath || hdPathString
+ this.accounts = opts.accounts || []
+ return Promise.resolve()
+ }
+
+ unlock () {
+ if (this.hdk.publicKey) return Promise.resolve()
+
+ return new Promise((resolve, reject) => {
+ TrezorConnect.getXPubKey(
+ this.hdPath,
+ response => {
+ log.debug('TREZOR CONNECT RESPONSE: ')
+ log.debug(response)
+ if (response.success) {
+ this.hdk.publicKey = new Buffer(response.publicKey, 'hex')
+ this.hdk.chainCode = new Buffer(response.chainCode, 'hex')
+ resolve()
+ } else {
+ reject(response.error || 'Unknown error')
+ }
+ },
+ TREZOR_FIRMWARE_VERSION
+ )
+ })
+ }
+
+ addAccounts (n = 1) {
+ return new Promise((resolve, reject) => {
+ return this.unlock()
+ .then(_ => {
+ const pathBase = 'm'
+ const from = n
+ const to = n + 1
+
+ this.accounts = []
+
+ for (let i = from; i < to; i++) {
+ const dkey = this.hdk.derive(`${pathBase}/${i}`)
+ const address = ethUtil
+ .publicToAddress(dkey.publicKey, true)
+ .toString('hex')
+ this.accounts.push(ethUtil.toChecksumAddress(address))
+ this.page = 0
+ }
+ resolve(this.accounts)
+ })
+ .catch(e => {
+ reject(e)
+ })
+ })
+ }
+
+ async getPage () {
+ return new Promise((resolve, reject) => {
+ return this.unlock()
+ .then(_ => {
+ const pathBase = 'm'
+ const from = this.page === 0 ? 0 : (this.page - 1) * this.perPage
+ const to = from + this.perPage
+
+ const accounts = []
+
+ for (let i = from; i < to; i++) {
+ const dkey = this.hdk.derive(`${pathBase}/${i}`)
+ const address = ethUtil
+ .publicToAddress(dkey.publicKey, true)
+ .toString('hex')
+ accounts.push({
+ address: ethUtil.toChecksumAddress(address),
+ balance: 0,
+ index: i,
+ })
+ }
+ resolve(accounts)
+ })
+ .catch(e => {
+ reject(e)
+ })
+ })
+ }
+
+ async getPrevAccountSet () {
+ this.page--
+ return await this.getPage()
+ }
+
+ async getNextAccountSet () {
+ this.page++
+ return await this.getPage()
+ }
+
+ getAccounts () {
+ return Promise.resolve(this.accounts.slice())
+ }
+
+ // tx is an instance of the ethereumjs-transaction class.
+ async signTransaction (address, tx) {
+ throw new Error('Not supported on this device')
+ /*
+ await this.lock.acquire()
+ try {
+
+ // Look before we leap
+ await this._checkCorrectTrezorAttached()
+
+ let accountId = await this._findAddressId(address)
+ let eth = await this._getEth()
+ tx.v = tx._chainId
+ let TrezorSig = await eth.signTransaction(
+ this._derivePath(accountId),
+ tx.serialize().toString('hex')
+ )
+ tx.v = parseInt(TrezorSig.v, 16)
+ tx.r = '0x' + TrezorSig.r
+ tx.s = '0x' + TrezorSig.s
+
+ // Since look before we leap check is racy, also check that signature is for account expected
+ let addressSignedWith = ethUtil.bufferToHex(tx.getSenderAddress())
+ if (addressSignedWith.toLowerCase() !== address.toLowerCase()) {
+ throw new Error(
+ `Signature is for ${addressSignedWith} but expected ${address} - is the correct Trezor device attached?`
+ )
+ }
+
+ return tx
+
+ } finally {
+ await this.lock.release()
+ }*/
+ }
+
+ async signMessage (withAccount, data) {
+ throw new Error('Not supported on this device')
+ }
+
+ // For personal_sign, we need to prefix the message:
+ async signPersonalMessage (withAccount, message) {
+ throw new Error('Not supported on this device')
+ /*
+ await this.lock.acquire()
+ try {
+ // Look before we leap
+ await this._checkCorrectTrezorAttached()
+
+ let accountId = await this._findAddressId(withAccount)
+ let eth = await this._getEth()
+ let msgHex = ethUtil.stripHexPrefix(message)
+ let TrezorSig = await eth.signPersonalMessage(
+ this._derivePath(accountId),
+ msgHex
+ )
+ let signature = this._personalToRawSig(TrezorSig)
+
+ // Since look before we leap check is racy, also check that signature is for account expected
+ let addressSignedWith = sigUtil.recoverPersonalSignature({
+ data: message,
+ sig: signature,
+ })
+ if (addressSignedWith.toLowerCase() !== withAccount.toLowerCase()) {
+ throw new Error(
+ `Signature is for ${addressSignedWith} but expected ${withAccount} - is the correct Trezor device attached?`
+ )
+ }
+
+ return signature
+
+ } finally {
+ await this.lock.release()
+ } */
+ }
+
+ async signTypedData (withAccount, typedData) {
+ throw new Error('Not supported on this device')
+ }
+
+ async exportAccount (address) {
+ throw new Error('Not supported on this device')
+ }
+
+ async _findAddressId (addr) {
+ const result = this.accounts.indexOf(addr)
+ if (result === -1) throw new Error('Unknown address')
+ else return result
+ }
+
+ async _addressFromId (i) {
+ /* Must be called with lock acquired
+ const eth = await this._getEth()
+ return (await eth.getAddress(this._derivePath(i))).address*/
+ const result = this.accounts[i]
+ if (!result) throw new Error('Unknown address')
+ else return result
+ }
+
+ async _checkCorrectTrezorAttached () {
+ return true
+ /* Must be called with lock acquired
+ if (this.accounts.length > 0) {
+ const expectedFirstAccount = this.accounts[0]
+ let actualFirstAccount = await this._addressFromId(0)
+ if (expectedFirstAccount !== actualFirstAccount) {
+ throw new Error(
+ `Incorrect Trezor device attached - expected device containg account ${expectedFirstAccount}, but found ${actualFirstAccount}`
+ )
+ }
+ }*/
+ }
+
+ _derivePath (i) {
+ return this.hdPath + '/' + i
+ }
+
+ _personalToRawSig (TrezorSig) {
+ var v = TrezorSig['v'] - 27
+ v = v.toString(16)
+ if (v.length < 2) {
+ v = '0' + v
+ }
+ return '0x' + TrezorSig['r'] + TrezorSig['s'] + v
+ }
+}
+
+TrezorKeyring.type = keyringType
+module.exports = TrezorKeyring \ No newline at end of file