From 2c303d4e4a5aacb20f837f23e5eba52eb065ad7c Mon Sep 17 00:00:00 2001 From: Richard Liu Date: Mon, 13 Feb 2023 02:18:27 -0800 Subject: [PATCH] WIP --- app/_locales/en/messages.json | 7 + app/scripts/background.js | 5 + app/scripts/lib/createMetamaskMiddleware.js | 4 +- app/scripts/lib/middleware/plume.js | 11 +- app/scripts/lib/plume-signature-manager.js | 20 +- app/scripts/metamask-controller.js | 53 ++- lavamoat/browserify/beta/policy.json | 27 +- lavamoat/browserify/flask/policy.json | 27 +- lavamoat/browserify/main/policy.json | 27 +- package.json | 3 +- ui/helpers/constants/routes.ts | 4 + .../confirm-plume-signature.component.js | 305 ++++++++++++++++++ .../confirm-plume-signature.container.js | 74 +++++ .../confirm-plume-signature.scss | 278 ++++++++++++++++ ui/pages/confirm-plume-signature/index.js | 1 + .../confirm-transaction-switch.component.js | 3 + .../confirm-transaction.component.js | 7 + ui/pages/pages.scss | 1 + yarn.lock | 17 +- 19 files changed, 828 insertions(+), 46 deletions(-) create mode 100644 ui/pages/confirm-plume-signature/confirm-plume-signature.component.js create mode 100644 ui/pages/confirm-plume-signature/confirm-plume-signature.container.js create mode 100644 ui/pages/confirm-plume-signature/confirm-plume-signature.scss create mode 100644 ui/pages/confirm-plume-signature/index.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 5870df9b4d9d..7fda4bae68e8 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2846,6 +2846,13 @@ "pleaseConfirm": { "message": "Please confirm" }, + "plumeNotice": { + "message": "$1 would like to sign this message with your Plume. This does not reveal anything about your account.", + "description": "$1 is the web3 site name" + }, + "plumeRequest": { + "message": "Sign message with Plume" + }, "plusXMore": { "message": "+ $1 more", "description": "$1 is a number of additional but unshown items in a list- this message will be shown in place of those items" diff --git a/app/scripts/background.js b/app/scripts/background.js index 7d0273a1b9d5..6b19116d6646 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -772,10 +772,14 @@ function setupController(initState, initLangCode) { * Opens the browser popup for user confirmation */ async function triggerUi() { + console.log('trigger UI'); + console.log(1); const tabs = await platform.getActiveTabs(); + console.log(2, { tabs }); const currentlyActiveMetamaskTab = Boolean( tabs.find((tab) => openMetamaskTabsIDs[tab.id]), ); + console.log(2, { currentlyActiveMetamaskTab }); // Vivaldi is not closing port connection on popup close, so popupIsOpen does not work correctly // To be reviewed in the future if this behaviour is fixed - also the way we determine isVivaldi variable might change at some point const isVivaldi = @@ -794,6 +798,7 @@ async function triggerUi() { uiIsTriggering = false; } } + console.log(3, { isVivaldi, popupIsOpen, uiIsTriggering }); } /** diff --git a/app/scripts/lib/createMetamaskMiddleware.js b/app/scripts/lib/createMetamaskMiddleware.js index c74606d658b2..8c5a44d12f92 100644 --- a/app/scripts/lib/createMetamaskMiddleware.js +++ b/app/scripts/lib/createMetamaskMiddleware.js @@ -17,7 +17,7 @@ export default function createMetamaskMiddleware({ processPersonalMessage, processDecryptMessage, processEncryptionPublicKey, - getPlumeSignature, + processGetPlumeSignature, getPendingNonce, getPendingTransactionByHash, }) { @@ -39,7 +39,7 @@ export default function createMetamaskMiddleware({ }), createPendingNonceMiddleware({ getPendingNonce }), createPendingTxMiddleware({ getPendingTransactionByHash }), - createGetPlumeSignatureMiddleware({ getPlumeSignature }), + createGetPlumeSignatureMiddleware({ processGetPlumeSignature }), ]); return metamaskMiddleware; } diff --git a/app/scripts/lib/middleware/plume.js b/app/scripts/lib/middleware/plume.js index 505ba22dcc67..caa5715a3250 100644 --- a/app/scripts/lib/middleware/plume.js +++ b/app/scripts/lib/middleware/plume.js @@ -1,13 +1,20 @@ +import { ethErrors } from 'eth-rpc-errors'; import { createAsyncMiddleware } from 'json-rpc-engine'; -export function createGetPlumeSignatureMiddleware({ getPlumeSignature }) { +export function createGetPlumeSignatureMiddleware({ + processGetPlumeSignature, +}) { return createAsyncMiddleware(async (req, res, next) => { + if (!processGetPlumeSignature) { + throw ethErrors.rpc.methodNotSupported(); + } const { method, params } = req; if (method !== 'eth_getPlumeSignature') { next(); return; } - res.result = `plume result ${params.data}`; + const [data, from] = params; + res.result = await processGetPlumeSignature({ data, from }, req); // TODO: Insert Plume logic here }); } diff --git a/app/scripts/lib/plume-signature-manager.js b/app/scripts/lib/plume-signature-manager.js index 46d0330c874b..33e71a75faf2 100644 --- a/app/scripts/lib/plume-signature-manager.js +++ b/app/scripts/lib/plume-signature-manager.js @@ -61,17 +61,17 @@ export default class PlumeSignatureManager extends EventEmitter { * the new PlumeMessage to this.messages, and to save the unapproved PlumeMessages from that list to * this.memStore. * - * @param {object} address - The param for the eth_getPlumeSignature call to be made after the message is approved. + * @param {object} msgParams - The param for the eth_getPlumeSignature call to be made after the message is approved. * @param {object} [req] - The original request object possibly containing the origin * @returns {Promise} The genereated plume signature */ - addUnapprovedMessageAsync(address, req) { + addUnapprovedMessageAsync(msgParams, req) { return new Promise((resolve, reject) => { - if (!address) { - reject(new Error('MetaMask Message: address field is required.')); + if (!msgParams.from) { + reject(new Error('MetaMask Message: from field is required.')); return; } - const msgId = this.addUnapprovedMessage(address, req); + const msgId = this.addUnapprovedMessage(msgParams, req); this.once(`${msgId}:finished`, (data) => { switch (data.status) { case 'received': @@ -91,7 +91,7 @@ export default class PlumeSignatureManager extends EventEmitter { reject( new Error( `MetaMask PlumeSignature: Unknown problem: ${JSON.stringify( - address, + msgParams, )}`, ), ); @@ -105,18 +105,18 @@ export default class PlumeSignatureManager extends EventEmitter { * the new PlumeMessage to this.messages, and to save the unapproved PlumeMessages from that list to * this.memStore. * - * @param {object} address - The param for the eth_getPlumeSignature call to be made after the message is approved. + * @param {object} msgParams - The param for the eth_getPlumeSignature 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 PlumeMessage. */ - addUnapprovedMessage(address, req) { - log.debug(`PlumeSignatureManager addUnapprovedMessage: ${address}`); + addUnapprovedMessage(msgParams, req) { + log.debug(`PlumeSignatureManager addUnapprovedMessage: ${msgParams}`); // create txData obj with parameters and meta data const time = new Date().getTime(); const msgId = createId(); const msgData = { id: msgId, - msgParams: address, + msgParams, time, status: 'unapproved', type: MESSAGE_TYPE.ETH_GET_PLUME_SIGNATURE, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e6136c0a181a..e7a019c01cd5 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -6,6 +6,7 @@ import { JsonRpcEngine } from 'json-rpc-engine'; import { debounce } from 'lodash'; import createEngineStream from 'json-rpc-middleware-stream/engineStream'; import { providerAsMiddleware } from 'eth-json-rpc-middleware'; +import { computeAllInputs } from 'plume-sig'; import { KeyringController, keyringBuilderFactory, @@ -1162,6 +1163,7 @@ export default class MetamaskController extends EventEmitter { processDecryptMessage: this.newRequestDecryptMessage.bind(this), processGetPlumeSignature: this.newRequestGetPlumeSignature.bind(this), processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this), + getPlumeSignature: this.getPlumeSignature.bind(this), getPendingNonce: this.getPendingNonce.bind(this), getPendingTransactionByHash: (hash) => this.txController.getTransactions({ @@ -3184,13 +3186,43 @@ export default class MetamaskController extends EventEmitter { * Passed back to the requesting Dapp. */ async newRequestGetPlumeSignature(msgParams, req) { - const promise = this.plumeSignatureManager.addUnapprovedMessageAsync( - msgParams, - req, + console.log('newRequestGetPlumeSignature', { msgParams, req }); + console.log(1); + + // return this.keyringController.exportAccount(msgParams.from); + const privateKey = await this.keyringController.exportAccount( + msgParams.from, ); - this.sendUpdate(); - this.opts.showUserConfirmation(); - return promise; + // return { window: typeof window, hi: 'zxcv' }; + const { plume, s, publicKey, c, gPowR, hashMPKPowR } = + await computeAllInputs(msgParams.data, privateKey); + console.log({ privateKey, gPowR, plume, s, publicKey, c, hashMPKPowR }); + return { + plume: plume.toHex(true), + publicKey: Buffer.from(publicKey).toString('hex'), + hashMPKPowR: hashMPKPowR.toHex(true), + gPowR: gPowR.toHex(true), + c, + s, + }; + + // const promise = this.plumeSignatureManager.addUnapprovedMessageAsync( + // msgParams, + // req, + // ); + // REMOVE FROM HERE + // const privateKey = await this.keyringController.exportAccount( + // msgParams.from, + // ); + + // // console.log({ privateKey }); + // // computeHashMPk(); + // // REMOVE END HERE + // console.log(2); + // this.sendUpdate(); + // console.log(3); + // this.opts.showUserConfirmation(); + // return promise; } /** @@ -3204,14 +3236,13 @@ export default class MetamaskController extends EventEmitter { log.info('MetaMaskController - plumeMessage'); const msgId = msgParams.metamaskId; // sets the status op the message to 'approved' - // and removes the metamaskId for decryption + // and removes the metamaskId for generating plume try { const params = await this.plumeSignatureManager.approveMessage(msgParams); - // TODO / FIXME: Import and use zk-nullifier library - const mockPlume = `mockPlume-${params.data}`; + const inputs = await computeAllInputs(msgParams.data, privateKey); - // tells the listener that the message has been decrypted and can be returned to the dapp - this.plumeSignatureManager.setMsgStatusReceived(msgId, mockPlume); + // tells the listener that the message has been received and can be returned to the dapp + this.plumeSignatureManager.setMsgStatusReceived(msgId, inputs); } catch (error) { log.info('MetaMaskController - eth_getPlumeSignature failed.', error); this.plumeSignatureManager.errorMessage(msgId, error); diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 1a320eae929a..04acab4620e6 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -1243,7 +1243,7 @@ "@metamask/snaps-utils>@noble/hashes": true, "@metamask/snaps-utils>@scure/base": true, "@metamask/utils": true, - "plume-sig>@noble/secp256k1": true + "@noble/secp256k1": true } }, "@metamask/rpc-methods>@metamask/key-tree>@noble/ed25519": { @@ -1362,6 +1362,14 @@ "define": true } }, + "@noble/secp256k1": { + "globals": { + "crypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, "@popperjs/core": { "globals": { "Element": true, @@ -3569,12 +3577,23 @@ "readable-stream": true } }, - "plume-sig>@noble/secp256k1": { + "plume-sig": { "globals": { - "crypto": true + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true + "@noble/secp256k1": true, + "browserify>buffer": true, + "plume-sig>amcl-js": true, + "plume-sig>js-sha512": true + } + }, + "plume-sig>js-sha512": { + "globals": { + "define": true + }, + "packages": { + "browserify>process": true } }, "promise-to-callback": { diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 7b74cab94ced..707e51c98b5b 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1335,7 +1335,7 @@ "@metamask/snaps-utils>@noble/hashes": true, "@metamask/snaps-utils>@scure/base": true, "@metamask/utils": true, - "plume-sig>@noble/secp256k1": true + "@noble/secp256k1": true } }, "@metamask/rpc-methods>@metamask/key-tree>@noble/ed25519": { @@ -1686,6 +1686,14 @@ "define": true } }, + "@noble/secp256k1": { + "globals": { + "crypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, "@popperjs/core": { "globals": { "Element": true, @@ -3911,12 +3919,23 @@ "readable-stream": true } }, - "plume-sig>@noble/secp256k1": { + "plume-sig": { "globals": { - "crypto": true + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true + "@noble/secp256k1": true, + "browserify>buffer": true, + "plume-sig>amcl-js": true, + "plume-sig>js-sha512": true + } + }, + "plume-sig>js-sha512": { + "globals": { + "define": true + }, + "packages": { + "browserify>process": true } }, "promise-to-callback": { diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 1a320eae929a..04acab4620e6 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1243,7 +1243,7 @@ "@metamask/snaps-utils>@noble/hashes": true, "@metamask/snaps-utils>@scure/base": true, "@metamask/utils": true, - "plume-sig>@noble/secp256k1": true + "@noble/secp256k1": true } }, "@metamask/rpc-methods>@metamask/key-tree>@noble/ed25519": { @@ -1362,6 +1362,14 @@ "define": true } }, + "@noble/secp256k1": { + "globals": { + "crypto": true + }, + "packages": { + "browserify>browser-resolve": true + } + }, "@popperjs/core": { "globals": { "Element": true, @@ -3569,12 +3577,23 @@ "readable-stream": true } }, - "plume-sig>@noble/secp256k1": { + "plume-sig": { "globals": { - "crypto": true + "TextEncoder": true }, "packages": { - "browserify>browser-resolve": true + "@noble/secp256k1": true, + "browserify>buffer": true, + "plume-sig>amcl-js": true, + "plume-sig>js-sha512": true + } + }, + "plume-sig>js-sha512": { + "globals": { + "define": true + }, + "packages": { + "browserify>process": true } }, "promise-to-callback": { diff --git a/package.json b/package.json index be95a2093041..005e8383b676 100644 --- a/package.json +++ b/package.json @@ -252,6 +252,7 @@ "@metamask/subject-metadata-controller": "^1.0.0", "@metamask/utils": "^3.4.1", "@ngraveio/bc-ur": "^1.1.6", + "@noble/secp256k1": "^1.7.1", "@popperjs/core": "^2.4.0", "@reduxjs/toolkit": "^1.6.2", "@segment/loosely-validate-event": "^2.0.0", @@ -312,7 +313,7 @@ "nonce-tracker": "^1.0.0", "obj-multiplex": "^1.0.0", "pify": "^5.0.0", - "plume-sig": "^1.0.0", + "plume-sig": "^1.1.2", "promise-to-callback": "^1.0.0", "prop-types": "^15.6.1", "pubnub": "4.27.3", diff --git a/ui/helpers/constants/routes.ts b/ui/helpers/constants/routes.ts index 3de4f8c7ce86..a97ce71503ce 100644 --- a/ui/helpers/constants/routes.ts +++ b/ui/helpers/constants/routes.ts @@ -98,6 +98,7 @@ 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'; +const PLUME_SIGNATURE_PATH = '/plume-signature-request'; const CONFIRMATION_V_NEXT_ROUTE = '/confirmation'; // Used to pull a convenient name for analytics tracking events. The key must @@ -165,6 +166,8 @@ const PATH_NAME_MAP = { 'Decrypt Message Request Page', [`${CONFIRM_TRANSACTION_ROUTE}/:id${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`]: 'Encryption Public Key Request Page', + [`${CONFIRM_TRANSACTION_ROUTE}/:id${PLUME_SIGNATURE_PATH}`]: + 'Plume Signature Request Page', [INITIALIZE_ROUTE]: 'Initialization Page', [INITIALIZE_WELCOME_ROUTE]: 'Install Welcome Page', [INITIALIZE_UNLOCK_ROUTE]: 'Initialization Unlock page', @@ -228,6 +231,7 @@ export { SIGNATURE_REQUEST_PATH, DECRYPT_MESSAGE_REQUEST_PATH, ENCRYPTION_PUBLIC_KEY_REQUEST_PATH, + PLUME_SIGNATURE_PATH, CONFIRMATION_V_NEXT_ROUTE, INITIALIZE_METAMETRICS_OPT_IN_ROUTE, ADVANCED_ROUTE, diff --git a/ui/pages/confirm-plume-signature/confirm-plume-signature.component.js b/ui/pages/confirm-plume-signature/confirm-plume-signature.component.js new file mode 100644 index 000000000000..a09d1a322549 --- /dev/null +++ b/ui/pages/confirm-plume-signature/confirm-plume-signature.component.js @@ -0,0 +1,305 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import copyToClipboard from 'copy-to-clipboard'; +import classnames from 'classnames'; + +import AccountListItem from '../../components/app/account-list-item'; +import Identicon from '../../components/ui/identicon'; +import Tooltip from '../../components/ui/tooltip'; +import Copy from '../../components/ui/icon/copy-icon.component'; +import { PageContainerFooter } from '../../components/ui/page-container'; + +import { EVENT } from '../../../shared/constants/metametrics'; +import { SECOND } from '../../../shared/constants/time'; +import { Numeric } from '../../../shared/modules/Numeric'; +import { EtherDenomination } from '../../../shared/constants/common'; + +export default class ConfirmDecryptMessage extends Component { + static contextTypes = { + t: PropTypes.func.isRequired, + trackEvent: PropTypes.func.isRequired, + }; + + static propTypes = { + fromAccount: PropTypes.shape({ + address: PropTypes.string.isRequired, + balance: PropTypes.string, + name: PropTypes.string, + }).isRequired, + clearConfirmTransaction: PropTypes.func.isRequired, + cancelDecryptMessage: PropTypes.func.isRequired, + decryptMessage: PropTypes.func.isRequired, + decryptMessageInline: PropTypes.func.isRequired, + conversionRate: PropTypes.number, + history: PropTypes.object.isRequired, + mostRecentOverviewPage: PropTypes.string.isRequired, + requesterAddress: PropTypes.string, + txData: PropTypes.object, + subjectMetadata: PropTypes.object, + nativeCurrency: PropTypes.string.isRequired, + }; + + state = { + fromAccount: this.props.fromAccount, + copyToClipboardPressed: false, + hasCopied: false, + }; + + copyMessage = () => { + copyToClipboard(this.state.rawMessage); + this.context.trackEvent({ + category: EVENT.CATEGORIES.MESSAGES, + event: 'Copy', + properties: { + action: 'Plume Signature Copy', + legacy_event: true, + }, + }); + this.setState({ hasCopied: true }); + setTimeout(() => this.setState({ hasCopied: false }), SECOND * 3); + }; + + renderHeader = () => { + return ( +
+
+ +
+ {this.context.t('decryptRequest')} +
+ +
+
+
+
+ ); + }; + + renderAccount = () => { + const { fromAccount } = this.state; + const { t } = this.context; + + return ( +
+
+ {`${t('account')}:`} +
+ +
+ +
+
+ ); + }; + + renderBalance = () => { + const { conversionRate, nativeCurrency } = this.props; + const { + fromAccount: { balance }, + } = this.state; + const { t } = this.context; + + const nativeCurrencyBalance = new Numeric( + balance, + 16, + EtherDenomination.WEI, + ) + .applyConversionRate(conversionRate) + .round(6) + .toBase(10); + + return ( +
+
+ {`${t('balance')}:`} +
+
+ {`${nativeCurrencyBalance} ${nativeCurrency}`} +
+
+ ); + }; + + renderRequestIcon = () => { + const { requesterAddress } = this.props; + + return ( +
+ +
+ ); + }; + + renderAccountInfo = () => { + return ( +
+ {this.renderAccount()} + {this.renderRequestIcon()} + {this.renderBalance()} +
+ ); + }; + + renderBody = () => { + const { decryptMessageInline, subjectMetadata, txData } = this.props; + const { t } = this.context; + + const targetSubjectMetadata = subjectMetadata[txData.msgParams.origin]; + const name = targetSubjectMetadata?.name || txData.msgParams.origin; + const notice = t('decryptMessageNotice', [txData.msgParams.origin]); + + const { + hasCopied, + hasDecrypted, + hasError, + rawMessage, + errorMessage, + copyToClipboardPressed, + } = this.state; + + return ( +
+ {this.renderAccountInfo()} +
+
+ {targetSubjectMetadata?.iconUrl ? ( + + ) : ( + + {name.charAt(0).toUpperCase()} + + )} +
{notice}
+
+
+
+
+ {!hasDecrypted && !hasError ? txData.msgParams.data : rawMessage} + {hasError ? errorMessage : ''} +
+
+
{ + decryptMessageInline(txData, event).then((result) => { + if (result.error) { + this.setState({ + hasError: true, + errorMessage: this.context.t('decryptInlineError', [ + result.error, + ]), + }); + } else { + this.setState({ + hasDecrypted: true, + rawMessage: result.rawData, + }); + } + }); + }} + > +
+ +
+ {t('decryptMetamask')} +
+
+
+
+ {hasDecrypted ? ( +
this.copyMessage()} + onMouseDown={() => this.setState({ copyToClipboardPressed: true })} + onMouseUp={() => this.setState({ copyToClipboardPressed: false })} + > + +
+ {t('decryptCopy')} +
+ +
+
+ ) : ( +
+ )} +
+ ); + }; + + renderFooter = () => { + const { + cancelDecryptMessage, + clearConfirmTransaction, + decryptMessage, + history, + mostRecentOverviewPage, + txData, + } = this.props; + const { trackEvent, t } = this.context; + + return ( + { + await cancelDecryptMessage(txData, event); + trackEvent({ + category: EVENT.CATEGORIES.MESSAGES, + event: 'Cancel', + properties: { + action: 'Decrypt Message Request', + legacy_event: true, + }, + }); + clearConfirmTransaction(); + history.push(mostRecentOverviewPage); + }} + onSubmit={async (event) => { + await decryptMessage(txData, event); + trackEvent({ + category: EVENT.CATEGORIES.MESSAGES, + event: 'Confirm', + properties: { + action: 'Decrypt Message Request', + legacy_event: true, + }, + }); + clearConfirmTransaction(); + history.push(mostRecentOverviewPage); + }} + /> + ); + }; + + render = () => { + return ( +
+ {this.renderHeader()} + {this.renderBody()} + {this.renderFooter()} +
+ ); + }; +} diff --git a/ui/pages/confirm-plume-signature/confirm-plume-signature.container.js b/ui/pages/confirm-plume-signature/confirm-plume-signature.container.js new file mode 100644 index 000000000000..f20da9b96585 --- /dev/null +++ b/ui/pages/confirm-plume-signature/confirm-plume-signature.container.js @@ -0,0 +1,74 @@ +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { withRouter } from 'react-router-dom'; + +import { + goHome, + decryptMsg, + cancelDecryptMsg, + decryptMsgInline, +} from '../../store/actions'; +import { + getTargetAccountWithSendEtherInfo, + unconfirmedTransactionsListSelector, + conversionRateSelector, +} from '../../selectors'; +import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck'; +import { getMostRecentOverviewPage } from '../../ducks/history/history'; +import { getNativeCurrency } from '../../ducks/metamask/metamask'; +import ConfirmPlumeSignature from './confirm-plume-signature.component'; + +function mapStateToProps(state) { + const { + metamask: { subjectMetadata = {} }, + } = state; + + const unconfirmedTransactions = unconfirmedTransactionsListSelector(state); + + const txData = unconfirmedTransactions[0]; + + const { + msgParams: { from }, + } = txData; + + const fromAccount = getTargetAccountWithSendEtherInfo(state, from); + + return { + txData, + subjectMetadata, + fromAccount, + requester: null, + requesterAddress: null, + conversionRate: conversionRateSelector(state), + mostRecentOverviewPage: getMostRecentOverviewPage(state), + nativeCurrency: getNativeCurrency(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), +)(ConfirmPlumeSignature); diff --git a/ui/pages/confirm-plume-signature/confirm-plume-signature.scss b/ui/pages/confirm-plume-signature/confirm-plume-signature.scss new file mode 100644 index 000000000000..2d392102995f --- /dev/null +++ b/ui/pages/confirm-plume-signature/confirm-plume-signature.scss @@ -0,0 +1,278 @@ +.request-plume-signature { + &__container { + width: 380px; + border-radius: 8px; + background-color: var(--color-background-default); + box-shadow: var(--shadow-size-xs) var(--color-shadow-default); + display: flex; + flex-flow: column nowrap; + z-index: 25; + align-items: center; + position: relative; + height: 100%; + + @include screen-sm-max { + width: 100%; + top: 0; + box-shadow: none; + } + + @include screen-sm-min { + 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: var(--color-background-alternative); + z-index: 2; + width: 100%; + height: 100%; + } + + &__header__text { + @include H3; + + color: var(--color-text-alternative); + z-index: 3; + text-align: center; + } + + &__header__tip-container { + width: 100%; + display: flex; + justify-content: center; + } + + &__header__tip { + height: 25px; + width: 25px; + background: var(--color-background-alternative); + 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: var(--color-text-alternative); + margin-left: 17px; + } + + &__account-text { + @include H6; + } + + &__account-item { + @include H7; + + height: 22px; + background-color: var(--color-background-default); + 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: var(--color-text-alternative); + margin-right: 17px; + width: 124px; + } + + &__balance-text { + @include H6; + + text-align: right; + } + + &__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 { + @include H6; + + text-align: center; + margin-top: 15px; + margin-bottom: 11px; + width: 100%; + } + + &__message { + overflow-wrap: break-word; + margin: 20px; + overflow: hidden; + border: 1px solid var(--color-border-alternative); + padding: 5px; + border-radius: 5px; + position: relative; + + &-text { + @include H7; + + height: 115px; + } + + &-cover { + background-color: var(--color-background-default); + opacity: 0.75; + position: absolute; + height: 100%; + width: 100%; + top: 0; + } + + &-lock { + position: absolute; + height: 100%; + width: 100%; + top: 0; + cursor: pointer; + + &__container { + padding: 16px; + background-color: var(--color-background-default); + position: absolute; + left: 50%; + top: 50%; + border-radius: 3px; + transform: translate(-50%, calc(-50%)); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + + &__icon { + color: var(--color-icon-default); + display: flex; + margin-bottom: 16px; + } + + &__text { + @include H7; + } + } + + &--pressed { + display: none; + } + } + + &-copy { + @include H7; + + justify-content: space-evenly; + margin-left: 20px; + margin-right: 20px; + display: flex; + cursor: pointer; + } + + &-copy-text { + margin-right: 10px; + display: inline; + } + + &-copy-tooltip { + float: right; + } + } + + &__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: var(--color-background-alternative); + color: var(--color-text-default); + width: 48px; + height: 48px; + border-radius: 24px; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + } + } + } + } + \ No newline at end of file diff --git a/ui/pages/confirm-plume-signature/index.js b/ui/pages/confirm-plume-signature/index.js new file mode 100644 index 000000000000..222455bbd32d --- /dev/null +++ b/ui/pages/confirm-plume-signature/index.js @@ -0,0 +1 @@ +export { default } from './confirm-plume-signature.container'; diff --git a/ui/pages/confirm-transaction-switch/confirm-transaction-switch.component.js b/ui/pages/confirm-transaction-switch/confirm-transaction-switch.component.js index 2211ef81adf1..171aa4890f1b 100644 --- a/ui/pages/confirm-transaction-switch/confirm-transaction-switch.component.js +++ b/ui/pages/confirm-transaction-switch/confirm-transaction-switch.component.js @@ -15,6 +15,7 @@ import { ENCRYPTION_PUBLIC_KEY_REQUEST_PATH, CONFIRM_SAFE_TRANSFER_FROM_PATH, CONFIRM_SET_APPROVAL_FOR_ALL_PATH, + PLUME_SIGNATURE_PATH, } from '../../helpers/constants/routes'; import { MESSAGE_TYPE } from '../../../shared/constants/app'; import { TransactionType } from '../../../shared/constants/transaction'; @@ -81,6 +82,8 @@ export default class ConfirmTransactionSwitch extends Component { pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${DECRYPT_MESSAGE_REQUEST_PATH}`; } else if (txData.type === MESSAGE_TYPE.ETH_GET_ENCRYPTION_PUBLIC_KEY) { pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`; + } else if (txData.type === MESSAGE_TYPE.ETH_GET_PLUME_SIGNATURE) { + pathname = `${CONFIRM_TRANSACTION_ROUTE}/${txData.id}${PLUME_SIGNATURE_PATH}`; } return ; } diff --git a/ui/pages/confirm-transaction/confirm-transaction.component.js b/ui/pages/confirm-transaction/confirm-transaction.component.js index 540a28a07f4b..14d5312db133 100644 --- a/ui/pages/confirm-transaction/confirm-transaction.component.js +++ b/ui/pages/confirm-transaction/confirm-transaction.component.js @@ -8,6 +8,7 @@ import ConfirmSendEther from '../confirm-send-ether'; import ConfirmDeployContract from '../confirm-deploy-contract'; import ConfirmDecryptMessage from '../confirm-decrypt-message'; import ConfirmEncryptionPublicKey from '../confirm-encryption-public-key'; +import confirmPlumeSignature from '../confirm-plume-signature'; import { CONFIRM_TRANSACTION_ROUTE, @@ -18,6 +19,7 @@ import { DECRYPT_MESSAGE_REQUEST_PATH, ENCRYPTION_PUBLIC_KEY_REQUEST_PATH, DEFAULT_ROUTE, + PLUME_SIGNATURE_PATH, } from '../../helpers/constants/routes'; import { disconnectGasFeeEstimatePoller, @@ -197,6 +199,11 @@ export default class ConfirmTransaction extends Component { path={`${CONFIRM_TRANSACTION_ROUTE}/:id?${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`} component={ConfirmEncryptionPublicKey} /> + ) : ( diff --git a/ui/pages/pages.scss b/ui/pages/pages.scss index bf1ad5887af7..4a3866785822 100644 --- a/ui/pages/pages.scss +++ b/ui/pages/pages.scss @@ -7,6 +7,7 @@ @import 'confirm-approve/index'; @import 'confirm-decrypt-message/confirm-decrypt-message'; @import 'confirm-encryption-public-key/confirm-encryption-public-key'; +@import 'confirm-plume-signature/confirm-plume-signature'; @import 'confirmation/confirmation'; @import 'connected-sites/index'; @import 'connected-accounts/index'; diff --git a/yarn.lock b/yarn.lock index 4434efe1dd47..97435da18f63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4556,7 +4556,7 @@ __metadata: languageName: node linkType: hard -"@noble/secp256k1@npm:^1.5.5, @noble/secp256k1@npm:^1.7.0": +"@noble/secp256k1@npm:^1.5.5, @noble/secp256k1@npm:^1.7.0, @noble/secp256k1@npm:^1.7.1": version: 1.7.1 resolution: "@noble/secp256k1@npm:1.7.1" checksum: d2301f1f7690368d8409a3152450458f27e54df47e3f917292de3de82c298770890c2de7c967d237eff9c95b70af485389a9695f73eb05a43e2bd562d18b18cb @@ -9078,7 +9078,7 @@ __metadata: languageName: node linkType: hard -"amcl-js@npm:^3.0.0": +"amcl-js@npm:3.0.0": version: 3.0.0 resolution: "amcl-js@npm:3.0.0" dependencies: @@ -24941,6 +24941,7 @@ __metadata: "@metamask/test-dapp": ^5.2.1 "@metamask/utils": ^3.4.1 "@ngraveio/bc-ur": ^1.1.6 + "@noble/secp256k1": ^1.7.1 "@popperjs/core": ^2.4.0 "@reduxjs/toolkit": ^1.6.2 "@segment/loosely-validate-event": ^2.0.0 @@ -25117,7 +25118,7 @@ __metadata: obj-multiplex: ^1.0.0 pify: ^5.0.0 playwright: ^1.29.2 - plume-sig: ^1.0.0 + plume-sig: ^1.1.2 polyfill-crypto.getrandomvalues: ^1.0.0 prettier: ^2.7.1 prettier-plugin-sort-json: ^1.0.0 @@ -28029,15 +28030,15 @@ __metadata: languageName: node linkType: hard -"plume-sig@npm:^1.0.0": - version: 1.0.0 - resolution: "plume-sig@npm:1.0.0" +"plume-sig@npm:^1.1.2": + version: 1.1.2 + resolution: "plume-sig@npm:1.1.2" dependencies: "@noble/secp256k1": ^1.7.0 - amcl-js: ^3.0.0 + amcl-js: 3.0.0 jest: ^29.3.1 js-sha512: ^0.8.0 - checksum: 7557f6335ee506eeb3fdf471028bba7d03d6f0ba159ecac8032cde9ea5f93f763ce9b6ea52834a7a14aad93a9f3308354d3f85aa90385008bfb6c79316872cad + checksum: bea9f76b27c94846c02b2793b8ff1e2fd4cac02fce2856d43ebb5eea0ecb2f1794f08a7eabb08cd1df75dd367ce169a938c24a7754cc3f7232e097f7b85256d7 languageName: node linkType: hard