1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-25 03:20:23 +01:00
metamask-extension/app/scripts/lib/trezorKeyring.js
2018-06-10 18:48:42 -04:00

262 lines
6.9 KiB
JavaScript

const { EventEmitter } = require('events')
const ethUtil = require('ethereumjs-util')
// const sigUtil = require('eth-sig-util')
const hdPathString = `m/44'/60'/0'/0`
const keyringType = 'Trezor Hardware'
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.accounts = []
this.hdk = new HDKey()
this.deserialize(opts)
this.page = 0
this.perPage = 5
}
serialize () {
return Promise.resolve({
hdPath: this.hdPath,
accounts: this.accounts,
page: this.page,
})
}
deserialize (opts = {}) {
this.hdPath = opts.hdPath || hdPathString
this.accounts = opts.accounts || []
this.page = opts.page || 0
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