diff options
author | Dan Finlay <dan@danfinlay.com> | 2017-01-18 08:22:22 +0800 |
---|---|---|
committer | Dan Finlay <dan@danfinlay.com> | 2017-01-18 08:24:45 +0800 |
commit | 1ff4894b674bbcbac1998228454129018e4642b6 (patch) | |
tree | 5ddf81cc89253ea6ca8a19f19cee0efe328c2742 | |
parent | 958cbfbde44c201cf71f5dfcb7b1748bb43e597f (diff) | |
download | tangerine-wallet-browser-1ff4894b674bbcbac1998228454129018e4642b6.tar tangerine-wallet-browser-1ff4894b674bbcbac1998228454129018e4642b6.tar.gz tangerine-wallet-browser-1ff4894b674bbcbac1998228454129018e4642b6.tar.bz2 tangerine-wallet-browser-1ff4894b674bbcbac1998228454129018e4642b6.tar.lz tangerine-wallet-browser-1ff4894b674bbcbac1998228454129018e4642b6.tar.xz tangerine-wallet-browser-1ff4894b674bbcbac1998228454129018e4642b6.tar.zst tangerine-wallet-browser-1ff4894b674bbcbac1998228454129018e4642b6.zip |
Allow importing of private key strings
Fixes #1021
A top-right menu item now allows `Account Import`. It has a menu (with one item for now) that allows importing a private key string.
Errors are displayed, and a success navigates the user to their account list, where the imported account is labeled `LOOSE`.
-rw-r--r-- | app/scripts/keyring-controller.js | 5 | ||||
-rw-r--r-- | app/scripts/keyrings/simple.js | 18 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 7 | ||||
-rw-r--r-- | development/states/account-list-with-imported.json | 84 | ||||
-rw-r--r-- | development/states/import-private-key-warning.json | 92 | ||||
-rw-r--r-- | development/states/import-private-key.json | 64 | ||||
-rw-r--r-- | ui/app/accounts/import/index.js | 21 | ||||
-rw-r--r-- | ui/app/accounts/import/private-key.js | 69 | ||||
-rw-r--r-- | ui/app/accounts/index.js | 3 | ||||
-rw-r--r-- | ui/app/actions.js | 10 |
10 files changed, 357 insertions, 16 deletions
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 4be00a5a5..e609403cc 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -234,7 +234,10 @@ module.exports = class KeyringController extends EventEmitter { addNewKeyring (type, opts) { const Keyring = this.getKeyringClassForType(type) const keyring = new Keyring(opts) - return keyring.getAccounts() + return keyring.deserialize(opts) + .then(() => { + return keyring.getAccounts() + }) .then((accounts) => { this.keyrings.push(keyring) return this.setupAccounts(accounts) diff --git a/app/scripts/keyrings/simple.js b/app/scripts/keyrings/simple.js index d604430b8..46687fcaf 100644 --- a/app/scripts/keyrings/simple.js +++ b/app/scripts/keyrings/simple.js @@ -20,13 +20,19 @@ class SimpleKeyring extends EventEmitter { } deserialize (privateKeys = []) { - this.wallets = privateKeys.map((privateKey) => { - const stripped = ethUtil.stripHexPrefix(privateKey) - const buffer = new Buffer(stripped, 'hex') - const wallet = Wallet.fromPrivateKey(buffer) - return wallet + return new Promise((resolve, reject) => { + try { + this.wallets = privateKeys.map((privateKey) => { + const stripped = ethUtil.stripHexPrefix(privateKey) + const buffer = new Buffer(stripped, 'hex') + const wallet = Wallet.fromPrivateKey(buffer) + return wallet + }) + } catch (e) { + reject(e) + } + resolve() }) - return Promise.resolve() } addAccounts (n = 1) { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b94b98eac..629216e42 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -115,7 +115,12 @@ module.exports = class MetamaskController extends EventEmitter { .then((newState) => { cb(null, newState) }) .catch((reason) => { cb(reason) }) }, - addNewKeyring: nodeify(keyringController.addNewKeyring).bind(keyringController), + addNewKeyring: (type, opts, cb) => { + keyringController.addNewKeyring(type, opts) + .then(() => keyringController.fullUpdate()) + .then((newState) => { cb(null, newState) }) + .catch((reason) => { cb(reason) }) + }, addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController), setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController), saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), diff --git a/development/states/account-list-with-imported.json b/development/states/account-list-with-imported.json new file mode 100644 index 000000000..e32327743 --- /dev/null +++ b/development/states/account-list-with-imported.json @@ -0,0 +1,84 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683": { + "address": "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683", + "name": "Account 1" + }, + "0x9858e7d8b79fc3e6d989636721584498926da38a": { + "address": "0x9858e7d8b79fc3e6d989636721584498926da38a", + "name": "Imported Account" + } + }, + "unconfTxs": {}, + "currentFiat": "USD", + "conversionRate": 10.19458075, + "conversionDate": 1484696373, + "noActiveNotices": true, + "network": "3", + "accounts": { + "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683": { + "code": "0x", + "balance": "0x0", + "nonce": "0x0", + "address": "0x58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683" + }, + "0x9858e7d8b79fc3e6d989636721584498926da38a": { + "code": "0x", + "balance": "0x0", + "nonce": "0x0", + "address": "0x9858e7d8b79fc3e6d989636721584498926da38a" + } + }, + "transactions": [], + "provider": { + "type": "testnet" + }, + "selectedAccount": "0x9858e7d8b79fc3e6d989636721584498926da38a", + "selectedAccountTxList": [], + "isDisclaimerConfirmed": true, + "unconfMsgs": {}, + "messages": [], + "shapeShiftTxList": [], + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "58bda1f9d87dc7d2bcc6f7c2513efc9d03fca683" + ] + }, + { + "type": "Simple Key Pair", + "accounts": [ + "0x9858e7d8b79fc3e6d989636721584498926da38a" + ] + } + ], + "lostAccounts": [], + "seedWords": null + }, + "appState": { + "menuOpen": false, + "currentView": { + "name": "accounts" + }, + "accountDetail": { + "subview": "transactions", + "accountExport": "none", + "privateKey": "" + }, + "transForward": true, + "isLoading": false, + "warning": null, + "scrollToBottom": false, + "forgottenPassword": false + }, + "identities": {} +}
\ No newline at end of file diff --git a/development/states/import-private-key-warning.json b/development/states/import-private-key-warning.json new file mode 100644 index 000000000..f4ac99b05 --- /dev/null +++ b/development/states/import-private-key-warning.json @@ -0,0 +1,92 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0x01208723ba84e15da2e71656544a2963b0c06d40": { + "address": "0x01208723ba84e15da2e71656544a2963b0c06d40", + "name": "Account 1" + } + }, + "unconfTxs": {}, + "currentFiat": "USD", + "conversionRate": 10.1219126, + "conversionDate": 1484695442, + "noActiveNotices": true, + "network": "3", + "accounts": { + "0x01208723ba84e15da2e71656544a2963b0c06d40": { + "nonce": "0x0", + "balance": "0x0", + "code": "0x", + "address": "0x01208723ba84e15da2e71656544a2963b0c06d40" + } + }, + "transactions": [], + "provider": { + "type": "testnet" + }, + "selectedAccount": "0x01208723ba84e15da2e71656544a2963b0c06d40", + "selectedAccountTxList": [], + "seedWords": false, + "isDisclaimerConfirmed": true, + "unconfMsgs": {}, + "messages": [], + "shapeShiftTxList": [], + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "Simple Key Pair", + "accounts": [] + }, + { + "type": "Simple Key Pair", + "accounts": [] + }, + { + "type": "Simple Key Pair", + "accounts": [] + }, + { + "type": "Simple Key Pair", + "accounts": [] + }, + { + "type": "Simple Key Pair", + "accounts": [] + }, + { + "type": "Simple Key Pair", + "accounts": [] + }, + { + "type": "Simple Key Pair", + "accounts": [] + }, + { + "type": "HD Key Tree", + "accounts": [ + "01208723ba84e15da2e71656544a2963b0c06d40" + ] + } + ], + "lostAccounts": [] + }, + "appState": { + "menuOpen": false, + "currentView": { + "name": "import-menu" + }, + "accountDetail": { + "subview": "transactions" + }, + "transForward": true, + "isLoading": false, + "warning": "Invalid hex string" + }, + "identities": {} +}
\ No newline at end of file diff --git a/development/states/import-private-key.json b/development/states/import-private-key.json new file mode 100644 index 000000000..c70f02a36 --- /dev/null +++ b/development/states/import-private-key.json @@ -0,0 +1,64 @@ +{ + "metamask": { + "isInitialized": true, + "isUnlocked": true, + "rpcTarget": "https://rawtestrpc.metamask.io/", + "identities": { + "0x01208723ba84e15da2e71656544a2963b0c06d40": { + "address": "0x01208723ba84e15da2e71656544a2963b0c06d40", + "name": "Account 1" + } + }, + "unconfTxs": {}, + "currentFiat": "USD", + "conversionRate": 10.10788584, + "conversionDate": 1484694362, + "noActiveNotices": true, + "network": "3", + "accounts": { + "0x01208723ba84e15da2e71656544a2963b0c06d40": { + "balance": "0x0", + "code": "0x", + "nonce": "0x0", + "address": "0x01208723ba84e15da2e71656544a2963b0c06d40" + } + }, + "transactions": [], + "provider": { + "type": "testnet" + }, + "selectedAccount": "0x01208723ba84e15da2e71656544a2963b0c06d40", + "selectedAccountTxList": [], + "seedWords": null, + "isDisclaimerConfirmed": true, + "unconfMsgs": {}, + "messages": [], + "shapeShiftTxList": [], + "keyringTypes": [ + "Simple Key Pair", + "HD Key Tree" + ], + "keyrings": [ + { + "type": "HD Key Tree", + "accounts": [ + "01208723ba84e15da2e71656544a2963b0c06d40" + ] + } + ], + "lostAccounts": [] + }, + "appState": { + "menuOpen": false, + "currentView": { + "name": "import-menu" + }, + "accountDetail": { + "subview": "transactions" + }, + "transForward": true, + "isLoading": false, + "warning": null + }, + "identities": {} +}
\ No newline at end of file diff --git a/ui/app/accounts/import/index.js b/ui/app/accounts/import/index.js index a7c3252cd..18a6b985c 100644 --- a/ui/app/accounts/import/index.js +++ b/ui/app/accounts/import/index.js @@ -7,12 +7,17 @@ import Select from 'react-select' // Subviews const JsonImportView = require('./json.js') const SeedImportView = require('./seed.js') +const PrivateKeyImportView = require('./private-key.js') + +const menuItems = [ + 'Private Key', +] module.exports = connect(mapStateToProps)(AccountImportSubview) function mapStateToProps (state) { return { - types: state.metamask.keyringTypes, + menuItems, } } @@ -24,7 +29,7 @@ function AccountImportSubview () { AccountImportSubview.prototype.render = function () { const props = this.props const state = this.state || {} - const { types } = props + const { menuItems } = props const { type } = state return ( @@ -50,8 +55,8 @@ AccountImportSubview.prototype.render = function () { h(Select, { name: 'import-type-select', clearable: false, - value: type || types[0], - options: types.map((type) => { + value: type || menuItems[0], + options: menuItems.map((type) => { return { value: type, label: type, @@ -71,11 +76,15 @@ AccountImportSubview.prototype.render = function () { AccountImportSubview.prototype.renderImportView = function() { const props = this.props const state = this.state || {} - const { type } = state || props.types[0] + const { type } = state + const { menuItems } = props + const current = type || menuItems[0] - switch (type) { + switch (current) { case 'HD Key Tree': return h(SeedImportView) + case 'Private Key': + return h(PrivateKeyImportView) default: return h(JsonImportView) } diff --git a/ui/app/accounts/import/private-key.js b/ui/app/accounts/import/private-key.js new file mode 100644 index 000000000..6b988a76b --- /dev/null +++ b/ui/app/accounts/import/private-key.js @@ -0,0 +1,69 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const type = 'Simple Key Pair' +const actions = require('../../actions') + +module.exports = connect(mapStateToProps)(PrivateKeyImportView) + +function mapStateToProps (state) { + return { + error: state.appState.warning, + } +} + +inherits(PrivateKeyImportView, Component) +function PrivateKeyImportView () { + Component.call(this) +} + +PrivateKeyImportView.prototype.render = function () { + const { error } = this.props + + return ( + h('div', { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: '5px 15px 0px 15px', + }, + }, [ + h('span', 'Paste your private key string here'), + + h('input.large-input.letter-spacey', { + type: 'password', + id: 'private-key-box', + onKeyPress: this.createKeyringOnEnter.bind(this), + style: { + width: 260, + marginTop: 12, + }, + }), + + h('button.primary', { + onClick: this.createNewKeychain.bind(this), + style: { + margin: 12, + }, + }, 'Import'), + + error ? h('span.warning', error) : null, + ]) + ) +} + +PrivateKeyImportView.prototype.createKeyringOnEnter = function (event) { + if (event.key === 'Enter') { + event.preventDefault() + this.createNewKeychain() + } +} + +PrivateKeyImportView.prototype.createNewKeychain = function () { + const input = document.getElementById('private-key-box') + const privateKey = input.value + this.props.dispatch(actions.addNewKeyring(type, [ privateKey ])) +} + diff --git a/ui/app/accounts/index.js b/ui/app/accounts/index.js index 5ab31a1c0..e6f376735 100644 --- a/ui/app/accounts/index.js +++ b/ui/app/accounts/index.js @@ -73,7 +73,8 @@ AccountsScreen.prototype.render = function () { const simpleAddress = identity.address.substring(2).toLowerCase() const keyring = keyrings.find((kr) => { - return kr.accounts.includes(simpleAddress) + return kr.accounts.includes(simpleAddress) || + kr.accounts.includes(identity.address) }) return h(AccountListItem, { diff --git a/ui/app/actions.js b/ui/app/actions.js index 87b02966b..7934a329a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -253,7 +253,15 @@ function requestRevealSeed (password) { } function addNewKeyring (type, opts) { - return callBackgroundThenUpdate(background.addNewKeyring, type, opts) + return (dispatch) => { + dispatch(actions.showLoadingIndication()) + background.addNewKeyring(type, opts, (err, newState) => { + dispatch(actions.hideLoadingIndication()) + if (err) return dispatch(actions.displayWarning(err.message)) + dispatch(actions.updateMetamaskState(newState)) + dispatch(actions.showAccountsPage()) + }) + } } function navigateToNewAccountScreen() { |