|
|
@ -4,10 +4,11 @@ const ethUtil = require('ethereumjs-util') |
|
|
|
|
|
|
|
|
|
|
|
const hdPathString = `m/44'/60'/0'/0` |
|
|
|
const hdPathString = `m/44'/60'/0'/0` |
|
|
|
const keyringType = 'Trezor Hardware' |
|
|
|
const keyringType = 'Trezor Hardware' |
|
|
|
|
|
|
|
const Transaction = require('ethereumjs-tx') |
|
|
|
|
|
|
|
const pathBase = 'm' |
|
|
|
const TrezorConnect = require('./trezor-connect.js') |
|
|
|
const TrezorConnect = require('./trezor-connect.js') |
|
|
|
const HDKey = require('hdkey') |
|
|
|
const HDKey = require('hdkey') |
|
|
|
const TREZOR_FIRMWARE_VERSION = '1.4.0' |
|
|
|
const TREZOR_MIN_FIRMWARE_VERSION = '1.5.2' |
|
|
|
const log = require('loglevel') |
|
|
|
const log = require('loglevel') |
|
|
|
|
|
|
|
|
|
|
|
class TrezorKeyring extends EventEmitter { |
|
|
|
class TrezorKeyring extends EventEmitter { |
|
|
@ -19,7 +20,7 @@ class TrezorKeyring extends EventEmitter { |
|
|
|
this.deserialize(opts) |
|
|
|
this.deserialize(opts) |
|
|
|
this.page = 0 |
|
|
|
this.page = 0 |
|
|
|
this.perPage = 5 |
|
|
|
this.perPage = 5 |
|
|
|
this.accountToUnlock = 0 |
|
|
|
this.unlockedAccount = 0 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
serialize () { |
|
|
|
serialize () { |
|
|
@ -53,13 +54,13 @@ class TrezorKeyring extends EventEmitter { |
|
|
|
reject(response.error || 'Unknown error') |
|
|
|
reject(response.error || 'Unknown error') |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
TREZOR_FIRMWARE_VERSION |
|
|
|
TREZOR_MIN_FIRMWARE_VERSION |
|
|
|
) |
|
|
|
) |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
setAccountToUnlock (index) { |
|
|
|
setAccountToUnlock (index) { |
|
|
|
this.accountToUnlock = parseInt(index, 10) |
|
|
|
this.unlockedAccount = parseInt(index, 10) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
addAccounts (n = 1) { |
|
|
|
addAccounts (n = 1) { |
|
|
@ -67,18 +68,13 @@ class TrezorKeyring extends EventEmitter { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
return this.unlock() |
|
|
|
return this.unlock() |
|
|
|
.then(_ => { |
|
|
|
.then(_ => { |
|
|
|
const pathBase = 'm' |
|
|
|
const from = this.unlockedAccount |
|
|
|
const from = this.accountToUnlock |
|
|
|
|
|
|
|
const to = from + 1 |
|
|
|
const to = from + 1 |
|
|
|
|
|
|
|
|
|
|
|
this.accounts = [] |
|
|
|
this.accounts = [] |
|
|
|
|
|
|
|
|
|
|
|
for (let i = from; i < to; i++) { |
|
|
|
for (let i = from; i < to; i++) { |
|
|
|
const dkey = this.hdk.derive(`${pathBase}/${i}`) |
|
|
|
|
|
|
|
const address = ethUtil |
|
|
|
this.accounts.push(this.getEthAddress(pathBase, i)) |
|
|
|
.publicToAddress(dkey.publicKey, true) |
|
|
|
|
|
|
|
.toString('hex') |
|
|
|
|
|
|
|
this.accounts.push(ethUtil.toChecksumAddress(address)) |
|
|
|
|
|
|
|
this.page = 0 |
|
|
|
this.page = 0 |
|
|
|
} |
|
|
|
} |
|
|
|
resolve(this.accounts) |
|
|
|
resolve(this.accounts) |
|
|
@ -94,19 +90,16 @@ class TrezorKeyring extends EventEmitter { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
return this.unlock() |
|
|
|
return this.unlock() |
|
|
|
.then(_ => { |
|
|
|
.then(_ => { |
|
|
|
const pathBase = 'm' |
|
|
|
|
|
|
|
const from = this.page === 0 ? 0 : (this.page - 1) * this.perPage |
|
|
|
const from = this.page === 0 ? 0 : (this.page - 1) * this.perPage |
|
|
|
const to = from + this.perPage |
|
|
|
const to = from + this.perPage |
|
|
|
|
|
|
|
|
|
|
|
const accounts = [] |
|
|
|
const accounts = [] |
|
|
|
|
|
|
|
|
|
|
|
for (let i = from; i < to; i++) { |
|
|
|
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({ |
|
|
|
accounts.push({ |
|
|
|
address: ethUtil.toChecksumAddress(address), |
|
|
|
address: this.getEthAddress(pathBase, i), |
|
|
|
balance: 0, |
|
|
|
balance: 0, |
|
|
|
index: i, |
|
|
|
index: i, |
|
|
|
}) |
|
|
|
}) |
|
|
@ -134,40 +127,75 @@ class TrezorKeyring extends EventEmitter { |
|
|
|
return Promise.resolve(this.accounts.slice()) |
|
|
|
return Promise.resolve(this.accounts.slice()) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
padLeftEven (hex) { |
|
|
|
|
|
|
|
return hex.length % 2 !== 0 ? `0${hex}` : hex |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cleanData (buf) { |
|
|
|
|
|
|
|
return this.padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getEthAddress (pathBase, i) { |
|
|
|
|
|
|
|
const dkey = this.hdk.derive(`${pathBase}/${i}`) |
|
|
|
|
|
|
|
const address = ethUtil |
|
|
|
|
|
|
|
.publicToAddress(dkey.publicKey, true) |
|
|
|
|
|
|
|
.toString('hex') |
|
|
|
|
|
|
|
return ethUtil.toChecksumAddress(address) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// tx is an instance of the ethereumjs-transaction class.
|
|
|
|
// tx is an instance of the ethereumjs-transaction class.
|
|
|
|
async signTransaction (address, tx) { |
|
|
|
async signTransaction (address, tx) { |
|
|
|
throw new Error('Not supported on this device') |
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
await this.lock.acquire() |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Look before we leap
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
await this._checkCorrectTrezorAttached() |
|
|
|
log.debug('sign transaction ', address, tx) |
|
|
|
|
|
|
|
const account = `m/44'/60'/0'/${this.unlockedAccount}` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const txData = { |
|
|
|
|
|
|
|
account, |
|
|
|
|
|
|
|
nonce: this.cleanData(tx.nonce), |
|
|
|
|
|
|
|
gasPrice: this.cleanData(tx.gasPrice), |
|
|
|
|
|
|
|
gasLimit: this.cleanData(tx.gasLimit), |
|
|
|
|
|
|
|
to: this.cleanData(tx.to), |
|
|
|
|
|
|
|
value: this.cleanData(tx.value), |
|
|
|
|
|
|
|
data: this.cleanData(tx.data), |
|
|
|
|
|
|
|
chainId: tx._chainId, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let accountId = await this._findAddressId(address) |
|
|
|
TrezorConnect.ethereumSignTx( |
|
|
|
let eth = await this._getEth() |
|
|
|
txData.account, |
|
|
|
tx.v = tx._chainId |
|
|
|
txData.nonce, |
|
|
|
let TrezorSig = await eth.signTransaction( |
|
|
|
txData.gasPrice, |
|
|
|
this._derivePath(accountId), |
|
|
|
txData.gasLimit, |
|
|
|
tx.serialize().toString('hex') |
|
|
|
txData.to, |
|
|
|
) |
|
|
|
txData.value, |
|
|
|
tx.v = parseInt(TrezorSig.v, 16) |
|
|
|
txData.data === '' ? null : txData.data, |
|
|
|
tx.r = '0x' + TrezorSig.r |
|
|
|
txData.chainId, |
|
|
|
tx.s = '0x' + TrezorSig.s |
|
|
|
response => { |
|
|
|
|
|
|
|
if (response.success) { |
|
|
|
|
|
|
|
tx.v = `0x${response.v.toString(16)}` |
|
|
|
|
|
|
|
tx.r = `0x${response.r}` |
|
|
|
|
|
|
|
tx.s = `0x${response.s}` |
|
|
|
|
|
|
|
log.debug('about to create new tx with data', tx) |
|
|
|
|
|
|
|
|
|
|
|
// Since look before we leap check is racy, also check that signature is for account expected
|
|
|
|
const signedTx = new Transaction(tx) |
|
|
|
let addressSignedWith = ethUtil.bufferToHex(tx.getSenderAddress()) |
|
|
|
|
|
|
|
if (addressSignedWith.toLowerCase() !== address.toLowerCase()) { |
|
|
|
log.debug('signature is valid?', signedTx.verifySignature()) |
|
|
|
throw new Error( |
|
|
|
|
|
|
|
`Signature is for ${addressSignedWith} but expected ${address} - is the correct Trezor device attached?` |
|
|
|
const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) |
|
|
|
) |
|
|
|
const correctAddress = ethUtil.toChecksumAddress(address) |
|
|
|
|
|
|
|
if (addressSignedWith !== correctAddress) { |
|
|
|
|
|
|
|
// throw new Error('signature doesnt match the right address')
|
|
|
|
|
|
|
|
log.error('signature doesnt match the right address', addressSignedWith, correctAddress) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return tx |
|
|
|
resolve(signedTx) |
|
|
|
|
|
|
|
|
|
|
|
} finally { |
|
|
|
} else { |
|
|
|
await this.lock.release() |
|
|
|
throw new Error(response.error || 'Unknown error') |
|
|
|
}*/ |
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
TREZOR_MIN_FIRMWARE_VERSION) |
|
|
|
|
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async signMessage (withAccount, data) { |
|
|
|
async signMessage (withAccount, data) { |
|
|
|