From c8ef00c8948b3eacf715c5e8be714fdc741d61f6 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sun, 5 Aug 2018 02:45:22 -0400 Subject: [PATCH 01/11] getPublicKey and sign transaction working --- index.js | 127 +++++++++++++++++++++++++-------------------------- package.json | 3 +- 2 files changed, 64 insertions(+), 66 deletions(-) diff --git a/index.js b/index.js index 46bdd9e..c70f827 100644 --- a/index.js +++ b/index.js @@ -3,11 +3,10 @@ const ethUtil = require('ethereumjs-util') const sigUtil = require('eth-sig-util') const Transaction = require('ethereumjs-tx') const HDKey = require('hdkey') -const TrezorConnect = require('./trezor-connect.js') +const TrezorConnect = require('trezor-connect').default const hdPathString = `m/44'/60'/0'/0` const keyringType = 'Trezor Hardware' const pathBase = 'm' -const TREZOR_MIN_FIRMWARE_VERSION = '1.5.2' const MAX_INDEX = 1000 const DELAY_BETWEEN_POPUPS = 1000 @@ -48,23 +47,23 @@ class TrezorKeyring extends EventEmitter { } unlock () { - if (this.isUnlocked()) return Promise.resolve('already unlocked') - 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') + TrezorConnect.getPublicKey({ + path: this.hdPath, + coin: "ETH", + }).then(response => { + if(response.success){ + this.hdk.publicKey = new Buffer(response.payload.publicKey, 'hex') + this.hdk.chainCode = new Buffer(response.payload.chainCode, 'hex') resolve('just unlocked') } else { - reject(response.error || 'Unknown error') + reject(response.payload && response.payload.error || 'Unknown error') } - }, - TREZOR_MIN_FIRMWARE_VERSION - ) + }).catch(e => { + console.log('Error while trying to get public keys ', e) + reject(e && e.toString() || 'Unknown error') + }) }) } @@ -108,7 +107,6 @@ class TrezorKeyring extends EventEmitter { } __getPage (increment) { - this.page += increment if (this.page <= 0) { this.page = 1 } @@ -158,37 +156,40 @@ class TrezorKeyring extends EventEmitter { this.unlock() .then(status => { setTimeout(_ => { - TrezorConnect.ethereumSignTx( - this._pathFromAddress(address), - 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}` - - const signedTx = new Transaction(tx) - - const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) - const correctAddress = ethUtil.toChecksumAddress(address) - if (addressSignedWith !== correctAddress) { - reject('signature doesnt match the right address') - } - - resolve(signedTx) - - } else { - reject(response.error || 'Unknown error') + TrezorConnect.ethereumSignTransaction({ + path: this._pathFromAddress(address), + transaction: { + to: this._normalize(tx.to), + value: this._normalize(tx.value), + data: this._normalize(tx.data), + chanId: tx._chainId, + nonce: this._normalize(tx.nonce), + gasLimit: this._normalize(tx.gasLimit), + gasPrice: this._normalize(tx.gasPrice) + } + }).then( response => { + if(response.success){ + tx.v = response.payload.v + tx.r = response.payload.r + tx.s = response.payload.s + + const signedTx = new Transaction(tx) + + const addressSignedWith = ethUtil.toChecksumAddress(`0x${signedTx.from.toString('hex')}`) + const correctAddress = ethUtil.toChecksumAddress(address) + if (addressSignedWith !== correctAddress) { + reject('signature doesnt match the right address') } - }, - TREZOR_MIN_FIRMWARE_VERSION) + + resolve(signedTx) + } else { + reject(response.payload && response.payload.error || 'Unknown error') + } + + }).catch(e => { + console.log('Error while trying to sign transaction ', e) + reject(e && e.toString() || 'Unknown error') + }) // This is necessary to avoid popup collision // between the unlock & sign trezor popups @@ -209,23 +210,23 @@ class TrezorKeyring extends EventEmitter { .then(status => { setTimeout(_ => { const humanReadableMsg = this._toAscii(message) - TrezorConnect.ethereumSignMessage(this._pathFromAddress(withAccount), humanReadableMsg, response => { - if (response.success) { - - const signature = `0x${response.signature}` - const addressSignedWith = sigUtil.recoverPersonalSignature({data: message, sig: signature}) - - if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { - reject('signature doesnt match the right address') - } - - resolve(signature) - + TrezorConnect.ethereumSignMessage({ + path: this._pathFromAddress(withAccount), + message: humanReadableMsg + }).then (response => { + if(response.success){ + if (response.address !== ethUtil.toChecksumAddress(withAccount)) { + reject('signature doesnt match the right address') + } + const signature = `0x${response.signature}` + resolve(signature) } else { - reject(response.error || 'Unknown error') + reject(response.payload && response.payload.error || 'Unknown error') } - - }, TREZOR_MIN_FIRMWARE_VERSION) + }).catch(e => { + console.log('Error while trying to sign a message ', e) + reject(e && e.toString() || 'Unknown error') + }) // This is necessary to avoid popup collision // between the unlock & sign trezor popups }, status === 'just unlocked' ? DELAY_BETWEEN_POPUPS : 0) @@ -252,12 +253,8 @@ class TrezorKeyring extends EventEmitter { /* PRIVATE METHODS */ - _padLeftEven (hex) { - return hex.length % 2 !== 0 ? `0${hex}` : hex - } - _normalize (buf) { - return this._padLeftEven(ethUtil.bufferToHex(buf).substring(2).toLowerCase()) + return ethUtil.bufferToHex(buf).toString() } _addressFromIndex (pathBase, i) { diff --git a/package.json b/package.json index a4893c7..697b14d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "ethereumjs-tx": "^1.3.4", "ethereumjs-util": "^5.1.5", "events": "^2.0.0", - "hdkey": "0.8.0" + "hdkey": "0.8.0", + "trezor-connect": "^5.0.28" }, "devDependencies": { "assert": "^1.4.1", From 56e9fa6a13d5c6005985c637d4e44eb7ec57148a Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Sun, 5 Aug 2018 02:58:26 -0400 Subject: [PATCH 02/11] signMessage working --- index.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index c70f827..c067689 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,5 @@ const { EventEmitter } = require('events') const ethUtil = require('ethereumjs-util') -const sigUtil = require('eth-sig-util') const Transaction = require('ethereumjs-tx') const HDKey = require('hdkey') const TrezorConnect = require('trezor-connect').default @@ -51,9 +50,9 @@ class TrezorKeyring extends EventEmitter { return new Promise((resolve, reject) => { TrezorConnect.getPublicKey({ path: this.hdPath, - coin: "ETH", + coin: 'ETH', }).then(response => { - if(response.success){ + if (response.success) { this.hdk.publicKey = new Buffer(response.payload.publicKey, 'hex') this.hdk.chainCode = new Buffer(response.payload.chainCode, 'hex') resolve('just unlocked') @@ -165,10 +164,10 @@ class TrezorKeyring extends EventEmitter { chanId: tx._chainId, nonce: this._normalize(tx.nonce), gasLimit: this._normalize(tx.gasLimit), - gasPrice: this._normalize(tx.gasPrice) - } - }).then( response => { - if(response.success){ + gasPrice: this._normalize(tx.gasPrice), + }, + }).then(response => { + if (response.success) { tx.v = response.payload.v tx.r = response.payload.r tx.s = response.payload.s @@ -212,13 +211,13 @@ class TrezorKeyring extends EventEmitter { const humanReadableMsg = this._toAscii(message) TrezorConnect.ethereumSignMessage({ path: this._pathFromAddress(withAccount), - message: humanReadableMsg - }).then (response => { - if(response.success){ - if (response.address !== ethUtil.toChecksumAddress(withAccount)) { + message: humanReadableMsg, + }).then(response => { + if (response.success) { + if (response.payload.address !== ethUtil.toChecksumAddress(withAccount)) { reject('signature doesnt match the right address') } - const signature = `0x${response.signature}` + const signature = `0x${response.payload.signature}` resolve(signature) } else { reject(response.payload && response.payload.error || 'Unknown error') From 42fe7438b12f87e68a050cdbe5124ae837e3a6af Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Wed, 8 Aug 2018 14:10:30 -0400 Subject: [PATCH 03/11] remove unused file --- trezor-connect.js | 1138 --------------------------------------------- 1 file changed, 1138 deletions(-) delete mode 100644 trezor-connect.js diff --git a/trezor-connect.js b/trezor-connect.js deleted file mode 100644 index c6c7089..0000000 --- a/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 window = global.window || {} -var chrome = window && 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 From 25dfc854312425910f57c533bcf3806da126fd0c Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Wed, 8 Aug 2018 14:12:22 -0400 Subject: [PATCH 04/11] update tests --- test/test-eth-trezor-keyring.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-eth-trezor-keyring.js b/test/test-eth-trezor-keyring.js index 3e3793a..218ccaa 100644 --- a/test/test-eth-trezor-keyring.js +++ b/test/test-eth-trezor-keyring.js @@ -4,7 +4,7 @@ const {expect} = chai const EthereumTx = require('ethereumjs-tx') const assert = require('assert') const HDKey = require('hdkey') -const TrezorConnect = require('../trezor-connect.js') +const TrezorConnect = require('trezor-connect').default const TrezorKeyring = require('../') From 3cf22411e2e036e95569fb44f2592579afbabbed Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Wed, 8 Aug 2018 14:28:07 -0400 Subject: [PATCH 05/11] test passing --- test/navigator.shim.js | 5 +++++ test/test-eth-trezor-keyring.js | 15 +++++++++------ test/window.shim.js | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 test/navigator.shim.js create mode 100644 test/window.shim.js diff --git a/test/navigator.shim.js b/test/navigator.shim.js new file mode 100644 index 0000000..801f8c6 --- /dev/null +++ b/test/navigator.shim.js @@ -0,0 +1,5 @@ +try { + module.exports = window || {userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'} +} catch (e) { + module.exports = {userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'} +} diff --git a/test/test-eth-trezor-keyring.js b/test/test-eth-trezor-keyring.js index 218ccaa..04e3932 100644 --- a/test/test-eth-trezor-keyring.js +++ b/test/test-eth-trezor-keyring.js @@ -1,3 +1,6 @@ +global.window = require('./window.shim') +global.navigator = require('./window.shim') + const chai = require('chai') const spies = require('chai-spies') const {expect} = chai @@ -123,9 +126,9 @@ describe('TrezorKeyring', function () { }) }) - chai.spy.on(TrezorConnect, 'getXPubKey') + chai.spy.on(TrezorConnect, 'getPublicKey') - it('should call TrezorConnect.getXPubKey if we dont have a public key', async function () { + it('should call TrezorConnect.getPublicKey if we dont have a public key', async function () { keyring.hdk = new HDKey() try { await keyring.unlock() @@ -133,7 +136,7 @@ describe('TrezorKeyring', function () { // because we're trying to open the trezor popup in node // it will throw an exception } finally { - expect(TrezorConnect.getXPubKey).to.have.been.called() + expect(TrezorConnect.getPublicKey).to.have.been.called() } }) }) @@ -301,14 +304,14 @@ describe('TrezorKeyring', function () { }) describe('signTransaction', function () { - it('should call TrezorConnect.ethereumSignTx', function (done) { + it('should call TrezorConnect.ethereumSignTransaction', function (done) { - chai.spy.on(TrezorConnect, 'ethereumSignTx') + chai.spy.on(TrezorConnect, 'ethereumSignTransaction') keyring.signTransaction(fakeAccounts[0], fakeTx).catch(e => { // we expect this to be rejected because // we are trying to open a popup from node - expect(TrezorConnect.ethereumSignTx).to.have.been.called() + expect(TrezorConnect.ethereumSignTransaction).to.have.been.called() done() }) }) diff --git a/test/window.shim.js b/test/window.shim.js new file mode 100644 index 0000000..5f9e60c --- /dev/null +++ b/test/window.shim.js @@ -0,0 +1,19 @@ +try { + module.exports = window || { + __TREZOR_CONNECT_SRC: null, + location: { + protocol: 'https', + }, + addEventListener: _ => false, + setTimeout: _ => false, + } +} catch (e) { + module.exports = { + __TREZOR_CONNECT_SRC: null, + location: { + protocol: 'https', + }, + addEventListener: _ => false, + setTimeout: _ => false, + } +} From b84c67de8c438bd35641a77579ae3d3a53cc99f5 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Fri, 10 Aug 2018 11:32:21 -0400 Subject: [PATCH 06/11] fixes --- index.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index c067689..f4c4392 100644 --- a/index.js +++ b/index.js @@ -161,7 +161,7 @@ class TrezorKeyring extends EventEmitter { to: this._normalize(tx.to), value: this._normalize(tx.value), data: this._normalize(tx.data), - chanId: tx._chainId, + chainId: tx._chainId, nonce: this._normalize(tx.nonce), gasLimit: this._normalize(tx.gasLimit), gasPrice: this._normalize(tx.gasPrice), diff --git a/package.json b/package.json index 697b14d..23742a0 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "ethereumjs-util": "^5.1.5", "events": "^2.0.0", "hdkey": "0.8.0", - "trezor-connect": "^5.0.28" + "trezor-connect": "^5.0.29" }, "devDependencies": { "assert": "^1.4.1", From 3313ea9a03b557137f2471e275707f1844e9a49a Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Fri, 10 Aug 2018 12:26:01 -0400 Subject: [PATCH 07/11] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 23742a0..1afbbfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eth-trezor-keyring", - "version": "0.1.0", + "version": "0.2.0", "description": "A MetaMask compatible keyring, for trezor hardware wallets", "main": "index.js", "scripts": { From 01f9b0ea7d9f538512f895d52445e1da182a3b69 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Fri, 10 Aug 2018 12:29:19 -0400 Subject: [PATCH 08/11] fix indentation --- test/navigator.shim.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/navigator.shim.js b/test/navigator.shim.js index 801f8c6..869a3bc 100644 --- a/test/navigator.shim.js +++ b/test/navigator.shim.js @@ -1,5 +1,5 @@ try { module.exports = window || {userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'} } catch (e) { - module.exports = {userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'} + module.exports = {userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'} } From 7bdeb094dc3549c474c83fcebcfb38882f400ae1 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 20 Aug 2018 18:41:16 -0400 Subject: [PATCH 09/11] bump trezor connect lib --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1afbbfc..8cdf463 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "ethereumjs-util": "^5.1.5", "events": "^2.0.0", "hdkey": "0.8.0", - "trezor-connect": "^5.0.29" + "trezor-connect": "^5.0.30" }, "devDependencies": { "assert": "^1.4.1", From 3fe991b2f37a19a09aff999db0b381c213b44c52 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri Date: Tue, 4 Sep 2018 14:56:54 -0400 Subject: [PATCH 10/11] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8cdf463..6d559b9 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "ethereumjs-util": "^5.1.5", "events": "^2.0.0", "hdkey": "0.8.0", - "trezor-connect": "^5.0.30" + "trezor-connect": "^5.0.31" }, "devDependencies": { "assert": "^1.4.1", From 0c90bc9c9e286ad47081f1b54adfafb11d1ab7c6 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 4 Sep 2018 16:11:30 -0400 Subject: [PATCH 11/11] catch popup closed error --- index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index f4c4392..af44605 100644 --- a/index.js +++ b/index.js @@ -194,7 +194,10 @@ class TrezorKeyring extends EventEmitter { // between the unlock & sign trezor popups }, status === 'just unlocked' ? DELAY_BETWEEN_POPUPS : 0) - }) + }).catch(e => { + console.log('Error while trying to sign transaction ', e) + reject(e && e.toString() || 'Unknown error') + }) }) } @@ -229,7 +232,10 @@ class TrezorKeyring extends EventEmitter { // This is necessary to avoid popup collision // between the unlock & sign trezor popups }, status === 'just unlocked' ? DELAY_BETWEEN_POPUPS : 0) - }) + }).catch(e => { + console.log('Error while trying to sign a message ', e) + reject(e && e.toString() || 'Unknown error') + }) }) }