diff --git a/web/cashtab/package-lock.json b/web/cashtab/package-lock.json --- a/web/cashtab/package-lock.json +++ b/web/cashtab/package-lock.json @@ -16,6 +16,7 @@ "dotenv": "^8.2.0", "dotenv-expand": "^5.1.0", "ecashaddrjs": "^1.0.1", + "ecies-lite": "^1.0.7", "ethereum-blockies-base64": "^1.0.2", "localforage": "^1.9.0", "lodash.isempty": "^4.4.0", @@ -33,7 +34,8 @@ "react-image": "^4.0.3", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", - "styled-components": "^4.4.0" + "styled-components": "^4.4.0", + "wif": "^2.0.6" }, "devDependencies": { "@ant-design/dark-theme": "^1.0.3", @@ -8135,6 +8137,11 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ecies-lite": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ecies-lite/-/ecies-lite-1.0.7.tgz", + "integrity": "sha512-FcT30ao9Crn8LoKw4x/ekp85KddxsNrYp4jxoxfX6RdBG9rEAL/pNK5sJ4j9x1Z22ooA8cILj+iD9bVdMc9opw==" + }, "node_modules/ecurve": { "version": "1.0.6", "license": "MIT", @@ -28060,7 +28067,8 @@ }, "node_modules/wif": { "version": "2.0.6", - "license": "MIT", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", "dependencies": { "bs58check": "<3.0.0" } @@ -34210,6 +34218,11 @@ "safer-buffer": "^2.1.0" } }, + "ecies-lite": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/ecies-lite/-/ecies-lite-1.0.7.tgz", + "integrity": "sha512-FcT30ao9Crn8LoKw4x/ekp85KddxsNrYp4jxoxfX6RdBG9rEAL/pNK5sJ4j9x1Z22ooA8cILj+iD9bVdMc9opw==" + }, "ecurve": { "version": "1.0.6", "requires": { @@ -47634,6 +47647,8 @@ }, "wif": { "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", "requires": { "bs58check": "<3.0.0" } diff --git a/web/cashtab/package.json b/web/cashtab/package.json --- a/web/cashtab/package.json +++ b/web/cashtab/package.json @@ -12,6 +12,7 @@ "dotenv": "^8.2.0", "dotenv-expand": "^5.1.0", "ecashaddrjs": "^1.0.1", + "ecies-lite": "^1.0.7", "ethereum-blockies-base64": "^1.0.2", "localforage": "^1.9.0", "lodash.isempty": "^4.4.0", @@ -29,7 +30,8 @@ "react-image": "^4.0.3", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", - "styled-components": "^4.4.0" + "styled-components": "^4.4.0", + "wif": "^2.0.6" }, "lint-staged": { "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [ diff --git a/web/cashtab/src/components/Common/Ticker.js b/web/cashtab/src/components/Common/Ticker.js --- a/web/cashtab/src/components/Common/Ticker.js +++ b/web/cashtab/src/components/Common/Ticker.js @@ -35,7 +35,10 @@ appPrefixesHex: { eToken: '534c5000', cashtab: '00746162', + cashtabEncrypted: '65746162', }, + encryptedMsgCharLimit: 94, + unencryptedMsgCharLimit: 160, }, settingsValidation: { fiatCurrency: [ @@ -141,6 +144,12 @@ ) { // add the extracted Cashtab prefix to array resultArray[i] = currency.opReturn.appPrefixesHex.cashtab; + } else if ( + i === 0 && + message === currency.opReturn.appPrefixesHex.cashtabEncrypted + ) { + // add the Cashtab encryption prefix to array + resultArray[i] = currency.opReturn.appPrefixesHex.cashtabEncrypted; } else { // this is either an external message or a subsequent cashtab message loop to extract the message resultArray[i] = message; diff --git a/web/cashtab/src/components/Send/Send.js b/web/cashtab/src/components/Send/Send.js --- a/web/cashtab/src/components/Send/Send.js +++ b/web/cashtab/src/components/Send/Send.js @@ -12,7 +12,7 @@ import { Form, message, Modal, Alert, Collapse, Input, Button } from 'antd'; const { Panel } = Collapse; const { TextArea } = Input; -import { Row, Col } from 'antd'; +import { Row, Col, Switch } from 'antd'; import PrimaryButton, { SecondaryButton, SmartButton, @@ -90,6 +90,11 @@ const [msgToSign, setMsgToSign] = useState(''); const [signMessageIsValid, setSignMessageIsValid] = useState(null); const [isOneToManyXECSend, setIsOneToManyXECSend] = useState(false); + const [opReturnMsg, setOpReturnMsg] = useState(false); + const [isEncryptedOptionalOpReturnMsg, setIsEncryptedOptionalOpReturnMsg] = + useState(false); + const [bchObj, setBchObj] = useState(false); + // Get device window width // If this is less than 769, the page will open with QR scanner open const { width } = useWindowDimensions(); @@ -99,7 +104,6 @@ const [formData, setFormData] = useState({ value: '', address: '', - opReturnMsg: '', }); const [queryStringText, setQueryStringText] = useState(null); const [sendBchAddressError, setSendBchAddressError] = useState(false); @@ -119,8 +123,8 @@ setFormData({ value: '', address: '', - opReturnMsg: '', }); + setOpReturnMsg(''); // OP_RETURN message has its own state field }; const showModal = () => { @@ -138,9 +142,6 @@ const { getBCH, getRestUrl, sendXec, calcFee, signPkMessage } = useBCH(); - // jestBCH is only ever specified for unit tests, otherwise app will use getBCH(); - const BCH = jestBCH ? jestBCH : getBCH(); - // If the balance has changed, unlock the UI // This is redundant, if backend has refreshed in 1.75s timeout below, UI will already be unlocked useEffect(() => { @@ -148,6 +149,12 @@ }, [balances.totalBalance]); useEffect(() => { + // jestBCH is only ever specified for unit tests, otherwise app will use getBCH(); + const BCH = jestBCH ? jestBCH : getBCH(); + + // set the BCH instance to state, for other functions to reference + setBchObj(BCH); + // Manually parse for txInfo object on page load when Send.js is loaded with a query string // if this was routed from Wallet screen's Reply to message link then prepopulate the address and value field @@ -228,8 +235,6 @@ ...formData, }); - let optionalOpReturnMsg = formData.opReturnMsg; - if (isOneToManyXECSend) { // this is a one to many XEC send transactions @@ -254,11 +259,11 @@ toLegacyCashArray(addressAndValueArray); const link = await sendXec( - BCH, + bchObj, wallet, slpBalancesAndUtxos.nonSlpUtxos, currency.defaultFee, - optionalOpReturnMsg, + opReturnMsg, true, // indicate send mode is one to many cleanAddressAndValueArray, ); @@ -299,9 +304,20 @@ bchValue = fiatToCrypto(value, fiatPrice); } + // encrypted message limit truncation + let optionalOpReturnMsg; + if (isEncryptedOptionalOpReturnMsg) { + optionalOpReturnMsg = opReturnMsg.substring( + 0, + currency.opReturn.encryptedMsgCharLimit, + ); + } else { + optionalOpReturnMsg = opReturnMsg; + } + try { const link = await sendXec( - BCH, + bchObj, wallet, slpBalancesAndUtxos.nonSlpUtxos, currency.defaultFee, @@ -310,6 +326,7 @@ null, // address array not applicable for one to many tx cleanAddress, bchValue, + isEncryptedOptionalOpReturnMsg, ); sendXecNotification(link); clearInputForms(); @@ -454,15 +471,6 @@ })); }; - const handleOpReturnMsgChange = e => { - const { value, name } = e.target; - - setFormData(p => ({ - ...p, - [name]: value, - })); - }; - // true: renders the multi recipient