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`.
feature/default_network_editable
Dan Finlay 8 years ago
parent 958cbfbde4
commit 1ff4894b67
  1. 5
      app/scripts/keyring-controller.js
  2. 18
      app/scripts/keyrings/simple.js
  3. 7
      app/scripts/metamask-controller.js
  4. 84
      development/states/account-list-with-imported.json
  5. 92
      development/states/import-private-key-warning.json
  6. 64
      development/states/import-private-key.json
  7. 21
      ui/app/accounts/import/index.js
  8. 69
      ui/app/accounts/import/private-key.js
  9. 3
      ui/app/accounts/index.js
  10. 10
      ui/app/actions.js

@ -234,7 +234,10 @@ module.exports = class KeyringController extends EventEmitter {
addNewKeyring (type, opts) { addNewKeyring (type, opts) {
const Keyring = this.getKeyringClassForType(type) const Keyring = this.getKeyringClassForType(type)
const keyring = new Keyring(opts) const keyring = new Keyring(opts)
return keyring.getAccounts() return keyring.deserialize(opts)
.then(() => {
return keyring.getAccounts()
})
.then((accounts) => { .then((accounts) => {
this.keyrings.push(keyring) this.keyrings.push(keyring)
return this.setupAccounts(accounts) return this.setupAccounts(accounts)

@ -20,13 +20,19 @@ class SimpleKeyring extends EventEmitter {
} }
deserialize (privateKeys = []) { deserialize (privateKeys = []) {
this.wallets = privateKeys.map((privateKey) => { return new Promise((resolve, reject) => {
const stripped = ethUtil.stripHexPrefix(privateKey) try {
const buffer = new Buffer(stripped, 'hex') this.wallets = privateKeys.map((privateKey) => {
const wallet = Wallet.fromPrivateKey(buffer) const stripped = ethUtil.stripHexPrefix(privateKey)
return wallet const buffer = new Buffer(stripped, 'hex')
const wallet = Wallet.fromPrivateKey(buffer)
return wallet
})
} catch (e) {
reject(e)
}
resolve()
}) })
return Promise.resolve()
} }
addAccounts (n = 1) { addAccounts (n = 1) {

@ -115,7 +115,12 @@ module.exports = class MetamaskController extends EventEmitter {
.then((newState) => { cb(null, newState) }) .then((newState) => { cb(null, newState) })
.catch((reason) => { cb(reason) }) .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), addNewAccount: nodeify(keyringController.addNewAccount).bind(keyringController),
setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController), setSelectedAccount: nodeify(keyringController.setSelectedAccount).bind(keyringController),
saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController),

@ -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": {}
}

@ -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": {}
}

@ -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": {}
}

@ -7,12 +7,17 @@ import Select from 'react-select'
// Subviews // Subviews
const JsonImportView = require('./json.js') const JsonImportView = require('./json.js')
const SeedImportView = require('./seed.js') const SeedImportView = require('./seed.js')
const PrivateKeyImportView = require('./private-key.js')
const menuItems = [
'Private Key',
]
module.exports = connect(mapStateToProps)(AccountImportSubview) module.exports = connect(mapStateToProps)(AccountImportSubview)
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
types: state.metamask.keyringTypes, menuItems,
} }
} }
@ -24,7 +29,7 @@ function AccountImportSubview () {
AccountImportSubview.prototype.render = function () { AccountImportSubview.prototype.render = function () {
const props = this.props const props = this.props
const state = this.state || {} const state = this.state || {}
const { types } = props const { menuItems } = props
const { type } = state const { type } = state
return ( return (
@ -50,8 +55,8 @@ AccountImportSubview.prototype.render = function () {
h(Select, { h(Select, {
name: 'import-type-select', name: 'import-type-select',
clearable: false, clearable: false,
value: type || types[0], value: type || menuItems[0],
options: types.map((type) => { options: menuItems.map((type) => {
return { return {
value: type, value: type,
label: type, label: type,
@ -71,11 +76,15 @@ AccountImportSubview.prototype.render = function () {
AccountImportSubview.prototype.renderImportView = function() { AccountImportSubview.prototype.renderImportView = function() {
const props = this.props const props = this.props
const state = this.state || {} 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': case 'HD Key Tree':
return h(SeedImportView) return h(SeedImportView)
case 'Private Key':
return h(PrivateKeyImportView)
default: default:
return h(JsonImportView) return h(JsonImportView)
} }

@ -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 ]))
}

@ -73,7 +73,8 @@ AccountsScreen.prototype.render = function () {
const simpleAddress = identity.address.substring(2).toLowerCase() const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => { const keyring = keyrings.find((kr) => {
return kr.accounts.includes(simpleAddress) return kr.accounts.includes(simpleAddress) ||
kr.accounts.includes(identity.address)
}) })
return h(AccountListItem, { return h(AccountListItem, {

@ -253,7 +253,15 @@ function requestRevealSeed (password) {
} }
function addNewKeyring (type, opts) { 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() { function navigateToNewAccountScreen() {

Loading…
Cancel
Save