From 5ef80495cfd47a8f5e4caf4b16842155420de62e Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Fri, 10 Aug 2018 21:54:34 -0400 Subject: refactor to support multiple hw wallets --- app/manifest.json | 3 +- app/scripts/eth-ledger-keyring-listener.js | 105 +++++++++++++++++++++++ app/scripts/metamask-controller.js | 130 ++++++++++++----------------- app/vendor/ledger/content-script.js | 18 ++++ 4 files changed, 180 insertions(+), 76 deletions(-) create mode 100644 app/scripts/eth-ledger-keyring-listener.js create mode 100644 app/vendor/ledger/content-script.js (limited to 'app') diff --git a/app/manifest.json b/app/manifest.json index 84cedd687..0bdf84ef1 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -48,7 +48,8 @@ "https://*/*" ], "js": [ - "contentscript.js" + "contentscript.js", + "vendor/ledger/content-script.js" ], "run_at": "document_start", "all_frames": true diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js new file mode 100644 index 000000000..a6db49772 --- /dev/null +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -0,0 +1,105 @@ + 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' + +class LedgerKeyring extends EventEmitter { + constructor (opts = {}) { + super() + this.type = type + this.deserialize(opts) + } + + serialize () { + return Promise.resolve({hdPath: this.hdPath, accounts: this.accounts}) + } + + deserialize (opts = {}) { + this.hdPath = opts.hdPath || hdPathString + this.accounts = opts.accounts || [] + return Promise.resolve() + } + + async addAccounts (n = 1) { + return new Promise((resolve, reject) => { + extension.runtime.sendMessage({ + action: 'ledger-add-account', + n, + }) + + extension.runtime.onMessage.addListener(({action, success, payload}) => { + if (action === 'ledger-sign-transaction') { + if (success) { + resolve(payload) + } else { + reject(payload) + } + } + }) + }) + } + + async getAccounts () { + return this.accounts.slice() + } + + // 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) + } + } + }) + }) + } + + 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) { + 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) + } + } + }) + }) + } + + async signTypedData (withAccount, typedData) { + throw new Error('Not supported on this device') + } + + async exportAccount (address) { + throw new Error('Not supported on this device') + } +} + +LedgerKeyring.type = type +module.exports = LedgerKeyring diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index db323e3fe..593b722ff 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -49,6 +49,7 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const cleanErrorStack = require('./lib/cleanErrorStack') const log = require('loglevel') const TrezorKeyring = require('eth-trezor-keyring') +const LedgerKeyring = require('./eth-ledger-keyring-listener') module.exports = class MetamaskController extends EventEmitter { @@ -127,7 +128,7 @@ module.exports = class MetamaskController extends EventEmitter { }) // key mgmt - const additionalKeyrings = [TrezorKeyring] + const additionalKeyrings = [TrezorKeyring, LedgerKeyring] this.keyringController = new KeyringController({ keyringTypes: additionalKeyrings, initState: initState.KeyringController, @@ -377,9 +378,7 @@ module.exports = class MetamaskController extends EventEmitter { connectHardware: nodeify(this.connectHardware, this), forgetDevice: nodeify(this.forgetDevice, this), checkHardwareStatus: nodeify(this.checkHardwareStatus, this), - - // TREZOR - unlockTrezorAccount: nodeify(this.unlockTrezorAccount, this), + unlockHardwareWalletAccount: nodeify(this.unlockHardwareWalletAccount, this), // vault management submitPassword: nodeify(this.submitPassword, this), @@ -540,6 +539,28 @@ module.exports = class MetamaskController extends EventEmitter { // Hardware // + async getKeyringForDevice (deviceName) { + let keyringName = null + switch (deviceName) { + case 'trezor': + keyringName = TrezorKeyring.type + break + case 'ledger': + keyringName = TrezorKeyring.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) + } + + return keyring + + } + /** * Fetch account list from a trezor device. * @@ -547,38 +568,26 @@ module.exports = class MetamaskController extends EventEmitter { */ async connectHardware (deviceName, page) { - switch (deviceName) { - case 'trezor': - const keyringController = this.keyringController - const oldAccounts = await keyringController.getAccounts() - let keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware' - )[0] - if (!keyring) { - keyring = await this.keyringController.addNewKeyring('Trezor Hardware') - } - let accounts = [] - - switch (page) { - case -1: - accounts = await keyring.getPreviousPage() - break - case 1: - accounts = await keyring.getNextPage() - break - default: - accounts = await keyring.getFirstPage() - } - - // Merge with existing accounts - // and make sure addresses are not repeated - const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))] - this.accountTracker.syncWithAddresses(accountsToTrack) - return accounts - - default: - throw new Error('MetamaskController:connectHardware - Unknown device') + const oldAccounts = await this.keyringController.getAccounts() + const keyring = await this.getKeyringForDevice(deviceName) + let accounts = [] + + switch (page) { + case -1: + accounts = await keyring.getPreviousPage() + break + case 1: + accounts = await keyring.getNextPage() + break + default: + accounts = await keyring.getFirstPage() } + + // Merge with existing accounts + // and make sure addresses are not repeated + const accountsToTrack = [...new Set(oldAccounts.concat(accounts.map(a => a.address.toLowerCase())))] + this.accountTracker.syncWithAddresses(accountsToTrack) + return accounts } /** @@ -587,20 +596,8 @@ module.exports = class MetamaskController extends EventEmitter { * @returns {Promise} */ async checkHardwareStatus (deviceName) { - - switch (deviceName) { - case 'trezor': - const keyringController = this.keyringController - const keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware' - )[0] - if (!keyring) { - return false - } - return keyring.isUnlocked() - default: - throw new Error('MetamaskController:checkHardwareStatus - Unknown device') - } + const keyring = await this.getKeyringForDevice(deviceName) + return keyring.isUnlocked() } /** @@ -610,20 +607,9 @@ module.exports = class MetamaskController extends EventEmitter { */ async forgetDevice (deviceName) { - switch (deviceName) { - case 'trezor': - const keyringController = this.keyringController - const keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware' - )[0] - if (!keyring) { - throw new Error('MetamaskController:forgetDevice - Trezor Hardware keyring not found') - } - keyring.forgetDevice() - return true - default: - throw new Error('MetamaskController:forgetDevice - Unknown device') - } + const keyring = await this.getKeyringForDevice(deviceName) + keyring.forgetDevice() + return true } /** @@ -631,23 +617,17 @@ module.exports = class MetamaskController extends EventEmitter { * * @returns {} keyState */ - async unlockTrezorAccount (index) { - const keyringController = this.keyringController - const keyring = await keyringController.getKeyringsByType( - 'Trezor Hardware' - )[0] - if (!keyring) { - throw new Error('MetamaskController - No Trezor Hardware Keyring found') - } + async unlockHardwareWalletAccount (deviceName, index) { + const keyring = await this.getKeyringForDevice(deviceName) keyring.setAccountToUnlock(index) - const oldAccounts = await keyringController.getAccounts() - const keyState = await keyringController.addNewAccount(keyring) - const newAccounts = await keyringController.getAccounts() + const oldAccounts = await this.keyringController.getAccounts() + const keyState = await this.keyringController.addNewAccount(keyring) + const newAccounts = await this.keyringController.getAccounts() this.preferencesController.setAddresses(newAccounts) newAccounts.forEach(address => { if (!oldAccounts.includes(address)) { - this.preferencesController.setAccountLabel(address, `TREZOR #${parseInt(index, 10) + 1}`) + this.preferencesController.setAccountLabel(address, `${deviceName.toUpperCase()} #${parseInt(index, 10) + 1}`) this.preferencesController.setSelectedAddress(address) } }) diff --git a/app/vendor/ledger/content-script.js b/app/vendor/ledger/content-script.js new file mode 100644 index 000000000..425ff07a3 --- /dev/null +++ b/app/vendor/ledger/content-script.js @@ -0,0 +1,18 @@ +/* +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 -- cgit From 78a1cd3314455d6e4e08839a3eea3ec2868f4f59 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 02:35:20 -0400 Subject: iframe communication working --- app/manifest.json | 3 +- app/scripts/background.js | 3 +- app/scripts/contentscript.js | 2 +- app/scripts/eth-ledger-keyring-listener.js | 203 +++++++++++++++++++++++------ app/scripts/lib/setupLedgerIframe.js | 40 ++++++ app/scripts/metamask-controller.js | 6 +- app/vendor/ledger/content-script.js | 18 --- 7 files changed, 209 insertions(+), 66 deletions(-) create mode 100644 app/scripts/lib/setupLedgerIframe.js delete mode 100644 app/vendor/ledger/content-script.js (limited to 'app') 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) { + isUnlocked () { + return this.unlocked + } + + setAccountToUnlock (index) { + this.unlockedAccount = parseInt(index, 10) + } + + unlock () { + + if (this.isUnlocked()) return Promise.resolve('already unlocked') + return new Promise((resolve, reject) => { - extension.runtime.sendMessage({ - action: 'ledger-add-account', - n, + this.sendMessage({ + action: 'ledger-unlock', + params: { + hdPath: this.hdPath, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) + } }) + }) + } - extension.runtime.onMessage.addListener(({action, success, payload}) => { - if (action === 'ledger-sign-transaction') { + 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) - } - } + } + }) }) }) } - async getAccounts () { - return this.accounts.slice() + getFirstPage () { + this.page = 0 + return this.__getPage(1) } - // tx is an instance of the ethereumjs-transaction class. - async signTransaction (address, tx) { + 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) => { - extension.runtime.sendMessage({ - action: 'ledger-sign-transaction', - address, - tx, + this.unlock() + .then(_ => { + this.sendMessage({ + action: 'ledger-get-page', + params: { + page: this.page, + }, + }, + ({action, success, payload}) => { + if (success) { + resolve(payload) + } else { + reject(payload) + } + }) }) + }) + } - extension.runtime.onMessage.addListener(({action, success, payload}) => { - if (action === 'ledger-sign-transaction') { - 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) => { + 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 -- cgit From 8e842a8947997b7ea47cf35d1348788d21f2f467 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 05:02:02 -0400 Subject: able to add accounts --- app/scripts/eth-ledger-keyring-listener.js | 139 +++++++++++++++++++++-------- app/scripts/metamask-controller.js | 2 +- 2 files changed, 104 insertions(+), 37 deletions(-) (limited to 'app') diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index 1c02f0610..b9b8eb3c8 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -1,11 +1,16 @@ const extension = require('extensionizer') const {EventEmitter} = require('events') +const HDKey = require('hdkey') +const ethUtil = require('ethereumjs-util') +const sigUtil = require('eth-sig-util') +const Transaction = require('ethereumjs-tx') // 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' +const ORIGIN = 'https://localhost:3000' +const pathBase = 'm' class LedgerKeyring extends EventEmitter { constructor (opts = {}) { @@ -14,6 +19,7 @@ class LedgerKeyring extends EventEmitter { this.page = 0 this.perPage = 5 this.unlockedAccount = 0 + this.hdk = new HDKey() this.paths = {} this.iframe = null this.setupIframe() @@ -26,10 +32,6 @@ class LedgerKeyring extends EventEmitter { 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') } @@ -78,32 +80,39 @@ class LedgerKeyring extends EventEmitter { }, ({action, success, payload}) => { if (success) { - resolve(payload) + this.hdk.publicKey = new Buffer(payload.publicKey, 'hex') + this.hdk.chainCode = new Buffer(payload.chainCode, 'hex') + resolve('just unlocked') } else { - reject(payload) + reject(payload.error || 'Unknown error') } }) }) } - async addAccounts (n = 1) { + setAccountToUnlock (index) { + this.unlockedAccount = parseInt(index, 10) + } + + 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) - } + .then(_ => { + const from = this.unlockedAccount + const to = from + n + this.accounts = [] + + for (let i = from; i < to; i++) { + const address = this._addressFromIndex(pathBase, i) + this.accounts.push(address) + this.page = 0 + } + resolve(this.accounts) + }) + .catch(e => { + reject(e) }) - }) }) } @@ -129,20 +138,27 @@ class LedgerKeyring extends EventEmitter { 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) - } - }) - }) + + const from = (this.page - 1) * this.perPage + const to = from + this.perPage + + const accounts = [] + + for (let i = from; i < to; i++) { + const address = this._addressFromIndex(pathBase, i) + accounts.push({ + address: address, + balance: null, + index: i, + }) + this.paths[ethUtil.toChecksumAddress(address)] = i + + } + resolve(accounts) + }) + .catch(e => { + reject(e) + }) }) } @@ -157,6 +173,7 @@ class LedgerKeyring extends EventEmitter { 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) => { @@ -224,6 +241,56 @@ class LedgerKeyring extends EventEmitter { this.unlockedAccount = 0 this.paths = {} } + + /* PRIVATE METHODS */ + + _padLeftEven (hex) { + return hex.length % 2 !== 0 ? `0${hex}` : hex + } + + _normalize (buf) { + return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) + } + + _addressFromIndex (pathBase, i) { + const dkey = this.hdk.derive(`${pathBase}/${i}`) + const address = ethUtil + .publicToAddress(dkey.publicKey, true) + .toString('hex') + return ethUtil.toChecksumAddress(address) + } + + _pathFromAddress (address) { + const checksummedAddress = ethUtil.toChecksumAddress(address) + let index = this.paths[checksummedAddress] + if (typeof index === 'undefined') { + for (let i = 0; i < MAX_INDEX; i++) { + if (checksummedAddress === this._addressFromIndex(pathBase, i)) { + index = i + break + } + } + } + + if (typeof index === 'undefined') { + throw new Error('Unknown address') + } + return `${this.hdPath}/${index}` + } + + _toAscii (hex) { + let str = '' + let i = 0; const l = hex.length + if (hex.substring(0, 2) === '0x') { + i = 2 + } + for (; i < l; i += 2) { + const code = parseInt(hex.substr(i, 2), 16) + str += String.fromCharCode(code) + } + + return str + } } LedgerKeyring.type = type diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 58bab9789..c79e5141e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -615,7 +615,7 @@ module.exports = class MetamaskController extends EventEmitter { * * @returns {} keyState */ - async unlockHardwareWalletAccount (deviceName, index) { + async unlockHardwareWalletAccount (index, deviceName) { const keyring = await this.getKeyringForDevice(deviceName) keyring.setAccountToUnlock(index) -- cgit From aa6a42e3deda1725cb5e4e0df97549f9facc18f5 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 05:11:21 -0400 Subject: rename keyring --- app/scripts/eth-ledger-keyring-listener.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index b9b8eb3c8..1b30c4d6d 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -8,7 +8,7 @@ const Transaction = require('ethereumjs-tx') // 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 type = 'Ledger Hardware' const ORIGIN = 'https://localhost:3000' const pathBase = 'm' -- cgit From 011cc141b3f2971522a1d77cea7370f61bbc08d1 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 16:00:29 -0400 Subject: tx signing should work --- app/scripts/eth-ledger-keyring-listener.js | 49 ++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 10 deletions(-) (limited to 'app') diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index 1b30c4d6d..ad214138f 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -7,10 +7,11 @@ const Transaction = require('ethereumjs-tx') // 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 hdPathString = `44'/60'/0'` const type = 'Ledger Hardware' const ORIGIN = 'https://localhost:3000' const pathBase = 'm' +const MAX_INDEX = 1000 class LedgerKeyring extends EventEmitter { constructor (opts = {}) { @@ -39,11 +40,11 @@ class LedgerKeyring extends EventEmitter { 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) + window.addEventListener('message', ({ origin, data }) => { + if(origin !== ORIGIN) return false + if (data && data.action && data.action === `${msg.action}-reply`) { + console.log('[LEDGER]: GOT MESAGE FROM IFRAME', data) + cb(data) } }) } @@ -176,20 +177,41 @@ class LedgerKeyring extends EventEmitter { // tx is an instance of the ethereumjs-transaction class. async signTransaction (address, tx) { + return new Promise((resolve, reject) => { this.unlock() .then(_ => { console.log('[LEDGER]: sending message ', 'ledger-sign-transaction') + this.sendMessage({ action: 'ledger-sign-transaction', params: { - address, - tx, + tx: { + to: this._normalize(tx.to), + value: this._normalize(tx.value), + data: this._normalize(tx.data), + chainId: tx._chainId, + nonce: this._fixNonce(this._normalize(tx.nonce)), + gasLimit: this._normalize(tx.gasLimit), + gasPrice: this._normalize(tx.gasPrice), + }, + path: this._pathFromAddress(address) }, }, ({action, success, payload}) => { if (success) { - resolve(payload) + console.log('[LEDGER]: got tx signed!', payload.txData) + const signedTx = new Transaction(payload.txData) + // Validate that the signature matches the right address + const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) + const correctAddress = ethUtil.toChecksumAddress(address) + if (addressSignedWith !== correctAddress) { + reject('signature doesnt match the right address') + } + console.log('[LEDGER]: all good!', signedTx.toJSON()) + console.log('[LEDGER]: signedTX', `0x${signedTx.serialize().toString('hex')}`) + + resolve(signedTx) } else { reject(payload) } @@ -249,7 +271,7 @@ class LedgerKeyring extends EventEmitter { } _normalize (buf) { - return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) + return this._padLeftEven(ethUtil.bufferToHex(buf).toLowerCase()) } _addressFromIndex (pathBase, i) { @@ -291,6 +313,13 @@ class LedgerKeyring extends EventEmitter { return str } + + _fixNonce(nonce){ + if(nonce === '0x'){ + return `${nonce}0` + } + return nonce + } } LedgerKeyring.type = type -- cgit From 12b41b8fc213b930eb96f77f49157e78b141ff43 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 18:20:50 -0400 Subject: tx signature is valid --- app/scripts/eth-ledger-keyring-listener.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app') diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index ad214138f..92c85f600 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -187,6 +187,7 @@ class LedgerKeyring extends EventEmitter { action: 'ledger-sign-transaction', params: { tx: { + from: this._normalize(address), to: this._normalize(tx.to), value: this._normalize(tx.value), data: this._normalize(tx.data), -- cgit From 068bf43615fa0d0038f43798fbf9e04d03369e68 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 19:27:24 -0400 Subject: working --- app/scripts/controllers/transactions/index.js | 4 ++-- app/scripts/eth-ledger-keyring-listener.js | 34 ++++++++++----------------- 2 files changed, 14 insertions(+), 24 deletions(-) (limited to 'app') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 8e2288aed..7ac6ec2e6 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -289,10 +289,10 @@ class TransactionController extends EventEmitter { // sign tx const fromAddress = txParams.from const ethTx = new Transaction(txParams) - await this.signEthTx(ethTx, fromAddress) + const signedTx = await this.signEthTx(ethTx, fromAddress) // set state to signed this.txStateManager.setTxStatusSigned(txMeta.id) - const rawTx = ethUtil.bufferToHex(ethTx.serialize()) + const rawTx = ethUtil.bufferToHex(signedTx.serialize()) return rawTx } diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index 92c85f600..0043058e6 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -9,7 +9,7 @@ const Transaction = require('ethereumjs-tx') // HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger const hdPathString = `44'/60'/0'` const type = 'Ledger Hardware' -const ORIGIN = 'https://localhost:3000' +const ORIGIN = 'https://localhost:3000' const pathBase = 'm' const MAX_INDEX = 1000 @@ -27,21 +27,18 @@ class LedgerKeyring extends EventEmitter { this.deserialize(opts) } - setupIframe(){ + setupIframe () { this.iframe = document.createElement('iframe') this.iframe.src = ORIGIN console.log('Injecting ledger iframe') document.head.appendChild(this.iframe) - - console.log('[LEDGER]: LEDGER FROM-IFRAME LISTENER READY') - } - sendMessage(msg, cb) { + sendMessage (msg, cb) { console.log('[LEDGER]: SENDING MESSAGE TO IFRAME', msg) this.iframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') window.addEventListener('message', ({ origin, data }) => { - if(origin !== ORIGIN) return false + if (origin !== ORIGIN) return false if (data && data.action && data.action === `${msg.action}-reply`) { console.log('[LEDGER]: GOT MESAGE FROM IFRAME', data) cb(data) @@ -79,7 +76,7 @@ class LedgerKeyring extends EventEmitter { hdPath: this.hdPath, }, }, - ({action, success, payload}) => { + ({action, success, payload}) => { if (success) { this.hdk.publicKey = new Buffer(payload.publicKey, 'hex') this.hdk.chainCode = new Buffer(payload.chainCode, 'hex') @@ -177,12 +174,10 @@ class LedgerKeyring extends EventEmitter { // tx is an instance of the ethereumjs-transaction class. async signTransaction (address, tx) { - + return new Promise((resolve, reject) => { this.unlock() .then(_ => { - console.log('[LEDGER]: sending message ', 'ledger-sign-transaction') - this.sendMessage({ action: 'ledger-sign-transaction', params: { @@ -196,26 +191,22 @@ class LedgerKeyring extends EventEmitter { gasLimit: this._normalize(tx.gasLimit), gasPrice: this._normalize(tx.gasPrice), }, - path: this._pathFromAddress(address) + path: this._pathFromAddress(address), }, }, ({action, success, payload}) => { if (success) { - console.log('[LEDGER]: got tx signed!', payload.txData) const signedTx = new Transaction(payload.txData) // Validate that the signature matches the right address const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) const correctAddress = ethUtil.toChecksumAddress(address) if (addressSignedWith !== correctAddress) { reject('signature doesnt match the right address') - } - console.log('[LEDGER]: all good!', signedTx.toJSON()) - console.log('[LEDGER]: signedTX', `0x${signedTx.serialize().toString('hex')}`) - + } resolve(signedTx) } else { reject(payload) - } + } }) }) }) @@ -230,7 +221,6 @@ class LedgerKeyring extends EventEmitter { return new Promise((resolve, reject) => { this.unlock() .then(_ => { - console.log('[LEDGER]: sending message ', 'ledger-sign-personal-message') this.sendMessage({ action: 'ledger-sign-personal-message', params: { @@ -243,7 +233,7 @@ class LedgerKeyring extends EventEmitter { resolve(payload) } else { reject(payload) - } + } }) }) }) @@ -315,8 +305,8 @@ class LedgerKeyring extends EventEmitter { return str } - _fixNonce(nonce){ - if(nonce === '0x'){ + _fixNonce (nonce) { + if (nonce === '0x') { return `${nonce}0` } return nonce -- cgit From e6d64cecf84d91f1db3aab5777db3530c9cd8ab7 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sat, 11 Aug 2018 20:26:34 -0400 Subject: message signing works --- app/scripts/eth-ledger-keyring-listener.js | 22 +++++++++++++++++----- app/scripts/lib/createLoggerMiddleware.js | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) (limited to 'app') diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index 0043058e6..bc5801106 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -1,4 +1,3 @@ -const extension = require('extensionizer') const {EventEmitter} = require('events') const HDKey = require('hdkey') const ethUtil = require('ethereumjs-util') @@ -202,7 +201,7 @@ class LedgerKeyring extends EventEmitter { const correctAddress = ethUtil.toChecksumAddress(address) if (addressSignedWith !== correctAddress) { reject('signature doesnt match the right address') - } + } resolve(signedTx) } else { reject(payload) @@ -218,19 +217,32 @@ class LedgerKeyring extends EventEmitter { // For personal_sign, we need to prefix the message: async signPersonalMessage (withAccount, message) { + const humanReadableMsg = this._toAscii(message) + const bufferMsg = Buffer.from(humanReadableMsg).toString('hex') return new Promise((resolve, reject) => { this.unlock() .then(_ => { this.sendMessage({ action: 'ledger-sign-personal-message', params: { - withAccount, - message, + path: this._pathFromAddress(withAccount ), + message: bufferMsg, }, }, ({action, success, payload}) => { if (success) { - resolve(payload) + const { result } = payload + let v = result['v'] - 27 + v = v.toString(16) + if (v.length < 2) { + v = `0${v}` + } + const signature = `0x${result['r']}${result['s']}${v}` + const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature}) + if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { + reject('signature doesnt match the right address') + } + resolve(signature) } else { reject(payload) } diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js index 996c3477c..53913921c 100644 --- a/app/scripts/lib/createLoggerMiddleware.js +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -14,7 +14,7 @@ function createLoggerMiddleware (opts) { log.error('Error in RPC response:\n', res) } if (req.isMetamaskInternal) return - log.info(`RPC (${opts.origin}):`, req, '->', res) + //log.info(`RPC (${opts.origin}):`, req, '->', res) cb() }) } -- cgit From 2355573340d0287582d0cd6a467e13126fbddd7c Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sun, 12 Aug 2018 00:06:36 -0400 Subject: clean up --- app/scripts/eth-ledger-keyring-listener.js | 81 +++++++++++++----------------- 1 file changed, 36 insertions(+), 45 deletions(-) (limited to 'app') diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js index bc5801106..91507455a 100644 --- a/app/scripts/eth-ledger-keyring-listener.js +++ b/app/scripts/eth-ledger-keyring-listener.js @@ -4,11 +4,9 @@ const ethUtil = require('ethereumjs-util') const sigUtil = require('eth-sig-util') const Transaction = require('ethereumjs-tx') - -// HD path differs from eth-hd-keyring - MEW, Parity, Geth and Official Ledger clients use same unusual derivation for Ledger const hdPathString = `44'/60'/0'` const type = 'Ledger Hardware' -const ORIGIN = 'https://localhost:3000' +const BRIDGE_URL = 'https://localhost:3000' const pathBase = 'm' const MAX_INDEX = 1000 @@ -28,18 +26,15 @@ class LedgerKeyring extends EventEmitter { setupIframe () { this.iframe = document.createElement('iframe') - this.iframe.src = ORIGIN - console.log('Injecting ledger iframe') + this.iframe.src = BRIDGE_URL document.head.appendChild(this.iframe) } sendMessage (msg, cb) { - console.log('[LEDGER]: SENDING MESSAGE TO IFRAME', msg) this.iframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') window.addEventListener('message', ({ origin, data }) => { - if (origin !== ORIGIN) return false + if (origin !== BRIDGE_URL) return false if (data && data.action && data.action === `${msg.action}-reply`) { - console.log('[LEDGER]: GOT MESAGE FROM IFRAME', data) cb(data) } }) @@ -75,7 +70,7 @@ class LedgerKeyring extends EventEmitter { hdPath: this.hdPath, }, }, - ({action, success, payload}) => { + ({success, payload}) => { if (success) { this.hdk.publicKey = new Buffer(payload.publicKey, 'hex') this.hdk.chainCode = new Buffer(payload.chainCode, 'hex') @@ -87,10 +82,6 @@ class LedgerKeyring extends EventEmitter { }) } - setAccountToUnlock (index) { - this.unlockedAccount = parseInt(index, 10) - } - addAccounts (n = 1) { return new Promise((resolve, reject) => { @@ -173,36 +164,44 @@ class LedgerKeyring extends EventEmitter { // tx is an instance of the ethereumjs-transaction class. async signTransaction (address, tx) { - return new Promise((resolve, reject) => { this.unlock() .then(_ => { + + const newTx = new Transaction({ + from: this._normalize(address), + 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), + v: ethUtil.bufferToHex(tx.getChainId()), + r: '0x00', + s: '0x00', + }) + this.sendMessage({ action: 'ledger-sign-transaction', params: { - tx: { - from: this._normalize(address), - to: this._normalize(tx.to), - value: this._normalize(tx.value), - data: this._normalize(tx.data), - chainId: tx._chainId, - nonce: this._fixNonce(this._normalize(tx.nonce)), - gasLimit: this._normalize(tx.gasLimit), - gasPrice: this._normalize(tx.gasPrice), - }, - path: this._pathFromAddress(address), + tx: newTx.serialize().toString('hex'), + hdPath: this._pathFromAddress(address), }, }, - ({action, success, payload}) => { + ({success, payload}) => { if (success) { - const signedTx = new Transaction(payload.txData) - // Validate that the signature matches the right address - const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) - const correctAddress = ethUtil.toChecksumAddress(address) - if (addressSignedWith !== correctAddress) { - reject('signature doesnt match the right address') + + newTx.v = Buffer.from(payload.v, 'hex') + newTx.r = Buffer.from(payload.r, 'hex') + newTx.s = Buffer.from(payload.s, 'hex') + + const valid = newTx.verifySignature() + if (valid) { + resolve(newTx) + } else { + reject('The transaction signature is not valid') } - resolve(signedTx) } else { reject(payload) } @@ -225,19 +224,18 @@ class LedgerKeyring extends EventEmitter { this.sendMessage({ action: 'ledger-sign-personal-message', params: { - path: this._pathFromAddress(withAccount ), + hdPath: this._pathFromAddress(withAccount), message: bufferMsg, }, }, - ({action, success, payload}) => { + ({success, payload}) => { if (success) { - const { result } = payload - let v = result['v'] - 27 + let v = payload['v'] - 27 v = v.toString(16) if (v.length < 2) { v = `0${v}` } - const signature = `0x${result['r']}${result['s']}${v}` + const signature = `0x${payload['r']}${payload['s']}${v}` const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature}) if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { reject('signature doesnt match the right address') @@ -316,13 +314,6 @@ class LedgerKeyring extends EventEmitter { return str } - - _fixNonce (nonce) { - if (nonce === '0x') { - return `${nonce}0` - } - return nonce - } } LedgerKeyring.type = type -- cgit From 0b9b892c6b27c55a016378387cb8d227957f8528 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sun, 12 Aug 2018 01:34:01 -0400 Subject: this should be ready to go --- app/scripts/eth-ledger-keyring-listener.js | 320 ----------------------------- app/scripts/metamask-controller.js | 6 +- 2 files changed, 3 insertions(+), 323 deletions(-) delete mode 100644 app/scripts/eth-ledger-keyring-listener.js (limited to 'app') diff --git a/app/scripts/eth-ledger-keyring-listener.js b/app/scripts/eth-ledger-keyring-listener.js deleted file mode 100644 index 91507455a..000000000 --- a/app/scripts/eth-ledger-keyring-listener.js +++ /dev/null @@ -1,320 +0,0 @@ -const {EventEmitter} = require('events') -const HDKey = require('hdkey') -const ethUtil = require('ethereumjs-util') -const sigUtil = require('eth-sig-util') -const Transaction = require('ethereumjs-tx') - -const hdPathString = `44'/60'/0'` -const type = 'Ledger Hardware' -const BRIDGE_URL = 'https://localhost:3000' -const pathBase = 'm' -const MAX_INDEX = 1000 - -class LedgerKeyring extends EventEmitter { - constructor (opts = {}) { - super() - this.type = type - this.page = 0 - this.perPage = 5 - this.unlockedAccount = 0 - this.hdk = new HDKey() - this.paths = {} - this.iframe = null - this.setupIframe() - this.deserialize(opts) - } - - setupIframe () { - this.iframe = document.createElement('iframe') - this.iframe.src = BRIDGE_URL - document.head.appendChild(this.iframe) - } - - sendMessage (msg, cb) { - this.iframe.contentWindow.postMessage({...msg, target: 'LEDGER-IFRAME'}, '*') - window.addEventListener('message', ({ origin, data }) => { - if (origin !== BRIDGE_URL) return false - if (data && data.action && data.action === `${msg.action}-reply`) { - cb(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() - } - - isUnlocked () { - return this.unlocked - } - - 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, - }, - }, - ({success, payload}) => { - if (success) { - this.hdk.publicKey = new Buffer(payload.publicKey, 'hex') - this.hdk.chainCode = new Buffer(payload.chainCode, 'hex') - resolve('just unlocked') - } else { - reject(payload.error || 'Unknown error') - } - }) - }) - } - - addAccounts (n = 1) { - - return new Promise((resolve, reject) => { - this.unlock() - .then(_ => { - const from = this.unlockedAccount - const to = from + n - this.accounts = [] - - for (let i = from; i < to; i++) { - const address = this._addressFromIndex(pathBase, i) - this.accounts.push(address) - this.page = 0 - } - resolve(this.accounts) - }) - .catch(e => { - reject(e) - }) - }) - } - - 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(_ => { - - const from = (this.page - 1) * this.perPage - const to = from + this.perPage - - const accounts = [] - - for (let i = from; i < to; i++) { - const address = this._addressFromIndex(pathBase, i) - accounts.push({ - address: address, - balance: null, - index: i, - }) - this.paths[ethUtil.toChecksumAddress(address)] = i - - } - resolve(accounts) - }) - .catch(e => { - reject(e) - }) - }) - } - - 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) => { - this.unlock() - .then(_ => { - - const newTx = new Transaction({ - from: this._normalize(address), - 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), - v: ethUtil.bufferToHex(tx.getChainId()), - r: '0x00', - s: '0x00', - }) - - this.sendMessage({ - action: 'ledger-sign-transaction', - params: { - tx: newTx.serialize().toString('hex'), - hdPath: this._pathFromAddress(address), - }, - }, - ({success, payload}) => { - if (success) { - - newTx.v = Buffer.from(payload.v, 'hex') - newTx.r = Buffer.from(payload.r, 'hex') - newTx.s = Buffer.from(payload.s, 'hex') - - const valid = newTx.verifySignature() - if (valid) { - resolve(newTx) - } else { - reject('The transaction signature is not valid') - } - } else { - reject(payload) - } - }) - }) - }) - } - - 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) { - const humanReadableMsg = this._toAscii(message) - const bufferMsg = Buffer.from(humanReadableMsg).toString('hex') - return new Promise((resolve, reject) => { - this.unlock() - .then(_ => { - this.sendMessage({ - action: 'ledger-sign-personal-message', - params: { - hdPath: this._pathFromAddress(withAccount), - message: bufferMsg, - }, - }, - ({success, payload}) => { - if (success) { - let v = payload['v'] - 27 - v = v.toString(16) - if (v.length < 2) { - v = `0${v}` - } - const signature = `0x${payload['r']}${payload['s']}${v}` - const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature}) - if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { - reject('signature doesnt match the right address') - } - resolve(signature) - } else { - reject(payload) - } - }) - }) - }) - } - - async signTypedData (withAccount, typedData) { - throw new Error('Not supported on this device') - } - - 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 = {} - } - - /* PRIVATE METHODS */ - - _padLeftEven (hex) { - return hex.length % 2 !== 0 ? `0${hex}` : hex - } - - _normalize (buf) { - return this._padLeftEven(ethUtil.bufferToHex(buf).toLowerCase()) - } - - _addressFromIndex (pathBase, i) { - const dkey = this.hdk.derive(`${pathBase}/${i}`) - const address = ethUtil - .publicToAddress(dkey.publicKey, true) - .toString('hex') - return ethUtil.toChecksumAddress(address) - } - - _pathFromAddress (address) { - const checksummedAddress = ethUtil.toChecksumAddress(address) - let index = this.paths[checksummedAddress] - if (typeof index === 'undefined') { - for (let i = 0; i < MAX_INDEX; i++) { - if (checksummedAddress === this._addressFromIndex(pathBase, i)) { - index = i - break - } - } - } - - if (typeof index === 'undefined') { - throw new Error('Unknown address') - } - return `${this.hdPath}/${index}` - } - - _toAscii (hex) { - let str = '' - let i = 0; const l = hex.length - if (hex.substring(0, 2) === '0x') { - i = 2 - } - for (; i < l; i += 2) { - const code = parseInt(hex.substr(i, 2), 16) - str += String.fromCharCode(code) - } - - return str - } -} - -LedgerKeyring.type = type -module.exports = LedgerKeyring diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c79e5141e..fed00077e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -49,7 +49,7 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const cleanErrorStack = require('./lib/cleanErrorStack') const log = require('loglevel') const TrezorKeyring = require('eth-trezor-keyring') -const LedgerKeyring = require('./eth-ledger-keyring-listener') +const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring') module.exports = class MetamaskController extends EventEmitter { @@ -128,7 +128,7 @@ module.exports = class MetamaskController extends EventEmitter { }) // key mgmt - const additionalKeyrings = [TrezorKeyring, LedgerKeyring] + const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring] this.keyringController = new KeyringController({ keyringTypes: additionalKeyrings, initState: initState.KeyringController, @@ -546,7 +546,7 @@ module.exports = class MetamaskController extends EventEmitter { keyringName = TrezorKeyring.type break case 'ledger': - keyringName = LedgerKeyring.type + keyringName = LedgerBridgeKeyring.type break default: throw new Error('MetamaskController:connectHardware - Unknown device') -- cgit From 8f9a0a535cbe904b0816c954c170c71843f62da9 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sun, 12 Aug 2018 01:43:41 -0400 Subject: clean up --- app/scripts/background.js | 3 ++- app/scripts/contentscript.js | 2 +- app/scripts/lib/createLoggerMiddleware.js | 2 +- app/scripts/lib/setupLedgerIframe.js | 40 ------------------------------- 4 files changed, 4 insertions(+), 43 deletions(-) delete mode 100644 app/scripts/lib/setupLedgerIframe.js (limited to 'app') diff --git a/app/scripts/background.js b/app/scripts/background.js index bff559469..3d3afdd4e 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -68,6 +68,8 @@ initialize().catch(log.error) // setup metamask mesh testing container setupMetamaskMeshMetrics() + + /** * An object representing a transaction, in whatever state it is in. * @typedef TransactionMeta @@ -444,4 +446,3 @@ 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 01df9e327..e0a2b0061 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/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js index 53913921c..996c3477c 100644 --- a/app/scripts/lib/createLoggerMiddleware.js +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -14,7 +14,7 @@ function createLoggerMiddleware (opts) { log.error('Error in RPC response:\n', res) } if (req.isMetamaskInternal) return - //log.info(`RPC (${opts.origin}):`, req, '->', res) + log.info(`RPC (${opts.origin}):`, req, '->', res) cb() }) } diff --git a/app/scripts/lib/setupLedgerIframe.js b/app/scripts/lib/setupLedgerIframe.js deleted file mode 100644 index 2831d072e..000000000 --- a/app/scripts/lib/setupLedgerIframe.js +++ /dev/null @@ -1,40 +0,0 @@ -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 -- cgit From 4e1d8ba19db729f5c282c4e3c6b43433b562a45e Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 13 Aug 2018 19:29:43 -0400 Subject: good progress adding paths --- app/scripts/metamask-controller.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'app') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index fed00077e..beaf04c0d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -539,7 +539,7 @@ module.exports = class MetamaskController extends EventEmitter { // Hardware // - async getKeyringForDevice (deviceName) { + async getKeyringForDevice (deviceName, hdPath = null) { let keyringName = null switch (deviceName) { case 'trezor': @@ -555,6 +555,10 @@ module.exports = class MetamaskController extends EventEmitter { if (!keyring) { keyring = await this.keyringController.addNewKeyring(keyringName) } + if (hdPath) { + console.log('[LEDGER]: HDPATH set', hdPath) + keyring.hdPath = hdPath + } return keyring @@ -565,9 +569,8 @@ module.exports = class MetamaskController extends EventEmitter { * * @returns [] accounts */ - async connectHardware (deviceName, page) { - - const keyring = await this.getKeyringForDevice(deviceName) + async connectHardware (deviceName, page, hdPath) { + const keyring = await this.getKeyringForDevice(deviceName, hdPath) let accounts = [] switch (page) { case -1: @@ -593,8 +596,8 @@ module.exports = class MetamaskController extends EventEmitter { * * @returns {Promise} */ - async checkHardwareStatus (deviceName) { - const keyring = await this.getKeyringForDevice(deviceName) + async checkHardwareStatus (deviceName, hdPath) { + const keyring = await this.getKeyringForDevice(deviceName, hdPath) return keyring.isUnlocked() } @@ -615,8 +618,8 @@ module.exports = class MetamaskController extends EventEmitter { * * @returns {} keyState */ - async unlockHardwareWalletAccount (index, deviceName) { - const keyring = await this.getKeyringForDevice(deviceName) + async unlockHardwareWalletAccount (index, deviceName, hdPath) { + const keyring = await this.getKeyringForDevice(deviceName, hdPath) keyring.setAccountToUnlock(index) const oldAccounts = await this.keyringController.getAccounts() -- cgit From 61a279204a804ddf4815d0db6103c19c5640bbb9 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 01:26:18 -0400 Subject: legacy and new hd path working --- app/_locales/en/messages.json | 6 ++++++ app/scripts/metamask-controller.js | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 62ec4ce37..a4ee97525 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -538,6 +538,9 @@ "learnMore": { "message": "Learn more" }, + "ledgerAccountRestriction": { + "message": "You need to make use your last account before you can add a new one." + }, "lessThanMax": { "message": "must be less than or equal to $1.", "description": "helper for inputting hex as decimal input" @@ -922,6 +925,9 @@ "selectAnAccountHelp": { "message": "These are the accounts available in your hardware wallet. Select the one you’d like to use in MetaMask." }, + "selectPathHelp": { + "message": "If you don't see your existing Ledger address(es), please try selecting a different HD Path \"Legacy (MEW / MyCrypto)\"" + }, "sendTokensAnywhere": { "message": "Send Tokens to anyone with an Ethereum account" }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index beaf04c0d..b1473390b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -556,10 +556,11 @@ module.exports = class MetamaskController extends EventEmitter { keyring = await this.keyringController.addNewKeyring(keyringName) } if (hdPath) { - console.log('[LEDGER]: HDPATH set', hdPath) keyring.hdPath = hdPath } + keyring.network = this.networkController.getProviderConfig().type + return keyring } -- cgit From b77cc3d9690304362471fbfe207bdad461c2ca3a Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 03:42:23 -0400 Subject: fix tx tests --- app/scripts/controllers/transactions/index.js | 4 ++-- app/scripts/metamask-controller.js | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'app') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 7ac6ec2e6..8e2288aed 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -289,10 +289,10 @@ class TransactionController extends EventEmitter { // sign tx const fromAddress = txParams.from const ethTx = new Transaction(txParams) - const signedTx = await this.signEthTx(ethTx, fromAddress) + await this.signEthTx(ethTx, fromAddress) // set state to signed this.txStateManager.setTxStatusSigned(txMeta.id) - const rawTx = ethUtil.bufferToHex(signedTx.serialize()) + const rawTx = ethUtil.bufferToHex(ethTx.serialize()) return rawTx } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b1473390b..c813c58ac 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -549,14 +549,14 @@ module.exports = class MetamaskController extends EventEmitter { keyringName = LedgerBridgeKeyring.type break default: - throw new Error('MetamaskController:connectHardware - Unknown device') + throw new Error('MetamaskController:getKeyringForDevice - Unknown device') } let keyring = await this.keyringController.getKeyringsByType(keyringName)[0] if (!keyring) { keyring = await this.keyringController.addNewKeyring(keyringName) } - if (hdPath) { - keyring.hdPath = hdPath + if (hdPath && keyring.setHdPath) { + keyring.setHdPath(hdPath) } keyring.network = this.networkController.getProviderConfig().type -- cgit From c72ced79aeb3d79d12ae240b680e0561122e5209 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 19:22:00 -0400 Subject: ui fixes --- app/_locales/en/messages.json | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index a4ee97525..36a7a0d13 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -119,8 +119,8 @@ "close": { "message": "Close" }, - "chromeRequiredForTrezor":{ - "message": "You need to use Metamask on Google Chrome in order to connect to your TREZOR device." + "chromeRequiredForHardwareWallets":{ + "message": "You need to use Metamask on Google Chrome in order to connect to your Hardware Wallet." }, "confirm": { "message": "Confirm" @@ -149,11 +149,8 @@ "connectToTrezor": { "message": "Connect to Trezor" }, - "connectToTrezorHelp": { - "message": "Metamask is able to access your TREZOR ethereum accounts. First make sure your device is connected and unlocked." - }, - "connectToTrezorTrouble": { - "message": "If you are having trouble, please make sure you are using the latest version of the TREZOR firmware." + "connectToLedger": { + "message": "Connect to Ledger" }, "continue": { "message": "Continue" @@ -426,11 +423,11 @@ "hardwareWalletConnected": { "message": "Hardware wallet connected" }, - "hardwareSupport": { - "message": "Hardware Support" + "hardwareWallets": { + "message": "Hardware Wallets" }, - "hardwareSupportMsg": { - "message": "You can now view your Hardware accounts in MetaMask! Scroll down and read how it works." + "hardwareWalletsMsg": { + "message": "You can now view your Hardware wallet accounts in MetaMask! Scroll down and read how it works." }, "havingTroubleConnecting": { "message": "Having trouble connecting?" -- cgit From fdf202efb066008f6625ba15cec8bcaef1edfec6 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 21:19:01 -0400 Subject: fixed unit tests --- app/scripts/metamask-controller.js | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'app') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c813c58ac..ac188c4df 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -551,15 +551,21 @@ module.exports = class MetamaskController extends EventEmitter { default: throw new Error('MetamaskController:getKeyringForDevice - Unknown device') } + console.log('getting keyring for device ', deviceName, hdPath) let keyring = await this.keyringController.getKeyringsByType(keyringName)[0] + console.log('got Keyring', keyring) if (!keyring) { + console.log('we did not so lets add it', keyringName) keyring = await this.keyringController.addNewKeyring(keyringName) + console.log('what about now', keyring) } + console.log('setting hdPath', hdPath) if (hdPath && keyring.setHdPath) { keyring.setHdPath(hdPath) } keyring.network = this.networkController.getProviderConfig().type + console.log('setting network', keyring.network) return keyring -- cgit From 82a5ed1e03e0a6a9bc19b946bc178b236d9aaa98 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 14 Aug 2018 22:40:29 -0400 Subject: remove console logs --- app/scripts/metamask-controller.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'app') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ac188c4df..1e1aa035f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -551,21 +551,15 @@ module.exports = class MetamaskController extends EventEmitter { default: throw new Error('MetamaskController:getKeyringForDevice - Unknown device') } - console.log('getting keyring for device ', deviceName, hdPath) let keyring = await this.keyringController.getKeyringsByType(keyringName)[0] - console.log('got Keyring', keyring) if (!keyring) { - console.log('we did not so lets add it', keyringName) keyring = await this.keyringController.addNewKeyring(keyringName) - console.log('what about now', keyring) } - console.log('setting hdPath', hdPath) if (hdPath && keyring.setHdPath) { keyring.setHdPath(hdPath) } keyring.network = this.networkController.getProviderConfig().type - console.log('setting network', keyring.network) return keyring @@ -635,7 +629,7 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController.setAddresses(newAccounts) newAccounts.forEach(address => { if (!oldAccounts.includes(address)) { - this.preferencesController.setAccountLabel(address, `${deviceName.toUpperCase()} #${parseInt(index, 10) + 1}`) + this.preferencesController.setAccountLabel(address, `${deviceName.toUpperCase()} ${parseInt(index, 10) + 1}`) this.preferencesController.setSelectedAddress(address) } }) -- cgit From 837be704f531b0b8851d285f269ed48849b0a425 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Wed, 15 Aug 2018 11:44:00 -0400 Subject: change Metamask for MetaMask --- app/_locales/en/messages.json | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 1614ede81..515f8becb 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -120,7 +120,7 @@ "message": "Close" }, "chromeRequiredForHardwareWallets":{ - "message": "You need to use Metamask on Google Chrome in order to connect to your Hardware Wallet." + "message": "You need to use MetaMask on Google Chrome in order to connect to your Hardware Wallet." }, "confirm": { "message": "Confirm" @@ -152,12 +152,6 @@ "connectToTrezor": { "message": "Connect to Trezor" }, - "connectToTrezorHelp": { - "message": "MetaMask is able to access your TREZOR Ethereum accounts. First make sure your device is connected and unlocked." - }, - "connectToTrezorTrouble": { - "message": "If you are having trouble, please make sure you are using the latest version of the TREZOR firmware." - }, "continue": { "message": "Continue" }, -- cgit From 2ea05e303dedfd75cad6fdfddfa82da2ee32e92d Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Thu, 16 Aug 2018 19:39:52 -0400 Subject: connect screen ready --- app/_locales/en/messages.json | 10 +++++----- app/images/ledger-logo.svg | 1 + app/images/trezor-logo.svg | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 app/images/ledger-logo.svg create mode 100644 app/images/trezor-logo.svg (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 515f8becb..52d11ff1c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -286,8 +286,8 @@ "downloadStateLogs": { "message": "Download State Logs" }, - "dontHaveATrezorWallet": { - "message": "Don't have a TREZOR hardware wallet?" + "dontHaveAHardwareWallet": { + "message": "Don’t have a hardware wallet?" }, "dropped": { "message": "Dropped" @@ -424,10 +424,10 @@ "message": "Hardware wallet connected" }, "hardwareWallets": { - "message": "Hardware Wallets" + "message": "Connect a hardware wallet" }, "hardwareWalletsMsg": { - "message": "You can now view your Hardware wallet accounts in MetaMask! Scroll down and read how it works." + "message": "Select a hardware wallet you'd like to use with MetaMask" }, "havingTroubleConnecting": { "message": "Having trouble connecting?" @@ -908,7 +908,7 @@ "description": "displays token symbol" }, "orderOneHere": { - "message": "Order one here." + "message": "Order a Trezor or Ledger and keep your funds in cold storage" }, "searchTokens": { "message": "Search Tokens" diff --git a/app/images/ledger-logo.svg b/app/images/ledger-logo.svg new file mode 100644 index 000000000..21b99d0e5 --- /dev/null +++ b/app/images/ledger-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/images/trezor-logo.svg b/app/images/trezor-logo.svg new file mode 100644 index 000000000..b8d85e3af --- /dev/null +++ b/app/images/trezor-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file -- cgit From 285814646fa147560ffe6a8903eec7220eff09bb Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Thu, 16 Aug 2018 20:41:23 -0400 Subject: ui ready --- app/_locales/en/messages.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'app') diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 52d11ff1c..a25a2bd59 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -920,10 +920,13 @@ "message": "Select an Account" }, "selectAnAccountHelp": { - "message": "These are the accounts available in your hardware wallet. Select the one you’d like to use in MetaMask." + "message": "Select the account to view in MetaMask" + }, + "selectHdPath": { + "message": "Select HD Path" }, "selectPathHelp": { - "message": "If you don't see your existing Ledger address(es), please try selecting a different HD Path \"Legacy (MEW / MyCrypto)\"" + "message": "If you don't see your existing Ledger accounts below, try switching paths to \"Legacy (MEW / MyCrypto)\"" }, "sendTokensAnywhere": { "message": "Send Tokens to anyone with an Ethereum account" -- cgit From e1c6306f838177249b96ab20882968f521a5b59f Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 25 Sep 2018 11:57:28 -0400 Subject: bump to v4.11.1 --- app/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app') diff --git a/app/manifest.json b/app/manifest.json index 086d5ba00..23085e1fd 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "4.9.3", + "version": "4.11.1", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", -- cgit