From 64ded72e8e25c91a0ad9f3d5e454277100995b51 Mon Sep 17 00:00:00 2001 From: Phillip Date: Sat, 29 Apr 2023 16:19:18 +0300 Subject: [PATCH 01/11] added two events for encryption --- .../core/components/qdn-action-types.js | 8 +- .../plugins/core/qdn/browser/browser.src.js | 119 +++++++++++++++++- 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/qdn-action-types.js b/qortal-ui-plugins/plugins/core/components/qdn-action-types.js index 1eb816a6..48ea7214 100644 --- a/qortal-ui-plugins/plugins/core/components/qdn-action-types.js +++ b/qortal-ui-plugins/plugins/core/components/qdn-action-types.js @@ -35,4 +35,10 @@ export const GET_LIST_ITEMS = 'GET_LIST_ITEMS' export const ADD_LIST_ITEMS = 'ADD_LIST_ITEMS' // DELETE_LIST_ITEM -export const DELETE_LIST_ITEM = 'DELETE_LIST_ITEM' \ No newline at end of file +export const DELETE_LIST_ITEM = 'DELETE_LIST_ITEM' + +// ENCRYPT_DATA +export const ENCRYPT_DATA = 'ENCRYPT_DATA' + +// DECRYPT_DATA +export const DECRYPT_DATA = 'DECRYPT_DATA' \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js index 003b2a76..812109be 100644 --- a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js +++ b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js @@ -8,11 +8,11 @@ import { translateUnsafeHTML, registerTranslateConfig, } from 'lit-translate'; -import * as actions from '../../components/qdn-action-types'; registerTranslateConfig({ loader: (lang) => fetch(`/language/${lang}.json`).then((res) => res.json()), }); +import * as actions from '../../components/qdn-action-types'; import '@material/mwc-button'; import '@material/mwc-icon'; import '@material/mwc-checkbox' @@ -21,6 +21,8 @@ import WebWorkerChat from 'web-worker:./computePowWorker.src.js'; import { publishData } from '../../../utils/publish-image.js'; import { Loader } from '../../../utils/loader.js'; import { QORT_DECIMALS } from 'qortal-ui-crypto/api/constants'; +import nacl from '../../../../../qortal-ui-crypto/api/deps/nacl-fast.js' +import ed2curve from '../../../../../qortal-ui-crypto/api/deps/nacl-fast.js' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); class WebBrowser extends LitElement { @@ -558,6 +560,121 @@ class WebBrowser extends LitElement { break; } } + case actions.ENCRYPT_DATA: { + const requiredFields = ['Uint8ArrayData', 'destinationPublicKey']; + const missingFields = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = `Missing fields: ${missingFieldsString}` + let data = {}; + data['error'] = errorMsg; + response = JSON.stringify(data); + break + } + const { Uint8ArrayData, destinationPublicKey } = data + + if (!(Uint8ArrayData instanceof Uint8Array)) { + data['error'] = "The Uint8ArrayData you've submitted is invalid"; + response = JSON.stringify(data); + break + } + try { + const privateKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey + if (!privateKey) { + data['error'] = "Unable to retrieve keys" + response = JSON.stringify(data); + break + } + const convertedPrivateKey = ed2curve.convertSecretKey(privateKey) + const convertedPublicKey = ed2curve.convertPublicKey(destinationPublicKey) + const sharedSecret = new Uint8Array(32) + nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey) + + const chatEncryptionSeed = new Sha256().process(sharedSecret).finish().result + + const nonce = new Uint8Array(24); + window.crypto.getRandomValues(nonce); + const encryptedData = nacl.secretbox(Uint8ArrayData, nonce, chatEncryptionSeed) + const combinedData = new Uint8Array(nonce.length + encryptedData.length); + combinedData.set(nonce); + combinedData.set(encryptedData, nonce.length); + + + let data = {}; + data['encryptedData'] = combinedData + data['destinationPublicKey'] = destinationPublicKey + response = JSON.stringify(data); + break; + } catch (error) { + const data = {}; + const errorMsg = error.message || "Error in encrypting data" + data['error'] = errorMsg; + response = JSON.stringify(data); + break + } + } + case actions.DECRYPT_DATA: { + const requiredFields = ['encryptedData']; + const missingFields = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = `Missing fields: ${missingFieldsString}` + let data = {}; + data['error'] = errorMsg; + response = JSON.stringify(data); + break + } + const { encryptedData } = data + + + try { + const combinedData = encryptedData + const nonce = combinedData.slice(0, 24); + const _encryptedData = combinedData.slice(24); + + const privateKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey + const publicKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.publicKey + + if (!privateKey || !publicKey) { + data['error'] = "Unable to retrieve keys" + response = JSON.stringify(data); + break + } + + const convertedPrivateKey = ed2curve.convertSecretKey(privateKey) + const convertedPublicKey = ed2curve.convertPublicKey(publicKey) + const sharedSecret = new Uint8Array(32); + nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey) + + const _chatEncryptionSeed = new Sha256().process(sharedSecret).finish().result + const _decryptedData = nacl.secretbox.open(_encryptedData, nonce, _chatEncryptionSeed) + + let data = {}; + data['decryptedData'] = _decryptedData + response = JSON.stringify(data); + break; + } catch (error) { + const data = {}; + const errorMsg = error.message || "Error in decrypting data" + data['error'] = errorMsg; + response = JSON.stringify(data); + break + } + } case actions.GET_LIST_ITEMS: { const requiredFields = ['list_name']; const missingFields = []; From 92ab5de2822158a3b22222082146932af52b84f4 Mon Sep 17 00:00:00 2001 From: Phillip Date: Sat, 29 Apr 2023 20:38:10 +0300 Subject: [PATCH 02/11] add missing import --- qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js index 812109be..7111f819 100644 --- a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js +++ b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js @@ -22,7 +22,7 @@ import { publishData } from '../../../utils/publish-image.js'; import { Loader } from '../../../utils/loader.js'; import { QORT_DECIMALS } from 'qortal-ui-crypto/api/constants'; import nacl from '../../../../../qortal-ui-crypto/api/deps/nacl-fast.js' -import ed2curve from '../../../../../qortal-ui-crypto/api/deps/nacl-fast.js' +import ed2curve from '../../../../../qortal-ui-crypto/api/deps/ed2curve.js' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); class WebBrowser extends LitElement { @@ -597,7 +597,7 @@ class WebBrowser extends LitElement { const sharedSecret = new Uint8Array(32) nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey) - const chatEncryptionSeed = new Sha256().process(sharedSecret).finish().result + const chatEncryptionSeed = new window.parent.Sha256().process(sharedSecret).finish().result const nonce = new Uint8Array(24); window.crypto.getRandomValues(nonce); @@ -660,7 +660,7 @@ class WebBrowser extends LitElement { const sharedSecret = new Uint8Array(32); nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey) - const _chatEncryptionSeed = new Sha256().process(sharedSecret).finish().result + const _chatEncryptionSeed = new window.parent.Sha256().process(sharedSecret).finish().result const _decryptedData = nacl.secretbox.open(_encryptedData, nonce, _chatEncryptionSeed) let data = {}; From cf122612f0937f13a202a76cb5663bcc2f24655c Mon Sep 17 00:00:00 2001 From: Phillip Date: Sun, 30 Apr 2023 02:03:37 +0300 Subject: [PATCH 03/11] convert back to unit8array --- .../plugins/core/qdn/browser/browser.src.js | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js index 7111f819..903e4495 100644 --- a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js +++ b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js @@ -580,7 +580,8 @@ class WebBrowser extends LitElement { } const { Uint8ArrayData, destinationPublicKey } = data - if (!(Uint8ArrayData instanceof Uint8Array)) { + const uint8Array = new Uint8Array(Object.values(Uint8ArrayData)); + if (!(uint8Array instanceof Uint8Array)) { data['error'] = "The Uint8ArrayData you've submitted is invalid"; response = JSON.stringify(data); break @@ -592,8 +593,10 @@ class WebBrowser extends LitElement { response = JSON.stringify(data); break } + const publicKeyUnit8Array = window.parent.Base58.decode(destinationPublicKey) + const convertedPrivateKey = ed2curve.convertSecretKey(privateKey) - const convertedPublicKey = ed2curve.convertPublicKey(destinationPublicKey) + const convertedPublicKey = ed2curve.convertPublicKey(publicKeyUnit8Array) const sharedSecret = new Uint8Array(32) nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey) @@ -601,7 +604,7 @@ class WebBrowser extends LitElement { const nonce = new Uint8Array(24); window.crypto.getRandomValues(nonce); - const encryptedData = nacl.secretbox(Uint8ArrayData, nonce, chatEncryptionSeed) + const encryptedData = nacl.secretbox(uint8Array, nonce, chatEncryptionSeed) const combinedData = new Uint8Array(nonce.length + encryptedData.length); combinedData.set(nonce); combinedData.set(encryptedData, nonce.length); @@ -621,7 +624,7 @@ class WebBrowser extends LitElement { } } case actions.DECRYPT_DATA: { - const requiredFields = ['encryptedData']; + const requiredFields = ['encryptedData', 'senderPublicKey']; const missingFields = []; requiredFields.forEach((field) => { @@ -638,16 +641,17 @@ class WebBrowser extends LitElement { response = JSON.stringify(data); break } - const { encryptedData } = data - + const { encryptedData, senderPublicKey } = data + const uint8Array = new Uint8Array(Object.values(encryptedData)); try { - const combinedData = encryptedData + const combinedData = uint8Array const nonce = combinedData.slice(0, 24); const _encryptedData = combinedData.slice(24); const privateKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey - const publicKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.publicKey + const publicKey = window.parent.Base58.decode(senderPublicKey) + // const publicKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.publicKey if (!privateKey || !publicKey) { data['error'] = "Unable to retrieve keys" @@ -662,7 +666,6 @@ class WebBrowser extends LitElement { const _chatEncryptionSeed = new window.parent.Sha256().process(sharedSecret).finish().result const _decryptedData = nacl.secretbox.open(_encryptedData, nonce, _chatEncryptionSeed) - let data = {}; data['decryptedData'] = _decryptedData response = JSON.stringify(data); From 6333e4f3a0367210439b082b9d81ac4b71077e12 Mon Sep 17 00:00:00 2001 From: Phillip Date: Sun, 30 Apr 2023 15:25:35 +0300 Subject: [PATCH 04/11] added qortalEncryptedData string --- .../plugins/core/qdn/browser/browser.src.js | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js index 903e4495..ee5186be 100644 --- a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js +++ b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js @@ -605,9 +605,17 @@ class WebBrowser extends LitElement { const nonce = new Uint8Array(24); window.crypto.getRandomValues(nonce); const encryptedData = nacl.secretbox(uint8Array, nonce, chatEncryptionSeed) - const combinedData = new Uint8Array(nonce.length + encryptedData.length); - combinedData.set(nonce); - combinedData.set(encryptedData, nonce.length); + + const str = "qortalEncryptedData"; + const strEncoder = new TextEncoder(); + const strUint8Array = strEncoder.encode(str); + + const combinedData = new Uint8Array(strUint8Array.length + nonce.length + encryptedData.length); + + combinedData.set(strUint8Array); + + combinedData.set(nonce, strUint8Array.length); + combinedData.set(encryptedData, strUint8Array.length + nonce.length); let data = {}; @@ -642,16 +650,21 @@ class WebBrowser extends LitElement { break } const { encryptedData, senderPublicKey } = data - const uint8Array = new Uint8Array(Object.values(encryptedData)); + try { + const uint8Array = new Uint8Array(Object.values(encryptedData)); const combinedData = uint8Array - const nonce = combinedData.slice(0, 24); - const _encryptedData = combinedData.slice(24); + const str = "qortalEncryptedData"; + const strEncoder = new TextEncoder(); + const strUint8Array = strEncoder.encode(str); + + const strData = combinedData.slice(0, strUint8Array.length); + const nonce = combinedData.slice(strUint8Array.length, strUint8Array.length + 24); + const _encryptedData = combinedData.slice(strUint8Array.length + 24); const privateKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey const publicKey = window.parent.Base58.decode(senderPublicKey) - // const publicKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.publicKey if (!privateKey || !publicKey) { data['error'] = "Unable to retrieve keys" From a1240569b8efbdc623ce7ea83d5521c54ae08880 Mon Sep 17 00:00:00 2001 From: Phillip Date: Thu, 4 May 2023 01:58:30 +0300 Subject: [PATCH 05/11] added download action type --- .../core/components/qdn-action-constants.js | 56 +++++++ .../plugins/core/qdn/browser/browser.src.js | 155 +++++++++++++----- 2 files changed, 172 insertions(+), 39 deletions(-) create mode 100644 qortal-ui-plugins/plugins/core/components/qdn-action-constants.js diff --git a/qortal-ui-plugins/plugins/core/components/qdn-action-constants.js b/qortal-ui-plugins/plugins/core/components/qdn-action-constants.js new file mode 100644 index 00000000..c03dba03 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/qdn-action-constants.js @@ -0,0 +1,56 @@ +export const mimeToExtensionMap = { + // Documents + "application/pdf": ".pdf", + "application/msword": ".doc", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx", + "application/vnd.ms-excel": ".xls", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx", + "application/vnd.ms-powerpoint": ".ppt", + "application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx", + "application/vnd.oasis.opendocument.text": ".odt", + "application/vnd.oasis.opendocument.spreadsheet": ".ods", + "application/vnd.oasis.opendocument.presentation": ".odp", + "text/plain": ".txt", + "text/csv": ".csv", + "text/html": ".html", + "application/xhtml+xml": ".xhtml", + "application/xml": ".xml", + "application/json": ".json", + + // Images + "image/jpeg": ".jpg", + "image/png": ".png", + "image/gif": ".gif", + "image/webp": ".webp", + "image/svg+xml": ".svg", + "image/tiff": ".tif", + "image/bmp": ".bmp", + + // Audio + "audio/mpeg": ".mp3", + "audio/ogg": ".ogg", + "audio/wav": ".wav", + "audio/webm": ".weba", + "audio/aac": ".aac", + + // Video + "video/mp4": ".mp4", + "video/webm": ".webm", + "video/ogg": ".ogv", + "video/x-msvideo": ".avi", + "video/quicktime": ".mov", + "video/x-ms-wmv": ".wmv", + "video/mpeg": ".mpeg", + "video/3gpp": ".3gp", + "video/3gpp2": ".3g2", + "video/x-matroska": ".mkv", + "video/x-flv": ".flv", + + // Archives + "application/zip": ".zip", + "application/x-rar-compressed": ".rar", + "application/x-tar": ".tar", + "application/x-7z-compressed": ".7z", + "application/x-gzip": ".gz", + "application/x-bzip2": ".bz2", +}; \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js index ee5186be..1782f60a 100644 --- a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js +++ b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js @@ -23,6 +23,7 @@ import { Loader } from '../../../utils/loader.js'; import { QORT_DECIMALS } from 'qortal-ui-crypto/api/constants'; import nacl from '../../../../../qortal-ui-crypto/api/deps/nacl-fast.js' import ed2curve from '../../../../../qortal-ui-crypto/api/deps/ed2curve.js' +import { mimeToExtensionMap } from '../../components/qdn-action-constants'; const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); class WebBrowser extends LitElement { @@ -150,8 +151,7 @@ class WebBrowser extends LitElement { if ( this.identifier && this.identifier != 'null' && this.identifier != 'default' - ) - { + ) { displayUrl = displayUrl.concat('/' + this.identifier); } if (this.path != null && this.path != '/') @@ -202,7 +202,7 @@ class WebBrowser extends LitElement { } } - this.selectedAddress = {} + this.selectedAddress = {} this.btcFeePerByte = 100 this.ltcFeePerByte = 30 this.dogeFeePerByte = 1000 @@ -270,7 +270,7 @@ class WebBrowser extends LitElement { `; } - renderFullScreen() { + renderFullScreen() { if (window.innerHeight == screen.height) { return html` ` } - } + } goFullScreen() { var elem = this.shadowRoot.getElementById('websitesWrapper') @@ -311,7 +311,7 @@ class WebBrowser extends LitElement { } exitFullScreen() { - if(document.exitFullscreen) { + if (document.exitFullscreen) { document.exitFullscreen() } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen() @@ -537,7 +537,7 @@ class WebBrowser extends LitElement { } let res1; if (!skip) { - res1 = await showModalAndWait( + res1 = await showModalAndWait( actions.GET_USER_ACCOUNT, { service: this.service, @@ -966,15 +966,15 @@ class WebBrowser extends LitElement { isBase64: true, filename: filename, title, - description, - category, - tag1, - tag2, - tag3, - tag4, - tag5, + description, + category, + tag1, + tag2, + tag3, + tag4, + tag5, apiVersion: 2, - withFee: res2.userData.isWithFee === true ? true: false + withFee: res2.userData.isWithFee === true ? true : false }); response = JSON.stringify(resPublish); @@ -1142,14 +1142,14 @@ class WebBrowser extends LitElement { const groupId = data.groupId; const isRecipient = groupId ? false : true const sendMessage = async (messageText, chatReference) => { - + let _reference = new Uint8Array(64); window.crypto.getRandomValues(_reference); let reference = window.parent.Base58.encode(_reference); const sendMessageRequest = async () => { - let chatResponse - - if(isRecipient){ + let chatResponse + + if (isRecipient) { chatResponse = await parentEpml.request('chat', { type: 18, nonce: this.selectedAddress.nonce, @@ -1170,13 +1170,13 @@ class WebBrowser extends LitElement { } - if(!isRecipient){ - chatResponse = await parentEpml.request('chat', { + if (!isRecipient) { + chatResponse = await parentEpml.request('chat', { type: 181, nonce: this.selectedAddress.nonce, params: { timestamp: Date.now(), - groupID: Number(groupId), + groupID: Number(groupId), hasReceipient: 0, hasChatReference: 0, chatReference: chatReference, @@ -1190,7 +1190,7 @@ class WebBrowser extends LitElement { } - + const msgResponse = await _computePow(chatResponse) return msgResponse; }; @@ -1242,12 +1242,12 @@ class WebBrowser extends LitElement { if (result.action === "accept") { let hasPublicKey = true; - if(isRecipient){ + if (isRecipient) { const res = await parentEpml.request('apiCall', { type: 'api', url: `/addresses/publickey/${recipient}` }); - + if (res.error === 102) { this._publicKey.key = '' this._publicKey.hasPubKey = false @@ -1261,14 +1261,14 @@ class WebBrowser extends LitElement { hasPublicKey = false; } } - + if (!hasPublicKey && isRecipient) { response = '{"error": "Cannot send an encrypted message to this user since they do not have their publickey on chain."}'; break } - + const tiptapJson = { type: 'doc', @@ -1307,16 +1307,16 @@ class WebBrowser extends LitElement { response = msgResponse; } catch (error) { console.error(error); - if(error.message){ + if (error.message) { let data = {}; data['error'] = error.message; - response = JSON.stringify(data); - break + response = JSON.stringify(data); + break } response = '{"error": "Request could not be fulfilled"}'; } finally { this.loader.hide(); - + } } else { @@ -1392,6 +1392,83 @@ class WebBrowser extends LitElement { // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}` break; } + case 'DOWNLOAD': { + try { + const requiredFields = ['filename', 'blob']; + const missingFields = []; + + requiredFields.forEach((field) => { + if (!data[field]) { + missingFields.push(field); + } + }); + + if (missingFields.length > 0) { + const missingFieldsString = missingFields.join(', '); + const errorMsg = `Missing fields: ${missingFieldsString}` + let data = {}; + data['error'] = errorMsg; + response = JSON.stringify(data); + break + } + + const filename = data.filename + const blob = data.blob + + const mimeType = blob.type || data.mimeType + let backupExention = filename.split('.').pop() + if (backupExention) { + backupExention = '.' + backupExention + } + const fileExtension = mimeToExtensionMap[mimeType] || backupExention + let fileHandleOptions = {} + if (!mimeType) { + const obj = {}; + const errorMsg = 'A mimeType could not be derived'; + obj['error'] = errorMsg; + response = JSON.stringify(obj); + break + } + if (!fileExtension) { + const obj = {}; + const errorMsg = 'A file extension could not be derived'; + obj['error'] = errorMsg; + response = JSON.stringify(obj); + break + } + if (fileExtension && mimeType) { + fileHandleOptions = { + accept: { + [mimeType]: [fileExtension] + } + } + } + const fileHandle = await self.showSaveFilePicker({ + suggestedName: filename, + types: [ + { + description: mimeType, + ...fileHandleOptions + }, + ] + + + }) + const writeFile = async (fileHandle, contents) => { + const writable = await fileHandle.createWritable() + await writable.write(contents) + await writable.close() + } + writeFile(fileHandle, blob).then(() => console.log("FILE SAVED")) + response = JSON.stringify(true); + } catch (error) { + const obj = {}; + const errorMsg = error.message || 'Failed to initiate download'; + obj['error'] = errorMsg; + response = JSON.stringify(obj); + } + break; + } // case 'DEPLOY_AT': { // const requiredFields = ['name', 'description', 'tags', 'creationBytes', 'amount', 'assetId', 'type']; @@ -1466,19 +1543,19 @@ class WebBrowser extends LitElement { url: `/addresses/balance/${qortAddress}?apiKey=${this.getApiKey()}`, }) response = QORTBalance - - + + } catch (error) { console.error(error); const data = {}; const errorMsg = error.message || get("browserpage.bchange21"); data['error'] = errorMsg; response = JSON.stringify(data); - + } finally { this.loader.hide(); } - } + } // else { // let _url = `` // let _body = null @@ -1540,7 +1617,7 @@ class WebBrowser extends LitElement { break; } - + case actions.SEND_COIN: { const requiredFields = ['coin', 'destinationAddress', 'amount'] @@ -1599,7 +1676,7 @@ class WebBrowser extends LitElement { const balance = (Number(transformDecimals) / 1e8).toFixed(8) const fee = await this.sendQortFee() - if (amountDecimals + (fee * QORT_DECIMALS) > walletBalanceDecimals) { + if (amountDecimals + (fee * QORT_DECIMALS) > walletBalanceDecimals) { let errorMsg = "Insufficient Funds!" let failedMsg = get("walletpage.wchange26") let pleaseMsg = get("walletpage.wchange44") @@ -1754,7 +1831,7 @@ class WebBrowser extends LitElement { this.loader.hide() throw new Error('Error: could not send coin') } - + } try { @@ -2905,7 +2982,7 @@ async function showModalAndWait(type, data) { if (checkbox) { checkbox.addEventListener('click', (e) => { if (e.target.checked) { - window.parent.reduxStore.dispatch( window.parent.reduxAction.removeQAPPAutoAuth(false)) + window.parent.reduxStore.dispatch(window.parent.reduxAction.removeQAPPAutoAuth(false)) return } window.parent.reduxStore.dispatch(window.parent.reduxAction.allowQAPPAutoAuth(true)) From 82bfcc3b282fb1a4b570f223dba2412ba0f830c1 Mon Sep 17 00:00:00 2001 From: Phillip Date: Thu, 4 May 2023 13:45:22 +0300 Subject: [PATCH 06/11] added encryption to the publish action --- qortal-ui-core/language/us.json | 3 +- .../core/components/qdn-action-encryption.js | 84 +++++++++++++++++++ .../plugins/core/qdn/browser/browser.src.js | 36 +++++++- 3 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 qortal-ui-plugins/plugins/core/components/qdn-action-encryption.js diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index e9fae851..b998e557 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -644,7 +644,8 @@ "bchange41": "Do you give this application permission to access this list?", "bchange42": "Items", "bchange43": "Do you give this application permission to add to this list?", - "bchange44": "Do you give this application permission to delete from this list?" + "bchange44": "Do you give this application permission to delete from this list?", + "bchange45": "Encrypt" }, "datapage": { "dchange1": "Data Management", diff --git a/qortal-ui-plugins/plugins/core/components/qdn-action-encryption.js b/qortal-ui-plugins/plugins/core/components/qdn-action-encryption.js new file mode 100644 index 00000000..98d69a09 --- /dev/null +++ b/qortal-ui-plugins/plugins/core/components/qdn-action-encryption.js @@ -0,0 +1,84 @@ +import nacl from '../../../../qortal-ui-crypto/api/deps/nacl-fast.js' +import ed2curve from '../../../../qortal-ui-crypto/api/deps/ed2curve.js' + + + +export function uint8ArrayToBase64(uint8Array) { + const length = uint8Array.length; + let base64String = ''; + const chunkSize = 1024 * 1024; // Process 1MB at a time + + for (let i = 0; i < length; i += chunkSize) { + const chunkEnd = Math.min(i + chunkSize, length); + const chunk = uint8Array.subarray(i, chunkEnd); + const binaryString = chunk.reduce((acc, byte) => acc + String.fromCharCode(byte), ''); + base64String += btoa(binaryString); + } + + return base64String; +} + +export function base64ToUint8Array(base64) { + const binaryString = atob(base64) + const len = binaryString.length + const bytes = new Uint8Array(len) + + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i) + } + + return bytes +} + + +export const encryptData = ({ data64, recipientPublicKey }) => { + + + const Uint8ArrayData = base64ToUint8Array(data64) + const uint8Array = Uint8ArrayData + + if (!(uint8Array instanceof Uint8Array)) { + + throw new Error("The Uint8ArrayData you've submitted is invalid") + } + try { + const privateKey = window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey + if (!privateKey) { + + throw new Error("Unable to retrieve keys") + } + const publicKeyUnit8Array = window.parent.Base58.decode(recipientPublicKey) + + const convertedPrivateKey = ed2curve.convertSecretKey(privateKey) + const convertedPublicKey = ed2curve.convertPublicKey(publicKeyUnit8Array) + const sharedSecret = new Uint8Array(32) + nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey) + + const chatEncryptionSeed = new window.parent.Sha256().process(sharedSecret).finish().result + + const nonce = new Uint8Array(24); + window.crypto.getRandomValues(nonce); + const encryptedData = nacl.secretbox(uint8Array, nonce, chatEncryptionSeed) + + const str = "qortalEncryptedData"; + const strEncoder = new TextEncoder(); + const strUint8Array = strEncoder.encode(str); + + const combinedData = new Uint8Array(strUint8Array.length + nonce.length + encryptedData.length); + + combinedData.set(strUint8Array); + + combinedData.set(nonce, strUint8Array.length); + combinedData.set(encryptedData, strUint8Array.length + nonce.length); + + const uint8arrayToData64 = uint8ArrayToBase64(combinedData) + + return { + encryptedData: uint8arrayToData64, + recipientPublicKey + } + } catch (error) { + console.log({ error }) + throw new Error("Error in encrypting data") + } +} \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js index 1782f60a..5b8ee326 100644 --- a/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js +++ b/qortal-ui-plugins/plugins/core/qdn/browser/browser.src.js @@ -24,6 +24,7 @@ import { QORT_DECIMALS } from 'qortal-ui-crypto/api/constants'; import nacl from '../../../../../qortal-ui-crypto/api/deps/nacl-fast.js' import ed2curve from '../../../../../qortal-ui-crypto/api/deps/ed2curve.js' import { mimeToExtensionMap } from '../../components/qdn-action-constants'; +import { encryptData } from '../../components/qdn-action-encryption'; const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); class WebBrowser extends LitElement { @@ -908,6 +909,7 @@ class WebBrowser extends LitElement { return; case actions.PUBLISH_QDN_RESOURCE: { + // optional fields: encrypt:boolean recipientPublicKey:string const requiredFields = ['service', 'name', 'data64']; const missingFields = []; @@ -929,7 +931,7 @@ class WebBrowser extends LitElement { const service = data.service; const name = data.name; let identifier = data.identifier; - const data64 = data.data64; + let data64 = data.data64; const filename = data.filename; const title = data.title; const description = data.description; @@ -942,12 +944,39 @@ class WebBrowser extends LitElement { if (data.identifier == null) { identifier = 'default'; } + + if (data.encrypt && !data.recipientPublicKey) { + let data = {}; + data['error'] = "Encrypting data requires the recipient's public key"; + response = JSON.stringify(data); + break + } + + if (data.encrypt) { + try { + const encryptDataResponse = encryptData({ + data64, recipientPublicKey: data.recipientPublicKey + }) + if (encryptDataResponse.encryptedData) { + data64 = encryptDataResponse.encryptedData + } + + } catch (error) { + const obj = {}; + const errorMsg = error.message || 'Upload failed due to failed encryption'; + obj['error'] = errorMsg; + response = JSON.stringify(obj); + break + } + + } const res2 = await showModalAndWait( actions.PUBLISH_QDN_RESOURCE, { name, identifier, - service + service, + encrypt: data.encrypt } ); if (res2.action === 'accept') { @@ -1034,6 +1063,7 @@ class WebBrowser extends LitElement { actions.PUBLISH_MULTIPLE_QDN_RESOURCES, { resources, + encrypt: data.encrypt } ); @@ -2852,6 +2882,7 @@ async function showModalAndWait(type, data) { ${type === actions.PUBLISH_MULTIPLE_QDN_RESOURCES ? `