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`.
This commit is contained in:
parent
958cbfbde4
commit
1ff4894b67
|
@ -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…
Reference in New Issue