Skip to content

Commit

Permalink
Custom derivation paths and access to funds in accounts derived from …
Browse files Browse the repository at this point in the history
…ETH dPath
  • Loading branch information
vbaranov committed Apr 15, 2020
1 parent 8d7b856 commit 005637f
Show file tree
Hide file tree
Showing 24 changed files with 312 additions and 169 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Current Master

- [#356](https://github.com/poanetwork/nifty-wallet/pull/356) - (Backwards-compatibility feature) Custom derivation paths and access to funds in accounts derived from ETH dPath
- [#355](https://github.com/poanetwork/nifty-wallet/pull/355) - (Feature) Add RSK/testnet default tokens
- [#354](https://github.com/poanetwork/nifty-wallet/pull/354) - (Fix) `accountsChanged` event emittance (a part of EIP-1193)
- [#353](https://github.com/poanetwork/nifty-wallet/pull/353) - (Fix) synchronous eth_accounts request
Expand Down
6 changes: 2 additions & 4 deletions app/scripts/controllers/network/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import createJsonRpcClient from './createJsonRpcClient'
import createLocalhostClient from './createLocalhostClient'
const createPocketClient = require('./createPocketClient')
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
const ethNetProps = require('eth-net-props')
import ethNetProps from 'eth-net-props'
import parse from 'url-parse'
const networks = { networkList: {} }
const { isKnownProvider, getDPath } = require('../../../../old-ui/app/util')
const { isKnownProvider } = require('../../../../old-ui/app/util')

const {
ROPSTEN,
Expand Down Expand Up @@ -205,8 +205,6 @@ module.exports = class NetworkController extends EventEmitter {
const previousNetworkID = this.getNetworkState()
this.setNetworkState('loading')
this._configureProvider(opts)
const dPath = getDPath(opts.type)
this.store.updateState({ dPath })
this.emit('networkDidChange', opts.type, previousNetworkID)
}

Expand Down
10 changes: 5 additions & 5 deletions app/scripts/lib/seed-phrase-verifier.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const KeyringController = require('eth-keychain-controller')
const log = require('loglevel')
import KeyringController from 'eth-keychain-controller'
import log from 'loglevel'
const { getDPath } = require('../../../old-ui/app/util')

const seedPhraseVerifier = {
Expand All @@ -17,14 +17,14 @@ const seedPhraseVerifier = {
* @returns {Promise<void>} Promises undefined
*
*/
verifyAccounts (createdAccounts, seedWords, network) {
verifyAccounts (createdAccounts, seedWords, network, isCreatedWithCorrectDPath) {
return new Promise((resolve, reject) => {

if (!createdAccounts || createdAccounts.length < 1) {
return reject(new Error('No created accounts defined.'))
}

const dPath = getDPath(network)
const dPath = getDPath(network, isCreatedWithCorrectDPath)
const keyringController = new KeyringController({})
const Keyring = keyringController.getKeyringClassForType('HD Key Tree')
const opts = {
Expand Down Expand Up @@ -56,4 +56,4 @@ const seedPhraseVerifier = {
},
}

module.exports = seedPhraseVerifier
export default seedPhraseVerifier
77 changes: 42 additions & 35 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const version = require('../manifest.json').version
import ethUtil, { BN } from 'ethereumjs-util'
const GWEI_BN = new BN('1000000000')
import percentile from 'percentile'
const seedPhraseVerifier = require('./lib/seed-phrase-verifier')
import seedPhraseVerifier from './lib/seed-phrase-verifier'
import log from 'loglevel'
const TrezorKeyring = require('eth-trezor-keyring')
const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring')
Expand Down Expand Up @@ -169,26 +169,32 @@ module.exports = class MetamaskController extends EventEmitter {
})

// ensure accountTracker updates balances after network change
this.networkController.on('networkDidChange', (newType, previousNetworkIDStr) => {
const dPath = getDPath(newType)
this.deriveKeyringFromNewDPath(dPath)
.then(accounts => {
this.accountTracker._updateAccounts()
this.detectTokensController.restartTokenDetection()

const previousNetworkID = parseInt(previousNetworkIDStr, 10)
const nextNetwork = getNetworkID({network: newType})
const nextNetworkID = parseInt(nextNetwork && nextNetwork.netId, 10)
if (nextNetworkID !== previousNetworkID) {
const isPreviousETC = previousNetworkID === CLASSIC_CODE
const isPreviousRSK = ifRSK(previousNetworkID)
const isNextETC = nextNetworkID === CLASSIC_CODE
const isNextRSK = ifRSK(nextNetworkID)
if (isPreviousETC || isPreviousRSK || isNextETC || isNextRSK) {
this.forgetDevice(LEDGER, false)
this.forgetDevice(TREZOR, false)
this.networkController.on('networkDidChange', (newNetworkType, previousNetworkIDStr) => {
this.keyringController.isCreatedWithCorrectDPath()
.then(isCreatedWithCorrectDPath => {
const dPath = getDPath(newNetworkType, isCreatedWithCorrectDPath)
this.deriveKeyringFromNewDPath(dPath)
.then(_accounts => {
this.accountTracker._updateAccounts()
this.detectTokensController.restartTokenDetection()

const previousNetworkID = parseInt(previousNetworkIDStr, 10)
const nextNetwork = getNetworkID({network: newNetworkType})
const nextNetworkID = parseInt(nextNetwork && nextNetwork.netId, 10)
if (nextNetworkID !== previousNetworkID) {
const isPreviousETC = previousNetworkID === CLASSIC_CODE
const isPreviousRSK = ifRSK(previousNetworkID)
const isNextETC = nextNetworkID === CLASSIC_CODE
const isNextRSK = ifRSK(nextNetworkID)
if (isPreviousETC || isPreviousRSK || isNextETC || isNextRSK) {
this.forgetDevice(LEDGER, false)
this.forgetDevice(TREZOR, false)
}
}
}
})
.catch(e => {
console.log(e)
})
})
.catch(e => {
console.log(e)
Expand Down Expand Up @@ -493,6 +499,7 @@ module.exports = class MetamaskController extends EventEmitter {
addNewKeyring: nodeify(keyringController.addNewKeyring, keyringController),
addNewMultisig: nodeify(keyringController.addNewMultisig, keyringController),
exportAccount: nodeify(keyringController.exportAccount, keyringController),
isCreatedWithCorrectDPath: nodeify(keyringController.isCreatedWithCorrectDPath, keyringController),

// txController
cancelTransaction: nodeify(txController.cancelTransaction, txController),
Expand Down Expand Up @@ -582,7 +589,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {} password
* @param {} seed
*/
async createNewVaultAndRestore (password, seed) {
async createNewVaultAndRestore (password, seed, dPath) {
const releaseLock = await this.createVaultMutex.acquire()
try {
let accounts, lastBalance
Expand All @@ -592,9 +599,8 @@ module.exports = class MetamaskController extends EventEmitter {
// clear known identities
this.preferencesController.setAddresses([])
// create new vault
const network = this.networkController.getProviderConfig().type
const dPath = getDPath(network)
this.store.updateState({dPath})
const networkType = this.networkController.getProviderConfig().type
const isCreatedWithCorrectDPath = true
const vault = await keyringController.createNewVaultAndRestore(password, seed, dPath)

const ethQuery = new EthQuery(this.provider)
Expand All @@ -606,7 +612,7 @@ module.exports = class MetamaskController extends EventEmitter {
throw new Error('MetamaskController - No HD Key Tree found')
}

setDPath(primaryKeyring, network)
setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath)

// seek out the first zero balance
while (lastBalance !== '0x0') {
Expand Down Expand Up @@ -911,13 +917,14 @@ module.exports = class MetamaskController extends EventEmitter {
* @returns {} keyState
*/
async addNewAccount () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
const keyringController = this.keyringController
const primaryKeyring = keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found')
}
const network = this.networkController.getProviderConfig().type
setDPath(primaryKeyring, network)
const keyringController = this.keyringController
const networkType = this.networkController.getProviderConfig().type
const isCreatedWithCorrectDPath = await keyringController.isCreatedWithCorrectDPath()
setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath)
const oldAccounts = await keyringController.getAccounts()
const keyState = await keyringController.addNewAccount(primaryKeyring)
const newAccounts = await keyringController.getAccounts()
Expand Down Expand Up @@ -965,12 +972,14 @@ module.exports = class MetamaskController extends EventEmitter {
* @returns {Promise<string>} Seed phrase to be confirmed by the user.
*/
async verifySeedPhrase () {
const primaryKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
const keyringController = this.keyringController
const isCreatedWithCorrectDPath = await keyringController.isCreatedWithCorrectDPath()
const primaryKeyring = keyringController.getKeyringsByType('HD Key Tree')[0]
if (!primaryKeyring) {
throw new Error('MetamaskController - No HD Key Tree found')
}
const network = this.networkController.getProviderConfig().type
setDPath(primaryKeyring, network)
const networkType = this.networkController.getProviderConfig().type
setDPath(primaryKeyring, networkType, isCreatedWithCorrectDPath)

const serialized = await primaryKeyring.serialize()
const seedWords = serialized.mnemonic
Expand All @@ -981,7 +990,7 @@ module.exports = class MetamaskController extends EventEmitter {
}

try {
await seedPhraseVerifier.verifyAccounts(accounts, seedWords, network)
await seedPhraseVerifier.verifyAccounts(accounts, seedWords, networkType, isCreatedWithCorrectDPath)
return seedWords
} catch (err) {
log.error(err.message)
Expand Down Expand Up @@ -1086,8 +1095,6 @@ module.exports = class MetamaskController extends EventEmitter {
const privateKey = await accountImporter.importAccount(strategy, args)
keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ])
}
const network = this.networkController.getProviderConfig().type
setDPath(keyring, network)
const accounts = await keyring.getAccounts()
// update accounts in preferences controller
const allAccounts = await this.keyringController.getAccounts()
Expand Down
Binary file added nifty-wallet-chrome-5.0.2-fix-dpath.zip
Binary file not shown.
Binary file added nifty-wallet-chrome-5.0.2.zip
Binary file not shown.
84 changes: 70 additions & 14 deletions old-ui/app/account-detail.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('../../ui/app/actions')
const { getCurrentKeyring, ifContractAcc, valuesFor, toChecksumAddress } = require('./util')
const { getCurrentKeyring, ifContractAcc, valuesFor, toChecksumAddress, ifLooseAcc, ifRSK, ifETC } = require('./util')
const Identicon = require('./components/identicon')
const EthBalance = require('./components/eth-balance')
const TransactionList = require('./components/transaction-list')
Expand All @@ -14,10 +14,10 @@ const TabBar = require('./components/tab-bar')
const TokenList = require('./components/token-list')
const AccountDropdowns = require('./components/account-dropdowns/account-dropdowns.component').AccountDropdowns
const CopyButton = require('./components/copy/copy-button')
const ToastComponent = require('./components/toast')
import * as Toast from './components/toast'
import { getMetaMaskAccounts } from '../../ui/app/selectors'

module.exports = connect(mapStateToProps)(AccountDetailScreen)
module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailScreen)

function mapStateToProps (state) {
const accounts = getMetaMaskAccounts(state)
Expand All @@ -42,11 +42,66 @@ function mapStateToProps (state) {
}
}

function mapDispatchToProps (dispatch) {
return {
actions: {
showSendPage: () => dispatch(actions.showSendPage()),
showSendContractPage: ({ methodSelected, methodABI, inputValues }) => dispatch(actions.showSendContractPage({methodSelected, methodABI, inputValues})),
buyEthView: (selected) => dispatch(actions.buyEthView(selected)),
viewPendingTx: (txId) => dispatch(actions.viewPendingTx(txId)),
setAccountLabel: (account, label) => dispatch(actions.setAccountLabel(account, label)),
showRemoveTokenPage: (token) => dispatch(actions.showRemoveTokenPage(token)),
showAddSuggestedTokenPage: () => dispatch(actions.showAddSuggestedTokenPage()),
showAddTokenPage: () => dispatch(actions.showAddTokenPage()),
setCurrentAccountTab: (key) => dispatch(actions.setCurrentAccountTab(key)),
displayToast: (msg) => dispatch(actions.displayToast(msg)),
isCreatedWithCorrectDPath: () => dispatch(actions.isCreatedWithCorrectDPath()),
},
}
}

inherits(AccountDetailScreen, Component)
function AccountDetailScreen () {
Component.call(this)
}

AccountDetailScreen.prototype.componentDidMount = function () {
const props = this.props
const { address, network, keyrings, identities } = props
props.actions.isCreatedWithCorrectDPath()
.then(isCreatedWithCorrectDPath => {
if (!isCreatedWithCorrectDPath) {
const currentKeyring = getCurrentKeyring(address, network, keyrings, identities)
if (!ifLooseAcc(currentKeyring) && !ifContractAcc(currentKeyring) && (ifRSK(network) || ifETC(network))) {
props.actions.displayToast(Toast.ERROR_ON_INCORRECT_DPATH)
}
}
})
}

AccountDetailScreen.prototype.componentWillUpdate = function (nextProps) {
const {
network: oldNet,
} = this.props
const {
network: newNet,
} = nextProps

if (oldNet !== newNet) {
const props = this.props
const { address, keyrings, identities } = props
props.actions.isCreatedWithCorrectDPath()
.then(isCreatedWithCorrectDPath => {
if (!isCreatedWithCorrectDPath) {
const currentKeyring = getCurrentKeyring(address, newNet, keyrings, identities)
if (!ifLooseAcc(currentKeyring) && !ifContractAcc(currentKeyring) && (ifRSK(newNet) || ifETC(newNet))) {
props.actions.displayToast(Toast.ERROR_ON_INCORRECT_DPATH)
}
}
})
}
}

AccountDetailScreen.prototype.render = function () {
const props = this.props
const { network, conversionRate, currentCurrency } = props
Expand All @@ -56,7 +111,7 @@ AccountDetailScreen.prototype.render = function () {
const account = props.accounts[selected]

if (Object.keys(props.suggestedTokens).length > 0) {
this.props.dispatch(actions.showAddSuggestedTokenPage())
this.props.actions.showAddSuggestedTokenPage()
}

const currentKeyring = getCurrentKeyring(props.address, network, props.keyrings, props.identities)
Expand All @@ -65,8 +120,9 @@ AccountDetailScreen.prototype.render = function () {

h('.account-detail-section.full-flex-height', [

h(ToastComponent, {
isSuccess: false,
h(Toast.ToastComponent, {
type: Toast.TOAST_TYPE_ERROR,
hideManually: true,
}),

// identicon, label, balance, etc
Expand Down Expand Up @@ -108,7 +164,7 @@ AccountDetailScreen.prototype.render = function () {
isEditingLabel: false,
},
saveText: (text) => {
props.dispatch(actions.setAccountLabel(selected, text))
props.actions.setAccountLabel(selected, text)
},
}, [

Expand Down Expand Up @@ -223,16 +279,16 @@ AccountDetailScreen.prototype.render = function () {
h('.flex-grow'),

!ifContractAcc(currentKeyring) ? h('button', {
onClick: () => props.dispatch(actions.buyEthView(selected)),
onClick: () => props.actions.buyEthView(selected),
style: { marginRight: '10px' },
}, 'Buy') : null,

h('button', {
onClick: () => {
if (ifContractAcc(currentKeyring)) {
return props.dispatch(actions.showSendContractPage({}))
return props.actions.showSendContractPage({})
} else {
return props.dispatch(actions.showSendPage())
return props.actions.showSendPage()
}
},
}, ifContractAcc(currentKeyring) ? 'Execute methods' : 'Send'),
Expand Down Expand Up @@ -278,7 +334,7 @@ AccountDetailScreen.prototype.tabSections = function () {
],
defaultTab: currentAccountTab || 'history',
tabSelected: (key) => {
this.props.dispatch(actions.setCurrentAccountTab(key))
this.props.actions.setCurrentAccountTab(key)
},
}),

Expand All @@ -297,8 +353,8 @@ AccountDetailScreen.prototype.tabSwitchView = function () {
userAddress: address,
network,
tokens,
addToken: () => this.props.dispatch(actions.showAddTokenPage()),
removeToken: (token) => this.props.dispatch(actions.showRemoveTokenPage(token)),
addToken: () => this.props.actions.showAddTokenPage(),
removeToken: (token) => this.props.actions.showRemoveTokenPage(token),
})
default:
return this.transactionList()
Expand All @@ -317,7 +373,7 @@ AccountDetailScreen.prototype.transactionList = function () {
address,
shapeShiftTxList,
viewPendingTx: (txId) => {
this.props.dispatch(actions.viewPendingTx(txId))
this.props.actions.viewPendingTx(txId)
},
})
}
Loading

0 comments on commit 005637f

Please sign in to comment.