Skip to content
This repository has been archived by the owner on Oct 7, 2024. It is now read-only.

Upgrade to TrezorConnect v5 #11

Closed
wants to merge 12 commits into from
Closed
134 changes: 68 additions & 66 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
const { EventEmitter } = require('events')
const ethUtil = require('ethereumjs-util')
const sigUtil = require('eth-sig-util')
const Transaction = require('ethereumjs-tx')
const HDKey = require('hdkey')
const TrezorConnect = require('./trezor-connect.js')
const TrezorConnect = require('trezor-connect').default
const hdPathString = `m/44'/60'/0'/0`
const keyringType = 'Trezor Hardware'
const pathBase = 'm'
const TREZOR_MIN_FIRMWARE_VERSION = '1.5.2'
const MAX_INDEX = 1000
const DELAY_BETWEEN_POPUPS = 1000

Expand Down Expand Up @@ -48,23 +46,23 @@ class TrezorKeyring extends EventEmitter {
}

unlock () {

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

return new Promise((resolve, reject) => {
TrezorConnect.getXPubKey(
this.hdPath,
response => {
TrezorConnect.getPublicKey({
path: this.hdPath,
coin: 'ETH',
}).then(response => {
if (response.success) {
this.hdk.publicKey = new Buffer(response.publicKey, 'hex')
this.hdk.chainCode = new Buffer(response.chainCode, 'hex')
this.hdk.publicKey = new Buffer(response.payload.publicKey, 'hex')
this.hdk.chainCode = new Buffer(response.payload.chainCode, 'hex')
resolve('just unlocked')
} else {
reject(response.error || 'Unknown error')
reject(response.payload && response.payload.error || 'Unknown error')
}
},
TREZOR_MIN_FIRMWARE_VERSION
)
}).catch(e => {
console.log('Error while trying to get public keys ', e)
reject(e && e.toString() || 'Unknown error')
})
})
}

Expand Down Expand Up @@ -108,7 +106,6 @@ class TrezorKeyring extends EventEmitter {
}

__getPage (increment) {

this.page += increment

if (this.page <= 0) { this.page = 1 }
Expand Down Expand Up @@ -158,43 +155,49 @@ class TrezorKeyring extends EventEmitter {
this.unlock()
.then(status => {
setTimeout(_ => {
TrezorConnect.ethereumSignTx(
this._pathFromAddress(address),
this._normalize(tx.nonce),
this._normalize(tx.gasPrice),
this._normalize(tx.gasLimit),
this._normalize(tx.to),
this._normalize(tx.value),
this._normalize(tx.data),
tx._chainId,
response => {
if (response.success) {

tx.v = `0x${response.v.toString(16)}`
tx.r = `0x${response.r}`
tx.s = `0x${response.s}`

const signedTx = new Transaction(tx)

const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`)
const correctAddress = ethUtil.toChecksumAddress(address)
if (addressSignedWith !== correctAddress) {
reject('signature doesnt match the right address')
}

resolve(signedTx)

} else {
reject(response.error || 'Unknown error')
}
TrezorConnect.ethereumSignTransaction({
path: this._pathFromAddress(address),
transaction: {
to: this._normalize(tx.to),
value: this._normalize(tx.value),
data: this._normalize(tx.data),
chainId: tx._chainId,
nonce: this._normalize(tx.nonce),
gasLimit: this._normalize(tx.gasLimit),
gasPrice: this._normalize(tx.gasPrice),
},
TREZOR_MIN_FIRMWARE_VERSION)
}).then(response => {
if (response.success) {
tx.v = response.payload.v
tx.r = response.payload.r
tx.s = response.payload.s

const signedTx = new Transaction(tx)

const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`)
const correctAddress = ethUtil.toChecksumAddress(address)
if (addressSignedWith !== correctAddress) {
reject('signature doesnt match the right address')
}

resolve(signedTx)
} else {
reject(response.payload && response.payload.error || 'Unknown error')
}

}).catch(e => {
console.log('Error while trying to sign transaction ', e)
reject(e && e.toString() || 'Unknown error')
})

// This is necessary to avoid popup collision
// between the unlock & sign trezor popups
}, status === 'just unlocked' ? DELAY_BETWEEN_POPUPS : 0)

})
}).catch(e => {
console.log('Error while trying to sign transaction ', e)
reject(e && e.toString() || 'Unknown error')
})
})
}

Expand All @@ -209,27 +212,30 @@ class TrezorKeyring extends EventEmitter {
.then(status => {
setTimeout(_ => {
const humanReadableMsg = this._toAscii(message)
TrezorConnect.ethereumSignMessage(this._pathFromAddress(withAccount), humanReadableMsg, response => {
TrezorConnect.ethereumSignMessage({
path: this._pathFromAddress(withAccount),
message: humanReadableMsg,
}).then(response => {
if (response.success) {

const signature = `0x${response.signature}`
const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature})

if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) {
reject('signature doesnt match the right address')
}

resolve(signature)

if (response.payload.address !== ethUtil.toChecksumAddress(withAccount)) {
reject('signature doesnt match the right address')
}
const signature = `0x${response.payload.signature}`
resolve(signature)
} else {
reject(response.error || 'Unknown error')
reject(response.payload && response.payload.error || 'Unknown error')
}

}, TREZOR_MIN_FIRMWARE_VERSION)
}).catch(e => {
console.log('Error while trying to sign a message ', e)
reject(e && e.toString() || 'Unknown error')
})
// This is necessary to avoid popup collision
// between the unlock & sign trezor popups
}, status === 'just unlocked' ? DELAY_BETWEEN_POPUPS : 0)
})
}).catch(e => {
console.log('Error while trying to sign a message ', e)
reject(e && e.toString() || 'Unknown error')
})
})
}

Expand All @@ -252,12 +258,8 @@ class TrezorKeyring extends EventEmitter {

/* PRIVATE METHODS */

_padLeftEven (hex) {
return hex.length % 2 !== 0 ? `0${hex}` : hex
}

_normalize (buf) {
return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase())
return ethUtil.bufferToHex(buf).toString()
}

_addressFromIndex (pathBase, i) {
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eth-trezor-keyring",
"version": "0.1.0",
"version": "0.2.0",
"description": "A MetaMask compatible keyring, for trezor hardware wallets",
"main": "index.js",
"scripts": {
Expand Down Expand Up @@ -29,7 +29,8 @@
"ethereumjs-tx": "^1.3.4",
"ethereumjs-util": "^5.1.5",
"events": "^2.0.0",
"hdkey": "0.8.0"
"hdkey": "0.8.0",
"trezor-connect": "^5.0.31"
},
"devDependencies": {
"assert": "^1.4.1",
Expand Down
5 changes: 5 additions & 0 deletions test/navigator.shim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
try {
module.exports = window || {userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'}
} catch (e) {
module.exports = {userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'}
}
17 changes: 10 additions & 7 deletions test/test-eth-trezor-keyring.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
global.window = require('./window.shim')
global.navigator = require('./window.shim')

const chai = require('chai')
const spies = require('chai-spies')
const {expect} = chai
const EthereumTx = require('ethereumjs-tx')
const assert = require('assert')
const HDKey = require('hdkey')
const TrezorConnect = require('../trezor-connect.js')
const TrezorConnect = require('trezor-connect').default

const TrezorKeyring = require('../')

Expand Down Expand Up @@ -123,17 +126,17 @@ describe('TrezorKeyring', function () {
})
})

chai.spy.on(TrezorConnect, 'getXPubKey')
chai.spy.on(TrezorConnect, 'getPublicKey')

it('should call TrezorConnect.getXPubKey if we dont have a public key', async function () {
it('should call TrezorConnect.getPublicKey if we dont have a public key', async function () {
keyring.hdk = new HDKey()
try {
await keyring.unlock()
} catch (e) {
// because we're trying to open the trezor popup in node
// it will throw an exception
} finally {
expect(TrezorConnect.getXPubKey).to.have.been.called()
expect(TrezorConnect.getPublicKey).to.have.been.called()
}
})
})
Expand Down Expand Up @@ -301,14 +304,14 @@ describe('TrezorKeyring', function () {
})

describe('signTransaction', function () {
it('should call TrezorConnect.ethereumSignTx', function (done) {
it('should call TrezorConnect.ethereumSignTransaction', function (done) {

chai.spy.on(TrezorConnect, 'ethereumSignTx')
chai.spy.on(TrezorConnect, 'ethereumSignTransaction')

keyring.signTransaction(fakeAccounts[0], fakeTx).catch(e => {
// we expect this to be rejected because
// we are trying to open a popup from node
expect(TrezorConnect.ethereumSignTx).to.have.been.called()
expect(TrezorConnect.ethereumSignTransaction).to.have.been.called()
done()
})
})
Expand Down
19 changes: 19 additions & 0 deletions test/window.shim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
try {
module.exports = window || {
__TREZOR_CONNECT_SRC: null,
location: {
protocol: 'https',
},
addEventListener: _ => false,
setTimeout: _ => false,
}
} catch (e) {
module.exports = {
__TREZOR_CONNECT_SRC: null,
location: {
protocol: 'https',
},
addEventListener: _ => false,
setTimeout: _ => false,
}
}
Loading