From 8763ea898e7838d08315063b0e2181405a2ae3d5 Mon Sep 17 00:00:00 2001 From: Bruno Date: Wed, 13 Jun 2018 01:32:13 -0400 Subject: [PATCH] move TrezorKeyring to its own package --- app/scripts/lib/trezor-connect.js | 1138 ----------------- app/scripts/lib/trezorKeyring.js | 239 ---- app/scripts/metamask-controller.js | 5 +- package-lock.json | 46 + package.json | 2 +- .../pages/create-account/connect-hardware.js | 6 +- 6 files changed, 53 insertions(+), 1383 deletions(-) delete mode 100644 app/scripts/lib/trezor-connect.js delete mode 100644 app/scripts/lib/trezorKeyring.js diff --git a/app/scripts/lib/trezor-connect.js b/app/scripts/lib/trezor-connect.js deleted file mode 100644 index 574e88104..000000000 --- a/app/scripts/lib/trezor-connect.js +++ /dev/null @@ -1,1138 +0,0 @@ -/* eslint-disable */ -/* prettier-ignore */ - -/** - * (C) 2017 SatoshiLabs - * - * GPLv3 - */ -var TREZOR_CONNECT_VERSION = 4; - -if (!Array.isArray) { - Array.isArray = function(arg) { - return Object.prototype.toString.call(arg) === "[object Array]"; - }; -} - -var HD_HARDENED = 0x80000000; - -// react sometimes adds some other parameters that should not be there -function _fwStrFix(obj, fw) { - if (typeof fw === "string") { - obj.requiredFirmware = fw; - } - return obj; -} - -("use strict"); - -var chrome = window.chrome; -var IS_CHROME_APP = chrome && chrome.app && chrome.app.window; - -var ERR_TIMED_OUT = "Loading timed out"; -var ERR_WINDOW_CLOSED = "Window closed"; -var ERR_WINDOW_BLOCKED = "Window blocked"; -var ERR_ALREADY_WAITING = "Already waiting for a response"; -var ERR_CHROME_NOT_CONNECTED = "Internal Chrome popup is not responding."; - -var DISABLE_LOGIN_BUTTONS = window.TREZOR_DISABLE_LOGIN_BUTTONS || false; -var CHROME_URL = window.TREZOR_CHROME_URL || "./chrome/wrapper.html"; -var POPUP_ORIGIN = window.TREZOR_POPUP_ORIGIN || "https://connect.trezor.io"; -var POPUP_PATH = - window.TREZOR_POPUP_PATH || POPUP_ORIGIN + "/" + TREZOR_CONNECT_VERSION; -var POPUP_URL = - window.TREZOR_POPUP_URL || - POPUP_PATH + "/popup/popup.html?v=" + new Date().getTime(); - -var POPUP_INIT_TIMEOUT = 15000; - -/** - * Public API. - */ -function TrezorConnect() { - var manager = new PopupManager(); - - /** - * Popup errors. - */ - this.ERR_TIMED_OUT = ERR_TIMED_OUT; - this.ERR_WINDOW_CLOSED = ERR_WINDOW_CLOSED; - this.ERR_WINDOW_BLOCKED = ERR_WINDOW_BLOCKED; - this.ERR_ALREADY_WAITING = ERR_ALREADY_WAITING; - this.ERR_CHROME_NOT_CONNECTED = ERR_CHROME_NOT_CONNECTED; - - /** - * Open the popup for further communication. All API functions open the - * popup automatically, but if you need to generate some parameters - * asynchronously, use `open` first to avoid popup blockers. - * @param {function(?Error)} callback - */ - this.open = function(callback) { - var onchannel = function(result) { - if (result instanceof Error) { - callback(result); - } else { - callback(); - } - }; - manager.waitForChannel(onchannel); - }; - - /** - * Close the opened popup, if any. - */ - this.close = function() { - manager.close(); - }; - - /** - * Enable or disable closing the opened popup after a successful call. - * @param {boolean} value - */ - this.closeAfterSuccess = function(value) { - manager.closeAfterSuccess = value; - }; - - /** - * Enable or disable closing the opened popup after a failed call. - * @param {boolean} value - */ - this.closeAfterFailure = function(value) { - manager.closeAfterFailure = value; - }; - - /** - * Set bitcore server - * @param {string|Array} value - */ - this.setBitcoreURLS = function(value) { - if (typeof value === "string") { - manager.bitcoreURLS = [value]; - } else if (value instanceof Array) { - manager.bitcoreURLS = value; - } - }; - - /** - * Set currency. Human readable coin name - * @param {string|Array} value - */ - this.setCurrency = function(value) { - if (typeof value === "string") { - manager.currency = value; - } - }; - - /** - * Set currency units (mBTC, BTC) - * @param {string|Array} value - */ - this.setCurrencyUnits = function(value) { - if (typeof value === "string") { - manager.currencyUnits = value; - } - }; - - /** - * Set coin info json url - * @param {string|Array} value - */ - this.setCoinInfoURL = function(value) { - if (typeof value === "string") { - manager.coinInfoURL = value; - } - }; - - /** - * Set max. limit for account discovery - * @param {number} value - */ - this.setAccountDiscoveryLimit = function(value) { - if (!isNaN(value)) manager.accountDiscoveryLimit = value; - }; - - /** - * Set max. gap for account discovery - * @param {number} value - */ - this.setAccountDiscoveryGapLength = function(value) { - if (!isNaN(value)) manager.accountDiscoveryGapLength = value; - }; - - /** - * Set discovery BIP44 coin type - * @param {number} value - */ - this.setAccountDiscoveryBip44CoinType = function(value) { - if (!isNaN(value)) manager.accountDiscoveryBip44CoinType = value; - }; - - /** - * @typedef XPubKeyResult - * @param {boolean} success - * @param {?string} error - * @param {?string} xpubkey serialized extended public key - * @param {?string} path BIP32 serializd path of the key - */ - - /** - * Load BIP32 extended public key by path. - * - * Path can be specified either in the string form ("m/44'/1/0") or as - * raw integer array. In case you omit the path, user is asked to select - * a BIP32 account to export, and the result contains m/44'/0'/x' node - * of the account. - * - * @param {?(string|array)} path - * @param {function(XPubKeyResult)} callback - * @param {?(string|array)} requiredFirmware - */ - this.getXPubKey = function(path, callback, requiredFirmware) { - if (typeof path === "string") { - path = parseHDPath(path); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "xpubkey", - path: path - }, - requiredFirmware - ), - callback - ); - }; - - this.getFreshAddress = function(callback, requiredFirmware) { - var wrapperCallback = function(result) { - if (result.success) { - callback({ success: true, address: result.freshAddress }); - } else { - callback(result); - } - }; - - manager.sendWithChannel( - _fwStrFix( - { - type: "accountinfo" - }, - requiredFirmware - ), - wrapperCallback - ); - }; - - this.getAccountInfo = function(input, callback, requiredFirmware) { - try { - manager.sendWithChannel( - _fwStrFix( - { - type: "accountinfo", - description: input - }, - requiredFirmware - ), - callback - ); - } catch (e) { - callback({ success: false, error: e }); - } - }; - - this.getAllAccountsInfo = function(callback, requiredFirmware) { - try { - manager.sendWithChannel( - _fwStrFix( - { - type: "allaccountsinfo", - description: "all" - }, - requiredFirmware - ), - callback - ); - } catch (e) { - callback({ success: false, error: e }); - } - }; - - this.getBalance = function(callback, requiredFirmware) { - manager.sendWithChannel( - _fwStrFix( - { - type: "accountinfo" - }, - requiredFirmware - ), - callback - ); - }; - - /** - * @typedef SignTxResult - * @param {boolean} success - * @param {?string} error - * @param {?string} serialized_tx serialized tx, in hex, including signatures - * @param {?array} signatures array of input signatures, in hex - */ - - /** - * Sign a transaction in the device and return both serialized - * transaction and the signatures. - * - * @param {array} inputs - * @param {array} outputs - * @param {function(SignTxResult)} callback - * @param {?(string|array)} requiredFirmware - * - * @see https://github.com/trezor/trezor-common/blob/master/protob/types.proto - */ - this.signTx = function(inputs, outputs, callback, requiredFirmware, coin) { - manager.sendWithChannel( - _fwStrFix( - { - type: "signtx", - inputs: inputs, - outputs: outputs, - coin: coin - }, - requiredFirmware - ), - callback - ); - }; - - // new implementation with ethereum at beginnig - this.ethereumSignTx = function() { - this.signEthereumTx.apply(this, arguments); - }; - - // old fallback - this.signEthereumTx = function( - address_n, - nonce, - gas_price, - gas_limit, - to, - value, - data, - chain_id, - callback, - requiredFirmware - ) { - if (requiredFirmware == null) { - requiredFirmware = "1.4.0"; // first firmware that supports ethereum - } - if (typeof address_n === "string") { - address_n = parseHDPath(address_n); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "signethtx", - address_n: address_n, - nonce: nonce, - gas_price: gas_price, - gas_limit: gas_limit, - to: to, - value: value, - data: data, - chain_id: chain_id - }, - requiredFirmware - ), - callback - ); - }; - - /** - * @typedef TxRecipient - * @param {number} amount the amount to send, in satoshis - * @param {string} address the address of the recipient - */ - - /** - * Compose a transaction by doing BIP-0044 discovery, letting the user - * select an account, and picking UTXO by internal preferences. - * Transaction is then signed and returned in the same format as - * `signTx`. Only supports BIP-0044 accounts (single-signature). - * - * @param {array} recipients - * @param {function(SignTxResult)} callback - * @param {?(string|array)} requiredFirmware - */ - this.composeAndSignTx = function(recipients, callback, requiredFirmware) { - manager.sendWithChannel( - _fwStrFix( - { - type: "composetx", - recipients: recipients - }, - requiredFirmware - ), - callback - ); - }; - - /** - * @typedef RequestLoginResult - * @param {boolean} success - * @param {?string} error - * @param {?string} public_key public key used for signing, in hex - * @param {?string} signature signature, in hex - */ - - /** - * Sign a login challenge for active origin. - * - * @param {?string} hosticon - * @param {string} challenge_hidden - * @param {string} challenge_visual - * @param {string|function(RequestLoginResult)} callback - * @param {?(string|array)} requiredFirmware - * - * @see https://github.com/trezor/trezor-common/blob/master/protob/messages.proto - */ - this.requestLogin = function( - hosticon, - challenge_hidden, - challenge_visual, - callback, - requiredFirmware - ) { - if (typeof callback === "string") { - // special case for a login through button. - // `callback` is name of global var - callback = window[callback]; - } - if (!callback) { - throw new TypeError("TrezorConnect: login callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "login", - icon: hosticon, - challenge_hidden: challenge_hidden, - challenge_visual: challenge_visual - }, - requiredFirmware - ), - callback - ); - }; - - /** - * @typedef SignMessageResult - * @param {boolean} success - * @param {?string} error - * @param {?string} address address (in base58check) - * @param {?string} signature signature, in base64 - */ - - /** - * Sign a message - * - * @param {string|array} path - * @param {string} message to sign (ascii) - * @param {string|function(SignMessageResult)} callback - * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) - * @param {?(string|array)} requiredFirmware - * - */ - this.signMessage = function( - path, - message, - callback, - opt_coin, - requiredFirmware - ) { - if (typeof path === "string") { - path = parseHDPath(path); - } - if (!opt_coin) { - opt_coin = "Bitcoin"; - } - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "signmsg", - path: path, - message: message, - coin: opt_coin - }, - requiredFirmware - ), - callback - ); - }; - - /** - * Sign an Ethereum message - * - * @param {string|array} path - * @param {string} message to sign (ascii) - * @param {string|function(SignMessageResult)} callback - * @param {?(string|array)} requiredFirmware - * - */ - this.ethereumSignMessage = function( - path, - message, - callback, - requiredFirmware - ) { - if (typeof path === "string") { - path = parseHDPath(path); - } - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "signethmsg", - path: path, - message: message - }, - requiredFirmware - ), - callback - ); - }; - - /** - * Verify message - * - * @param {string} address - * @param {string} signature (base64) - * @param {string} message (string) - * @param {string|function()} callback - * @param {?string} opt_coin - (optional) name of coin (default Bitcoin) - * @param {?(string|array)} requiredFirmware - * - */ - this.verifyMessage = function( - address, - signature, - message, - callback, - opt_coin, - requiredFirmware - ) { - if (!opt_coin) { - opt_coin = "Bitcoin"; - } - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "verifymsg", - address: address, - signature: signature, - message: message, - coin: { coin_name: opt_coin } - }, - requiredFirmware - ), - callback - ); - }; - - /** - * Verify ethereum message - * - * @param {string} address - * @param {string} signature (base64) - * @param {string} message (string) - * @param {string|function()} callback - * @param {?(string|array)} requiredFirmware - * - */ - this.ethereumVerifyMessage = function( - address, - signature, - message, - callback, - requiredFirmware - ) { - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "verifyethmsg", - address: address, - signature: signature, - message: message - }, - requiredFirmware - ), - callback - ); - }; - - /** - * Symmetric key-value encryption - * - * @param {string|array} path - * @param {string} key to show on device display - * @param {string} value hexadecimal value, length a multiple of 16 bytes - * @param {boolean} encrypt / decrypt direction - * @param {boolean} ask_on_encrypt (should user confirm on encrypt?) - * @param {boolean} ask_on_decrypt (should user confirm on decrypt?) - * @param {string|function()} callback - * @param {?(string|array)} requiredFirmware - * - */ - this.cipherKeyValue = function( - path, - key, - value, - encrypt, - ask_on_encrypt, - ask_on_decrypt, - callback, - requiredFirmware - ) { - if (typeof path === "string") { - path = parseHDPath(path); - } - if (typeof value !== "string") { - throw new TypeError("TrezorConnect: Value must be a string"); - } - if (!/^[0-9A-Fa-f]*$/.test(value)) { - throw new TypeError("TrezorConnect: Value must be hexadecimal"); - } - if (value.length % 32 !== 0) { - // 1 byte == 2 hex strings - throw new TypeError( - "TrezorConnect: Value length must be multiple of 16 bytes" - ); - } - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "cipherkeyvalue", - path: path, - key: key, - value: value, - encrypt: !!encrypt, - ask_on_encrypt: !!ask_on_encrypt, - ask_on_decrypt: !!ask_on_decrypt - }, - requiredFirmware - ), - callback - ); - }; - - this.nemGetAddress = function( - address_n, - network, - callback, - requiredFirmware - ) { - if (requiredFirmware == null) { - requiredFirmware = "1.6.0"; // first firmware that supports NEM - } - if (typeof address_n === "string") { - address_n = parseHDPath(address_n); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "nemGetAddress", - address_n: address_n, - network: network - }, - requiredFirmware - ), - callback - ); - }; - - this.nemSignTx = function( - address_n, - transaction, - callback, - requiredFirmware - ) { - if (requiredFirmware == null) { - requiredFirmware = "1.6.0"; // first firmware that supports NEM - } - if (typeof address_n === "string") { - address_n = parseHDPath(address_n); - } - manager.sendWithChannel( - _fwStrFix( - { - type: "nemSignTx", - address_n: address_n, - transaction: transaction - }, - requiredFirmware - ), - callback - ); - }; - - this.pushTransaction = function(rawTx, callback) { - if (!/^[0-9A-Fa-f]*$/.test(rawTx)) { - throw new TypeError("TrezorConnect: Transaction must be hexadecimal"); - } - if (!callback) { - throw new TypeError("TrezorConnect: callback not found"); - } - - manager.sendWithChannel( - { - type: "pushtx", - rawTx: rawTx - }, - callback - ); - }; - - /** - * Display address on device - * - * @param {array} address - * @param {string} coin - * @param {boolean} segwit - * @param {?(string|array)} requiredFirmware - * - */ - this.getAddress = function( - address, - coin, - segwit, - callback, - requiredFirmware - ) { - if (typeof address === "string") { - address = parseHDPath(address); - } - - manager.sendWithChannel( - _fwStrFix( - { - type: "getaddress", - address_n: address, - coin: coin, - segwit: segwit - }, - requiredFirmware - ), - callback - ); - }; - - /** - * Display ethereum address on device - * - * @param {array} address - * @param {?(string|array)} requiredFirmware - * - */ - this.ethereumGetAddress = function(address, callback, requiredFirmware) { - if (typeof address === "string") { - address = parseHDPath(address); - } - - manager.sendWithChannel( - _fwStrFix( - { - type: "ethgetaddress", - address_n: address - }, - requiredFirmware - ), - callback - ); - }; - - var LOGIN_CSS = - ''; - - var LOGIN_ONCLICK = - "TrezorConnect.requestLogin(" + - "'@hosticon@','@challenge_hidden@','@challenge_visual@','@callback@'" + - ")"; - - var LOGIN_HTML = - '
' + - ' ' + - ' ' + - ' @text@' + - " " + - ' ' + - ' What is TREZOR?' + - " " + - "
"; - - /** - * Find elements and replace them with login buttons. - * It's not required to use these special elements, feel free to call - * `TrezorConnect.requestLogin` directly. - */ - this.renderLoginButtons = function() { - var elements = document.getElementsByTagName("trezor:login"); - - for (var i = 0; i < elements.length; i++) { - var e = elements[i]; - var text = e.getAttribute("text") || "Sign in with TREZOR"; - var callback = e.getAttribute("callback") || ""; - var hosticon = e.getAttribute("icon") || ""; - var challenge_hidden = e.getAttribute("challenge_hidden") || ""; - var challenge_visual = e.getAttribute("challenge_visual") || ""; - - // it's not valid to put markup into attributes, so let users - // supply a raw text and make TREZOR bold - text = text.replace("TREZOR", "TREZOR"); - e.outerHTML = (LOGIN_CSS + LOGIN_HTML) - .replace("@text@", text) - .replace("@callback@", callback) - .replace("@hosticon@", hosticon) - .replace("@challenge_hidden@", challenge_hidden) - .replace("@challenge_visual@", challenge_visual) - .replace("@connect_path@", POPUP_PATH); - } - }; -} - -/* - * `getXPubKey()` - */ - -function parseHDPath(string) { - return string - .toLowerCase() - .split("/") - .filter(function(p) { - return p !== "m"; - }) - .map(function(p) { - var hardened = false; - if (p[p.length - 1] === "'") { - hardened = true; - p = p.substr(0, p.length - 1); - } - if (isNaN(p)) { - throw new Error("Not a valid path."); - } - var n = parseInt(p); - if (hardened) { - // hardened index - n = (n | 0x80000000) >>> 0; - } - return n; - }); -} - -/* - * Popup management - */ - -function ChromePopup(url, name, width, height) { - var left = (screen.width - width) / 2; - var top = (screen.height - height) / 2; - var opts = { - id: name, - innerBounds: { - width: width, - height: height, - left: left, - top: top - } - }; - - var closed = function() { - if (this.onclose) { - this.onclose(false); // never report as blocked - } - }.bind(this); - - var opened = function(w) { - this.window = w; - this.window.onClosed.addListener(closed); - }.bind(this); - - chrome.app.window.create(url, opts, opened); - - this.name = name; - this.window = null; - this.onclose = null; -} - -function ChromeChannel(popup, waiting) { - var port = null; - - var respond = function(data) { - if (waiting) { - var w = waiting; - waiting = null; - w(data); - } - }; - - var setup = function(p) { - if (p.name === popup.name) { - port = p; - port.onMessage.addListener(respond); - chrome.runtime.onConnect.removeListener(setup); - } - }; - - chrome.runtime.onConnect.addListener(setup); - - this.respond = respond; - - this.close = function() { - chrome.runtime.onConnect.removeListener(setup); - port.onMessage.removeListener(respond); - port.disconnect(); - port = null; - }; - - this.send = function(value, callback) { - if (waiting === null) { - waiting = callback; - - if (port) { - port.postMessage(value); - } else { - throw new Error(ERR_CHROME_NOT_CONNECTED); - } - } else { - throw new Error(ERR_ALREADY_WAITING); - } - }; -} - -function Popup(url, origin, name, width, height) { - var left = (screen.width - width) / 2; - var top = (screen.height - height) / 2; - var opts = - "width=" + - width + - ",height=" + - height + - ",left=" + - left + - ",top=" + - top + - ",menubar=no" + - ",toolbar=no" + - ",location=no" + - ",personalbar=no" + - ",status=no"; - var w = window.open(url, name, opts); - - var interval; - var blocked = w.closed; - var iterate = function() { - if (w.closed) { - clearInterval(interval); - if (this.onclose) { - this.onclose(blocked); - } - } - }.bind(this); - interval = setInterval(iterate, 100); - - this.window = w; - this.origin = origin; - this.onclose = null; -} - -function Channel(popup, waiting) { - var respond = function(data) { - if (waiting) { - var w = waiting; - waiting = null; - w(data); - } - }; - - var receive = function(event) { - var org1 = event.origin.match(/^.+\:\/\/[^\‌​/]+/)[0]; - var org2 = popup.origin.match(/^.+\:\/\/[^\‌​/]+/)[0]; - //if (event.source === popup.window && event.origin === popup.origin) { - if (event.source === popup.window && org1 === org2) { - respond(event.data); - } - }; - - window.addEventListener("message", receive); - - this.respond = respond; - - this.close = function() { - window.removeEventListener("message", receive); - }; - - this.send = function(value, callback) { - if (waiting === null) { - waiting = callback; - popup.window.postMessage(value, popup.origin); - } else { - throw new Error(ERR_ALREADY_WAITING); - } - }; -} - -function ConnectedChannel(p) { - var ready = function() { - clearTimeout(this.timeout); - this.popup.onclose = null; - this.ready = true; - this.onready(); - }.bind(this); - - var closed = function(blocked) { - clearTimeout(this.timeout); - this.channel.close(); - if (blocked) { - this.onerror(new Error(ERR_WINDOW_BLOCKED)); - } else { - this.onerror(new Error(ERR_WINDOW_CLOSED)); - } - }.bind(this); - - var timedout = function() { - this.popup.onclose = null; - if (this.popup.window) { - this.popup.window.close(); - } - this.channel.close(); - this.onerror(new Error(ERR_TIMED_OUT)); - }.bind(this); - - if (IS_CHROME_APP) { - this.popup = new ChromePopup(p.chromeUrl, p.name, p.width, p.height); - this.channel = new ChromeChannel(this.popup, ready); - } else { - this.popup = new Popup(p.url, p.origin, p.name, p.width, p.height); - this.channel = new Channel(this.popup, ready); - } - - this.timeout = setTimeout(timedout, POPUP_INIT_TIMEOUT); - - this.popup.onclose = closed; - - this.ready = false; - this.onready = null; - this.onerror = null; -} - -function PopupManager() { - var cc = null; - - var closed = function() { - cc.channel.respond(new Error(ERR_WINDOW_CLOSED)); - cc.channel.close(); - cc = null; - }; - - var open = function(callback) { - cc = new ConnectedChannel({ - name: "trezor-connect", - width: 600, - height: 500, - origin: POPUP_ORIGIN, - path: POPUP_PATH, - url: POPUP_URL, - chromeUrl: CHROME_URL - }); - cc.onready = function() { - cc.popup.onclose = closed; - callback(cc.channel); - }; - cc.onerror = function(error) { - cc = null; - callback(error); - }; - }.bind(this); - - this.closeAfterSuccess = true; - this.closeAfterFailure = true; - - this.close = function() { - if (cc && cc.popup.window) { - cc.popup.window.close(); - } - }; - - this.waitForChannel = function(callback) { - if (cc) { - if (cc.ready) { - callback(cc.channel); - } else { - callback(new Error(ERR_ALREADY_WAITING)); - } - } else { - try { - open(callback); - } catch (e) { - callback(new Error(ERR_WINDOW_BLOCKED)); - } - } - }; - - this.sendWithChannel = function(message, callback) { - message.bitcoreURLS = this.bitcoreURLS || null; - message.currency = this.currency || null; - message.currencyUnits = this.currencyUnits || null; - message.coinInfoURL = this.coinInfoURL || null; - message.accountDiscoveryLimit = this.accountDiscoveryLimit || null; - message.accountDiscoveryGapLength = this.accountDiscoveryGapLength || null; - message.accountDiscoveryBip44CoinType = - this.accountDiscoveryBip44CoinType || null; - - var respond = function(response) { - var succ = response.success && this.closeAfterSuccess; - var fail = !response.success && this.closeAfterFailure; - if (succ || fail) { - this.close(); - } - callback(response); - }.bind(this); - - var onresponse = function(response) { - if (response instanceof Error) { - var error = response; - respond({ success: false, error: error.message }); - } else { - respond(response); - } - }; - - var onchannel = function(channel) { - if (channel instanceof Error) { - var error = channel; - respond({ success: false, error: error.message }); - } else { - channel.send(message, onresponse); - } - }; - - this.waitForChannel(onchannel); - }; -} - -const connect = new TrezorConnect(); -module.exports = connect; \ No newline at end of file diff --git a/app/scripts/lib/trezorKeyring.js b/app/scripts/lib/trezorKeyring.js deleted file mode 100644 index 6a483fdcd..000000000 --- a/app/scripts/lib/trezorKeyring.js +++ /dev/null @@ -1,239 +0,0 @@ -const { EventEmitter } = require('events') -const ethUtil = require('ethereumjs-util') -const sigUtil = require('eth-sig-util') - -const hdPathString = `m/44'/60'/0'/0` -const keyringType = 'Trezor Hardware' -const Transaction = require('ethereumjs-tx') -const pathBase = 'm' -const TrezorConnect = require('./trezor-connect.js') -const HDKey = require('hdkey') -const TREZOR_MIN_FIRMWARE_VERSION = '1.5.2' -const log = require('loglevel') - -class TrezorKeyring extends EventEmitter { - constructor (opts = {}) { - super() - this.type = keyringType - this.accounts = [] - this.hdk = new HDKey() - this.deserialize(opts) - this.page = 0 - this.perPage = 5 - this.unlockedAccount = 0 - } - - serialize () { - return Promise.resolve({ - hdPath: this.hdPath, - accounts: this.accounts, - page: this.page, - }) - } - - deserialize (opts = {}) { - this.hdPath = opts.hdPath || hdPathString - this.accounts = opts.accounts || [] - this.page = opts.page || 0 - return Promise.resolve() - } - - unlock () { - - if (this.hdk.publicKey) return Promise.resolve() - - return new Promise((resolve, reject) => { - TrezorConnect.getXPubKey( - this.hdPath, - response => { - if (response.success) { - this.hdk.publicKey = new Buffer(response.publicKey, 'hex') - this.hdk.chainCode = new Buffer(response.chainCode, 'hex') - resolve() - } else { - reject(response.error || 'Unknown error') - } - }, - TREZOR_MIN_FIRMWARE_VERSION - ) - }) - } - - setAccountToUnlock (index) { - this.unlockedAccount = parseInt(index, 10) - } - - addAccounts (n = 1) { - - return new Promise((resolve, reject) => { - return this.unlock() - .then(_ => { - const from = this.unlockedAccount - const to = from + 1 - this.accounts = [] - - for (let i = from; i < to; i++) { - - this.accounts.push(this._addressFromId(pathBase, i)) - this.page = 0 - } - resolve(this.accounts) - }) - .catch(e => { - reject(e) - }) - }) - } - - getPage () { - - return new Promise((resolve, reject) => { - return this.unlock() - .then(_ => { - - const from = this.page === 0 ? 0 : (this.page - 1) * this.perPage - const to = from + this.perPage - - const accounts = [] - - for (let i = from; i < to; i++) { - - accounts.push({ - address: this._addressFromId(pathBase, i), - balance: 0, - index: i, - }) - } - log.debug(accounts) - resolve(accounts) - }) - .catch(e => { - reject(e) - }) - }) - } - - async getPrevAccountSet () { - this.page-- - return await this.getPage() - } - - async getNextAccountSet () { - this.page++ - return await this.getPage() - } - - getAccounts () { - return Promise.resolve(this.accounts.slice()) - } - - // tx is an instance of the ethereumjs-transaction class. - async signTransaction (address, tx) { - - return new Promise((resolve, reject) => { - - log.debug('sign transaction ', address, tx) - - TrezorConnect.ethereumSignTx( - this._getUnlockedAccount(), - this._normalize(tx.nonce), - this._normalize(tx.gasPrice), - this._normalize(tx.gasLimit), - this._normalize(tx.to), - this._normalize(tx.value), - this._normalize(tx.data), - tx._chainId, - response => { - if (response.success) { - - tx.v = `0x${response.v.toString(16)}` - tx.r = `0x${response.r}` - tx.s = `0x${response.s}` - log.debug('about to create new tx with data', tx) - - const signedTx = new Transaction(tx) - - const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) - const correctAddress = ethUtil.toChecksumAddress(address) - if (addressSignedWith !== correctAddress) { - log.error('signature doesnt match the right address', addressSignedWith, correctAddress) - throw new Error('signature doesnt match the right address') - } - - resolve(signedTx) - - } else { - throw new Error(response.error || 'Unknown error') - } - }, - TREZOR_MIN_FIRMWARE_VERSION) - }) - } - - 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) { - - TrezorConnect.ethereumSignMessage(this._getUnlockedAccount(), message, response => { - if (response.success) { - - const signature = this._personalToRawSig(response.signature) - const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature}) - const correctAddress = ethUtil.toChecksumAddress(withAccount) - if (addressSignedWith !== correctAddress) { - log.error('signature doesnt match the right address', addressSignedWith, correctAddress) - throw new Error('signature doesnt match the right address') - } - return signature - - } else { - throw new Error(response.error || 'Unknown error') - } - - }, TREZOR_MIN_FIRMWARE_VERSION) - } - - async signTypedData (withAccount, typedData) { - // Waiting on trezor to enable this - throw new Error('Not supported on this device') - } - - async exportAccount (address) { - throw new Error('Not supported on this device') - } - - _padLeftEven (hex) { - return hex.length % 2 !== 0 ? `0${hex}` : hex - } - - _normalize (buf) { - return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) - } - - _addressFromId (pathBase, i) { - const dkey = this.hdk.derive(`${pathBase}/${i}`) - const address = ethUtil - .publicToAddress(dkey.publicKey, true) - .toString('hex') - return ethUtil.toChecksumAddress(address) - } - - _getUnlockedAccount () { - return `${this.hdPath}/${this.unlockedAccount}` - } - - _personalToRawSig (signature) { - var v = signature['v'] - 27 - v = v.toString(16) - if (v.length < 2) { - v = '0' + v - } - return '0x' + signature['r'] + signature['s'] + v - } -} - -TrezorKeyring.type = keyringType -module.exports = TrezorKeyring diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index daab5baa5..abe7ff8a2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -48,7 +48,7 @@ const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const cleanErrorStack = require('./lib/cleanErrorStack') const DiagnosticsReporter = require('./lib/diagnostics-reporter') const log = require('loglevel') -const TrezorKeyring = require('./lib/trezorKeyring') +const TrezorKeyring = require('eth-trezor-keyring') module.exports = class MetamaskController extends EventEmitter { @@ -549,7 +549,8 @@ module.exports = class MetamaskController extends EventEmitter { throw new Error('MetamaskController - No Trezor Hardware Keyring found') } - const accounts = page === -1 ? await keyring.getPrevAccountSet(this.provider) : await keyring.getNextAccountSet(this.provider) + const accounts = await keyring.getPage(page) + this.accountTracker.syncWithAddresses(accounts.map(a => a.address)) return accounts diff --git a/package-lock.json b/package-lock.json index e8df9cc11..0fdbc02d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8488,6 +8488,52 @@ } } }, + "eth-trezor-keyring": { + "version": "github:brunobar79/eth-trezor-keyring#c138d26c36a01f15be5e12a81e4fcbf56b044fa4", + "from": "github:brunobar79/eth-trezor-keyring#c138d26c36a01f15be5e12a81e4fcbf56b044fa4", + "requires": { + "eth-sig-util": "^1.4.2", + "ethereumjs-tx": "^1.3.4", + "ethereumjs-util": "^5.1.5", + "events": "^2.0.0", + "hdkey": "0.8.0" + }, + "dependencies": { + "ethereum-common": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz", + "integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8=" + }, + "ethereumjs-tx": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.4.tgz", + "integrity": "sha512-kOgUd5jC+0tgV7t52UDECMMz9Uf+Lro+6fSpCvzWemtXfMEcwI3EOxf5mVPMRbTFkMMhuERokNNVF3jItAjidg==", + "requires": { + "ethereum-common": "^0.0.18", + "ethereumjs-util": "^5.0.0" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "^4.11.0", + "create-hash": "^1.1.2", + "ethjs-util": "^0.1.3", + "keccak": "^1.0.2", + "rlp": "^2.0.0", + "safe-buffer": "^5.1.1", + "secp256k1": "^3.0.1" + } + }, + "events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==" + } + } + }, "eth-tx-summary": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/eth-tx-summary/-/eth-tx-summary-3.2.1.tgz", diff --git a/package.json b/package.json index 830382590..fbe79b234 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "eth-query": "^2.1.2", "eth-sig-util": "^1.4.2", "eth-token-tracker": "^1.1.4", + "eth-trezor-keyring": "github:brunobar79/eth-trezor-keyring#c138d26c36a01f15be5e12a81e4fcbf56b044fa4", "ethereumjs-abi": "^0.6.4", "ethereumjs-tx": "^1.3.0", "ethereumjs-util": "github:ethereumjs/ethereumjs-util#ac5d0908536b447083ea422b435da27f26615de9", @@ -124,7 +125,6 @@ "gulp-eslint": "^4.0.0", "gulp-sass": "^4.0.0", "hat": "0.0.3", - "hdkey": "0.8.0", "human-standard-token-abi": "^1.0.2", "idb-global": "^2.1.0", "identicon.js": "^2.3.1", diff --git a/ui/app/components/pages/create-account/connect-hardware.js b/ui/app/components/pages/create-account/connect-hardware.js index 6f1e03550..152a4f275 100644 --- a/ui/app/components/pages/create-account/connect-hardware.js +++ b/ui/app/components/pages/create-account/connect-hardware.js @@ -25,10 +25,10 @@ class ConnectHardwareForm extends Component { return null } this.setState({ btnText: 'Connecting...' }) - this.getPage() + this.getPage(1) } - getPage (page = 1) { + getPage (page) { this.props .connectHardware('trezor', page) .then(accounts => { @@ -133,7 +133,7 @@ class ConnectHardwareForm extends Component { h( 'button.btn-primary.hw-list-pagination__button', { - onClick: () => this.getPage(), + onClick: () => this.getPage(1), }, 'Next >' ),