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 @@ -36,7 +36,9 @@ appPrefixesHex: { eToken: '534c5000', cashtab: '00746162', + cashtabEncrypted: '65746162', }, + encryptedMsgCharLimit: 94, }, settingsValidation: { fiatCurrency: [ @@ -142,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,10 @@ const [msgToSign, setMsgToSign] = useState(''); const [signMessageIsValid, setSignMessageIsValid] = useState(null); const [isOneToManyXECSend, setIsOneToManyXECSend] = useState(false); + const [opReturnMsg, setOpReturnMsg] = useState(false); + const [isEncryptedMsg, setIsEncryptedMsg] = 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(); @@ -140,9 +144,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(() => { @@ -150,6 +151,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 @@ -231,8 +238,6 @@ dirty: false, }); - let optionalOpReturnMsg = formData.opReturnMsg; - if (isOneToManyXECSend) { // this is a one to many XEC send transactions @@ -257,11 +262,11 @@ toLegacyArray(addressAndValueArray); const link = await sendXec( - BCH, + bchObj, wallet, slpBalancesAndUtxos.nonSlpUtxos, currency.defaultFee, - optionalOpReturnMsg, + opReturnMsg, true, // indicate send mode is one to many cleanAddressAndValueArray, ); @@ -324,17 +329,29 @@ bchValue = fiatToCrypto(value, fiatPrice); } + // encrypted message limit truncation + let encryptedOpReturnMsg; + if (isEncryptedMsg) { + encryptedOpReturnMsg = opReturnMsg.substring( + 0, + currency.opReturn.encryptedMsgCharLimit, + ); + } else { + encryptedOpReturnMsg = opReturnMsg; + } + try { const link = await sendXec( - BCH, + bchObj, wallet, slpBalancesAndUtxos.nonSlpUtxos, currency.defaultFee, - optionalOpReturnMsg, + encryptedOpReturnMsg, false, // sendToMany boolean flag null, // address array not applicable for one to many tx cleanAddress, bchValue, + isEncryptedMsg, ); sendXecNotification(link); clearInputForms(); @@ -350,7 +367,7 @@ let addressString = value; // parse address - const addressInfo = parseAddress(BCH, addressString); + const addressInfo = parseAddress(bchObj, addressString); /* Model @@ -428,7 +445,7 @@ let addressString = addressStringArray[i].split(',')[0]; let valueString = addressStringArray[i].split(',')[1]; - let addressInfo = parseAddress(BCH, addressString); + let addressInfo = parseAddress(bchObj, addressString); let validation = isValidSendToMany( addressInfo, @@ -468,15 +485,6 @@ })); }; - const handleOpReturnMsgChange = e => { - const { value, name } = e.target; - - setFormData(p => ({ - ...p, - [name]: value, - })); - }; - // true: renders the multi recipient