Merge pull request #427 from poanetwork/vb-eip712

Fix support of eth_signTypedData_v4 (eip-712)
This commit is contained in:
Victor Baranov 2020-11-13 15:45:24 +03:00 committed by GitHub
commit 141e153321
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 652 additions and 325 deletions

View File

@ -2,6 +2,7 @@
## Current Master
- [#427](https://github.com/poanetwork/nifty-wallet/pull/427) - Fix support of eth_signTypedData_v4 (eip-712)
- [#426](https://github.com/poanetwork/nifty-wallet/pull/426) - Update inpage provider: add ethereum.request method support
## 5.1.6 Tue Oct 27 2020

View File

@ -40,6 +40,33 @@ const CLASSIC_CODE = 61
const RSK_CODE = 30
const RSK_TESTNET_CODE = 31
const MAINNET_NETWORK_ID = '1'
const ROPSTEN_NETWORK_ID = '3'
const RINKEBY_NETWORK_ID = '4'
const GOERLI_NETWORK_ID = '5'
const KOVAN_NETWORK_ID = '42'
const POA_NETWORK_ID = '99'
const DAI_NETWORK_ID = '100'
const POA_SOKOL_NETWORK_ID = '77'
const CLASSIC_NETWORK_ID = '61'
const RSK_NETWORK_ID = '30'
const RSK_TESTNET_NETWORK_ID = '31'
const NETWORK_TYPE_TO_ID_MAP = {
[ROPSTEN]: { networkId: ROPSTEN_NETWORK_ID, chainId: ROPSTEN_CHAINID },
[RINKEBY]: { networkId: RINKEBY_NETWORK_ID, chainId: RINKEBY_CHAINID },
[KOVAN]: { networkId: KOVAN_NETWORK_ID, chainId: KOVAN_CHAINID },
[GOERLI_TESTNET]: { networkId: GOERLI_NETWORK_ID, chainId: GOERLI_TESTNET_CHAINID },
[MAINNET]: { networkId: MAINNET_NETWORK_ID, chainId: MAINNET_CHAINID },
[POA]: { networkId: POA_NETWORK_ID, chainId: POA_CHAINID },
[DAI]: { networkId: DAI_NETWORK_ID, chainId: DAI_CHAINID },
[POA_SOKOL]: { networkId: POA_SOKOL_NETWORK_ID, chainId: POA_SOKOL_CHAINID },
[CLASSIC]: { networkId: CLASSIC_NETWORK_ID, chainId: CLASSIC_CHAINID },
[RSK]: { networkId: RSK_NETWORK_ID, chainId: RSK_CHAINID },
[RSK_TESTNET]: { networkId: RSK_TESTNET_NETWORK_ID, chainId: RSK_TESTNET_CHAINID },
}
const POA_DISPLAY_NAME = 'POA'
const DAI_DISPLAY_NAME = 'xDai'
const POA_SOKOL_DISPLAY_NAME = 'Sokol Testnet'
@ -141,4 +168,5 @@ module.exports = {
DROPDOWN_RSK_TESTNET_DISPLAY_NAME,
chainTypes,
customDPaths,
NETWORK_TYPE_TO_ID_MAP,
}

View File

@ -16,6 +16,9 @@ import ethNetProps from 'eth-net-props'
import parse from 'url-parse'
const networks = { networkList: {} }
const { isKnownProvider } = require('../../../../old-ui/app/util')
import {
NETWORK_TYPE_TO_ID_MAP,
} from './enums'
const {
ROPSTEN,
@ -155,6 +158,11 @@ module.exports = class NetworkController extends EventEmitter {
})
}
getCurrentChainId () {
const { type, chainId: configChainId } = this.getProviderConfig()
return NETWORK_TYPE_TO_ID_MAP[type] ? NETWORK_TYPE_TO_ID_MAP[type].chainId : configChainId
}
setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
const providerConfig = {
type: 'rpc',

View File

@ -67,6 +67,7 @@ export const SAFE_METHODS = [
'eth_sendTransaction',
'eth_sign',
'personal_sign',
'personal_ecRecover',
'eth_signTypedData',
'eth_signTypedData_v1',
'eth_signTypedData_v3',

View File

@ -1,16 +0,0 @@
module.exports = createProviderMiddleware
/**
* Forwards an HTTP request to the current Web3 provider
*
* @param {{ provider: Object }} config Configuration containing current Web3 provider
*/
function createProviderMiddleware ({ provider }) {
return (req, res, next, end) => {
provider.sendAsync(req, (err, _res) => {
if (err) return end(err)
res.result = _res.result
end()
})
}
}

View File

@ -2,10 +2,12 @@ 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'
import { addHexPrefix } from './util'
import createId from './random-id'
import { MESSAGE_TYPE } from './enums'
const hexRe = /^[0-9A-Fa-f]+$/gu
/**
* Represents, and contains data about, an 'eth_decrypt' type decryption request. These are created when a
@ -29,10 +31,10 @@ 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 The observable store where DecryptMessage are saved.
* @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
* @property {number} memStore.unapprovedDecryptMsgCount The count of all DecryptMessages in this.memStore.unapprovedDecryptMsgs
* @property {Array} messages Holds all messages that have been created by this DecryptMessageManager
*
*/
constructor () {
@ -62,9 +64,11 @@ export default class DecryptMessageManager extends EventEmitter {
*
*/
getUnapprovedMsgs () {
return this.messages.filter((msg) => msg.status === 'unapproved')
return this.messages
.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
result[msg.id] = msg
return result
}, {})
}
@ -73,27 +77,41 @@ export default class DecryptMessageManager extends EventEmitter {
* 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
* @param {Object} msgParams - The params for the eth_decrypt call to be made after the message is approved.
* @param {Object} [req] - 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.'))
reject(new Error('MetaMask Decryption: from field is required.'))
return
}
const msgId = this.addUnapprovedMessage(msgParams, req)
this.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'decrypted':
return resolve(data.rawData)
resolve(data.rawData)
return
case 'rejected':
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message for Decryption: User denied message decryption.'))
reject(
ethErrors.provider.userRejectedRequest(
'MetaMask Decryption: User denied message decryption.',
),
)
return
case 'errored':
return reject(new Error('This message cannot be decrypted'))
reject(new Error('This message cannot be decrypted'))
return
default:
return reject(new Error(`MetaMask Message for Decryption: Unknown problem: ${JSON.stringify(msgParams)}`))
reject(
new Error(
`MetaMask Decryption: Unknown problem: ${JSON.stringify(
msgParams,
)}`,
),
)
}
})
})
@ -104,27 +122,31 @@ export default class DecryptMessageManager extends EventEmitter {
* 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
* @param {Object} msgParams - The params for the eth_decryptMsg call to be made after the message is approved.
* @param {Object} [req] - 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)}`)
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 time = new Date().getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: msgParams,
time: time,
msgParams,
time,
status: 'unapproved',
type: 'eth_decrypt',
type: MESSAGE_TYPE.ETH_DECRYPT,
}
this.addMsg(msgData)
@ -247,12 +269,18 @@ export default class DecryptMessageManager extends EventEmitter {
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) {
throw new Error('DecryptMessageManager - Message not found for id: "${msgId}".')
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') {
if (
status === 'rejected' ||
status === 'decrypted' ||
status === 'errored'
) {
this.emit(`${msgId}:finished`, msg)
}
}
@ -262,7 +290,7 @@ export default class DecryptMessageManager extends EventEmitter {
* unapprovedDecryptMsgs index to storage via this._saveMsgList
*
* @private
* @param {msg} DecryptMessage A DecryptMessage that will replace an existing DecryptMessage (with the same
* @param {DecryptMessage} msg - A DecryptMessage that will replace an existing DecryptMessage (with the same
* id) in this.messages
*
*/
@ -284,7 +312,10 @@ export default class DecryptMessageManager extends EventEmitter {
_saveMsgList () {
const unapprovedDecryptMsgs = this.getUnapprovedMsgs()
const unapprovedDecryptMsgCount = Object.keys(unapprovedDecryptMsgs).length
this.memStore.updateState({ unapprovedDecryptMsgs, unapprovedDecryptMsgCount })
this.memStore.updateState({
unapprovedDecryptMsgs,
unapprovedDecryptMsgCount,
})
this.emit('updateBadge')
}
@ -299,7 +330,7 @@ export default class DecryptMessageManager extends EventEmitter {
try {
const stripped = ethUtil.stripHexPrefix(data)
if (stripped.match(hexRe)) {
return ethUtil.addHexPrefix(stripped)
return addHexPrefix(stripped)
}
} catch (e) {
log.debug(`Message was not hex encoded, interpreting as utf8.`)
@ -307,5 +338,4 @@ export default class DecryptMessageManager extends EventEmitter {
return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
}
}

View File

@ -1,8 +1,9 @@
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'
import createId from './random-id'
import { MESSAGE_TYPE } from './enums'
/**
* Represents, and contains data about, an 'eth_getEncryptionPublicKey' type request. These are created when
@ -29,7 +30,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
* @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
* @property {Array} messages Holds all messages that have been created by this EncryptionPublicKeyManager
*
*/
constructor () {
@ -59,9 +60,11 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
*
*/
getUnapprovedMsgs () {
return this.messages.filter((msg) => msg.status === 'unapproved')
return this.messages
.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
result[msg.id] = msg
return result
}, {})
}
@ -70,25 +73,38 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
* 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
* @param {Object} address - The param for the eth_getEncryptionPublicKey call to be made after the message is approved.
* @param {Object} [req] - 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.'))
reject(new Error('MetaMask Message: address field is required.'))
return
}
const msgId = this.addUnapprovedMessage(address, req)
this.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'received':
return resolve(data.rawData)
resolve(data.rawData)
return
case 'rejected':
return reject(ethErrors.provider.userRejectedRequest('MetaMask Message for EncryptionPublicKey: User denied message EncryptionPublicKey.'))
reject(
ethErrors.provider.userRejectedRequest(
'MetaMask EncryptionPublicKey: User denied message EncryptionPublicKey.',
),
)
return
default:
return reject(new Error(`MetaMask Message for EncryptionPublicKey: Unknown problem: ${JSON.stringify(address)}`))
reject(
new Error(
`MetaMask EncryptionPublicKey: Unknown problem: ${JSON.stringify(
address,
)}`,
),
)
}
})
})
@ -99,22 +115,22 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
* 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
* @param {Object} address - The param for the eth_getEncryptionPublicKey call to be made after the message is approved.
* @param {Object} [req] - 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 time = new Date().getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: address,
time: time,
time,
status: 'unapproved',
type: 'eth_getEncryptionPublicKey',
type: MESSAGE_TYPE.ETH_GET_ENCRYPTION_PUBLIC_KEY,
}
if (req) {
@ -242,7 +258,9 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) {
throw new Error('EncryptionPublicKeyManager - Message not found for id: "${msgId}".')
throw new Error(
`EncryptionPublicKeyManager - Message not found for id: "${msgId}".`,
)
}
msg.status = status
this._updateMsg(msg)
@ -257,7 +275,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
* unapprovedEncryptionPublicKeyMsgs index to storage via this._saveMsgList
*
* @private
* @param {msg} EncryptionPublicKey A EncryptionPublicKey that will replace an existing EncryptionPublicKey (with the same
* @param {EncryptionPublicKey} msg - A EncryptionPublicKey that will replace an existing EncryptionPublicKey (with the same
* id) in this.messages
*
*/
@ -278,9 +296,13 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
*/
_saveMsgList () {
const unapprovedEncryptionPublicKeyMsgs = this.getUnapprovedMsgs()
const unapprovedEncryptionPublicKeyMsgCount = Object.keys(unapprovedEncryptionPublicKeyMsgs).length
this.memStore.updateState({ unapprovedEncryptionPublicKeyMsgs, unapprovedEncryptionPublicKeyMsgCount })
const unapprovedEncryptionPublicKeyMsgCount = Object.keys(
unapprovedEncryptionPublicKeyMsgs,
).length
this.memStore.updateState({
unapprovedEncryptionPublicKeyMsgs,
unapprovedEncryptionPublicKeyMsgCount,
})
this.emit('updateBadge')
}
}

View File

@ -9,11 +9,21 @@ const PLATFORM_EDGE = 'Edge'
const PLATFORM_FIREFOX = 'Firefox'
const PLATFORM_OPERA = 'Opera'
const MESSAGE_TYPE = {
ETH_DECRYPT: 'eth_decrypt',
ETH_GET_ENCRYPTION_PUBLIC_KEY: 'eth_getEncryptionPublicKey',
ETH_SIGN: 'eth_sign',
ETH_SIGN_TYPED_DATA: 'eth_signTypedData',
LOG_WEB3_USAGE: 'metamask_logInjectedWeb3Usage',
PERSONAL_SIGN: 'personal_sign',
}
export {
ENVIRONMENT_TYPE_POPUP,
ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_FULLSCREEN,
ENVIRONMENT_TYPE_BACKGROUND,
MESSAGE_TYPE,
PLATFORM_BRAVE,
PLATFORM_CHROME,
PLATFORM_EDGE,

View File

@ -3,6 +3,7 @@ import ObservableStore from 'obs-store'
import ethUtil from 'ethereumjs-util'
import { ethErrors } from 'eth-json-rpc-errors'
import createId from './random-id'
import { MESSAGE_TYPE } from './enums'
/**
* Represents, and contains data about, an 'eth_sign' type signature request. These are created when a signature for
@ -23,16 +24,14 @@ import createId from './random-id'
*/
export default class MessageManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - Messages.
*
* @typedef {Object} MessageManager
* @param {Object} opts @deprecated
* @property {Object} memStore The observable store where Messages are saved.
* @property {Object} memStore.unapprovedMsgs A collection of all Messages in the 'unapproved' state
* @property {number} memStore.unapprovedMsgCount The count of all Messages in this.memStore.unapprobedMsgs
* @property {array} messages Holds all messages that have been created by this MessageManager
* @property {number} memStore.unapprovedMsgCount The count of all Messages in this.memStore.unapprovedMsgs
* @property {Array} messages Holds all messages that have been created by this MessageManager
*
*/
constructor () {
@ -47,7 +46,7 @@ export default class MessageManager extends EventEmitter {
/**
* A getter for the number of 'unapproved' Messages in this.messages
*
* @returns {number} - The number of 'unapproved' Messages in this.messages
* @returns {number} The number of 'unapproved' Messages in this.messages
*
*/
get unapprovedMsgCount () {
@ -57,13 +56,15 @@ export default class MessageManager extends EventEmitter {
/**
* A getter for the 'unapproved' Messages in this.messages
*
* @returns {Object} - An index of Message ids to Messages, for all 'unapproved' Messages in this.messages
* @returns {Object} An index of Message ids to Messages, for all 'unapproved' Messages in this.messages
*
*/
getUnapprovedMsgs () {
return this.messages.filter((msg) => msg.status === 'unapproved')
return this.messages
.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
result[msg.id] = msg
return result
}, {})
}
@ -72,8 +73,8 @@ export default class MessageManager extends EventEmitter {
* new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
*
* @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {promise} - after signature has been
* @param {Object} [req] - The original request object possibly containing the origin
* @returns {promise} after signature has been
*
*/
addUnapprovedMessageAsync (msgParams, req) {
@ -85,9 +86,19 @@ export default class MessageManager extends EventEmitter {
case 'signed':
return resolve(data.rawSig)
case 'rejected':
return reject(ethErrors.provider.userRejectedRequest('Nifty Wallet Message Signature: User denied message signature.'))
return reject(
ethErrors.provider.userRejectedRequest(
'Nifty Wallet Message Signature: User denied message signature.',
),
)
default:
return reject(new Error(`Nifty Wallet Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
return reject(
new Error(
`Nifty Wallet Message Signature: Unknown problem: ${JSON.stringify(
msgParams,
)}`,
),
)
}
})
})
@ -98,8 +109,8 @@ export default class MessageManager extends EventEmitter {
* new Message to this.messages, and to save the unapproved Messages from that list to this.memStore.
*
* @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved.
* @param {Object} req (optional) The original request object where the origin may be specificied
* @returns {number} - The id of the newly created message.
* @param {Object} [req] - The original request object where the origin may be specified
* @returns {number} The id of the newly created message.
*
*/
addUnapprovedMessage (msgParams, req) {
@ -109,14 +120,14 @@ export default class MessageManager extends EventEmitter {
}
msgParams.data = normalizeMsgData(msgParams.data)
// create txData obj with parameters and meta data
const time = (new Date()).getTime()
const time = new Date().getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: msgParams,
time: time,
msgParams,
time,
status: 'unapproved',
type: 'eth_sign',
type: MESSAGE_TYPE.ETH_SIGN,
}
this.addMsg(msgData)
@ -141,7 +152,7 @@ export default class MessageManager extends EventEmitter {
* Returns a specified Message.
*
* @param {number} msgId - The id of the Message to get
* @returns {Message|undefined} - The Message with the id that matches the passed msgId, or undefined if no Message has that id.
* @returns {Message|undefined} The Message with the id that matches the passed msgId, or undefined if no Message has that id.
*
*/
getMsg (msgId) {
@ -154,7 +165,7 @@ export default class MessageManager extends EventEmitter {
*
* @param {Object} msgParams - The msgParams to be used when eth_sign 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.
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
*
*/
approveMessage (msgParams) {
@ -191,7 +202,7 @@ export default class MessageManager extends EventEmitter {
* 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
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
*
*/
prepMsgForSigning (msgParams) {
@ -224,7 +235,7 @@ export default class MessageManager extends EventEmitter {
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) {
throw new Error('MessageManager - Message not found for id: "${msgId}".')
throw new Error(`MessageManager - Message not found for id: "${msgId}".`)
}
msg.status = status
this._updateMsg(msg)
@ -263,22 +274,20 @@ export default class MessageManager extends EventEmitter {
this.memStore.updateState({ unapprovedMsgs, unapprovedMsgCount })
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
* @returns {string} A hex string conversion of the buffer data
*
*/
function normalizeMsgData (data) {
if (data.slice(0, 2) === '0x') {
// data is already hex
return data
} else {
// data is unicode, convert to hex
return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
}
// data is unicode, convert to hex
return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
}

View File

@ -2,10 +2,12 @@ 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'
import { addHexPrefix } from './util'
import createId from './random-id'
import { MESSAGE_TYPE } from './enums'
const hexRe = /^[0-9A-Fa-f]+$/gu
/**
* Represents, and contains data about, an 'personal_sign' type signature request. These are created when a
@ -31,11 +33,10 @@ export default class PersonalMessageManager extends EventEmitter {
* Controller in charge of managing - storing, adding, removing, updating - PersonalMessage.
*
* @typedef {Object} PersonalMessageManager
* @param {Object} opts @deprecated
* @property {Object} memStore The observable store where PersonalMessage are saved with persistance.
* @property {Object} memStore The observable store where PersonalMessage are saved.
* @property {Object} memStore.unapprovedPersonalMsgs A collection of all PersonalMessages in the 'unapproved' state
* @property {number} memStore.unapprovedPersonalMsgCount The count of all PersonalMessages in this.memStore.unapprobedMsgs
* @property {array} messages Holds all messages that have been created by this PersonalMessageManager
* @property {Array} messages Holds all messages that have been created by this PersonalMessageManager
*
*/
constructor () {
@ -50,7 +51,7 @@ export default class PersonalMessageManager extends EventEmitter {
/**
* A getter for the number of 'unapproved' PersonalMessages in this.messages
*
* @returns {number} - The number of 'unapproved' PersonalMessages in this.messages
* @returns {number} The number of 'unapproved' PersonalMessages in this.messages
*
*/
get unapprovedPersonalMsgCount () {
@ -60,14 +61,16 @@ export default class PersonalMessageManager extends EventEmitter {
/**
* A getter for the 'unapproved' PersonalMessages in this.messages
*
* @returns {Object} - An index of PersonalMessage ids to PersonalMessages, for all 'unapproved' PersonalMessages in
* @returns {Object} An index of PersonalMessage ids to PersonalMessages, for all 'unapproved' PersonalMessages in
* this.messages
*
*/
getUnapprovedMsgs () {
return this.messages.filter((msg) => msg.status === 'unapproved')
return this.messages
.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
result[msg.id] = msg
return result
}, {})
}
@ -77,24 +80,37 @@ export default class PersonalMessageManager extends EventEmitter {
* this.memStore.
*
* @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {promise} - When the message has been signed or rejected
* @param {Object} [req] - The original request object possibly containing the origin
* @returns {promise} When the message has been signed or rejected
*
*/
addUnapprovedMessageAsync (msgParams, req) {
return new Promise((resolve, reject) => {
if (!msgParams.from) {
reject(new Error('Nifty Wallet Message Signature: from field is required.'))
return
}
const msgId = this.addUnapprovedMessage(msgParams, req)
this.once(`${msgId}:finished`, (data) => {
switch (data.status) {
case 'signed':
return resolve(data.rawSig)
resolve(data.rawSig)
return
case 'rejected':
return reject(ethErrors.provider.userRejectedRequest('Nifty Wallet Message Signature: User denied message signature.'))
reject(
ethErrors.provider.userRejectedRequest(
'Nifty Wallet Message Signature: User denied message signature.',
),
)
return
default:
return reject(new Error(`Nifty Wallet Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
reject(
new Error(
`Nifty Wallet Message Signature: Unknown problem: ${JSON.stringify(
msgParams,
)}`,
),
)
}
})
})
@ -106,26 +122,30 @@ export default class PersonalMessageManager extends EventEmitter {
* this.memStore.
*
* @param {Object} msgParams - The params for the eth_sign 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 PersonalMessage.
* @param {Object} [req] - The original request object possibly containing the origin
* @returns {number} The id of the newly created PersonalMessage.
*
*/
addUnapprovedMessage (msgParams, req) {
log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
log.debug(
`PersonalMessageManager 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 time = new Date().getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: msgParams,
time: time,
msgParams,
time,
status: 'unapproved',
type: 'personal_sign',
type: MESSAGE_TYPE.PERSONAL_SIGN,
}
this.addMsg(msgData)
@ -150,7 +170,7 @@ export default class PersonalMessageManager extends EventEmitter {
* Returns a specified PersonalMessage.
*
* @param {number} msgId - The id of the PersonalMessage to get
* @returns {PersonalMessage|undefined} - The PersonalMessage with the id that matches the passed msgId, or undefined
* @returns {PersonalMessage|undefined} The PersonalMessage with the id that matches the passed msgId, or undefined
* if no PersonalMessage has that id.
*
*/
@ -164,7 +184,7 @@ export default class PersonalMessageManager extends EventEmitter {
*
* @param {Object} msgParams - The msgParams to be used when eth_sign 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.
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
*
*/
approveMessage (msgParams) {
@ -201,7 +221,7 @@ export default class PersonalMessageManager extends EventEmitter {
* 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
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
*
*/
prepMsgForSigning (msgParams) {
@ -235,7 +255,9 @@ export default class PersonalMessageManager extends EventEmitter {
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) {
throw new Error(`PersonalMessageManager - Message not found for id: "${msgId}".`)
throw new Error(
`PersonalMessageManager - Message not found for id: "${msgId}".`,
)
}
msg.status = status
this._updateMsg(msg)
@ -271,8 +293,12 @@ export default class PersonalMessageManager extends EventEmitter {
*/
_saveMsgList () {
const unapprovedPersonalMsgs = this.getUnapprovedMsgs()
const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs).length
this.memStore.updateState({ unapprovedPersonalMsgs, unapprovedPersonalMsgCount })
const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs)
.length
this.memStore.updateState({
unapprovedPersonalMsgs,
unapprovedPersonalMsgCount,
})
this.emit('updateBadge')
}
@ -280,14 +306,14 @@ export default class PersonalMessageManager extends EventEmitter {
* 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
* @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)
return addHexPrefix(stripped)
}
} catch (e) {
log.debug(`Message was not hex encoded, interpreting as utf8.`)
@ -295,5 +321,4 @@ export default class PersonalMessageManager extends EventEmitter {
return ethUtil.bufferToHex(Buffer.from(data, 'utf8'))
}
}

View File

@ -1,11 +1,13 @@
import EventEmitter from 'events'
import ObservableStore from 'obs-store'
import createId from './random-id'
import assert from 'assert'
import ObservableStore from 'obs-store'
import { ethErrors } from 'eth-json-rpc-errors'
import sigUtil from 'eth-sig-util'
import { typedSignatureHash, TYPED_MESSAGE_SCHEMA } from 'eth-sig-util'
import { isValidAddress } from 'ethereumjs-util'
import log from 'loglevel'
import jsonschema from 'jsonschema'
import createId from './random-id'
import { MESSAGE_TYPE } from './enums'
/**
* Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a
@ -29,9 +31,9 @@ export default class TypedMessageManager extends EventEmitter {
/**
* Controller in charge of managing - storing, adding, removing, updating - TypedMessage.
*/
constructor ({ networkController }) {
constructor ({ getCurrentChainId }) {
super()
this.networkController = networkController
this._getCurrentChainId = getCurrentChainId
this.memStore = new ObservableStore({
unapprovedTypedMessages: {},
unapprovedTypedMessagesCount: 0,
@ -42,7 +44,7 @@ export default class TypedMessageManager extends EventEmitter {
/**
* A getter for the number of 'unapproved' TypedMessages in this.messages
*
* @returns {number} - The number of 'unapproved' TypedMessages in this.messages
* @returns {number} The number of 'unapproved' TypedMessages in this.messages
*
*/
get unapprovedTypedMessagesCount () {
@ -52,14 +54,16 @@ export default class TypedMessageManager extends EventEmitter {
/**
* A getter for the 'unapproved' TypedMessages in this.messages
*
* @returns {Object} - An index of TypedMessage ids to TypedMessages, for all 'unapproved' TypedMessages in
* @returns {Object} An index of TypedMessage ids to TypedMessages, for all 'unapproved' TypedMessages in
* this.messages
*
*/
getUnapprovedMsgs () {
return this.messages.filter((msg) => msg.status === 'unapproved')
return this.messages
.filter((msg) => msg.status === 'unapproved')
.reduce((result, msg) => {
result[msg.id] = msg; return result
result[msg.id] = msg
return result
}, {})
}
@ -69,8 +73,8 @@ export default class TypedMessageManager extends EventEmitter {
* this.memStore. Before any of this is done, msgParams are validated
*
* @param {Object} msgParams - The params for the eth_sign call to be made after the message is approved.
* @param {Object} req (optional) The original request object possibly containing the origin
* @returns {promise} - When the message has been signed or rejected
* @param {Object} [req] - The original request object possibly containing the origin
* @returns {promise} When the message has been signed or rejected
*
*/
addUnapprovedMessageAsync (msgParams, req, version) {
@ -81,11 +85,23 @@ export default class TypedMessageManager extends EventEmitter {
case 'signed':
return resolve(data.rawSig)
case 'rejected':
return reject(ethErrors.provider.userRejectedRequest('Nifty Wallet Message Signature: User denied message signature.'))
return reject(
ethErrors.provider.userRejectedRequest(
'Nifty Wallet Message Signature: User denied message signature.',
),
)
case 'errored':
return reject(new Error(`Nifty Wallet Message Signature: ${data.error}`))
return reject(
new Error(`Nifty Wallet Message Signature: ${data.error}`),
)
default:
return reject(new Error(`Nifty Wallet Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))
return reject(
new Error(
`Nifty Wallet Message Signature: Unknown problem: ${JSON.stringify(
msgParams,
)}`,
),
)
}
})
})
@ -97,28 +113,30 @@ export default class TypedMessageManager extends EventEmitter {
* this.memStore. Before any of this is done, msgParams are validated
*
* @param {Object} msgParams - The params for the eth_sign 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 TypedMessage.
* @param {Object} [req] - The original request object possibly containing the origin
* @returns {number} The id of the newly created TypedMessage.
*
*/
addUnapprovedMessage (msgParams, req, version) {
msgParams.version = version
this.validateParams(msgParams)
// add origin from request
if (req) {
msgParams.origin = req.origin
}
this.validateParams(msgParams)
log.debug(
`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`,
)
log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`)
// create txData obj with parameters and meta data
const time = (new Date()).getTime()
const time = new Date().getTime()
const msgId = createId()
const msgData = {
id: msgId,
msgParams: msgParams,
time: time,
msgParams,
time,
status: 'unapproved',
type: 'eth_signTypedData',
type: MESSAGE_TYPE.ETH_SIGN_TYPED_DATA,
}
this.addMsg(msgData)
@ -134,37 +152,59 @@ export default class TypedMessageManager extends EventEmitter {
*
*/
validateParams (params) {
assert.ok(params && typeof params === 'object', 'Params must be an object.')
assert.ok('data' in params, 'Params must include a "data" field.')
assert.ok('from' in params, 'Params must include a "from" field.')
assert.ok(
typeof params.from === 'string' && isValidAddress(params.from),
'"from" field must be a valid, lowercase, hexadecimal Ethereum address string.',
)
switch (params.version) {
case 'V1':
assert.equal(typeof params, 'object', 'Params should ben an object.')
assert.ok('data' in params, 'Params must include a data field.')
assert.ok('from' in params, 'Params must include a from field.')
assert.ok(Array.isArray(params.data), 'Data should be an array.')
assert.equal(typeof params.from, 'string', 'From field must be a string.')
assert.ok(Array.isArray(params.data), '"params.data" must be an array.')
assert.doesNotThrow(() => {
sigUtil.typedSignatureHash(params.data)
}, 'Expected EIP712 typed data')
typedSignatureHash(params.data)
}, 'Signing data must be valid EIP-712 typed data.')
break
case 'V3':
case 'V4':
case 'V4': {
assert.equal(
typeof params.data,
'string',
'"params.data" must be a string.',
)
let data
assert.equal(typeof params, 'object', 'Params should be an object.')
assert.ok('data' in params, 'Params must include a data field.')
assert.ok('from' in params, 'Params must include a from field.')
assert.equal(typeof params.from, 'string', 'From field must be a string.')
assert.equal(typeof params.data, 'string', 'Data must be passed as a valid JSON string.')
assert.doesNotThrow(() => {
data = JSON.parse(params.data)
}, 'Data must be passed as a valid JSON string.')
const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA)
assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`)
assert.equal(validation.errors.length, 0, 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.')
const chainId = data.domain.chainId
const activeChainId = parseInt(this.networkController.getNetworkState())
chainId && assert.equal(chainId, activeChainId, `Provided chainId (${chainId}) must match the active chainId (${activeChainId})`)
}, '"data" must be a valid JSON string.')
const validation = jsonschema.validate(data, TYPED_MESSAGE_SCHEMA)
assert.ok(
data.primaryType in data.types,
`Primary type of "${data.primaryType}" has no type definition.`,
)
assert.equal(
validation.errors.length,
0,
'Signing data must conform to EIP-712 schema. See https://git.io/fNtcx.',
)
const { chainId } = data.domain
if (chainId) {
const activeChainId = parseInt(this._getCurrentChainId(), 16)
assert.ok(
!Number.isNaN(activeChainId),
`Cannot sign messages for chainId "${chainId}", because MetaMask is switching networks.`,
)
assert.equal(
chainId,
activeChainId,
`Provided chainId "${chainId}" must match the active chainId "${activeChainId}"`,
)
}
break
}
default:
assert.fail(`Unknown params.version ${params.version}`)
assert.fail(`Unknown typed data version "${params.version}"`)
}
}
@ -184,7 +224,7 @@ export default class TypedMessageManager extends EventEmitter {
* Returns a specified TypedMessage.
*
* @param {number} msgId - The id of the TypedMessage to get
* @returns {TypedMessage|undefined} - The TypedMessage with the id that matches the passed msgId, or undefined
* @returns {TypedMessage|undefined} The TypedMessage with the id that matches the passed msgId, or undefined
* if no TypedMessage has that id.
*
*/
@ -198,7 +238,7 @@ export default class TypedMessageManager extends EventEmitter {
*
* @param {Object} msgParams - The msgParams to be used when eth_sign 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.
* @returns {Promise<object>} Promises the msgParams object with metamaskId removed.
*
*/
approveMessage (msgParams) {
@ -235,7 +275,7 @@ export default class TypedMessageManager extends EventEmitter {
* 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
* @returns {Promise<object>} Promises the msgParams with the metamaskId property removed
*
*/
prepMsgForSigning (msgParams) {
@ -287,7 +327,9 @@ export default class TypedMessageManager extends EventEmitter {
_setMsgStatus (msgId, status) {
const msg = this.getMsg(msgId)
if (!msg) {
throw new Error('TypedMessageManager - Message not found for id: "${msgId}".')
throw new Error(
`TypedMessageManager - Message not found for id: "${msgId}".`,
)
}
msg.status = status
this._updateMsg(msg)
@ -323,9 +365,12 @@ export default class TypedMessageManager extends EventEmitter {
*/
_saveMsgList () {
const unapprovedTypedMessages = this.getUnapprovedMsgs()
const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages).length
this.memStore.updateState({ unapprovedTypedMessages, unapprovedTypedMessagesCount })
const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages)
.length
this.memStore.updateState({
unapprovedTypedMessages,
unapprovedTypedMessagesCount,
})
this.emit('updateBadge')
}
}

View File

@ -84,6 +84,28 @@ function sufficientBalance (txParams, hexBalance) {
return balance.gte(maxCost)
}
/**
* Prefixes a hex string with '0x' or '-0x' and returns it. Idempotent.
*
* @param {string} str - The string to prefix.
* @returns {string} The prefixed string.
*/
const addHexPrefix = (str) => {
if (typeof str !== 'string' || str.match(/^-?0x/u)) {
return str
}
if (str.match(/^-?0X/u)) {
return str.replace('0X', '0x')
}
if (str.startsWith('-')) {
return str.replace('-', '-0x')
}
return `0x${str}`
}
/**
* Converts a BN object to a hex string with a '0x' prefix
*
@ -171,6 +193,7 @@ module.exports = {
getEnvironmentType,
sufficientBalance,
hexToBn,
addHexPrefix,
bnToHex,
BnMultiplyByFraction,
capitalizeFirstLetter,

View File

@ -284,7 +284,11 @@ module.exports = class MetamaskController extends EventEmitter {
this.personalMessageManager = new PersonalMessageManager()
this.decryptMessageManager = new DecryptMessageManager()
this.encryptionPublicKeyManager = new EncryptionPublicKeyManager()
this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
this.typedMessageManager = new TypedMessageManager({
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
),
})
// ensure isClientOpenAndUnlocked is updated when memState updates
this.on('update', (memState) => {
@ -1694,10 +1698,6 @@ module.exports = class MetamaskController extends EventEmitter {
// engine.push(this.permissionsController.createMiddleware({ origin, extensionId }))
// watch asset
engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController))
// sign typed data middleware
engine.push(this.createTypedDataMiddleware('eth_signTypedData', 'V1').bind(this))
engine.push(this.createTypedDataMiddleware('eth_signTypedData_v1', 'V1').bind(this))
engine.push(this.createTypedDataMiddleware('eth_signTypedData_v3', 'V3', true).bind(this))
// forward to metamask primary provider
engine.push(providerAsMiddleware(provider))
return engine
@ -2189,35 +2189,35 @@ module.exports = class MetamaskController extends EventEmitter {
this.tokenRatesController.isActive = active
}
/**
* Creates RPC engine middleware for processing eth_signTypedData requests
*
* @param {Object} req - request object
* @param {Object} res - response object
* @param {Function} - next
* @param {Function} - end
*/
createTypedDataMiddleware (methodName, version, reverse) {
return async (req, res, next, end) => {
const { method, params } = req
if (method === methodName) {
const promise = this.typedMessageManager.addUnapprovedMessageAsync({
data: reverse ? params[1] : params[0],
from: reverse ? params[0] : params[1],
}, req, version)
this.sendUpdate()
this.opts.showUnconfirmedMessage()
try {
res.result = await promise
end()
} catch (error) {
end(error)
}
} else {
next()
}
}
}
// /**
// * Creates RPC engine middleware for processing eth_signTypedData requests
// *
// * @param {Object} req - request object
// * @param {Object} res - response object
// * @param {Function} - next
// * @param {Function} - end
// */
// createTypedDataMiddleware (methodName, version, reverse) {
// return async (req, res, next, end) => {
// const { method, params } = req
// if (method === methodName) {
// const promise = this.typedMessageManager.addUnapprovedMessageAsync({
// data: reverse ? params[1] : params[0],
// from: reverse ? params[0] : params[1],
// }, req, version)
// this.sendUpdate()
// this.opts.showUnconfirmedMessage()
// try {
// res.result = await promise
// end()
// } catch (error) {
// end(error)
// }
// } else {
// next()
// }
// }
// }
/**
* Adds a domain to the PhishingController whitelist

View File

@ -1,70 +1,98 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
import React, { Component } from 'react'
import PropTypes from 'prop-types'
const extend = require('xtend')
const { ObjectInspector } = require('react-inspector')
import classnames from 'classnames'
module.exports = TypedMessageRenderer
inherits(TypedMessageRenderer, Component)
function TypedMessageRenderer () {
Component.call(this)
}
TypedMessageRenderer.prototype.render = function () {
const props = this.props
const { value, version, style } = props
let text
switch (version) {
case 'V1':
text = renderTypedData(value)
break
case 'V3':
text = renderTypedDataV3(value)
break
class TypedMessageRenderer extends Component {
static propTypes = {
value: PropTypes.object,
version: PropTypes.string,
style: PropTypes.object,
}
const defaultStyle = extend({
width: '100%',
maxHeight: '210px',
resize: 'none',
border: 'none',
background: '#542289',
color: 'white',
padding: '20px',
overflow: 'scroll',
}, style)
renderTypedData (values) {
return values.map(function (value, ind) {
let v = value.value
if (typeof v === 'boolean') {
v = v.toString()
}
return (
<div key={ind}>
<strong style={{display: 'block', fontWeight: 'bold'}}>{String(value.name) + ':'}</strong>
<div>{v}</div>
</div
>)
})
}
return (
h('div.font-small', {
style: defaultStyle,
}, text)
)
}
renderTypedDataV3V4 (values) {
const { message } = JSON.parse(values)
return [
message ? <div>
<h1>Message</h1>
{this.renderNode(message)}
</div> : '',
]
}
function renderTypedData (values) {
return values.map(function (value) {
let v = value.value
if (typeof v === 'boolean') {
v = v.toString()
renderNode (data) {
return (
<div className="signature-request-message--node">
{Object.entries(data).map(([label, value], i) => (
<div
className={classnames('signature-request-message--node', {
'signature-request-message--node-leaf':
typeof value !== 'object' || value === null,
})}
key={i}
>
<span className="signature-request-message--node-label">
{label}:{' '}
</span>
{typeof value === 'object' && value !== null ? (
this.renderNode(value)
) : (
<span className="signature-request-message--node-value">
{value}
</span>
)}
</div>
))}
</div>
)
}
render () {
const props = this.props
const { value, version, style } = props
let text
switch (version) {
case 'V1':
text = this.renderTypedData(value)
break
case 'V3':
case 'V4':
text = this.renderTypedDataV3V4(value)
break
}
return h('div', {}, [
h('strong', {style: {display: 'block', fontWeight: 'bold'}}, String(value.name) + ':'),
h('div', {}, v),
])
})
const defaultStyle = extend({
width: '100%',
maxHeight: '210px',
resize: 'none',
border: 'none',
background: '#542289',
color: 'white',
padding: '20px',
overflow: 'auto',
}, style)
return (
<div className="font-small" style={defaultStyle}>
{text}
</div>
)
}
}
function renderTypedDataV3 (values) {
const { domain, message } = JSON.parse(values)
return [
domain ? h('div', [
h('h1', 'Domain'),
h(ObjectInspector, { data: domain, expandLevel: 1, name: 'domain' }),
]) : '',
message ? h('div', [
h('h1', 'Message'),
h(ObjectInspector, { data: message, expandLevel: 1, name: 'message' }),
]) : '',
]
}
module.exports = TypedMessageRenderer

223
package-lock.json generated
View File

@ -3381,6 +3381,14 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true
},
"@types/pbkdf2": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz",
"integrity": "sha512-Cf63Rv7jCQ0LaL8tNXmEyqTHuIJxRdlS5vMh1mj5voN4+QFhVZnlZruezqpWYDiJ8UTzhP0VmeLXCmBk66YrMQ==",
"requires": {
"@types/node": "*"
}
},
"@types/prop-types": {
"version": "15.7.3",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
@ -3446,6 +3454,14 @@
"@types/react": "*"
}
},
"@types/secp256k1": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.1.tgz",
"integrity": "sha512-+ZjSA8ELlOp8SlKi0YLB2tz9d5iPNEmOBd+8Rz21wTMdaXQIa9b6TEnD6l5qKOCypE7FSyPyck12qZJxSDNoog==",
"requires": {
"@types/node": "*"
}
},
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@ -6573,6 +6589,11 @@
"safe-buffer": "^5.1.1"
}
},
"blakejs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz",
"integrity": "sha1-ad+S75U6qIylGjLfarHFShVfx6U="
},
"blob": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
@ -7085,22 +7106,6 @@
"randombytes": "^2.0.1"
}
},
"browserify-sha3": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/browserify-sha3/-/browserify-sha3-0.0.4.tgz",
"integrity": "sha1-CGxHuMgjFsnUcCLCYYWVRXbdjiY=",
"requires": {
"js-sha3": "^0.6.1",
"safe-buffer": "^5.1.1"
},
"dependencies": {
"js-sha3": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.6.1.tgz",
"integrity": "sha1-W4n3enR3Z5h39YxKB1JAk0sflcA="
}
}
},
"browserify-sign": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
@ -11383,6 +11388,44 @@
"xtend": "^4.0.1"
},
"dependencies": {
"eth-sig-util": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.5.2.tgz",
"integrity": "sha512-xvDojS/4reXsw8Pz/+p/qcM5rVB61FOdPbEtMZ8FQ0YHnPEzPy5F8zAAaZ+zj5ud0SwRLWPfor2Cacjm7EzMIw==",
"requires": {
"buffer": "^5.2.1",
"elliptic": "^6.4.0",
"ethereumjs-abi": "0.6.5",
"ethereumjs-util": "^5.1.1",
"tweetnacl": "^1.0.0",
"tweetnacl-util": "^0.15.0"
},
"dependencies": {
"ethereumjs-abi": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz",
"integrity": "sha1-WmN+8Wq0NHP6cqKa2QhxQFs/UkE=",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^4.3.0"
},
"dependencies": {
"ethereumjs-util": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz",
"integrity": "sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w==",
"requires": {
"bn.js": "^4.8.0",
"create-hash": "^1.1.2",
"elliptic": "^6.5.2",
"ethereum-cryptography": "^0.1.3",
"rlp": "^2.0.0"
}
}
}
}
}
},
"events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
@ -11866,9 +11909,9 @@
}
},
"eth-sig-util": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.5.1.tgz",
"integrity": "sha512-F5k+6gdSphUtHwCsj0QD59gwxxXoG+ZiFODgTiGS7xc53tPcjBSdBRuhcXdLwXGiytsjk+77oYQRRYeAKXIjBQ==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-3.0.0.tgz",
"integrity": "sha512-4eFkMOhpGbTxBQ3AMzVf0haUX2uTur7DpWiHzWyTURa28BVJJtOkcb9Ok5TV0YvEPG61DODPW7ZUATbJTslioQ==",
"requires": {
"buffer": "^5.2.1",
"elliptic": "^6.4.0",
@ -11888,15 +11931,15 @@
},
"dependencies": {
"ethereumjs-util": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz",
"integrity": "sha1-PpQosxfuvaPXJg2FT93alUsfG8Y=",
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz",
"integrity": "sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w==",
"requires": {
"bn.js": "^4.8.0",
"create-hash": "^1.1.2",
"keccakjs": "^0.2.0",
"rlp": "^2.0.0",
"secp256k1": "^3.0.1"
"elliptic": "^6.5.2",
"ethereum-cryptography": "^0.1.3",
"rlp": "^2.0.0"
}
}
}
@ -11916,6 +11959,44 @@
"xtend": "^4.0.1"
},
"dependencies": {
"eth-sig-util": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.5.2.tgz",
"integrity": "sha512-xvDojS/4reXsw8Pz/+p/qcM5rVB61FOdPbEtMZ8FQ0YHnPEzPy5F8zAAaZ+zj5ud0SwRLWPfor2Cacjm7EzMIw==",
"requires": {
"buffer": "^5.2.1",
"elliptic": "^6.4.0",
"ethereumjs-abi": "0.6.5",
"ethereumjs-util": "^5.1.1",
"tweetnacl": "^1.0.0",
"tweetnacl-util": "^0.15.0"
},
"dependencies": {
"ethereumjs-abi": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz",
"integrity": "sha1-WmN+8Wq0NHP6cqKa2QhxQFs/UkE=",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^4.3.0"
},
"dependencies": {
"ethereumjs-util": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.1.tgz",
"integrity": "sha512-WrckOZ7uBnei4+AKimpuF1B3Fv25OmoRgmYCpGsP7u8PFxXAmAgiJSYT2kRWnt6fVIlKaQlZvuwXp7PIrmn3/w==",
"requires": {
"bn.js": "^4.8.0",
"create-hash": "^1.1.2",
"elliptic": "^6.5.2",
"ethereum-cryptography": "^0.1.3",
"rlp": "^2.0.0"
}
}
}
}
}
},
"events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
@ -12114,6 +12195,49 @@
"resolved": "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.2.0.tgz",
"integrity": "sha512-XOnAR/3rntJgbCdGhqdaLIxDLWKLmsZOGhHdBKadEr6gEnJLH52k93Ou+TUdFaPN3hJc3isBZBal3U/XZ15abA=="
},
"ethereum-cryptography": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz",
"integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==",
"requires": {
"@types/pbkdf2": "^3.0.0",
"@types/secp256k1": "^4.0.1",
"blakejs": "^1.1.0",
"browserify-aes": "^1.2.0",
"bs58check": "^2.1.2",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
"hash.js": "^1.1.7",
"keccak": "^3.0.0",
"pbkdf2": "^3.0.17",
"randombytes": "^2.1.0",
"safe-buffer": "^5.1.2",
"scrypt-js": "^3.0.0",
"secp256k1": "^4.0.1",
"setimmediate": "^1.0.5"
},
"dependencies": {
"keccak": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.1.tgz",
"integrity": "sha512-epq90L9jlFWCW7+pQa6JOnKn2Xgl2mtI664seYR6MHskvI9agt7AnDqmAlp9TqU4/caMYbA08Hi5DMZAl5zdkA==",
"requires": {
"node-addon-api": "^2.0.0",
"node-gyp-build": "^4.2.0"
}
},
"secp256k1": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.2.tgz",
"integrity": "sha512-UDar4sKvWAksIlfX3xIaQReADn+WFnHvbVujpcbr+9Sf/69odMwy2MUsz5CKLQgX9nsIyrjuxL2imVyoNHa3fg==",
"requires": {
"elliptic": "^6.5.2",
"node-addon-api": "^2.0.0",
"node-gyp-build": "^4.2.0"
}
}
}
},
"ethereum-ens-network-map": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/ethereum-ens-network-map/-/ethereum-ens-network-map-1.0.2.tgz",
@ -36396,13 +36520,11 @@
"dev": true
},
"json-rpc-engine": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-5.1.8.tgz",
"integrity": "sha512-vTBSDEPJV1fPAsbm2g5sEuPjsgLdiab2f1CTn2PyRr8nxggUpA996PDlNQDsM0gnrA99F8KIBLq2nIKrOFl1Mg==",
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-5.3.0.tgz",
"integrity": "sha512-+diJ9s8rxB+fbJhT7ZEf8r8spaLRignLd8jTgQ/h5JSGppAHGtNMZtCoabipCaleR1B3GTGxbXBOqhaJSGmPGQ==",
"requires": {
"async": "^2.0.1",
"eth-json-rpc-errors": "^2.0.1",
"promise-to-callback": "^1.0.0",
"eth-rpc-errors": "^3.0.0",
"safe-event-emitter": "^1.0.1"
}
},
@ -37205,15 +37327,6 @@
"safe-buffer": "^5.2.0"
}
},
"keccakjs": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/keccakjs/-/keccakjs-0.2.3.tgz",
"integrity": "sha512-BjLkNDcfaZ6l8HBG9tH0tpmDv3sS2mA7FNQxFHpCdzP3Gb2MVruXBSuoM66SnVxKJpAr5dKGdkHD+bDokt8fTg==",
"requires": {
"browserify-sha3": "^0.0.4",
"sha3": "^1.2.2"
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@ -39784,6 +39897,11 @@
}
}
},
"node-addon-api": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="
},
"node-dir": {
"version": "0.1.17",
"resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz",
@ -39860,6 +39978,11 @@
}
}
},
"node-gyp-build": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg=="
},
"node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@ -44966,6 +45089,11 @@
"nan": "^2.0.8"
}
},
"scrypt-js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
"integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA=="
},
"scrypt.js": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/scrypt.js/-/scrypt.js-0.3.0.tgz",
@ -45215,21 +45343,6 @@
"safe-buffer": "^5.0.1"
}
},
"sha3": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/sha3/-/sha3-1.2.6.tgz",
"integrity": "sha512-KgLGmJGrmNB4JWVsAV11Yk6KbvsAiygWJc7t5IebWva/0NukNrjJqhtKhzy3Eiv2AKuGvhZZt7dt1mDo7HkoiQ==",
"requires": {
"nan": "2.13.2"
},
"dependencies": {
"nan": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw=="
}
}
},
"shallow-clone": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz",

View File

@ -124,7 +124,7 @@
"eth-net-props": "^1.0.36",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^2.5.1",
"eth-sig-util": "^3.0.0",
"eth-token-watcher": "^1.1.7",
"eth-trezor-keyring": "github:vbaranov/eth-trezor-keyring#0.4.0",
"ethereumjs-abi": "^0.6.7",
@ -148,7 +148,7 @@
"idb-global": "^2.1.0",
"iframe-stream": "^3.0.0",
"inject-css": "^0.1.1",
"json-rpc-engine": "^5.1.8",
"json-rpc-engine": "^5.3.0",
"json-rpc-middleware-stream": "^2.1.1",
"json-rpc-pocket": "0.0.1",
"jsonschema": "^1.2.4",