Implementation encrypt/decrypt feature (#7831)

Implement `eth_decrypt` and `eth_getEncryptionPublicKey`. This allows decryption backed by the user's private key. The message decryption uses a confirmation flow similar to the messaging signing flow, where the message to be decrypted is also able to be decrypted inline for the user to read directly before confirming.
feature/default_network_editable
Konstantin 5 years ago committed by GitHub
parent cab2f1b769
commit 6f47fece56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 32
      app/_locales/en/messages.json
  2. 42
      app/_locales/ru/messages.json
  3. 15
      app/scripts/background.js
  4. 4
      app/scripts/controllers/network/createMetamaskMiddleware.js
  5. 2
      app/scripts/controllers/permissions/enums.js
  6. 311
      app/scripts/lib/decrypt-message-manager.js
  7. 286
      app/scripts/lib/encryption-public-key-manager.js
  8. 158
      app/scripts/metamask-controller.js
  9. 2
      package.json
  10. 2
      test/data/2-state.json
  11. 4
      test/data/mock-state.json
  12. 4
      ui/app/css/itcss/components/index.scss
  13. 293
      ui/app/css/itcss/components/request-decrypt-message.scss
  14. 222
      ui/app/css/itcss/components/request-encryption-public-key.scss
  15. 4
      ui/app/helpers/constants/routes.js
  16. 2
      ui/app/helpers/constants/transactions.js
  17. 10
      ui/app/helpers/utils/transactions.util.js
  18. 333
      ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.component.js
  19. 62
      ui/app/pages/confirm-decrypt-message/confirm-decrypt-message.container.js
  20. 1
      ui/app/pages/confirm-decrypt-message/index.js
  21. 238
      ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.component.js
  22. 55
      ui/app/pages/confirm-encryption-public-key/confirm-encryption-public-key.container.js
  23. 1
      ui/app/pages/confirm-encryption-public-key/index.js
  24. 10
      ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.component.js
  25. 15
      ui/app/pages/confirm-transaction/confirm-transaction.component.js
  26. 4
      ui/app/pages/send/tests/send-selectors-test-data.js
  27. 22
      ui/app/selectors/confirm-transaction.js
  28. 4
      ui/app/selectors/selectors.js
  29. 4
      ui/app/selectors/tests/selectors-test-data.js
  30. 8
      ui/app/selectors/transactions.js
  31. 118
      ui/app/store/actions.js
  32. 2
      ui/index.js
  33. 12
      ui/lib/tx-helper.js
  34. 28
      yarn.lock

@ -385,7 +385,7 @@
"message": "Custom Spend Limit"
},
"dataBackupFoundInfo": {
"message": "Some of your account data was backed up during a previous installation of MetaMask. This could include your settings, contacts and tokens. Would you like to restore this data now?"
"message": "Some of your account data was backed up during a previous installation of MetaMask. This could include your settings, contacts, and tokens. Would you like to restore this data now?"
},
"decimalsMustZerotoTen": {
"message": "Decimals must be at least 0, and not over 36."
@ -1609,5 +1609,35 @@
},
"zeroGasPriceOnSpeedUpError": {
"message": "Zero gas price on speed up"
},
"decryptRequest": {
"message": "Decrypt request"
},
"decrypt": {
"message": "Decrypt"
},
"decryptMessageNotice": {
"message": "$1 would like to read this message to complete your action",
"description": "$1 is website or dapp name"
},
"decryptMetamask": {
"message": "Decrypt message"
},
"decryptCopy": {
"message": "Copy encrypted message"
},
"decryptInlineError": {
"message": "This message cannot be decrypted due to error: $1",
"description": "$1 is error message"
},
"provide": {
"message": "Provide"
},
"encryptionPublicKeyRequest": {
"message": "Request encryption public key"
},
"encryptionPublicKeyNotice": {
"message": "$1 would like your public encryption key. By consenting, this site will be able to compose encrypted messages to you.",
"description": "$1 is website or dapp name"
}
}

@ -124,6 +124,9 @@
"customRPC": {
"message": "Пользовательский RPC"
},
"dataBackupFoundInfo": {
"message": "Некоторые данные вашей учетной записи были экспортированы во время предыдущей установки MetaMask. Это может включать ваши настройки, контакты и токены. Вы хотите импортировать эти данные сейчас?"
},
"decimalsMustZerotoTen": {
"message": "Количество десятичных разрядов должно быть минимум 0 и максимум 36."
},
@ -962,8 +965,11 @@
"noConversionRateAvailable": {
"message": "Курсы валют недоступны"
},
"noThanks": {
"message": "Нет, спасибо"
},
"notEnoughGas": {
"message": "Нехватает газа"
"message": "Не хватает газа"
},
"noWebcamFoundTitle": {
"message": "Веб-камера не найдена"
@ -1046,6 +1052,10 @@
"restoreAccountWithSeed": {
"message": "Восстановите свой аккаунт с помощью секретной фразы"
},
"restoreWalletPreferences": {
"message": "Были найдены данные экспортированные от $1. Вы желаете восстановить настройки вашего кошелька?",
"description": "$1 is the date at which the data was backed up"
},
"requestsAwaitingAcknowledgement": {
"message": "запросы, ожидающие подтверждения"
},
@ -1339,5 +1349,35 @@
},
"yourPrivateSeedPhrase": {
"message": "Ваша сид-фраза"
},
"decryptRequest": {
"message": "Запрос расшифровки"
},
"decrypt": {
"message": "Расшифровать"
},
"decryptMessageNotice": {
"message": "Для $1 необходимо прочитать это сообщение, чтобы завершить Ваше действие",
"description": "$1 is website or dapp name"
},
"decryptMetamask": {
"message": "Расшифровать сообщение"
},
"decryptCopy": {
"message": "Скопировать расшифрованное сообщение"
},
"decryptInlineError": {
"message": "Это сообщение не может быть дешифровано из-за ошибки: $1",
"description": "$1 is error message"
},
"provide": {
"message": "Предоставить"
},
"encryptionPublicKeyRequest": {
"message": "Запрос публичного ключа шифрования"
},
"encryptionPublicKeyNotice": {
"message": "$1 запрашивает ваш открытый ключ шифрования. По согласованию, этот сайт сможет создавать для Вас зашифрованные сообщения.",
"description": "$1 is website or dapp name"
}
}

@ -127,6 +127,10 @@ initialize().catch(log.error)
* @property {number} unapprovedMsgCount - The number of messages in unapprovedMsgs.
* @property {Object} unapprovedPersonalMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
* @property {number} unapprovedPersonalMsgCount - The number of messages in unapprovedPersonalMsgs.
* @property {Object} EncryptionPublicKeyMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
* @property {number} unapprovedEncryptionPublicKeyMsgCount - The number of messages in EncryptionPublicKeyMsgs.
* @property {Object} unapprovedDecryptMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
* @property {number} unapprovedDecryptMsgCount - The number of messages in unapprovedDecryptMsgs.
* @property {Object} unapprovedTypedMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
* @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs.
* @property {string[]} keyringTypes - An array of unique keyring identifying strings, representing available strategies for creating accounts.
@ -413,6 +417,8 @@ function setupController (initState, initLangCode) {
controller.txController.on('update:badge', updateBadge)
controller.messageManager.on('updateBadge', updateBadge)
controller.personalMessageManager.on('updateBadge', updateBadge)
controller.decryptMessageManager.on('updateBadge', updateBadge)
controller.encryptionPublicKeyManager.on('updateBadge', updateBadge)
controller.typedMessageManager.on('updateBadge', updateBadge)
controller.permissionsController.permissions.subscribe(updateBadge)
@ -424,10 +430,13 @@ function setupController (initState, initLangCode) {
let label = ''
const unapprovedTxCount = controller.txController.getUnapprovedTxCount()
const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount
const unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount
const unapprovedPersonalMsgCount = controller.personalMessageManager.unapprovedPersonalMsgCount
const unapprovedDecryptMsgCount = controller.decryptMessageManager.unapprovedDecryptMsgCount
const unapprovedEncryptionPublicKeyMsgCount = controller.encryptionPublicKeyManager.unapprovedEncryptionPublicKeyMsgCount
const unapprovedTypedMessagesCount = controller.typedMessageManager.unapprovedTypedMessagesCount
const pendingPermissionRequests = Object.keys(controller.permissionsController.permissions.state.permissionsRequests).length
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingPermissionRequests
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount +
unapprovedTypedMessagesCount + pendingPermissionRequests
if (count) {
label = String(count)
}

@ -14,6 +14,8 @@ function createMetamaskMiddleware ({
processTypedMessageV3,
processTypedMessageV4,
processPersonalMessage,
processDecryptMessage,
processEncryptionPublicKey,
getPendingNonce,
getPendingTransactionByHash,
}) {
@ -31,6 +33,8 @@ function createMetamaskMiddleware ({
processTypedMessageV3,
processTypedMessageV4,
processPersonalMessage,
processDecryptMessage,
processEncryptionPublicKey,
}),
createPendingNonceMiddleware({ getPendingNonce }),
createPendingTxMiddleware({ getPendingTransactionByHash }),

@ -69,4 +69,6 @@ export const SAFE_METHODS = [
'eth_uninstallFilter',
'metamask_watchAsset',
'wallet_watchAsset',
'eth_getEncryptionPublicKey',
'eth_decrypt',
]

@ -0,0 +1,311 @@
import EventEmitter from 'events'
import ObservableStore from 'obs-store'
import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors'
import createId from './random-id'
const hexRe = /^[0-9A-Fa-f]+$/g
import log from 'loglevel'
/**
* Represents, and contains data about, an 'eth_decrypt' type decryption request. These are created when a
* decryption for an eth_decrypt call is requested.
*
* @typedef {Object} DecryptMessage
* @property {number} id An id to track and identify the message object
* @property {Object} msgParams The parameters to pass to the decryptMessage method once the decryption request is
* approved.
* @property {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @property {string} msgParams.data A hex string conversion of the raw buffer data of the decryption request
* @property {number} time The epoch time at which the this message was created
* @property {string} status Indicates whether the decryption request is 'unapproved', 'approved', 'decrypted' or 'rejected'
* @property {string} type The json-prc decryption method for which a decryption request has been made. A 'Message' will
* always have a 'eth_decrypt' type.
*
*/
export default class DecryptMessageManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - DecryptMessage.
*
* @typedef {Object} DecryptMessageManager
* @property {Object} memStore The observable store where DecryptMessage are saved with persistance.
* @property {Object} memStore.unapprovedDecryptMsgs A collection of all DecryptMessages in the 'unapproved' state
* @property {number} memStore.unapprovedDecryptMsgCount The count of all DecryptMessages in this.memStore.unapprobedMsgs
* @property {array} messages Holds all messages that have been created by this DecryptMessageManager
*
*/
constructor () {
super()
this.memStore = new ObservableStore({
unapprovedDecryptMsgs: {},
unapprovedDecryptMsgCount: 0,
})
this.messages = []
}
/**
* A getter for the number of 'unapproved' DecryptMessages in this.messages
*
* @returns {number} The number of 'unapproved' DecryptMessages in this.messages
*
*/
get unapprovedDecryptMsgCount () {
return Object.keys(this.getUnapprovedMsgs()).length
}
/**
* A getter for the 'unapproved' DecryptMessages in this.messages
*
* @returns {Object} An index of DecryptMessage ids to DecryptMessages, for all 'unapproved' DecryptMessages in
* this.messages
*
*/
getUnapprovedMsgs () {
return this.messages.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
}, {})
}
/**
* Creates a new DecryptMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
* the new DecryptMessage to this.messages, and to save the unapproved DecryptMessages from that list to
* this.memStore.
*
* @param {Object} msgParams The params for the eth_decrypt call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {Promise<Buffer>} The raw decrypted message contents
*
*/
addUnapprovedMessageAsync (msgParams, req) {
return new Promise((resolve, reject) => {
if (!msgParams.from) {
reject(new Error('MetaMask Message for Decryption: from field is required.'))
}
const msgId = this.addUnapprovedMessage(msgParams, req)
this.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'decrypted':
return resolve(data.rawData)
case 'rejected':
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message for Decryption: User denied message decryption.'))
case 'errored':
return reject(new Error('This message cannot be decrypted'))
default:
return reject(new Error(`MetaMask Message for Decryption: Unknown problem: ${JSON.stringify(msgParams)}`))
}
})
})
}
/**
* Creates a new DecryptMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
* the new DecryptMessage to this.messages, and to save the unapproved DecryptMessages from that list to
* this.memStore.
*
* @param {Object} msgParams The params for the eth_decryptMsg call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {number} The id of the newly created DecryptMessage.
*
*/
addUnapprovedMessage (msgParams, req) {
log.debug(`DecryptMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
// add origin from request
if (req) {
msgParams.origin = req.origin
}
msgParams.data = this.normalizeMsgData(msgParams.data)
// create txData obj with parameters and meta data
const time = (new Date()).getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: msgParams,
time: time,
status: 'unapproved',
type: 'eth_decrypt',
}
this.addMsg(msgData)
// signal update
this.emit('update')
return msgId
}
/**
* Adds a passed DecryptMessage to this.messages, and calls this._saveMsgList() to save the unapproved DecryptMessages from that
* list to this.memStore.
*
* @param {Message} msg The DecryptMessage to add to this.messages
*
*/
addMsg (msg) {
this.messages.push(msg)
this._saveMsgList()
}
/**
* Returns a specified DecryptMessage.
*
* @param {number} msgId The id of the DecryptMessage to get
* @returns {DecryptMessage|undefined} The DecryptMessage with the id that matches the passed msgId, or undefined
* if no DecryptMessage has that id.
*
*/
getMsg (msgId) {
return this.messages.find((msg) => msg.id === msgId)
}
/**
* Approves a DecryptMessage. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
* with the message params modified for proper decryption.
*
* @param {Object} msgParams The msgParams to be used when eth_decryptMsg is called, plus data added by MetaMask.
* @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
*
*/
approveMessage (msgParams) {
this.setMsgStatusApproved(msgParams.metamaskId)
return this.prepMsgForDecryption(msgParams)
}
/**
* Sets a DecryptMessage status to 'approved' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the DecryptMessage to approve.
*
*/
setMsgStatusApproved (msgId) {
this._setMsgStatus(msgId, 'approved')
}
/**
* Sets a DecryptMessage status to 'decrypted' via a call to this._setMsgStatus and updates that DecryptMessage in
* this.messages by adding the raw decryption data of the decryption request to the DecryptMessage
*
* @param {number} msgId The id of the DecryptMessage to decrypt.
* @param {buffer} rawData The raw data of the message request
*
*/
setMsgStatusDecrypted (msgId, rawData) {
const msg = this.getMsg(msgId)
msg.rawData = rawData
this._updateMsg(msg)
this._setMsgStatus(msgId, 'decrypted')
}
/**
* Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
*
* @param {Object} msgParams The msgParams to modify
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
*
*/
prepMsgForDecryption (msgParams) {
delete msgParams.metamaskId
return Promise.resolve(msgParams)
}
/**
* Sets a DecryptMessage status to 'rejected' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the DecryptMessage to reject.
*
*/
rejectMsg (msgId) {
this._setMsgStatus(msgId, 'rejected')
}
/**
* Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the TypedMessage to error
*
*/
errorMessage (msgId, error) {
const msg = this.getMsg(msgId)
msg.error = error
this._updateMsg(msg)
this._setMsgStatus(msgId, 'errored')
}
/**
* Updates the status of a DecryptMessage in this.messages via a call to this._updateMsg
*
* @private
* @param {number} msgId The id of the DecryptMessage to update.
* @param {string} status The new status of the DecryptMessage.
* @throws A 'DecryptMessageManager - DecryptMessage not found for id: "${msgId}".' if there is no DecryptMessage
* in this.messages with an id equal to the passed msgId
* @fires An event with a name equal to `${msgId}:${status}`. The DecryptMessage is also fired.
* @fires If status is 'rejected' or 'decrypted', an event with a name equal to `${msgId}:finished` is fired along
* with the DecryptMessage
*
*/
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) {
throw new Error('DecryptMessageManager - Message not found for id: "${msgId}".')
}
msg.status = status
this._updateMsg(msg)
this.emit(`${msgId}:${status}`, msg)
if (status === 'rejected' || status === 'decrypted' || status === 'errored') {
this.emit(`${msgId}:finished`, msg)
}
}
/**
* Sets a DecryptMessage in this.messages to the passed DecryptMessage if the ids are equal. Then saves the
* unapprovedDecryptMsgs index to storage via this._saveMsgList
*
* @private
* @param {msg} DecryptMessage A DecryptMessage that will replace an existing DecryptMessage (with the same
* id) in this.messages
*
*/
_updateMsg (msg) {
const index = this.messages.findIndex((message) => message.id === msg.id)
if (index !== -1) {
this.messages[index] = msg
}
this._saveMsgList()
}
/**
* Saves the unapproved DecryptMessages, and their count, to this.memStore
*
* @private
* @fires 'updateBadge'
*
*/
_saveMsgList () {
const unapprovedDecryptMsgs = this.getUnapprovedMsgs()
const unapprovedDecryptMsgCount = Object.keys(unapprovedDecryptMsgs).length
this.memStore.updateState({ unapprovedDecryptMsgs, unapprovedDecryptMsgCount })
this.emit('updateBadge')
}
/**
* A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex.
*
* @param {any} data The buffer data to convert to a hex
* @returns {string} A hex string conversion of the buffer data
*
*/
normalizeMsgData (data) {
try {
const stripped = ethUtil.stripHexPrefix(data)
if (stripped.match(hexRe)) {
return ethUtil.addHexPrefix(stripped)
}
} catch (e) {
log.debug(`Message was not hex encoded, interpreting as utf8.`)
}
return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
}
}

@ -0,0 +1,286 @@
import EventEmitter from 'events'
import ObservableStore from 'obs-store'
import { ethErrors } from 'eth-json-rpc-errors'
import createId from './random-id'
import log from 'loglevel'
/**
* Represents, and contains data about, an 'eth_getEncryptionPublicKey' type request. These are created when
* an eth_getEncryptionPublicKey call is requested.
*
* @typedef {Object} EncryptionPublicKey
* @property {number} id An id to track and identify the message object
* @property {Object} msgParams The parameters to pass to the encryptionPublicKey method once the request is
* approved.
* @property {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @property {string} msgParams.data A hex string conversion of the raw buffer data of the request
* @property {number} time The epoch time at which the this message was created
* @property {string} status Indicates whether the request is 'unapproved', 'approved', 'received' or 'rejected'
* @property {string} type The json-prc method for which a request has been made. A 'Message' will
* always have a 'eth_getEncryptionPublicKey' type.
*
*/
export default class EncryptionPublicKeyManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - EncryptionPublicKey.
*
* @typedef {Object} EncryptionPublicKeyManager
* @property {Object} memStore The observable store where EncryptionPublicKey are saved with persistance.
* @property {Object} memStore.unapprovedEncryptionPublicKeyMsgs A collection of all EncryptionPublicKeys in the 'unapproved' state
* @property {number} memStore.unapprovedEncryptionPublicKeyMsgCount The count of all EncryptionPublicKeys in this.memStore.unapprobedMsgs
* @property {array} messages Holds all messages that have been created by this EncryptionPublicKeyManager
*
*/
constructor () {
super()
this.memStore = new ObservableStore({
unapprovedEncryptionPublicKeyMsgs: {},
unapprovedEncryptionPublicKeyMsgCount: 0,
})
this.messages = []
}
/**
* A getter for the number of 'unapproved' EncryptionPublicKeys in this.messages
*
* @returns {number} The number of 'unapproved' EncryptionPublicKeys in this.messages
*
*/
get unapprovedEncryptionPublicKeyMsgCount () {
return Object.keys(this.getUnapprovedMsgs()).length
}
/**
* A getter for the 'unapproved' EncryptionPublicKeys in this.messages
*
* @returns {Object} An index of EncryptionPublicKey ids to EncryptionPublicKeys, for all 'unapproved' EncryptionPublicKeys in
* this.messages
*
*/
getUnapprovedMsgs () {
return this.messages.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
}, {})
}
/**
* Creates a new EncryptionPublicKey with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
* the new EncryptionPublicKey to this.messages, and to save the unapproved EncryptionPublicKeys from that list to
* this.memStore.
*
* @param {Object} address The param for the eth_getEncryptionPublicKey call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {Promise<Buffer>} The raw public key contents
*
*/
addUnapprovedMessageAsync (address, req) {
return new Promise((resolve, reject) => {
if (!address) {
reject(new Error('MetaMask Message for EncryptionPublicKey: address field is required.'))
}
const msgId = this.addUnapprovedMessage(address, req)
this.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'received':
return resolve(data.rawData)
case 'rejected':
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message for EncryptionPublicKey: User denied message EncryptionPublicKey.'))
default:
return reject(new Error(`MetaMask Message for EncryptionPublicKey: Unknown problem: ${JSON.stringify(address)}`))
}
})
})
}
/**
* Creates a new EncryptionPublicKey with an 'unapproved' status using the passed msgParams. this.addMsg is called to add
* the new EncryptionPublicKey to this.messages, and to save the unapproved EncryptionPublicKeys from that list to
* this.memStore.
*
* @param {Object} address The param for the eth_getEncryptionPublicKey call to be made after the message is approved.
* @param {Object} _req (optional) The original request object possibly containing the origin
* @returns {number} The id of the newly created EncryptionPublicKey.
*
*/
addUnapprovedMessage (address, _req) {
log.debug(`EncryptionPublicKeyManager addUnapprovedMessage: address`)
// create txData obj with parameters and meta data
const time = (new Date()).getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: address,
time: time,
status: 'unapproved',
type: 'eth_getEncryptionPublicKey',
}
if (_req) {
msgData.origin = _req.origin
}
this.addMsg(msgData)
// signal update
this.emit('update')
return msgId
}
/**
* Adds a passed EncryptionPublicKey to this.messages, and calls this._saveMsgList() to save the unapproved EncryptionPublicKeys from that
* list to this.memStore.
*
* @param {Message} msg The EncryptionPublicKey to add to this.messages
*
*/
addMsg (msg) {
this.messages.push(msg)
this._saveMsgList()
}
/**
* Returns a specified EncryptionPublicKey.
*
* @param {number} msgId The id of the EncryptionPublicKey to get
* @returns {EncryptionPublicKey|undefined} The EncryptionPublicKey with the id that matches the passed msgId, or undefined
* if no EncryptionPublicKey has that id.
*
*/
getMsg (msgId) {
return this.messages.find((msg) => msg.id === msgId)
}
/**
* Approves a EncryptionPublicKey. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise
* with any the message params modified for proper providing.
*
* @param {Object} msgParams The msgParams to be used when eth_getEncryptionPublicKey is called, plus data added by MetaMask.
* @param {Object} msgParams.metamaskId Added to msgParams for tracking and identification within MetaMask.
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
*
*/
approveMessage (msgParams) {
this.setMsgStatusApproved(msgParams.metamaskId)
return this.prepMsgForEncryptionPublicKey(msgParams)
}
/**
* Sets a EncryptionPublicKey status to 'approved' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the EncryptionPublicKey to approve.
*
*/
setMsgStatusApproved (msgId) {
this._setMsgStatus(msgId, 'approved')
}
/**
* Sets a EncryptionPublicKey status to 'received' via a call to this._setMsgStatus and updates that EncryptionPublicKey in
* this.messages by adding the raw data of request to the EncryptionPublicKey
*
* @param {number} msgId The id of the EncryptionPublicKey.
* @param {buffer} rawData The raw data of the message request
*
*/
setMsgStatusReceived (msgId, rawData) {
const msg = this.getMsg(msgId)
msg.rawData = rawData
this._updateMsg(msg)
this._setMsgStatus(msgId, 'received')
}
/**
* Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams
*
* @param {Object} msgParams The msgParams to modify
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
*
*/
prepMsgForEncryptionPublicKey (msgParams) {
delete msgParams.metamaskId
return Promise.resolve(msgParams)
}
/**
* Sets a EncryptionPublicKey status to 'rejected' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the EncryptionPublicKey to reject.
*
*/
rejectMsg (msgId) {
this._setMsgStatus(msgId, 'rejected')
}
/**
* Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus.
*
* @param {number} msgId The id of the TypedMessage to error
*
*/
errorMessage (msgId, error) {
const msg = this.getMsg(msgId)
msg.error = error
this._updateMsg(msg)
this._setMsgStatus(msgId, 'errored')
}
/**
* Updates the status of a EncryptionPublicKey in this.messages via a call to this._updateMsg
*
* @private
* @param {number} msgId The id of the EncryptionPublicKey to update.
* @param {string} status The new status of the EncryptionPublicKey.
* @throws A 'EncryptionPublicKeyManager - EncryptionPublicKey not found for id: "${msgId}".' if there is no EncryptionPublicKey
* in this.messages with an id equal to the passed msgId
* @fires An event with a name equal to `${msgId}:${status}`. The EncryptionPublicKey is also fired.
* @fires If status is 'rejected' or 'received', an event with a name equal to `${msgId}:finished` is fired along
* with the EncryptionPublicKey
*
*/
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) {
throw new Error('EncryptionPublicKeyManager - Message not found for id: "${msgId}".')
}
msg.status = status
this._updateMsg(msg)
this.emit(`${msgId}:${status}`, msg)
if (status === 'rejected' || status === 'received') {
this.emit(`${msgId}:finished`, msg)
}
}
/**
* Sets a EncryptionPublicKey in this.messages to the passed EncryptionPublicKey if the ids are equal. Then saves the
* unapprovedEncryptionPublicKeyMsgs index to storage via this._saveMsgList
*
* @private
* @param {msg} EncryptionPublicKey A EncryptionPublicKey that will replace an existing EncryptionPublicKey (with the same
* id) in this.messages
*
*/
_updateMsg (msg) {
const index = this.messages.findIndex((message) => message.id === msg.id)
if (index !== -1) {
this.messages[index] = msg
}
this._saveMsgList()
}
/**
* Saves the unapproved EncryptionPublicKeys, and their count, to this.memStore
*
* @private
* @fires 'updateBadge'
*
*/
_saveMsgList () {
const unapprovedEncryptionPublicKeyMsgs = this.getUnapprovedMsgs()
const unapprovedEncryptionPublicKeyMsgCount = Object.keys(unapprovedEncryptionPublicKeyMsgs).length
this.memStore.updateState({ unapprovedEncryptionPublicKeyMsgs, unapprovedEncryptionPublicKeyMsgCount })
this.emit('updateBadge')
}
}

@ -35,6 +35,8 @@ import ThreeBoxController from './controllers/threebox'
import RecentBlocksController from './controllers/recent-blocks'
import IncomingTransactionsController from './controllers/incoming-transactions'
import MessageManager from './lib/message-manager'
import DecryptMessageManager from './lib/decrypt-message-manager'
import EncryptionPublicKeyManager from './lib/encryption-public-key-manager'
import PersonalMessageManager from './lib/personal-message-manager'
import TypedMessageManager from './lib/typed-message-manager'
import TransactionController from './controllers/transactions'
@ -278,6 +280,8 @@ export default class MetamaskController extends EventEmitter {
this.networkController.lookupNetwork()
this.messageManager = new MessageManager()
this.personalMessageManager = new PersonalMessageManager()
this.decryptMessageManager = new DecryptMessageManager()
this.encryptionPublicKeyManager = new EncryptionPublicKeyManager()
this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
// ensure isClientOpenAndUnlocked is updated when memState updates
@ -313,6 +317,8 @@ export default class MetamaskController extends EventEmitter {
TokenRatesController: this.tokenRatesController.store,
MessageManager: this.messageManager.memStore,
PersonalMessageManager: this.personalMessageManager.memStore,
DecryptMessageManager: this.decryptMessageManager.memStore,
EncryptionPublicKeyManager: this.encryptionPublicKeyManager.memStore,
TypesMessageManager: this.typedMessageManager.memStore,
KeyringController: this.keyringController.memStore,
PreferencesController: this.preferencesController.store,
@ -363,6 +369,8 @@ export default class MetamaskController extends EventEmitter {
processTypedMessageV3: this.newUnsignedTypedMessage.bind(this),
processTypedMessageV4: this.newUnsignedTypedMessage.bind(this),
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
processDecryptMessage: this.newRequestDecryptMessage.bind(this),
processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this),
getPendingNonce: this.getPendingNonce.bind(this),
getPendingTransactionByHash: (hash) => this.txController.getFilteredTxList({ hash, status: 'submitted' })[0],
}
@ -539,6 +547,15 @@ export default class MetamaskController extends EventEmitter {
signTypedMessage: nodeify(this.signTypedMessage, this),
cancelTypedMessage: this.cancelTypedMessage.bind(this),
// decryptMessageManager
decryptMessage: nodeify(this.decryptMessage, this),
decryptMessageInline: nodeify(this.decryptMessageInline, this),
cancelDecryptMessage: this.cancelDecryptMessage.bind(this),
// EncryptionPublicKeyManager
encryptionPublicKey: nodeify(this.encryptionPublicKey, this),
cancelEncryptionPublicKey: this.cancelEncryptionPublicKey.bind(this),
// onboarding controller
setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController),
@ -1168,6 +1185,147 @@ export default class MetamaskController extends EventEmitter {
}
}
// eth_decrypt methods
/**
* Called when a dapp uses the eth_decrypt method.
*
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
* @param {Object} req - (optional) the original request, containing the origin
* Passed back to the requesting Dapp.
*/
async newRequestDecryptMessage (msgParams, req) {
const promise = this.decryptMessageManager.addUnapprovedMessageAsync(msgParams, req)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
return promise
}
/**
* Only decypt message and don't touch transaction state
*
* @param {Object} msgParams - The params of the message to decrypt.
* @returns {Promise<Object>} - A full state update.
*/
async decryptMessageInline (msgParams) {
log.info('MetaMaskController - decryptMessageInline')
// decrypt the message inline
const msgId = msgParams.metamaskId
const msg = this.decryptMessageManager.getMsg(msgId)
try {
const stripped = ethUtil.stripHexPrefix(msgParams.data)
const buff = Buffer.from(stripped, 'hex')
msgParams.data = JSON.parse(buff.toString('utf8'))
msg.rawData = await this.keyringController.decryptMessage(msgParams)
} catch (e) {
msg.error = e.message
}
this.decryptMessageManager._updateMsg(msg)
return this.getState()
}
/**
* Signifies a user's approval to decrypt a message in queue.
* Triggers decrypt, and the callback function from newUnsignedDecryptMessage.
*
* @param {Object} msgParams - The params of the message to decrypt & return to the Dapp.
* @returns {Promise<Object>} - A full state update.
*/
async decryptMessage (msgParams) {
log.info('MetaMaskController - decryptMessage')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for decryption
try {
const cleanMsgParams = await this.decryptMessageManager.approveMessage(msgParams)
const stripped = ethUtil.stripHexPrefix(cleanMsgParams.data)
const buff = Buffer.from(stripped, 'hex')
cleanMsgParams.data = JSON.parse(buff.toString('utf8'))
// decrypt the message
const rawMess = await this.keyringController.decryptMessage(cleanMsgParams)
// tells the listener that the message has been decrypted and can be returned to the dapp
this.decryptMessageManager.setMsgStatusDecrypted(msgId, rawMess)
} catch (error) {
log.info('MetaMaskController - eth_decrypt failed.', error)
this.decryptMessageManager.errorMessage(msgId, error)
}
return this.getState()
}
/**
* Used to cancel a eth_decrypt type message.
* @param {string} msgId - The ID of the message to cancel.
* @param {Function} cb - The callback function called with a full state update.
*/
cancelDecryptMessage (msgId, cb) {
const messageManager = this.decryptMessageManager
messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') {
cb(null, this.getState())
}
}
// eth_getEncryptionPublicKey methods
/**
* Called when a dapp uses the eth_getEncryptionPublicKey method.
*
* @param {Object} msgParams - The params of the message to sign & return to the Dapp.
* @param {Object} req - (optional) the original request, containing the origin
* Passed back to the requesting Dapp.
*/
async newRequestEncryptionPublicKey (msgParams, req) {
const promise = this.encryptionPublicKeyManager.addUnapprovedMessageAsync(msgParams, req)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
return promise
}
/**
* Signifies a user's approval to receiving encryption public key in queue.
* Triggers receiving, and the callback function from newUnsignedEncryptionPublicKey.
*
* @param {Object} msgParams - The params of the message to receive & return to the Dapp.
* @returns {Promise<Object>} - A full state update.
*/
async encryptionPublicKey (msgParams) {
log.info('MetaMaskController - encryptionPublicKey')
const msgId = msgParams.metamaskId
// sets the status op the message to 'approved'
// and removes the metamaskId for decryption
try {
const params = await this.encryptionPublicKeyManager.approveMessage(msgParams)
// EncryptionPublicKey message
const publicKey = await this.keyringController.getEncryptionPublicKey(params.data)
// tells the listener that the message has been processed
// and can be returned to the dapp
this.encryptionPublicKeyManager.setMsgStatusReceived(msgId, publicKey)
} catch (error) {
log.info('MetaMaskController - eth_getEncryptionPublicKey failed.', error)
this.encryptionPublicKeyManager.errorMessage(msgId, error)
}
return this.getState()
}
/**
* Used to cancel a eth_getEncryptionPublicKey type message.
* @param {string} msgId - The ID of the message to cancel.
* @param {Function} cb - The callback function called with a full state update.
*/
cancelEncryptionPublicKey (msgId, cb) {
const messageManager = this.encryptionPublicKeyManager
messageManager.rejectMsg(msgId)
if (cb && typeof cb === 'function') {
cb(null, this.getState())
}
}
// eth_signTypedData methods
/**

@ -88,7 +88,7 @@
"eth-json-rpc-errors": "^2.0.2",
"eth-json-rpc-filters": "^4.1.1",
"eth-json-rpc-infura": "^4.0.2",
"eth-json-rpc-middleware": "^4.4.0",
"eth-json-rpc-middleware": "^4.4.1",
"eth-keyring-controller": "^5.5.0",
"eth-ledger-bridge-keyring": "^0.2.0",
"eth-method-registry": "^1.2.0",

@ -18,6 +18,8 @@
"unapprovedMsgCount": 0,
"unapprovedPersonalMsgs": {},
"unapprovedPersonalMsgCount": 0,
"unapprovedDecryptMsgs": {},
"unapprovedDecryptMsgCount": 0,
"unapprovedTypedMessages": {},
"unapprovedTypedMessagesCount": 0,
"isUnlocked": true,

@ -136,6 +136,10 @@
"unapprovedMsgCount": 0,
"unapprovedPersonalMsgs": {},
"unapprovedPersonalMsgCount": 0,
"unapprovedDecryptMsgs": {},
"unapprovedDecryptMsgCount": 0,
"unapprovedEncryptionPublicKeyMsgs": {},
"unapprovedEncryptionPublicKeyMsgCount": 0,
"unapprovedTypedMessages": {},
"unapprovedTypedMessagesCount": 0,
"send": {

@ -42,6 +42,10 @@
@import './request-signature.scss';
@import './request-encryption-public-key.scss';
@import './request-decrypt-message.scss';
@import './account-details-dropdown.scss';
@import './editable-label.scss';

@ -0,0 +1,293 @@
.request-decrypt-message {
&__container {
width: 380px;
border-radius: 8px;
background-color: $white;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.08);
display: flex;
flex-flow: column nowrap;
z-index: 25;
align-items: center;
font-family: Roboto;
position: relative;
height: 100%;
@media screen and (max-width: $break-small) {
width: 100%;
top: 0;
box-shadow: none;
}
@media screen and (min-width: $break-large) {
height: 620px;
}
}
&__typed-container {
padding: 17px;
h1 {
font-weight: 900;
margin-bottom: 5px;
}
* {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
> div {
margin-bottom: 10px;
}
}
&__header {
height: 64px;
width: 100%;
position: relative;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
flex: 0 0 auto;
}
&__header-background {
position: absolute;
background-color: $athens-grey;
z-index: 2;
width: 100%;
height: 100%;
}
&__header__text {
color: #5B5D67;
font-family: Roboto;
font-size: 22px;
line-height: 29px;
z-index: 3;
text-align: center;
}
&__header__tip-container {
width: 100%;
display: flex;
justify-content: center;
}
&__header__tip {
height: 25px;
width: 25px;
background: $athens-grey;
transform: rotate(45deg);
position: absolute;
bottom: -8px;
z-index: 1;
}
&__account-info {
display: flex;
justify-content: space-between;
margin-top: 18px;
margin-bottom: 20px;
}
&__account {
color: $dusty-gray;
margin-left: 17px;
}
&__account-text {
font-size: 14px;
}
&__account-item {
height: 22px;
background-color: $white;
font-family: Roboto;
line-height: 16px;
font-size: 12px;
width: 124px;
.account-list-item {
margin-top: 6px;
}
.account-list-item__account-name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 80px;
}
.account-list-item__top-row {
margin: 0;
}
}
&__balance {
color: $dusty-gray;
margin-right: 17px;
width: 124px;
}
&__balance-text {
text-align: right;
font-size: 14px;
}
&__balance-value {
text-align: right;
margin-top: 2.5px;
}
&__request-icon {
margin-top: 25px;
}
&__body {
width: 100%;
height: 100%;
display: flex;
flex-flow: column;
flex: 1 1 auto;
height: 0;
}
&__notice {
font-family: "Avenir Next";
font-size: 14px;
line-height: 19px;
text-align: center;
margin-top: 15px;
margin-bottom: 11px;
width: 100%;
}
&__message {
overflow-wrap: break-word;
margin: 20px;
overflow: hidden;
border: 1px solid #dedede;
padding: 5px;
border-radius: 5px;
position: relative;
&-text {
font-size: 0.7em;
height: 115px;
}
&-cover {
background-color: white;
opacity: 0.75;
position: absolute;
height: 100%;
width: 100%;
top: 0px;
}
&-lock {
position: absolute;
height: 100%;
width: 100%;
top: 0px;
cursor: pointer;
img {
padding: 5px;
background-color: #fff;
left: calc(50% - 24px);
position: absolute;
top: calc(50% - 34px);
border-radius: 3px;
}
&--pressed {
display: none;
}
}
&-lock-text {
width: 200px;
font-size: 0.75em;
position: absolute;
top: calc(50% + 5px);
text-align: center;
left: calc(50% - 100px);
background-color: white;
line-height: 1em;
border-radius: 3px;
}
&-copy {
justify-content: space-evenly;
font-size: 0.75em;
margin-left: 20px;
margin-right: 20px;
display: flex;
cursor: pointer;
}
&-copy-text {
margin-right: 10px;
display: inline;
}
&-copy-tooltip {
float: right;
}
}
&__footer {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
position: relative;
flex: 0 0 auto;
border-top: 1px solid $geyser;
padding: 1.6rem;
button {
width: 165px;
}
&__cancel-button {
margin-right: 1.2rem;
}
}
&__visual {
display: flex;
flex-direction: row;
justify-content: space-evenly;
position: relative;
margin: 0 20px;
section {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
&-identicon {
width: 48px;
height: 48px;
&--default {
background-color: #777A87;
color: white;
width: 48px;
height: 48px;
border-radius: 24px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
}
}
}

@ -0,0 +1,222 @@
.request-encryption-public-key {
&__container {
width: 380px;
border-radius: 8px;
background-color: $white;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.08);
display: flex;
flex-flow: column nowrap;
z-index: 25;
align-items: center;
font-family: Roboto;
position: relative;
height: 100%;
@media screen and (max-width: $break-small) {
width: 100%;
top: 0;
box-shadow: none;
}
@media screen and (min-width: $break-large) {
height: 620px;
}
}
&__typed-container {
padding: 17px;
h1 {
font-weight: 900;
margin-bottom: 5px;
}
* {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
> div {
margin-bottom: 10px;
}
}
&__header {
height: 64px;
width: 100%;
position: relative;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
flex: 0 0 auto;
}
&__header-background {
position: absolute;
background-color: $athens-grey;
z-index: 2;
width: 100%;
height: 100%;
}
&__header__text {
color: #5B5D67;
font-family: Roboto;
font-size: 22px;
line-height: 29px;
z-index: 3;
text-align: center;
}
&__header__tip-container {
width: 100%;
display: flex;
justify-content: center;
}
&__header__tip {
height: 25px;
width: 25px;
background: $athens-grey;
transform: rotate(45deg);
position: absolute;
bottom: -8px;
z-index: 1;
}
&__account-info {
display: flex;
justify-content: space-between;
margin-top: 18px;
margin-bottom: 20px;
}
&__account {
color: $dusty-gray;
margin-left: 17px;
}
&__account-text {
font-size: 14px;
}
&__account-item {
height: 22px;
background-color: $white;
font-family: Roboto;
line-height: 16px;
font-size: 12px;
width: 124px;
.account-list-item {
margin-top: 6px;
}
.account-list-item__account-name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 80px;
}
.account-list-item__top-row {
margin: 0;
}
}
&__balance {
color: $dusty-gray;
margin-right: 17px;
width: 124px;
}
&__balance-text {
text-align: right;
font-size: 14px;
}
&__balance-value {
text-align: right;
margin-top: 2.5px;
}
&__request-icon {
margin-top: 25px;
}
&__body {
width: 100%;
height: 100%;
display: flex;
flex-flow: column;
flex: 1 1 auto;
height: 0;
}
&__notice {
font-family: "Avenir Next";
font-size: 14px;
line-height: 19px;
text-align: center;
margin-top: 41px;
margin-bottom: 11px;
width: 100%;
padding-left: 20px;
padding-right: 20px;
color: $dusty-gray;
}
&__footer {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
position: relative;
flex: 0 0 auto;
border-top: 1px solid $geyser;
padding: 1.6rem;
button {
width: 165px;
}
&__cancel-button {
margin-right: 1.2rem;
}
}
&__visual {
display: flex;
flex-direction: row;
justify-content: space-evenly;
position: relative;
margin: 0 20px;
section {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
&-identicon {
width: 48px;
height: 48px;
&--default {
background-color: #777A87;
color: white;
width: 48px;
height: 48px;
border-radius: 24px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
}
}
}

@ -48,6 +48,8 @@ const CONFIRM_APPROVE_PATH = '/approve'
const CONFIRM_TRANSFER_FROM_PATH = '/transfer-from'
const CONFIRM_TOKEN_METHOD_PATH = '/token-method'
const SIGNATURE_REQUEST_PATH = '/signature-request'
const DECRYPT_MESSAGE_REQUEST_PATH = '/decrypt-message-request'
const ENCRYPTION_PUBLIC_KEY_REQUEST_PATH = '/encryption-public-key-request'
export {
DEFAULT_ROUTE,
@ -81,6 +83,8 @@ export {
CONFIRM_TRANSFER_FROM_PATH,
CONFIRM_TOKEN_METHOD_PATH,
SIGNATURE_REQUEST_PATH,
DECRYPT_MESSAGE_REQUEST_PATH,
ENCRYPTION_PUBLIC_KEY_REQUEST_PATH,
INITIALIZE_METAMETRICS_OPT_IN_ROUTE,
ADVANCED_ROUTE,
SECURITY_ROUTE,

@ -18,6 +18,8 @@ export const APPROVE_ACTION_KEY = 'approve'
export const SEND_TOKEN_ACTION_KEY = 'sentTokens'
export const TRANSFER_FROM_ACTION_KEY = 'transferFrom'
export const SIGNATURE_REQUEST_KEY = 'signatureRequest'
export const DECRYPT_REQUEST_KEY = 'decryptRequest'
export const ENCRYPTION_PUBLIC_KEY_REQUEST_KEY = 'encryptionPublicKeyRequest'
export const CONTRACT_INTERACTION_KEY = 'contractInteraction'
export const CANCEL_ATTEMPT_ACTION_KEY = 'cancelAttempt'
export const DEPOSIT_TRANSACTION_KEY = 'deposit'

@ -19,6 +19,8 @@ import {
SEND_TOKEN_ACTION_KEY,
TRANSFER_FROM_ACTION_KEY,
SIGNATURE_REQUEST_KEY,
DECRYPT_REQUEST_KEY,
ENCRYPTION_PUBLIC_KEY_REQUEST_KEY,
CONTRACT_INTERACTION_KEY,
CANCEL_ATTEMPT_ACTION_KEY,
DEPOSIT_TRANSACTION_KEY,
@ -132,7 +134,13 @@ export function getTransactionActionKey (transaction) {
}
if (msgParams) {
return SIGNATURE_REQUEST_KEY
if (type === 'eth_decrypt') {
return DECRYPT_REQUEST_KEY
} else if (type === 'eth_getEncryptionPublicKey') {
return ENCRYPTION_PUBLIC_KEY_REQUEST_KEY
} else {
return SIGNATURE_REQUEST_KEY
}
}
if (isConfirmDeployContract(transaction)) {

@ -0,0 +1,333 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Tooltip from '../../components/ui/tooltip-v2'
import copyToClipboard from 'copy-to-clipboard'
import classnames from 'classnames'
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import Identicon from '../../components/ui/identicon'
import AccountListItem from '../send/account-list-item/account-list-item.component'
import { conversionUtil } from '../../helpers/utils/conversion-util'
import Button from '../../components/ui/button'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
export default class ConfirmDecryptMessage extends Component {
static contextTypes = {
t: PropTypes.func.isRequired,
metricsEvent: PropTypes.func.isRequired,
}
static propTypes = {
balance: PropTypes.string,
clearConfirmTransaction: PropTypes.func.isRequired,
cancelDecryptMessage: PropTypes.func.isRequired,
decryptMessage: PropTypes.func.isRequired,
decryptMessageInline: PropTypes.func.isRequired,
conversionRate: PropTypes.number,
history: PropTypes.object.isRequired,
requesterAddress: PropTypes.string,
selectedAccount: PropTypes.object,
txData: PropTypes.object,
domainMetadata: PropTypes.object,
}
state = {
selectedAccount: this.props.selectedAccount,
hasCopied: false,
copyToClipboardPressed: false,
}
componentDidMount = () => {
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) {
window.addEventListener('beforeunload', this._beforeUnload)
}
}
componentWillUnmount = () => {
this._removeBeforeUnload()
}
_beforeUnload = (event) => {
const { clearConfirmTransaction, cancelDecryptMessage } = this.props
const { metricsEvent } = this.context
metricsEvent({
eventOpts: {
category: 'Messages',
action: 'Decrypt Message Request',
name: 'Cancel Via Notification Close',
},
})
clearConfirmTransaction()
cancelDecryptMessage(event)
}
_removeBeforeUnload = () => {
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) {
window.removeEventListener('beforeunload', this._beforeUnload)
}
}
copyMessage = () => {
copyToClipboard(this.state.rawMessage)
this.context.metricsEvent({
eventOpts: {
category: 'Messages',
action: 'Decrypt Message Copy',
name: 'Copy',
},
})
this.setState({ hasCopied: true })
setTimeout(() => this.setState({ hasCopied: false }), 3000)
}
renderHeader = () => {
return (
<div className="request-decrypt-message__header">
<div className="request-decrypt-message__header-background" />
<div className="request-decrypt-message__header__text">
{ this.context.t('decryptRequest') }
</div>
<div className="request-decrypt-message__header__tip-container">
<div className="request-decrypt-message__header__tip" />
</div>
</div>
)
}
renderAccount = () => {
const { selectedAccount } = this.state
return (
<div className="request-decrypt-message__account">
<div className="request-decrypt-message__account-text">
{ `${this.context.t('account')}:` }
</div>
<div className="request-decrypt-message__account-item">
<AccountListItem
account={selectedAccount}
displayBalance={false}
/>
</div>
</div>
)
}
renderBalance = () => {
const { balance, conversionRate } = this.props
const balanceInEther = conversionUtil(balance, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
fromDenomination: 'WEI',
numberOfDecimals: 6,
conversionRate,
})
return (
<div className="request-decrypt-message__balance">
<div className="request-decrypt-message__balance-text">
{ `${this.context.t('balance')}:` }
</div>
<div className="request-decrypt-message__balance-value">
{ `${balanceInEther} ETH` }
</div>
</div>
)
}
renderRequestIcon = () => {
const { requesterAddress } = this.props
return (
<div className="request-decrypt-message__request-icon">
<Identicon
diameter={40}
address={requesterAddress}
/>
</div>
)
}
renderAccountInfo = () => {
return (
<div className="request-decrypt-message__account-info">
{ this.renderAccount() }
{ this.renderRequestIcon() }
{ this.renderBalance() }
</div>
)
}
renderBody = () => {
const { txData } = this.props
const origin = this.props.domainMetadata[txData.msgParams.origin]
const notice = this.context.t('decryptMessageNotice', [origin.name])
const {
hasCopied,
hasDecrypted,
hasError,
rawMessage,
errorMessage,
copyToClipboardPressed,
} = this.state
return (
<div className="request-decrypt-message__body">
{ this.renderAccountInfo() }
<div
className="request-decrypt-message__visual"
>
<section>
{origin.icon ? (
<img
className="request-decrypt-message__visual-identicon"
src={origin.icon}
/>
) : (
<i className="request-decrypt-message__visual-identicon--default">
{origin.name.charAt(0).toUpperCase()}
</i>
)}
<div
className="request-decrypt-message__notice"
>
{ notice }
</div>
</section>
</div>
<div
className="request-decrypt-message__message"
>
<div
className="request-decrypt-message__message-text"
>
{ !hasDecrypted && !hasError ? txData.msgParams.data : rawMessage }
{ !hasError ? '' : errorMessage }
</div>
<div
className={classnames({
'request-decrypt-message__message-cover': true,
'request-decrypt-message__message-lock--pressed': hasDecrypted || hasError,
})}
>
</div>
<div
className={classnames({
'request-decrypt-message__message-lock': true,
'request-decrypt-message__message-lock--pressed': hasDecrypted || hasError,
})}
onClick={(event) => {
this.props.decryptMessageInline(txData, event).then((result) => {
if (!result.error) {
this.setState({ hasDecrypted: true, rawMessage: result.rawData })
} else {
this.setState({ hasError: true, errorMessage: this.context.t('decryptInlineError', [result.error]) })
}
})
}}
>
<img src="images/lock.svg" />
<div
className="request-decrypt-message__message-lock-text"
>
{this.context.t('decryptMetamask')}
</div>
</div>
</div>
{ hasDecrypted ?
(
<div
className={classnames({
'request-decrypt-message__message-copy': true,
'request-decrypt-message__message-copy--pressed': copyToClipboardPressed,
})}
onClick={() => this.copyMessage()}
onMouseDown={() => this.setState({ copyToClipboardPressed: true })}
onMouseUp={() => this.setState({ copyToClipboardPressed: false })}
>
<Tooltip
position="bottom"
title={hasCopied ? this.context.t('copiedExclamation') : this.context.t('copyToClipboard')}
wrapperClassName="request-decrypt-message__message-copy-tooltip"
>
<div
className="request-decrypt-message__message-copy-text"
>
{this.context.t('decryptCopy')}
</div>
<img src="images/copy-to-clipboard.svg" />
</Tooltip>
</div>
)
:
<div></div>
}
</div>
)
}
renderFooter = () => {
const { txData } = this.props
return (
<div className="request-decrypt-message__footer">
<Button
type="default"
large
className="request-decrypt-message__footer__cancel-button"
onClick={async (event) => {
this._removeBeforeUnload()
await this.props.cancelDecryptMessage(txData, event)
this.context.metricsEvent({
eventOpts: {
category: 'Messages',
action: 'Decrypt Message Request',
name: 'Cancel',
},
})
this.props.clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE)
}}
>
{ this.context.t('cancel') }
</Button>
<Button
type="secondary"
large
className="request-decrypt-message__footer__sign-button"
onClick={async (event) => {
this._removeBeforeUnload()
await this.props.decryptMessage(txData, event)
this.context.metricsEvent({
eventOpts: {
category: 'Messages',
action: 'Decrypt Message Request',
name: 'Confirm',
},
})
this.props.clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE)
}}
>
{ this.context.t('decrypt') }
</Button>
</div>
)
}
render = () => {
return (
<div className="request-decrypt-message__container">
{ this.renderHeader() }
{ this.renderBody() }
{ this.renderFooter() }
</div>
)
}
}

@ -0,0 +1,62 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withRouter } from 'react-router-dom'
import { goHome, decryptMsg, cancelDecryptMsg, decryptMsgInline } from '../../store/actions'
import {
getSelectedAccount,
getCurrentAccountWithSendEtherInfo,
getSelectedAddress,
conversionRateSelector,
} from '../../selectors/selectors.js'
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'
import ConfirmDecryptMessage from './confirm-decrypt-message.component'
function mapStateToProps (state) {
const { confirmTransaction,
metamask: { domainMetadata = {} },
} = state
const {
txData = {},
} = confirmTransaction
return {
txData: txData,
domainMetadata: domainMetadata,
balance: getSelectedAccount(state).balance,
selectedAccount: getCurrentAccountWithSendEtherInfo(state),
selectedAddress: getSelectedAddress(state),
requester: null,
requesterAddress: null,
conversionRate: conversionRateSelector(state),
}
}
function mapDispatchToProps (dispatch) {
return {
goHome: () => dispatch(goHome()),
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
decryptMessage: (msgData, event) => {
const params = msgData.msgParams
params.metamaskId = msgData.id
event.stopPropagation(event)
return dispatch(decryptMsg(params))
},
cancelDecryptMessage: (msgData, event) => {
event.stopPropagation(event)
return dispatch(cancelDecryptMsg(msgData))
},
decryptMessageInline: (msgData, event) => {
const params = msgData.msgParams
params.metamaskId = msgData.id
event.stopPropagation(event)
return dispatch(decryptMsgInline(params))
},
}
}
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ConfirmDecryptMessage)

@ -0,0 +1 @@
export { default } from './confirm-decrypt-message.container'

@ -0,0 +1,238 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import Identicon from '../../components/ui/identicon'
import AccountListItem from '../send/account-list-item/account-list-item.component'
import { conversionUtil } from '../../helpers/utils/conversion-util'
import Button from '../../components/ui/button'
import { DEFAULT_ROUTE } from '../../helpers/constants/routes'
export default class ConfirmEncryptionPublicKey extends Component {
static contextTypes = {
t: PropTypes.func.isRequired,
metricsEvent: PropTypes.func.isRequired,
}
static propTypes = {
balance: PropTypes.string,
clearConfirmTransaction: PropTypes.func.isRequired,
cancelEncryptionPublicKey: PropTypes.func.isRequired,
encryptionPublicKey: PropTypes.func.isRequired,
conversionRate: PropTypes.number,
history: PropTypes.object.isRequired,
requesterAddress: PropTypes.string,
selectedAccount: PropTypes.object,
txData: PropTypes.object,
domainMetadata: PropTypes.object,
}
state = {
selectedAccount: this.props.selectedAccount,
}
componentDidMount = () => {
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) {
window.addEventListener('beforeunload', this._beforeUnload)
}
}
componentWillUnmount = () => {
this._removeBeforeUnload()
}
_beforeUnload = (event) => {
const { clearConfirmTransaction, cancelEncryptionPublicKey } = this.props
const { metricsEvent } = this.context
metricsEvent({
eventOpts: {
category: 'Messages',
action: 'Encryption public key Request',
name: 'Cancel Via Notification Close',
},
})
clearConfirmTransaction()
cancelEncryptionPublicKey(event)
}
_removeBeforeUnload = () => {
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) {
window.removeEventListener('beforeunload', this._beforeUnload)
}
}
renderHeader = () => {
return (
<div className="request-encryption-public-key__header">
<div className="request-encryption-public-key__header-background" />
<div className="request-encryption-public-key__header__text">
{ this.context.t('encryptionPublicKeyRequest') }
</div>
<div className="request-encryption-public-key__header__tip-container">
<div className="request-encryption-public-key__header__tip" />
</div>
</div>
)
}
renderAccount = () => {
const { selectedAccount } = this.state
return (
<div className="request-encryption-public-key__account">
<div className="request-encryption-public-key__account-text">
{ `${this.context.t('account')}:` }
</div>
<div className="request-encryption-public-key__account-item">
<AccountListItem
account={selectedAccount}
displayBalance={false}
/>
</div>
</div>
)
}
renderBalance = () => {
const { balance, conversionRate } = this.props
const balanceInEther = conversionUtil(balance, {
fromNumericBase: 'hex',
toNumericBase: 'dec',
fromDenomination: 'WEI',
numberOfDecimals: 6,
conversionRate,
})
return (
<div className="request-encryption-public-key__balance">
<div className="request-encryption-public-key__balance-text">
{ `${this.context.t('balance')}:` }
</div>
<div className="request-encryption-public-key__balance-value">
{ `${balanceInEther} ETH` }
</div>
</div>
)
}
renderRequestIcon = () => {
const { requesterAddress } = this.props
return (
<div className="request-encryption-public-key__request-icon">
<Identicon
diameter={40}
address={requesterAddress}
/>
</div>
)
}
renderAccountInfo = () => {
return (
<div className="request-encryption-public-key__account-info">
{ this.renderAccount() }
{ this.renderRequestIcon() }
{ this.renderBalance() }
</div>
)
}
renderBody = () => {
const { txData } = this.props
const origin = this.props.domainMetadata[txData.origin]
const notice = this.context.t('encryptionPublicKeyNotice', [origin.name])
return (
<div className="request-encryption-public-key__body">
{ this.renderAccountInfo() }
<div
className="request-encryption-public-key__visual"
>
<section>
{origin.icon ? (
<img
className="request-encryption-public-key__visual-identicon"
src={origin.icon}
/>
) : (
<i className="request-encryption-public-key__visual-identicon--default">
{origin.name.charAt(0).toUpperCase()}
</i>
)}
<div
className="request-encryption-public-key__notice"
>
{ notice }
</div>
</section>
</div>
</div>
)
}
renderFooter = () => {
const { txData } = this.props
return (
<div className="request-encryption-public-key__footer">
<Button
type="default"
large
className="request-encryption-public-key__footer__cancel-button"
onClick={async (event) => {
this._removeBeforeUnload()
await this.props.cancelEncryptionPublicKey(txData, event)
this.context.metricsEvent({
eventOpts: {
category: 'Messages',
action: 'Encryption public key Request',
name: 'Cancel',
},
})
this.props.clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE)
}}
>
{ this.context.t('cancel') }
</Button>
<Button
type="secondary"
large
className="request-encryption-public-key__footer__sign-button"
onClick={async (event) => {
this._removeBeforeUnload()
await this.props.encryptionPublicKey(txData, event)
this.context.metricsEvent({
eventOpts: {
category: 'Messages',
action: 'Encryption public key Request',
name: 'Confirm',
},
})
this.props.clearConfirmTransaction()
this.props.history.push(DEFAULT_ROUTE)
}}
>
{ this.context.t('provide') }
</Button>
</div>
)
}
render = () => {
return (
<div className="request-encryption-public-key__container">
{ this.renderHeader() }
{ this.renderBody() }
{ this.renderFooter() }
</div>
)
}
}

@ -0,0 +1,55 @@
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withRouter } from 'react-router-dom'
import { goHome, encryptionPublicKeyMsg, cancelEncryptionPublicKeyMsg } from '../../store/actions'
import {
getSelectedAccount,
getCurrentAccountWithSendEtherInfo,
getSelectedAddress,
conversionRateSelector,
} from '../../selectors/selectors.js'
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'
import ConfirmEncryptionPublicKey from './confirm-encryption-public-key.component'
function mapStateToProps (state) {
const { confirmTransaction,
metamask: { domainMetadata = {} },
} = state
const {
txData = {},
} = confirmTransaction
return {
txData: txData,
domainMetadata: domainMetadata,
balance: getSelectedAccount(state).balance,
selectedAccount: getCurrentAccountWithSendEtherInfo(state),
selectedAddress: getSelectedAddress(state),
requester: null,
requesterAddress: null,
conversionRate: conversionRateSelector(state),
}
}
function mapDispatchToProps (dispatch) {
return {
goHome: () => dispatch(goHome()),
clearConfirmTransaction: () => dispatch(clearConfirmTransaction()),
encryptionPublicKey: (msgData, event) => {
const params = { data: msgData.msgParams, metamaskId: msgData.id }
event.stopPropagation()
return dispatch(encryptionPublicKeyMsg(params))
},
cancelEncryptionPublicKey: (msgData, event) => {
event.stopPropagation()
return dispatch(cancelEncryptionPublicKeyMsg(msgData))
},
}
}
export default compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
)(ConfirmEncryptionPublicKey)

@ -0,0 +1 @@
export { default } from './confirm-encryption-public-key.container'

@ -11,6 +11,8 @@ import {
CONFIRM_TRANSFER_FROM_PATH,
CONFIRM_TOKEN_METHOD_PATH,
SIGNATURE_REQUEST_PATH,
DECRYPT_MESSAGE_REQUEST_PATH,
ENCRYPTION_PUBLIC_KEY_REQUEST_PATH,
} from '../../helpers/constants/routes'
import {
TOKEN_METHOD_TRANSFER,
@ -68,11 +70,15 @@ export default class ConfirmTransactionSwitch extends Component {
render () {
const { txData } = this.props
if (txData.txParams) {
return this.redirectToTransaction()
} else if (txData.msgParams) {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${SIGNATURE_REQUEST_PATH}`
let pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${SIGNATURE_REQUEST_PATH}`
if (txData.type === 'eth_decrypt') {
pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${DECRYPT_MESSAGE_REQUEST_PATH}`
} else if (txData.type === 'eth_getEncryptionPublicKey') {
pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`
}
return <Redirect to={{ pathname }} />
}

@ -10,6 +10,9 @@ import ConfirmDeployContract from '../confirm-deploy-contract'
import ConfirmApprove from '../confirm-approve'
import ConfirmTokenTransactionBaseContainer from '../confirm-token-transaction-base'
import ConfTx from './conf-tx'
import ConfirmDecryptMessage from '../confirm-decrypt-message'
import ConfirmEncryptionPublicKey from '../confirm-encryption-public-key'
import {
DEFAULT_ROUTE,
CONFIRM_TRANSACTION_ROUTE,
@ -20,6 +23,8 @@ import {
CONFIRM_TRANSFER_FROM_PATH,
CONFIRM_TOKEN_METHOD_PATH,
SIGNATURE_REQUEST_PATH,
DECRYPT_MESSAGE_REQUEST_PATH,
ENCRYPTION_PUBLIC_KEY_REQUEST_PATH,
} from '../../helpers/constants/routes'
export default class ConfirmTransaction extends Component {
@ -155,6 +160,16 @@ export default class ConfirmTransaction extends Component {
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${SIGNATURE_REQUEST_PATH}`}
component={ConfTx}
/>
<Route
exact
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${DECRYPT_MESSAGE_REQUEST_PATH}`}
component={ConfirmDecryptMessage}
/>
<Route
exact
path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`}
component={ConfirmEncryptionPublicKey}
/>
<Route path="*" component={ConfirmTransactionSwitch} />
</Switch>
)

@ -130,6 +130,10 @@ export default {
'unapprovedMsgCount': 0,
'unapprovedPersonalMsgs': {},
'unapprovedPersonalMsgCount': 0,
'unapprovedDecryptMsgs': {},
'unapprovedDecryptMsgCount': 0,
'unapprovedEncryptionPublicKeyMsgs': {},
'unapprovedEncryptionPublicKeyMsgCount': 0,
'keyringTypes': [
'Simple Key Pair',
'HD Key Tree',

@ -16,6 +16,8 @@ import {
const unapprovedTxsSelector = (state) => state.metamask.unapprovedTxs
const unapprovedMsgsSelector = (state) => state.metamask.unapprovedMsgs
const unapprovedPersonalMsgsSelector = (state) => state.metamask.unapprovedPersonalMsgs
const unapprovedDecryptMsgsSelector = (state) => state.metamask.unapprovedDecryptMsgs
const unapprovedEncryptionPublicKeyMsgsSelector = (state) => state.metamask.unapprovedEncryptionPublicKeyMsgs
const unapprovedTypedMessagesSelector = (state) => state.metamask.unapprovedTypedMessages
const networkSelector = (state) => state.metamask.network
@ -23,18 +25,24 @@ export const unconfirmedTransactionsListSelector = createSelector(
unapprovedTxsSelector,
unapprovedMsgsSelector,
unapprovedPersonalMsgsSelector,
unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector,
unapprovedTypedMessagesSelector,
networkSelector,
(
unapprovedTxs = {},
unapprovedMsgs = {},
unapprovedPersonalMsgs = {},
unapprovedDecryptMsgs = {},
unapprovedEncryptionPublicKeyMsgs = {},
unapprovedTypedMessages = {},
network
) => txHelper(
unapprovedTxs,
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedDecryptMsgs,
unapprovedEncryptionPublicKeyMsgs,
unapprovedTypedMessages,
network
) || []
@ -44,12 +52,16 @@ export const unconfirmedTransactionsHashSelector = createSelector(
unapprovedTxsSelector,
unapprovedMsgsSelector,
unapprovedPersonalMsgsSelector,
unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector,
unapprovedTypedMessagesSelector,
networkSelector,
(
unapprovedTxs = {},
unapprovedMsgs = {},
unapprovedPersonalMsgs = {},
unapprovedDecryptMsgs = {},
unapprovedEncryptionPublicKeyMsgs = {},
unapprovedTypedMessages = {},
network
) => {
@ -68,6 +80,8 @@ export const unconfirmedTransactionsHashSelector = createSelector(
...filteredUnapprovedTxs,
...unapprovedMsgs,
...unapprovedPersonalMsgs,
...unapprovedDecryptMsgs,
...unapprovedEncryptionPublicKeyMsgs,
...unapprovedTypedMessages,
}
}
@ -75,18 +89,24 @@ export const unconfirmedTransactionsHashSelector = createSelector(
const unapprovedMsgCountSelector = (state) => state.metamask.unapprovedMsgCount
const unapprovedPersonalMsgCountSelector = (state) => state.metamask.unapprovedPersonalMsgCount
const unapprovedDecryptMsgCountSelector = (state) => state.metamask.unapprovedDecryptMsgCount
const unapprovedEncryptionPublicKeyMsgCountSelector = (state) => state.metamask.unapprovedEncryptionPublicKeyMsgCount
const unapprovedTypedMessagesCountSelector = (state) => state.metamask.unapprovedTypedMessagesCount
export const unconfirmedTransactionsCountSelector = createSelector(
unapprovedTxsSelector,
unapprovedMsgCountSelector,
unapprovedPersonalMsgCountSelector,
unapprovedDecryptMsgCountSelector,
unapprovedEncryptionPublicKeyMsgCountSelector,
unapprovedTypedMessagesCountSelector,
networkSelector,
(
unapprovedTxs = {},
unapprovedMsgCount = 0,
unapprovedPersonalMsgCount = 0,
unapprovedDecryptMsgCount = 0,
unapprovedEncryptionPublicKeyMsgCount = 0,
unapprovedTypedMessagesCount = 0,
network
) => {
@ -96,7 +116,7 @@ export const unconfirmedTransactionsCountSelector = createSelector(
})
return filteredUnapprovedTxIds.length + unapprovedTypedMessagesCount + unapprovedMsgCount +
unapprovedPersonalMsgCount
unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount
}
)

@ -326,11 +326,13 @@ export function getTotalUnapprovedCount ({ metamask }) {
unapprovedTxs = {},
unapprovedMsgCount,
unapprovedPersonalMsgCount,
unapprovedDecryptMsgCount,
unapprovedEncryptionPublicKeyMsgCount,
unapprovedTypedMessagesCount,
} = metamask
return Object.keys(unapprovedTxs).length + unapprovedMsgCount + unapprovedPersonalMsgCount +
unapprovedTypedMessagesCount
unapprovedTypedMessagesCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount
}
export function getIsMainnet (state) {

@ -132,6 +132,10 @@ export default {
'unapprovedMsgCount': 0,
'unapprovedPersonalMsgs': {},
'unapprovedPersonalMsgCount': 0,
'unapprovedDecryptMsgs': {},
'unapprovedDecryptMsgCount': 0,
'unapprovedEncryptionPublicKeyMsgs': {},
'unapprovedEncryptionPublicKeyMsgCount': 0,
'keyringTypes': [
'Simple Key Pair',
'HD Key Tree',

@ -33,23 +33,31 @@ export const incomingTxListSelector = (state) => {
export const unapprovedMsgsSelector = (state) => state.metamask.unapprovedMsgs
export const selectedAddressTxListSelector = (state) => state.metamask.selectedAddressTxList
export const unapprovedPersonalMsgsSelector = (state) => state.metamask.unapprovedPersonalMsgs
export const unapprovedDecryptMsgsSelector = (state) => state.metamask.unapprovedDecryptMsgs
export const unapprovedEncryptionPublicKeyMsgsSelector = (state) => state.metamask.unapprovedEncryptionPublicKeyMsgs
export const unapprovedTypedMessagesSelector = (state) => state.metamask.unapprovedTypedMessages
export const networkSelector = (state) => state.metamask.network
export const unapprovedMessagesSelector = createSelector(
unapprovedMsgsSelector,
unapprovedPersonalMsgsSelector,
unapprovedDecryptMsgsSelector,
unapprovedEncryptionPublicKeyMsgsSelector,
unapprovedTypedMessagesSelector,
networkSelector,
(
unapprovedMsgs = {},
unapprovedPersonalMsgs = {},
unapprovedDecryptMsgs = {},
unapprovedEncryptionPublicKeyMsgs = {},
unapprovedTypedMessages = {},
network
) => txHelper(
{},
unapprovedMsgs,
unapprovedPersonalMsgs,
unapprovedDecryptMsgs,
unapprovedEncryptionPublicKeyMsgs,
unapprovedTypedMessages,
network
) || []

@ -631,6 +631,80 @@ export function signPersonalMsg (msgData) {
}
}
export function decryptMsgInline (decryptedMsgData) {
log.debug('action - decryptMsgInline')
return (dispatch) => {
return new Promise((resolve, reject) => {
log.debug(`actions calling background.decryptMessageInline`)
background.decryptMessageInline(decryptedMsgData, (err, newState) => {
log.debug('decryptMsgInline called back')
dispatch(updateMetamaskState(newState))
if (err) {
log.error(err)
dispatch(displayWarning(err.message))
return reject(err)
}
decryptedMsgData = newState.unapprovedDecryptMsgs[decryptedMsgData.metamaskId]
return resolve(decryptedMsgData)
})
})
}
}
export function decryptMsg (decryptedMsgData) {
log.debug('action - decryptMsg')
return (dispatch) => {
dispatch(showLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`actions calling background.decryptMessage`)
background.decryptMessage(decryptedMsgData, (err, newState) => {
log.debug('decryptMsg called back')
dispatch(updateMetamaskState(newState))
dispatch(hideLoadingIndication())
if (err) {
log.error(err)
dispatch(displayWarning(err.message))
return reject(err)
}
dispatch(completedTx(decryptedMsgData.metamaskId))
dispatch(closeCurrentNotificationWindow())
console.log(decryptedMsgData)
return resolve(decryptedMsgData)
})
})
}
}
export function encryptionPublicKeyMsg (msgData) {
log.debug('action - encryptionPublicKeyMsg')
return (dispatch) => {
dispatch(showLoadingIndication())
return new Promise((resolve, reject) => {
log.debug(`actions calling background.encryptionPublicKey`)
background.encryptionPublicKey(msgData, (err, newState) => {
log.debug('encryptionPublicKeyMsg called back')
dispatch(updateMetamaskState(newState))
dispatch(hideLoadingIndication())
if (err) {
log.error(err)
dispatch(displayWarning(err.message))
return reject(err)
}
dispatch(completedTx(msgData.metamaskId))
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
})
}
}
export function signTypedMsg (msgData) {
log.debug('action - signTypedMsg')
return (dispatch) => {
@ -1005,6 +1079,50 @@ export function cancelPersonalMsg (msgData) {
}
}
export function cancelDecryptMsg (msgData) {
return (dispatch) => {
dispatch(showLoadingIndication())
return new Promise((resolve, reject) => {
const id = msgData.id
background.cancelDecryptMessage(id, (err, newState) => {
dispatch(updateMetamaskState(newState))
dispatch(hideLoadingIndication())
if (err) {
return reject(err)
}
dispatch(completedTx(id))
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
})
}
}
export function cancelEncryptionPublicKeyMsg (msgData) {
return (dispatch) => {
dispatch(showLoadingIndication())
return new Promise((resolve, reject) => {
const id = msgData.id
background.cancelEncryptionPublicKey(id, (err, newState) => {
dispatch(updateMetamaskState(newState))
dispatch(hideLoadingIndication())
if (err) {
return reject(err)
}
dispatch(completedTx(id))
dispatch(closeCurrentNotificationWindow())
return resolve(msgData)
})
})
}
}
export function cancelTypedMsg (msgData) {
return (dispatch) => {
dispatch(showLoadingIndication())

@ -59,7 +59,7 @@ async function startApp (metamaskState, backgroundConnection, opts) {
})
// if unconfirmed txs, start on txConf page
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedDecryptMsgs, metamaskState.unapprovedEncryptionPublicKeyMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
const numberOfUnapprivedTx = unapprovedTxsAll.length
if (numberOfUnapprivedTx > 0) {
store.dispatch(actions.showConfTxPage({

@ -1,9 +1,9 @@
import { valuesFor } from '../app/helpers/utils/util'
import log from 'loglevel'
export default function txHelper (unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network) {
export default function txHelper (unapprovedTxs, unapprovedMsgs, personalMsgs, decryptMsgs, encryptionPublicKeyMsgs, typedMessages, network) {
log.debug('tx-helper called with params:')
log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, typedMessages, network })
log.debug({ unapprovedTxs, unapprovedMsgs, personalMsgs, decryptMsgs, encryptionPublicKeyMsgs, typedMessages, network })
const txValues = network ? valuesFor(unapprovedTxs).filter((txMeta) => txMeta.metamaskNetworkId === network) : valuesFor(unapprovedTxs)
log.debug(`tx helper found ${txValues.length} unapproved txs`)
@ -16,6 +16,14 @@ export default function txHelper (unapprovedTxs, unapprovedMsgs, personalMsgs, t
log.debug(`tx helper found ${personalValues.length} unsigned personal messages`)
allValues = allValues.concat(personalValues)
const decryptValues = valuesFor(decryptMsgs)
log.debug(`tx helper found ${decryptValues.length} decrypt requests`)
allValues = allValues.concat(decryptValues)
const encryptionPublicKeyValues = valuesFor(encryptionPublicKeyMsgs)
log.debug(`tx helper found ${encryptionPublicKeyValues.length} encryptionPublicKey requests`)
allValues = allValues.concat(encryptionPublicKeyValues)
const typedValues = valuesFor(typedMessages)
log.debug(`tx helper found ${typedValues.length} unsigned typed messages`)
allValues = allValues.concat(typedValues)

@ -10337,30 +10337,10 @@ eth-json-rpc-middleware@^1.5.0:
promise-to-callback "^1.0.0"
tape "^4.6.3"
eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5:
version "4.2.0"
resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-4.2.0.tgz#cfb77c5056cb8001548c6c7d54f4af5fce04d489"
integrity sha512-90LljqRyJhkg7fOwKunh1lu1Mr5bspXMBDitaTGyGPPNiFTbMrhtfbf9fteYlXRFCbq+aIFWwl/X+P7nkrdkLg==
dependencies:
btoa "^1.2.1"
clone "^2.1.1"
eth-json-rpc-errors "^1.0.1"
eth-query "^2.1.2"
eth-sig-util "^1.4.2"
ethereumjs-block "^1.6.0"
ethereumjs-tx "^1.3.7"
ethereumjs-util "^5.1.2"
ethereumjs-vm "^2.6.0"
fetch-ponyfill "^4.0.0"
json-rpc-engine "^5.1.3"
json-stable-stringify "^1.0.1"
pify "^3.0.0"
safe-event-emitter "^1.0.1"
eth-json-rpc-middleware@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-4.4.0.tgz#ef63b783b48dcbea9c1fe25c79e6ea01510e5877"
integrity sha512-IeOsil/XiHsybJO9nFf86+1+YIqGQWPPfiTEp3WLkpLZhJm97kw6tFM7GttIZXIcwtaO3zEXgY6PWAH1jkB3ag==
eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5, eth-json-rpc-middleware@^4.4.1:
version "4.4.1"
resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-4.4.1.tgz#07d3dd0724c24a8d31e4a172ee96271da71b4228"
integrity sha512-yoSuRgEYYGFdVeZg3poWOwAlRI+MoBIltmOB86MtpoZjvLbou9EB/qWMOWSmH2ryCWLW97VYY6NWsmWm3OAA7A==
dependencies:
btoa "^1.2.1"
clone "^2.1.1"

Loading…
Cancel
Save