diff --git a/app/manifest.json b/app/manifest.json index 0bdf84ef1..84cedd687 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -48,8 +48,7 @@ "https://*/*" ], "js": [ - "contentscript.js", - "vendor/ledger/content-script.js" + "contentscript.js" ], "run_at": "document_start", "all_frames": true diff --git a/app/scripts/background.js b/app/scripts/background.js index 3d3afdd4e..bff559469 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -68,8 +68,6 @@ initialize().catch(log.error) // setup metamask mesh testing container setupMetamaskMeshMetrics() - - /** * An object representing a transaction, in whatever state it is in. * @typedef TransactionMeta @@ -446,3 +444,4 @@ extension.runtime.onInstalled.addListener(function (details) { extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) } }) + diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index e0a2b0061..01df9e327 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -200,4 +200,4 @@ function redirectToPhishingWarning () { console.log('MetaMask - routing to Phishing Warning component') const extensionURL = extension.runtime.getURL('phishing.html') window.location.href = extensionURL -} +} \ No newline at end of file diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index a6db49772..1c02f0610 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -1,68 +1,182 @@ - const extension = require('extensionizer') +const extension = require('extensionizer') const {EventEmitter} = require('events') // HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger const hdPathString = `m/44'/60'/0'` const type = 'Ledger Hardware Keyring' +const ORIGIN = 'http://localhost:9000' class LedgerKeyring extends EventEmitter { constructor (opts = {}) { super() this.type = type + this.page = 0 + this.perPage = 5 + this.unlockedAccount = 0 + this.paths = {} + this.iframe = null + this.setupIframe() this.deserialize(opts) } + setupIframe(){ + this.iframe = document.createElement('iframe') + this.iframe.src = ORIGIN + console.log('Injecting ledger iframe') + document.head.appendChild(this.iframe) + + + /* + Passing messages from iframe to background script + */ + console.log('[LEDGER]: LEDGER FROM-IFRAME LISTENER READY') + + } + + sendMessage(msg, cb) { + console.log('[LEDGER]: SENDING MESSAGE TO IFRAME', msg) + this.iframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') + window.addEventListener('message', event => { + if(event.origin !== ORIGIN) return false + if (event.data && event.data.action && event.data.action.search(name) !== -1) { + console.log('[LEDGER]: GOT MESAGE FROM IFRAME', event.data) + cb(event.data) + } + }) + } + serialize () { return Promise.resolve({hdPath: this.hdPath, accounts: this.accounts}) } deserialize (opts = {}) { this.hdPath = opts.hdPath || hdPathString + this.unlocked = opts.unlocked || false this.accounts = opts.accounts || [] return Promise.resolve() } - async addAccounts (n = 1) { - return new Promise((resolve, reject) => { - extension.runtime.sendMessage({ - action: 'ledger-add-account', - n, - }) + isUnlocked () { + return this.unlocked + } - extension.runtime.onMessage.addListener(({action, success, payload}) => { - if (action === 'ledger-sign-transaction') { - if (success) { - resolve(payload) - } else { - reject(payload) - } + setAccountToUnlock (index) { + this.unlockedAccount = parseInt(index, 10) + } + + unlock () { + + if (this.isUnlocked()) return Promise.resolve('already unlocked') + + return new Promise((resolve, reject) => { + this.sendMessage({ + action: 'ledger-unlock', + params: { + hdPath: this.hdPath, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) } }) }) } - async getAccounts () { - return this.accounts.slice() + async addAccounts (n = 1) { + return new Promise((resolve, reject) => { + this.unlock() + .then(_ => { + this.sendMessage({ + action: 'ledger-add-account', + params: { + n, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) + } + }) + }) + }) + } + + getFirstPage () { + this.page = 0 + return this.__getPage(1) + } + + getNextPage () { + return this.__getPage(1) + } + + getPreviousPage () { + return this.__getPage(-1) + } + + __getPage (increment) { + + this.page += increment + + if (this.page <= 0) { this.page = 1 } + + return new Promise((resolve, reject) => { + this.unlock() + .then(_ => { + this.sendMessage({ + action: 'ledger-get-page', + params: { + page: this.page, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) + } + }) + }) + }) + } + + getAccounts () { + return Promise.resolve(this.accounts.slice()) + } + + removeAccount (address) { + if (!this.accounts.map(a => a.toLowerCase()).includes(address.toLowerCase())) { + throw new Error(`Address ${address} not found in this keyring`) + } + this.accounts = this.accounts.filter(a => a.toLowerCase() !== address.toLowerCase()) } // tx is an instance of the ethereumjs-transaction class. async signTransaction (address, tx) { return new Promise((resolve, reject) => { - extension.runtime.sendMessage({ - action: 'ledger-sign-transaction', - address, - tx, - }) - - extension.runtime.onMessage.addListener(({action, success, payload}) => { - if (action === 'ledger-sign-transaction') { - if (success) { - resolve(payload) - } else { - reject(payload) - } - } + this.unlock() + .then(_ => { + console.log('[LEDGER]: sending message ', 'ledger-sign-transaction') + this.sendMessage({ + action: 'ledger-sign-transaction', + params: { + address, + tx, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) + } + }) }) }) } @@ -74,20 +188,23 @@ class LedgerKeyring extends EventEmitter { // For personal_sign, we need to prefix the message: async signPersonalMessage (withAccount, message) { return new Promise((resolve, reject) => { - extension.runtime.sendMessage({ - action: 'ledger-sign-personal-message', - withAccount, - message, - }) - - extension.runtime.onMessage.addListener(({action, success, payload}) => { - if (action === 'ledger-sign-personal-message') { - if (success) { - resolve(payload) - } else { - reject(payload) - } - } + this.unlock() + .then(_ => { + console.log('[LEDGER]: sending message ', 'ledger-sign-personal-message') + this.sendMessage({ + action: 'ledger-sign-personal-message', + params: { + withAccount, + message, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) + } + }) }) }) } @@ -99,6 +216,14 @@ class LedgerKeyring extends EventEmitter { async exportAccount (address) { throw new Error('Not supported on this device') } + + forgetDevice () { + this.accounts = [] + this.unlocked = false + this.page = 0 + this.unlockedAccount = 0 + this.paths = {} + } } LedgerKeyring.type = type diff --git a/app/scripts/lib/setupLedgerIframe.js b/app/scripts/lib/setupLedgerIframe.js new file mode 100644 index 000000000..2831d072e --- /dev/null +++ b/app/scripts/lib/setupLedgerIframe.js @@ -0,0 +1,40 @@ +const extension = require('extensionizer') +module.exports = setupLedgerIframe +/** + * Injects an iframe into the current document to + * enable the interaction with ledger devices + */ +function setupLedgerIframe () { + const ORIGIN = 'http://localhost:9000' + const ledgerIframe = document.createElement('iframe') + ledgerIframe.src = ORIGIN + console.log('Injecting ledger iframe') + document.head.appendChild(ledgerIframe) + + console.log('[LEDGER]: LEDGER BG LISTENER READY') + extension.runtime.onMessage.addListener(({action, params}) => { + console.log('[LEDGER]: GOT MSG FROM THE KEYRING', action, params) + if (action.search('ledger-') !== -1) { + //Forward messages from the keyring to the iframe + sendMessage({action, params}) + } + }) + + function sendMessage(msg) { + ledgerIframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') + } + + /* + Passing messages from iframe to background script + */ + console.log('[LEDGER]: LEDGER FROM-IFRAME LISTENER READY') + window.addEventListener('message', event => { + if(event.origin !== ORIGIN) return false + if (event.data && event.data.action && event.data.action.search('ledger-') !== -1) { + // Forward messages from the iframe to the keyring + console.log('[LEDGER] : forwarding msg', event.data) + extension.runtime.sendMessage(event.data) + } + }) + + } \ No newline at end of file diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 593b722ff..58bab9789 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -546,12 +546,11 @@ module.exports = class MetamaskController extends EventEmitter { keyringName = TrezorKeyring.type break case 'ledger': - keyringName = TrezorKeyring.type + keyringName = LedgerKeyring.type break default: throw new Error('MetamaskController:connectHardware - Unknown device') } - let keyring = await this.keyringController.getKeyringsByType(keyringName)[0] if (!keyring) { keyring = await this.keyringController.addNewKeyring(keyringName) @@ -568,10 +567,8 @@ module.exports = class MetamaskController extends EventEmitter { */ async connectHardware (deviceName, page) { - const oldAccounts = await this.keyringController.getAccounts() const keyring = await this.getKeyringForDevice(deviceName) let accounts = [] - switch (page) { case -1: accounts = await keyring.getPreviousPage() @@ -585,6 +582,7 @@ module.exports = class MetamaskController extends EventEmitter { // Merge with existing accounts // and make sure addresses are not repeated + const oldAccounts = await this.keyringController.getAccounts() const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))] this.accountTracker.syncWithAddresses(accountsToTrack) return accounts diff --git a/app/vendor/ledger/content-script.js b/app/vendor/ledger/content-script.js deleted file mode 100644 index 425ff07a3..000000000 --- a/app/vendor/ledger/content-script.js +++ /dev/null @@ -1,18 +0,0 @@ -/* -Passing messages from background script to popup -*/ -let port = chrome.runtime.connect({ name: 'ledger' }); -port.onMessage.addListener(message => { - window.postMessage(message, window.location.origin); -}); -port.onDisconnect.addListener(d => { - port = null; -}); - /* -Passing messages from popup to background script -*/ - window.addEventListener('message', event => { - if (port && event.source === window && event.data) { - port.postMessage(event.data); - } -}); \ No newline at end of file diff --git a/ui/app/components/pages/create-account/connect-hardware/index.js b/ui/app/components/pages/create-account/connect-hardware/index.js index 646ba8bec..86a4d7257 100644 --- a/ui/app/components/pages/create-account/connect-hardware/index.js +++ b/ui/app/components/pages/create-account/connect-hardware/index.js @@ -49,7 +49,6 @@ class ConnectHardwareForm extends Component { } connectToHardwareWallet = (device) => { - debugger if (this.state.accounts.length) { return null }