diff --git a/main/rpc/index.js b/main/rpc/index.js index dafec0f1c..7f82fd4aa 100644 --- a/main/rpc/index.js +++ b/main/rpc/index.js @@ -46,8 +46,24 @@ const rpc = { }, // setSignerIndex: signers.setSignerIndex, // unsetSigner: signers.unsetSigner, - trezorPin: (id, pin, cb) => signers.trezorPin(id, pin, cb), - trezorPhrase: (id, phrase, cb) => signers.trezorPhrase(id, phrase, cb), + trezorPin: (id, pin, cb) => { + const signer = signers.get(id) + if (signer && signer.setPin) { + signer.setPin(pin) + cb(null, { status: 'ok' }) + } else { + cb(new Error('Set pin not available')) + } + }, + trezorPhrase: (id, phrase, cb) => { + const signer = signers.get(id) + if (signer && signer.setPhrase) { + signer.setPhrase(phrase || '') + cb(null, { status: 'ok' }) + } else { + cb(new Error('Set phrase not available')) + } + }, createLattice: async (id, cb) => { try { cb(null, await signers.createLattice(id)) diff --git a/main/signers/Signer/derive.ts b/main/signers/Signer/derive.ts index 5d49bcf49..6ab7266e5 100644 --- a/main/signers/Signer/derive.ts +++ b/main/signers/Signer/derive.ts @@ -26,9 +26,9 @@ export function deriveHDAccounts (publicKey: string, chainCode: string, cb: (err } const derivationPaths: { [key: string]: string } = { - [Derivation.legacy.valueOf()]: "44'/60'/0'/", - [Derivation.standard.valueOf()]: "44'/60'/0'/0/", - [Derivation.testnet.valueOf()]: "44'/1'/0'/0/" + [Derivation.legacy.valueOf()]: "44'/60'/0'", + [Derivation.standard.valueOf()]: "44'/60'/0'/0", + [Derivation.testnet.valueOf()]: "44'/1'/0'/0" } export function getDerivationPath (derivation: Derivation) { diff --git a/main/signers/Signer/index.ts b/main/signers/Signer/index.ts index 09b00e801..bf5abbc92 100644 --- a/main/signers/Signer/index.ts +++ b/main/signers/Signer/index.ts @@ -12,9 +12,10 @@ export default class Signer extends EventEmitter { type = ''; name = ''; status = ''; + coinbase = '0x'; + model = ''; appVersion: AppVersion = { major: 0, minor: 0, patch: 0 } - liveAddressesFound = 0; addresses: string[]; constructor () { @@ -48,7 +49,6 @@ export default class Signer extends EventEmitter { type: this.type, addresses: this.addresses, status: this.status, - liveAddressesFound: this.liveAddressesFound || 0, appVersion: this.appVersion || { major: 0, minor: 0, patch: 0 } } } diff --git a/main/signers/hot/HotSigner/worker.js b/main/signers/hot/HotSigner/worker.js index e4d8e42d4..cd409c8fa 100644 --- a/main/signers/hot/HotSigner/worker.js +++ b/main/signers/hot/HotSigner/worker.js @@ -53,6 +53,7 @@ class HotSignerWorker { signTransaction (key, rawTx, pseudoCallback) { sign(rawTx, tx => { const signedTx = tx.sign(key) + const serialized = signedTx.serialize().toString('hex') pseudoCallback(null, addHexPrefix(serialized)) diff --git a/main/signers/index.ts b/main/signers/index.ts index 824d6721a..bb80d3883 100644 --- a/main/signers/index.ts +++ b/main/signers/index.ts @@ -1,6 +1,6 @@ // @ts-nocheck -const EventEmitter = require('events') +import EventEmitter from 'events' import log from 'electron-log' import crypto from 'crypto' @@ -9,13 +9,15 @@ import { SignerAdapter } from './adapters' import hot from './hot' import LedgerAdapter from './ledger/adapter' -import trezorConnect from './trezor-connect' +import TrezorAdapter from './trezor/adapter' + import lattice from './lattice' import store from '../store' const registeredAdapters = [ - new LedgerAdapter() + new LedgerAdapter(), + new TrezorAdapter() ] interface AdapterSpec { @@ -41,8 +43,7 @@ class Signers extends EventEmitter { // TODO: convert these scans to adapters this.scans = { lattice: lattice.scan(this), - hot: hot.scan(this), - trezor: trezorConnect.scan(this) + hot: hot.scan(this) } registeredAdapters.forEach(this.addAdapter.bind(this)) @@ -88,26 +89,6 @@ class Signers extends EventEmitter { delete this.adapter[adapter.adapterType] } - trezorPin (id, pin, cb) { - const signer = this.get(id) - if (signer && signer.setPin) { - signer.setPin(pin) - cb(null, { status: 'ok' }) - } else { - cb(new Error('Set pin not avaliable...')) - } - } - - trezorPhrase (id, phrase, cb) { - const signer = this.get(id) - if (signer && signer.trezorPhrase) { - signer.trezorPhrase(phrase || '') - cb(null, { status: 'ok' }) - } else { - cb(new Error('Set phrase not avaliable...')) - } - } - async latticePair (id, pin) { try { const signer = this.get(id) diff --git a/main/signers/ledger/Ledger/index.ts b/main/signers/ledger/Ledger/index.ts index c90cc8127..9e93684e4 100644 --- a/main/signers/ledger/Ledger/index.ts +++ b/main/signers/ledger/Ledger/index.ts @@ -67,10 +67,11 @@ function getStatusForError (err: DeviceError) { export default class Ledger extends Signer { private eth: LedgerEthereumApp | undefined; - private devicePath: string; - private derivation: Derivation | undefined; - private accountLimit = 5; + devicePath: string; + + derivation: Derivation | undefined; + accountLimit = 5; // the Ledger device can only handle one request at a time; the transport will reject // all incoming requests while its busy, so we need to make sure requests are only executed @@ -78,8 +79,6 @@ export default class Ledger extends Signer { private requestQueue = new RequestQueue() private statusPoller = setTimeout(() => {}) - private coinbase = '0x' - constructor (devicePath: string) { super() @@ -269,7 +268,7 @@ export default class Ledger extends Signer { throw new Error('attempted to get path with unknown derivation!') } - return getDerivationPath(this.derivation) + index + return getDerivationPath(this.derivation) + '/' + index } // *** request enqueuing methods *** // diff --git a/main/signers/ledger/adapter.ts b/main/signers/ledger/adapter.ts index 727b39d64..6748c7fec 100644 --- a/main/signers/ledger/adapter.ts +++ b/main/signers/ledger/adapter.ts @@ -5,7 +5,7 @@ import log from 'electron-log' import { getDevices as getLedgerDevices } from '@ledgerhq/hw-transport-node-hid-noevents' import { UsbSignerAdapter } from '../adapters' -import Ledger, { Status } from './Ledger' +import Ledger from './Ledger' import store from '../../store' import { Derivation } from '../Signer/derive' @@ -22,7 +22,7 @@ export default class LedgerSignerAdapter extends UsbSignerAdapter { private observer: any; constructor () { - super('Ledger') + super('ledger') this.knownSigners = {} this.disconnections = [] diff --git a/main/signers/trezor-connect/Trezor/index.js b/main/signers/trezor-connect/Trezor/index.js deleted file mode 100644 index af7813e1c..000000000 --- a/main/signers/trezor-connect/Trezor/index.js +++ /dev/null @@ -1,244 +0,0 @@ -const log = require('electron-log') -const utils = require('web3-utils') -const { padToEven, stripHexPrefix, addHexPrefix } = require('ethereumjs-util') - -const store = require('../../../store') -const Signer = require('../../Signer').default -const flex = require('../../../flex') -const { sign, londonToLegacy } = require('../../../transaction') -const { v5: uuid } = require('uuid') - -const ns = '3bbcee75-cecc-5b56-8031-b6641c1ed1f1' - -// Base Paths -const BASE_PATH_STANDARD = "m/44'/60'/0'/0" -const BASE_PATH_LEGACY = "m/44'/60'/0'" -const BASE_PATH_TESTNET = "m/44'/1'/0'/0" - -class Trezor extends Signer { - constructor (device, signers) { - super() - this.addresses = [] - this.signers = signers - this.device = device - this.id = this.getId() - this.type = 'trezor' - this.status = 'loading' - this.derivationPath = store('main.trezor.derivation') - this.basePath = () => { - if (this.derivationPath === 'testnet') { - return BASE_PATH_TESTNET - } else if (this.derivationPath === 'legacy') { - return BASE_PATH_LEGACY - } else { - return BASE_PATH_STANDARD - } - } - this.getPath = (i = 0) => this.basePath() + '/' + i - this.handlers = {} - this.deviceStatus() - this.derivationPathObserver = store.observer(() => { - if (this.derivationPath !== store('main.trezor.derivation')) { - this.derivationPath = store('main.trezor.derivation') - this.reset() - this.deviceStatus() - } - }) - setTimeout(() => { - this.deviceStatus() - }, 2000) - } - - getId () { - return uuid('Trezor' + this.device.path, ns) - } - - update () { - if (this.closed) return - const id = this.getId() - if (this.id !== id) { // Singer address representation changed - store.removeSigner(this.id) - this.id = id - } - store.updateSigner(this.summary()) - } - - reset () { - this.status = 'loading' - this.addresses = [] - this.update() - } - - deviceStatus () { - this.lookupAddresses((err, addresses) => { - if (err) { - if (err === 'Device call in progress') return - this.status = 'loading' - if (err === 'ui-device_firmware_old') this.status = `Update Firmware (v${this.device.firmwareRelease.version.join('.')})` - if (err === 'ui-device_bootloader_mode') this.status = 'Device in Bootloader Mode' - this.addresses = [] - this.update() - } else if (addresses && addresses.length) { - if (addresses[0] !== this.coinbase || this.status !== 'ok') { - this.coinbase = addresses[0] - this.addresses = addresses - this.deviceStatus() - } - if (addresses.length > this.addresses.length) this.addresses = addresses - this.status = 'ok' - this.update() - } else { - this.status = 'Unable to find addresses' - this.addresses = [] - this.update() - } - }) - } - - close () { - this.derivationPathObserver.remove() - this.closed = true - store.removeSigner(this.id) - super.close() - } - - button (label) { - log.info(`Trezor button "${label}" was pressed`) - } - - getDeviceAddress (i, cb) { - flex.rpc('trezor.ethereumGetAddress', this.device.path, this.getPath(i), true, (err, result) => { - if (err) return cb(err) - cb(null, '0x' + result.message.address) - }) - } - - verifyAddress (index, current, display = false, cb = () => {}, attempt = 0) { - log.info('Verify Address, attempt: ' + attempt) - let timeout = false - const timer = setTimeout(() => { - timeout = true - this.signers.remove(this.id) - log.error('The flex.rpc, trezor.ethereumGetAddress call timed out') - cb(new Error('Address verification timed out')) - }, 60 * 1000) - flex.rpc('trezor.ethereumGetAddress', this.device.path, this.getPath(index), display, (err, result) => { - clearTimeout(timer) - if (timeout) return - if (err) { - if (err === 'Device call in progress' && attempt < 5) { - setTimeout(() => this.verifyAddress(index, current, display, cb, ++attempt), 1000 * (attempt + 1)) - } else { - log.info('Verify Address Error: ') - // TODO: Error Notification - log.error(err) - this.signers.remove(this.id) - cb(new Error('Verify Address Error')) - } - } else { - const address = result.address ? result.address.toLowerCase() : '' - const current = this.addresses[index] ? this.addresses[index].toLowerCase() : '' - log.info('Frame has the current address as: ' + current) - log.info('Trezor is reporting: ' + address) - if (address !== current) { - // TODO: Error Notification - log.error(new Error('Address does not match device')) - this.signers.remove(this.id) - cb(new Error('Address does not match device')) - } else { - log.info('Address matches device') - cb(null, true) - } - } - }) - } - - lookupAddresses (cb) { - flex.rpc('trezor.getPublicKey', this.device.path, this.basePath(), (err, result) => { - if (err) return cb(err) - this.deriveHDAccounts(result.publicKey, result.chainCode, cb) - }) - } - - needPhrase (cb) { - this.status = 'Enter Passphrase' - this.update() - this.trezorPhrase = (phrase) => { - this.status = 'loading' - this.update() - flex.rpc('trezor.inputPhrase', this.device.path, phrase, err => { - if (err) log.error(err) - setTimeout(() => this.deviceStatus(), 1000) - }) - } - } - - needPin () { - this.status = 'Need Pin' - this.update() - this.setPin = (pin) => { - this.status = 'loading' - this.update() - flex.rpc('trezor.inputPin', this.device.path, pin, err => { - if (err) log.error(err) - setTimeout(() => this.deviceStatus(), 250) - }) - } - } - - normalize (hex) { - return (hex && padToEven(stripHexPrefix(hex))) || '' - } - - hexToBuffer (hex) { - return Buffer.from(this.normalize(hex), 'hex') - } - - // Standard Methods - signMessage (index, message, cb) { - flex.rpc('trezor.ethereumSignMessage', this.device.path, this.getPath(index), this.normalize(message), (err, result) => { - if (err) { - log.error('signMessage Error') - log.error(err) - if (err.message === 'Unexpected message') err = new Error('Update Trezor Firmware') - cb(err) - } else { - cb(null, '0x' + result.signature) - } - }) - } - - _normalizeTransaction (rawTx) { - return { - nonce: this.normalize(rawTx.nonce), - gasPrice: this.normalize(rawTx.gasPrice), - gasLimit: this.normalize(rawTx.gasLimit), - to: this.normalize(rawTx.to), - value: this.normalize(rawTx.value), - data: this.normalize(rawTx.data), - chainId: utils.hexToNumber(rawTx.chainId) - } - } - - signTransaction (index, rawTx, cb) { - // as of 08-05-2021 Trezor doesn't support EIP-1559 transactions - const legacyTx = londonToLegacy(rawTx) - - const trezorTx = this._normalizeTransaction(legacyTx) - const path = this.getPath(index) - - sign(legacyTx, () => { - return new Promise((resolve, reject) => { - flex.rpc('trezor.ethereumSignTransaction', this.device.path, path, trezorTx, (err, result) => { - return err - ? reject(err) - : resolve({ v: result.v, r: result.r, s: result.s }) - }) - }) - }) - .then(signedTx => cb(null, addHexPrefix(signedTx.serialize().toString('hex')))) - .catch(err => cb(err.message)) - } -} - -module.exports = Trezor diff --git a/main/signers/trezor-connect/index.js b/main/signers/trezor-connect/index.js deleted file mode 100644 index f703eeffb..000000000 --- a/main/signers/trezor-connect/index.js +++ /dev/null @@ -1,38 +0,0 @@ -require('babel-polyfill') -const log = require('electron-log') -const flex = require('../../flex') -const Trezor = require('./Trezor') - -module.exports = { - scan: (signers) => { - flex.on('ready', () => { - flex.on('trezor:connect', device => { - log.info(':: Trezor Scan - Connected Device') - const signer = new Trezor(device, signers) - signers.add(signer) - }) - flex.on('trezor:disconnect', device => { - log.info(':: Trezor Scan - Disconnected Device') - const signer = signers.find(signer => (signer.device && signer.device.path) === device.path) - if (signer) signers.remove(signer.id) - }) - flex.on('trezor:update', device => { - log.info(':: Trezor Scan - Updated Device') - const signer = signers.find(signer => (signer.device && signer.device.path) === device.path) - if (signer) signer.update(device) - }) - flex.on('trezor:needPin', device => { - log.info(':: Trezor Scan - Device Needs Pin') - const signer = signers.find(signer => (signer.device && signer.device.path) === device.path) - if (signer) signer.needPin() - }) - flex.on('trezor:needPhrase', device => { - log.info(':: Trezor Scan - Device Needs Phrase') - const signer = signers.find(signer => (signer.device && signer.device.path) === device.path) - if (signer) signer.needPhrase() - }) - flex.rpc('trezor.scan', err => { if (err) return log.error(err) }) - }) - return () => flex.rpc('trezor.scan', err => { if (err) return log.error(err) }) - } -} diff --git a/main/signers/trezor/Trezor/index.ts b/main/signers/trezor/Trezor/index.ts new file mode 100644 index 000000000..406f471e1 --- /dev/null +++ b/main/signers/trezor/Trezor/index.ts @@ -0,0 +1,284 @@ +import log from 'electron-log' +import utils from 'web3-utils' +import { padToEven, stripHexPrefix, addHexPrefix } from 'ethereumjs-util' + +import Signer, { Callback } from '../../Signer' +import flex from '../../../flex' +import { sign, londonToLegacy, signerCompatibility, TransactionData } from '../../../transaction' + +// @ts-ignore +import { v5 as uuid } from 'uuid' +import { Derivation, getDerivationPath } from '../../signer/derive' +import { TypedTransaction } from '@ethereumjs/tx' + +const ns = '3bbcee75-cecc-5b56-8031-b6641c1ed1f1' + +type FlexCallback = (err: string | null, result: any | undefined) => void + +interface TrezorFirmwareRelease { + release: { + version: number[] + } +} + +interface TrezorFeatures { + model: string +} + +interface TrezorDevice { + type: string, + id: string, + path: string, + label: string, + state: string, + status: string, + mode: string, + firmware: string, + firmwareRelease: TrezorFirmwareRelease + features: TrezorFeatures +} + +export default class Trezor extends Signer { + private closed = false; + + device: TrezorDevice; + derivation: Derivation | undefined; + + constructor (device: TrezorDevice) { + super() + + this.addresses = [] + this.device = device + this.id = this.getId() + + const [major, minor, patch] = device.firmwareRelease?.release?.version || '1.8.0' + this.appVersion = { major, minor, patch } + this.model = ['Trezor', device.features.model].join(' ').trim() + + this.type = 'trezor' + this.status = 'loading' + } + + async open () { + this.closed = false + + this.reset() + this.deviceStatus() + + setTimeout(() => { + this.deviceStatus() + }, 2000) + } + + close () { + this.closed = true + + this.emit('close') + this.removeAllListeners() + + super.close() + } + + update () { + if (this.closed) return + + this.emit('update') + } + + reset () { + this.status = 'loading' + this.addresses = [] + this.update() + } + + private getId () { + return uuid('Trezor' + this.device.path, ns) + } + + private getPath (index: number) { + return `${this.basePath()}/${index}` + } + + private basePath () { + if (!this.derivation) { + throw new Error('attempted to get base path with unknown derivation!') + } + + return `m/${getDerivationPath(this.derivation)}` + } + + deviceStatus () { + this.lookupAddresses((err, addresses) => { + if (err) { + if (err === 'Device call in progress') return + this.status = 'loading' + if (err === 'ui-device_firmware_old') this.status = `Update Firmware (v${this.device.firmwareRelease.release.version.join('.')})` + if (err === 'ui-device_bootloader_mode') this.status = 'Device in Bootloader Mode' + this.addresses = [] + this.update() + } else if (addresses && addresses.length) { + if (addresses[0] !== this.coinbase || this.status !== 'ok') { + this.coinbase = addresses[0] + this.addresses = addresses + this.deviceStatus() + } + if (addresses.length > this.addresses.length) this.addresses = addresses + this.status = 'ok' + this.update() + } else { + this.status = 'Unable to find addresses' + this.addresses = [] + this.update() + } + }) + } + + verifyAddress (index: number, currentAddress: string, display = false, cb: Callback = () => {}, attempt = 0) { + log.info('Verify Address, attempt: ' + attempt) + let timeout = false + const timer = setTimeout(() => { + timeout = true + + this.close() + + log.error('The flex.rpc, trezor.ethereumGetAddress call timed out') + cb(new Error('Address verification timed out'), undefined) + }, 60 * 1000) + + const rpcCallback: FlexCallback = (err, result) => { + clearTimeout(timer) + if (timeout) return + if (err) { + if (err === 'Device call in progress' && attempt < 5) { + setTimeout(() => this.verifyAddress(index, currentAddress, display, cb, ++attempt), 1000 * (attempt + 1)) + } else { + log.error('Verify address error: ', err) + + this.close() + + cb(new Error('Verify Address Error'), undefined) + } + } else { + const address = result.address ? result.address.toLowerCase() : '' + const current = this.addresses[index] ? this.addresses[index].toLowerCase() : '' + log.info('Frame has the current address as: ' + current) + log.info('Trezor is reporting: ' + address) + if (address !== current) { + // TODO: Error Notification + log.error(new Error('Address does not match device')) + + this.close() + + cb(new Error('Address does not match device'), undefined) + } else { + log.info('Address matches device') + cb(null, true) + } + } + } + + flex.rpc('trezor.ethereumGetAddress', this.device.path, this.getPath(index), display, rpcCallback) + } + + lookupAddresses (cb: FlexCallback) { + const rpcCallback: FlexCallback = (err, result) => { + return err + ? cb(err, undefined) + : this.deriveHDAccounts(result.publicKey, result.chainCode, cb) + } + + flex.rpc('trezor.getPublicKey', this.device.path, this.basePath(), rpcCallback) + } + + setPhrase (phrase: string) { + const rpcCallback: FlexCallback = err => { + if (err) log.error(err) + setTimeout(() => this.deviceStatus(), 1000) + } + + this.status = 'loading' + this.update() + + flex.rpc('trezor.inputPhrase', this.device.path, phrase, rpcCallback) + } + + setPin (pin: string) { + const rpcCallback: FlexCallback = err => { + if (err) log.error(err) + setTimeout(() => this.deviceStatus(), 250) + } + + this.status = 'loading' + this.update() + + flex.rpc('trezor.inputPin', this.device.path, pin, rpcCallback) + } + + // Standard Methods + signMessage (index: number, message: string, cb: Callback) { + const rpcCallback: Callback = (err, result) => { + if (err) { + log.error('signMessage Error') + log.error(err) + if (err.message === 'Unexpected message') err = new Error('Update Trezor Firmware') + cb(err, undefined) + } else { + cb(null, '0x' + result.signature) + } + } + + flex.rpc('trezor.ethereumSignMessage', this.device.path, this.getPath(index), this.normalize(message), rpcCallback) + } + + signTransaction (index: number, rawTx: TransactionData, cb: Callback) { + const compatibility = signerCompatibility(rawTx, this.summary()) + const compatibleTx = compatibility.compatible ? { ...rawTx } : londonToLegacy(rawTx) + + sign(compatibleTx, tx => { + return new Promise((resolve, reject) => { + const trezorTx = this.normalizeTransaction(rawTx.chainId, tx) + const path = this.getPath(index) + + const rpcCallback: Callback = (err, result) => { + return err + ? reject(err) + : resolve({ v: result.v, r: result.r, s: result.s }) + } + + flex.rpc('trezor.ethereumSignTransaction', this.device.path, path, trezorTx, rpcCallback) + }) + }) + .then(signedTx => cb(null, addHexPrefix(signedTx.serialize().toString('hex')))) + .catch(err => cb(err.message, undefined)) + } + + private normalize (hex: string) { + return (hex && padToEven(stripHexPrefix(hex))) || '' + } + + private normalizeTransaction (chainId: string, tx: TypedTransaction) { + const txJson = tx.toJSON() + + const unsignedTx = { + nonce: this.normalize(txJson.nonce || ''), + gasLimit: this.normalize(txJson.gasLimit || ''), + to: this.normalize(txJson.to || ''), + value: this.normalize(txJson.value || ''), + data: this.normalize(txJson.data || ''), + chainId: utils.hexToNumber(chainId) + } + + const optionalFields = ['gasPrice', 'maxFeePerGas', 'maxPriorityFeePerGas'] + + optionalFields.forEach(field => { + // @ts-ignore + const val: string = txJson[field] + if (val) { + // @ts-ignore + unsignedTx[field] = this.normalize(val) + } + }) + + return unsignedTx + } +} diff --git a/main/signers/trezor/adapter.ts b/main/signers/trezor/adapter.ts new file mode 100644 index 000000000..2825fba08 --- /dev/null +++ b/main/signers/trezor/adapter.ts @@ -0,0 +1,133 @@ +import log from 'electron-log' + +import flex from '../../flex' +import { SignerAdapter } from '../adapters' +import Trezor from './Trezor' +import store from '../../store' + +export default class TrezorSignerAdapter extends SignerAdapter { + private flexListeners: { [event: string]: (device: any) => void }; + private knownSigners: { [id: string]: Trezor }; + private observer: any; + + constructor () { + super('trezor') + + this.flexListeners = {} + this.knownSigners = {} + } + + open () { + const connectListener = (device: any) => { + log.info(':: Trezor Scan - Connected Device') + log.debug({ trezorDevice: device }) + + const trezor = new Trezor(device) + trezor.derivation = store('main.trezor.derivation') + + trezor.on('close', () => { + delete this.knownSigners[device.path] + + this.emit('remove', trezor.id) + }) + + trezor.on('update', () => { + this.emit('update', trezor) + }) + + this.knownSigners[device.path] = trezor + + this.emit('add', trezor) + + trezor.open() + } + + const disconnectListener = (device: any) => { + log.info(':: Trezor Scan - Disconnected Device') + + this.withSigner(device, signer => { + signer.close() + + delete this.knownSigners[device.path] + + this.emit('remove', signer.id) + }) + } + + const updateListener = (device: any) => { + log.debug(':: Trezor Scan - Updated Device') + this.withSigner(device, signer => this.emit('update', signer)) + } + + const needPinListener = (device: any) => { + log.debug(':: Trezor Scan - Device Needs Pin') + + this.withSigner(device, signer => { + signer.status = 'Need Pin', + signer.update() + }) + } + + const needPhraseListener = (device: any) => { + log.debug(':: Trezor Scan - Device Needs Phrase') + + this.withSigner(device, signer => { + signer.status = 'Enter Passphrase' + signer.update() + }) + } + + const scanListener = (err: any) => { + if (err) return log.error(err) + } + + const readyListener = () => { + this.observer = store.observer(() => { + const trezorDerivation = store('main.trezor.derivation') + + Object.values(this.knownSigners).forEach(trezor => { + if (trezor.derivation !== trezorDerivation) { + trezor.derivation = trezorDerivation + trezor.reset() + trezor.deviceStatus() + } + }) + }) + + this.flexListeners = { + 'trezor:connect': connectListener, + 'trezor:disconnect': disconnectListener, + 'trezor:update': updateListener, + 'trezor:needPin': needPinListener, + 'trezor:needPhrase': needPhraseListener, + 'trezor:scan': scanListener + } + + Object.entries(this.flexListeners).forEach(([event, listener]) => flex.on(event, listener)) + } + + flex.on('ready', readyListener) + + this.flexListeners.ready = readyListener + + super.open() + } + + close () { + this.observer.remove() + + Object.entries(this.flexListeners).forEach(([event, listener]) => flex.off(event, listener)) + + super.close() + } + + private withSigner (device: any, fn: (signer: Trezor) => void) { + const signer = this.knownSigners[device.path] + + if (signer) { + fn(signer) + } else { + log.warn('got Trezor Connect event for unknown signer', device) + } + } +} diff --git a/main/transaction/index.ts b/main/transaction/index.ts index 16619a169..852ce98eb 100644 --- a/main/transaction/index.ts +++ b/main/transaction/index.ts @@ -8,6 +8,7 @@ const londonHardforkSigners: SignerCompatibilityByVersion = { seed: () => true, ring: () => true, ledger: version => version.major >= 2 || (version.major >= 1 && version.minor >= 9), + trezor: version => version.major >= 3 || (version.major >= 2 && version.minor >= 4 && version.patch >= 2), lattice: version => version.major >= 1 || version.minor >= 11 } @@ -50,7 +51,6 @@ export interface SignerSummary { type: string, addresses: string[], status: string, - liveAddressesFound: number, appVersion: AppVersion } diff --git a/package-lock.json b/package-lock.json index 687a1a4e9..842d23b62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,7 +52,7 @@ "react-restore": "0.5.0", "react-transition-group": "4.4.2", "semver": "7.3.5", - "trezor-connect": "8.1.29", + "trezor-connect": "^8.2.1", "usb": "1.7.2", "uuid": "8.3.2", "web3-utils": "1.5.2", @@ -44074,9 +44074,9 @@ } }, "node_modules/trezor-connect": { - "version": "8.1.29", - "resolved": "https://registry.npmjs.org/trezor-connect/-/trezor-connect-8.1.29.tgz", - "integrity": "sha512-SYIT2C3mqNxTsDTglqgz6FyhgXbBFBo0q5WH9ABkyLUarjPNNt2PrmmgF5nT78RE5GwX5QvHoGl9Efis6+4e5Q==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/trezor-connect/-/trezor-connect-8.2.1.tgz", + "integrity": "sha512-YqGgB6E440j8DHaIJCZumtjg5LdUgQyow2pzQRAIVFchhbTOgU3FnWGO3vEeRn9UQuY3pj8xVo0A0GKEtHAW8g==", "dependencies": { "@babel/runtime": "^7.12.5", "events": "^3.2.0", @@ -82816,9 +82816,9 @@ } }, "trezor-connect": { - "version": "8.1.29", - "resolved": "https://registry.npmjs.org/trezor-connect/-/trezor-connect-8.1.29.tgz", - "integrity": "sha512-SYIT2C3mqNxTsDTglqgz6FyhgXbBFBo0q5WH9ABkyLUarjPNNt2PrmmgF5nT78RE5GwX5QvHoGl9Efis6+4e5Q==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/trezor-connect/-/trezor-connect-8.2.1.tgz", + "integrity": "sha512-YqGgB6E440j8DHaIJCZumtjg5LdUgQyow2pzQRAIVFchhbTOgU3FnWGO3vEeRn9UQuY3pj8xVo0A0GKEtHAW8g==", "requires": { "@babel/runtime": "^7.12.5", "events": "^3.2.0", diff --git a/package.json b/package.json index 11a47f399..04e6401d5 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "react-restore": "0.5.0", "react-transition-group": "4.4.2", "semver": "7.3.5", - "trezor-connect": "8.1.29", + "trezor-connect": "8.2.1", "usb": "1.7.2", "uuid": "8.3.2", "web3-utils": "1.5.2", diff --git a/test/main/transaction/index.test.js b/test/main/transaction/index.test.js index da1f152b0..8e0adb36e 100644 --- a/test/main/transaction/index.test.js +++ b/test/main/transaction/index.test.js @@ -118,8 +118,8 @@ describe('#signerCompatibility', () => { expect(compatibility.compatible).toBe(true) }) - it('is not compatible for eip-1559 transactions on Trezor signers', () => { - const appVersion = { major: 1, minor: 1, patch: 1 } + it('is not compatible for eip-1559 transactions on Trezor signers using firmware prior to 2.4.2', () => { + const appVersion = { major: 2, minor: 3, patch: 1 } const tx = { type: '0x2' } @@ -130,6 +130,32 @@ describe('#signerCompatibility', () => { expect(compatibility.tx).toBe('london') expect(compatibility.compatible).toBe(false) }) + + it('is compatible for eip-1559 transactions on Trezor signers using firmware 2.4.2+', () => { + const appVersion = { major: 2, minor: 4, patch: 3 } + const tx = { + type: '0x2' + } + + const compatibility = signerCompatibility(tx, { type: 'trezor', appVersion }) + + expect(compatibility.signer).toBe('trezor') + expect(compatibility.tx).toBe('london') + expect(compatibility.compatible).toBe(true) + }) + + it('is compatible for eip-1559 transactions on Trezor signers using firmware 3.x', () => { + const appVersion = { major: 3, minor: 2, patch: 4 } + const tx = { + type: '0x2' + } + + const compatibility = signerCompatibility(tx, { type: 'trezor', appVersion }) + + expect(compatibility.signer).toBe('trezor') + expect(compatibility.tx).toBe('london') + expect(compatibility.compatible).toBe(true) + }) }) describe('#londonToLegacy', () => {