diff --git a/crypto/api/deps/Base64Message.js b/crypto/api/deps/Base64Message.js index d26c352b..09d4a509 100644 --- a/crypto/api/deps/Base64Message.js +++ b/crypto/api/deps/Base64Message.js @@ -33,6 +33,11 @@ Base64Message.decode = function (string, keys, ref) { let hubString = '' const res = decryptSingle(string, keys, false) + + if (res === 'noKey' || res === 'decryptionFailed') { + return '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"This message could not be decrypted"}]}]},"images":[""],"repliedTo":"","version":3}' + } + const decryptToUnit8Array = base64ToUint8Array(res) const responseData = uint8ArrayToObject(decryptToUnit8Array) diff --git a/plugins/plugins/core/components/GroupEncryption.js b/plugins/plugins/core/components/GroupEncryption.js index d18cfe3b..9f09d5ba 100644 --- a/plugins/plugins/core/components/GroupEncryption.js +++ b/plugins/plugins/core/components/GroupEncryption.js @@ -17,7 +17,8 @@ export function base64ToUint8Array(base64) { export function uint8ArrayToBase64(uint8Array) { const length = uint8Array.length let binaryString = '' - const chunkSize = 1024 * 1024 // Process 1MB at a time + // Process 1MB at a time + const chunkSize = 1024 * 1024 for (let i = 0; i < length; i += chunkSize) { const chunkEnd = Math.min(i + chunkSize, length) @@ -104,7 +105,8 @@ export function validateSecretKey(obj) { // Function to create a symmetric key and nonce export const createSymmetricKeyAndNonce = () => { - const messageKey = new Uint8Array(32) // 32 bytes for the symmetric key + // 32 bytes for the symmetric key + const messageKey = new Uint8Array(32) crypto.getRandomValues(messageKey) return { messageKey: uint8ArrayToBase64(messageKey)} @@ -233,11 +235,13 @@ export const encryptSingle = async (data64, secretKeyObject, typeNumber = 2) => encryptedDataBase64 = uint8ArrayToBase64(encryptedData) // Concatenate the highest key, type number, and encrypted data (old format) - const highestKeyStr = highestKey.toString().padStart(10, '0') // Fixed length of 10 digits + // Fixed length of 10 digits + const highestKeyStr = highestKey.toString().padStart(10, '0') finalEncryptedData = btoa(highestKeyStr + encryptedDataBase64) } else { // New format: Generate a random nonce and embed it in the message - nonce = new Uint8Array(24) // 24 bytes for the nonce + // 24 bytes for the nonce + nonce = new Uint8Array(24) crypto.getRandomValues(nonce) // Encrypt the data with the new nonce and message key @@ -248,7 +252,8 @@ export const encryptSingle = async (data64, secretKeyObject, typeNumber = 2) => const nonceBase64 = uint8ArrayToBase64(nonce) // Concatenate the highest key, type number, nonce, and encrypted data (new format) - const highestKeyStr = highestKey.toString().padStart(10, '0') // Fixed length of 10 digits + // Fixed length of 10 digits + const highestKeyStr = highestKey.toString().padStart(10, '0') const highestKeyBytes = new TextEncoder().encode(highestKeyStr.padStart(10, '0')) const typeNumberBytes = new TextEncoder().encode(typeNumberStr.padStart(3, '0')) @@ -306,7 +311,7 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { // Check if we have a valid secret key for the extracted highestKey if (!secretKeyObject[highestKey]) { - throw new Error('Cannot find correct secretKey') + return 'noKey' } const secretKeyEntry = secretKeyObject[highestKey] @@ -315,18 +320,24 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { // Determine if typeNumber exists by checking if the next 3 characters after keyStr are digits const possibleTypeNumberStr = decodeForNumber.slice(10, 13) - const hasTypeNumber = /^\d{3}$/.test(possibleTypeNumberStr) // Check if next 3 characters are digits + + // Check if next 3 characters are digits + const hasTypeNumber = /^\d{3}$/.test(possibleTypeNumberStr) if (secretKeyEntry.nonce) { // Old format: nonce is present in the secretKeyObject, so no type number exists nonceBase64 = secretKeyEntry.nonce - encryptedDataBase64 = decodeForNumber.slice(10) // The remaining part is the encrypted data + + // The remaining part is the encrypted data + encryptedDataBase64 = decodeForNumber.slice(10) } else { if (hasTypeNumber) { - // const typeNumberStr = new TextDecoder().decode(typeNumberBytes) if(decodeForNumber.slice(10, 13) !== '001'){ const decodedBinary = base64ToUint8Array(decodedData) - const highestKeyBytes = decodedBinary.slice(0, 10) // if ASCII digits only + + // if ASCII digits only + const highestKeyBytes = decodedBinary.slice(0, 10) + const highestKeyStr = new TextDecoder().decode(highestKeyBytes) const nonce = decodedBinary.slice(13, 13 + 24) const encryptedData = decodedBinary.slice(13 + 24) @@ -336,7 +347,7 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { // Check if decryption was successful if (!decryptedBytes) { - throw new Error("Decryption failed") + return 'decryptionFailed' } // Convert the decrypted Uint8Array back to a Base64 string @@ -344,13 +355,21 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { } // New format: Extract type number and nonce - typeNumberStr = possibleTypeNumberStr // Extract type number - nonceBase64 = decodeForNumber.slice(13, 45) // Extract nonce (next 32 characters after type number) - encryptedDataBase64 = decodeForNumber.slice(45) // The remaining part is the encrypted data + // Extract type number + typeNumberStr = possibleTypeNumberStr + + // Extract nonce (next 32 characters after type number) + nonceBase64 = decodeForNumber.slice(13, 45) + + // The remaining part is the encrypted data + encryptedDataBase64 = decodeForNumber.slice(45) } else { // Old format without type number (nonce is embedded in the message, first 32 characters after keyStr) - nonceBase64 = decodeForNumber.slice(10, 42) // First 32 characters for the nonce - encryptedDataBase64 = decodeForNumber.slice(42) // The remaining part is the encrypted data + // First 32 characters for the nonce + nonceBase64 = decodeForNumber.slice(10, 42) + + // The remaining part is the encrypted data + encryptedDataBase64 = decodeForNumber.slice(42) } } @@ -360,7 +379,7 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { const messageKey = base64ToUint8Array(secretKeyEntry.messageKey) if (!(Uint8ArrayData instanceof Uint8Array)) { - throw new Error("The Uint8ArrayData you've submitted is invalid") + return 'decryptionFailed' } // Decrypt the data using the nonce and messageKey @@ -368,7 +387,7 @@ export function decryptSingle(data64, secretKeyObject, skipDecodeBase64) { // Check if decryption was successful if (!decryptedData) { - throw new Error("Decryption failed") + return 'decryptionFailed' } // Convert the decrypted Uint8Array back to a Base64 string @@ -383,25 +402,33 @@ export const decryptGroupEncryptionWithSharingKey = async (data64EncryptedData, // Extract the nonce const nonceStartPosition = strUint8Array.length - const nonceEndPosition = nonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const nonceEndPosition = nonceStartPosition + 24 const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition) // Extract the shared keyNonce const keyNonceStartPosition = nonceEndPosition - const keyNonceEndPosition = keyNonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const keyNonceEndPosition = keyNonceStartPosition + 24 const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition) // Extract the sender's public key const senderPublicKeyStartPosition = keyNonceEndPosition - const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Public keys are 32 bytes + + // Public keys are 32 bytes + const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Calculate count first - const countStartPosition = allCombined.length - 4 // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + const countStartPosition = allCombined.length - 4 const countArray = allCombined.slice(countStartPosition, countStartPosition + 4) const count = new Uint32Array(countArray.buffer)[0] // Then use count to calculate encryptedData - const encryptedDataStartPosition = senderPublicKeyEndPosition // start position of encryptedData + // start position of encryptedData + const encryptedDataStartPosition = senderPublicKeyEndPosition const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4) const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition) const symmetricKey = base64ToUint8Array(key) @@ -426,26 +453,34 @@ export function decryptGroupDataQortalRequest(data64EncryptedData, privateKey) { // Extract the nonce const nonceStartPosition = strUint8Array.length - const nonceEndPosition = nonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const nonceEndPosition = nonceStartPosition + 24 const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition) // Extract the shared keyNonce const keyNonceStartPosition = nonceEndPosition - const keyNonceEndPosition = keyNonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const keyNonceEndPosition = keyNonceStartPosition + 24 const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition) // Extract the sender's public key const senderPublicKeyStartPosition = keyNonceEndPosition - const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Public keys are 32 bytes + + // Public keys are 32 bytes + const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 const senderPublicKey = allCombined.slice(senderPublicKeyStartPosition, senderPublicKeyEndPosition) // Calculate count first - const countStartPosition = allCombined.length - 4 // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + const countStartPosition = allCombined.length - 4 const countArray = allCombined.slice(countStartPosition, countStartPosition + 4) const count = new Uint32Array(countArray.buffer)[0] // Then use count to calculate encryptedData - const encryptedDataStartPosition = senderPublicKeyEndPosition // start position of encryptedData + // start position of encryptedData + const encryptedDataStartPosition = senderPublicKeyEndPosition const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4) const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition) @@ -474,6 +509,7 @@ export function decryptGroupDataQortalRequest(data64EncryptedData, privateKey) { if (decryptedKey) { // Decrypt the data using the symmetric key. const decryptedData = nacl.secretbox.open(encryptedData, nonce, decryptedKey) + // If decryption was successful, decryptedData will not be null. if (decryptedData) { return decryptedData @@ -493,26 +529,34 @@ export function decryptGroupData(data64EncryptedData, privateKey) { // Extract the nonce const nonceStartPosition = strUint8Array.length - const nonceEndPosition = nonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const nonceEndPosition = nonceStartPosition + 24 const nonce = allCombined.slice(nonceStartPosition, nonceEndPosition) // Extract the shared keyNonce const keyNonceStartPosition = nonceEndPosition - const keyNonceEndPosition = keyNonceStartPosition + 24 // Nonce is 24 bytes + + // Nonce is 24 bytes + const keyNonceEndPosition = keyNonceStartPosition + 24 const keyNonce = allCombined.slice(keyNonceStartPosition, keyNonceEndPosition) // Extract the sender's public key const senderPublicKeyStartPosition = keyNonceEndPosition - const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 // Public keys are 32 bytes + + // Public keys are 32 bytes + const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32 const senderPublicKey = allCombined.slice(senderPublicKeyStartPosition, senderPublicKeyEndPosition) // Calculate count first - const countStartPosition = allCombined.length - 4 // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + // 4 bytes before the end, since count is stored in Uint32 (4 bytes) + const countStartPosition = allCombined.length - 4 const countArray = allCombined.slice(countStartPosition, countStartPosition + 4) const count = new Uint32Array(countArray.buffer)[0] // Then use count to calculate encryptedData - const encryptedDataStartPosition = senderPublicKeyEndPosition // start position of encryptedData + // start position of encryptedData + const encryptedDataStartPosition = senderPublicKeyEndPosition const encryptedDataEndPosition = allCombined.length - ((count * (32 + 16)) + 4) const encryptedData = allCombined.slice(encryptedDataStartPosition, encryptedDataEndPosition) diff --git a/plugins/plugins/core/components/qdn-action-types.js b/plugins/plugins/core/components/qdn-action-types.js index cd795600..ecff51bd 100644 --- a/plugins/plugins/core/components/qdn-action-types.js +++ b/plugins/plugins/core/components/qdn-action-types.js @@ -114,3 +114,6 @@ export const OPEN_PROFILE = 'OPEN_PROFILE' // ADMIN_ACTION export const ADMIN_ACTION = 'ADMIN_ACTION' + +// SIGN_TRANSACTION +export const SIGN_TRANSACTION = 'SIGN_TRANSACTION' diff --git a/plugins/plugins/core/components/webworkerDecodeMessages.js b/plugins/plugins/core/components/webworkerDecodeMessages.js index dc8c0e09..a9fbef4b 100644 --- a/plugins/plugins/core/components/webworkerDecodeMessages.js +++ b/plugins/plugins/core/components/webworkerDecodeMessages.js @@ -2789,6 +2789,11 @@ const decode = (string, keys, ref) => { let hubString = '' const res = decryptSingle(string, keys, false) + + if (res === 'noKey' || res === 'decryptionFailed') { + return '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"This message could not be decrypted"}]}]},"images":[""],"repliedTo":"","version":3}' + } + const decryptToUnit8Array = base64ToUint8Array(res) const responseData = uint8ArrayToObject(decryptToUnit8Array) diff --git a/plugins/plugins/core/group-management/group-management.src.js b/plugins/plugins/core/group-management/group-management.src.js index 2fa4d3c1..7b1a6df9 100644 --- a/plugins/plugins/core/group-management/group-management.src.js +++ b/plugins/plugins/core/group-management/group-management.src.js @@ -2866,14 +2866,20 @@ class GroupManagement extends LitElement { let data let supArray = [] + let allSymKeys = [] let gAdmin = '' let gAddress = '' + let keysToOld = "Wait until an admin re-encrypts the keys. Only unencrypted messages will be displayed." + let retryDownload = "Retry downloading and decrypt keys in 5 seconds! Please wait..." + let failDownload = "Error downloading and decrypt keys! Only unencrypted messages will be displayed. Please try again later..." + let all_ok = false + let counter = 0 const symIdentifier = 'symmetric-qchat-group-' + groupId const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port - const getNameUrl = `${nodeUrl}/arbitrary/resources?service=DOCUMENT_PRIVATE&identifier=${symIdentifier}&limit=1&reverse=true` - const getAdminUrl = `${nodeUrl}/groups/members/${groupId}?onlyAdmins=true&limit=20` + const getNameUrl = `${nodeUrl}/arbitrary/resources?service=DOCUMENT_PRIVATE&identifier=${symIdentifier}&limit=0&reverse=true` + const getAdminUrl = `${nodeUrl}/groups/members/${groupId}?onlyAdmins=true&limit=0` supArray = await fetch(getNameUrl).then(response => { return response.json() @@ -2882,10 +2888,21 @@ class GroupManagement extends LitElement { if (this.isEmptyArray(supArray) || groupId === 0) { console.log("No Symetric Key") } else { - supArray.map(item => { - gAdmin = item.name + supArray.forEach(item => { + const symInfoObj = { + name: item.name, + identifier: item.identifier, + timestamp: item.updated ? item.updated : item.created + } + allSymKeys.push(symInfoObj) }) + let allSymKeysSorted = allSymKeys.sort(function(a, b) { + return b.timestamp - a.timestamp + }) + + gAdmin = allSymKeysSorted[0].name + const addressUrl = `${nodeUrl}/names/${gAdmin}` let addressObject = await fetch(addressUrl).then(response => { @@ -2907,20 +2924,41 @@ class GroupManagement extends LitElement { } if (adminExists(gAddress)) { + const sleep = (t) => new Promise(r => setTimeout(r, t)) const dataUrl = `${nodeUrl}/arbitrary/DOCUMENT_PRIVATE/${gAdmin}/${symIdentifier}?encoding=base64&rebuild=true&async=true` const res = await fetch(dataUrl) - data = await res.text() + do { + counter++ + + if (!res.ok) { + parentEpml.request('showSnackBar', `${retryDownload}`) + await sleep(5000) + } else { + data = await res.text() + all_ok = true + } + + if (counter > 10) { + parentEpml.request('showSnackBar', `${failDownload}`) + return + } + } while (!all_ok) const decryptedKey = await this.decryptGroupEncryption(data) - const dataint8Array = base64ToUint8Array(decryptedKey.data) - const decryptedKeyToObject = uint8ArrayToObject(dataint8Array) - if (!validateSecretKey(decryptedKeyToObject)) { - throw new Error("SecretKey is not valid") + if (decryptedKey === undefined) { + parentEpml.request('showSnackBar', `${keysToOld}`) + } else { + const dataint8Array = base64ToUint8Array(decryptedKey.data) + const decryptedKeyToObject = uint8ArrayToObject(dataint8Array) + + if (!validateSecretKey(decryptedKeyToObject)) { + throw new Error("SecretKey is not valid") + } + + this.secretKeys = decryptedKeyToObject } - - this.secretKeys = decryptedKeyToObject } } } @@ -2997,6 +3035,11 @@ class GroupManagement extends LitElement { let hubString = '' const res = decryptSingle(string, this.secretKeys, false) + + if (res === 'noKey' || res === 'decryptionFailed') { + return '{"messageText":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"This message could not be decrypted"}]}]},"images":[""],"repliedTo":"","version":3}' + } + const decryptToUnit8Array = base64ToUint8Array(res) const responseData = uint8ArrayToObject(decryptToUnit8Array) @@ -3080,7 +3123,7 @@ class GroupManagement extends LitElement { let getEditedArray = await parentEpml.request('apiCall', { url: `/chat/messages?txGroupId=${involved}&haschatreference=true&encoding=BASE64&limit=0&reverse=false` }) - + chaEditedArray = getEditedArray // Replace messages which got edited in the chatMessageArray @@ -3143,7 +3186,7 @@ class GroupManagement extends LitElement { if (this.shadowRoot.getElementById('chat-container').innerHTML === '') { this.shadowRoot.getElementById('chat-container').innerHTML = '' } - + if (this.isEmptyArray(renderArray)) { const chatEmpty = document.createElement('div') chatEmpty.classList.add('no-messages') @@ -5778,4 +5821,4 @@ class GroupManagement extends LitElement { } } -window.customElements.define('group-management', GroupManagement) \ No newline at end of file +window.customElements.define('group-management', GroupManagement) diff --git a/plugins/plugins/core/q-chat/q-chat.src.js b/plugins/plugins/core/q-chat/q-chat.src.js index 0b36ed80..cd9a11d1 100644 --- a/plugins/plugins/core/q-chat/q-chat.src.js +++ b/plugins/plugins/core/q-chat/q-chat.src.js @@ -447,17 +447,22 @@ class Chat extends LitElement { let data let supArray = [] + let allSymKeys = [] let gAdmin = '' let gAddress = '' let currentGroupId = url.substring(6) let symIdentifier = 'symmetric-qchat-group-' + currentGroupId - let locateString = "Downloading and decrypt keys ! Please wait..." + let locateString = "Downloading and decrypt keys! Please wait..." let keysToOld = "Wait until an admin re-encrypts the keys. Only unencrypted messages will be displayed." + let retryDownload = "Retry downloading and decrypt keys in 5 seconds! Please wait..." + let failDownload = "Error downloading and decrypt keys! Only unencrypted messages will be displayed. Please try again later..." + let all_ok = false + let counter = 0 const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port - const getNameUrl = `${nodeUrl}/arbitrary/resources?service=DOCUMENT_PRIVATE&identifier=${symIdentifier}&limit=1&reverse=true` - const getAdminUrl = `${nodeUrl}/groups/members/${currentGroupId}?onlyAdmins=true&limit=20` + const getNameUrl = `${nodeUrl}/arbitrary/resources?service=DOCUMENT_PRIVATE&identifier=${symIdentifier}&limit=0&reverse=true` + const getAdminUrl = `${nodeUrl}/groups/members/${currentGroupId}?onlyAdmins=true&limit=0` if (localStorage.getItem("symKeysCurrent") === null) { localStorage.setItem("symKeysCurrent", "") @@ -473,10 +478,21 @@ class Chat extends LitElement { } else { parentEpml.request('showSnackBar', `${locateString}`) - supArray.map(item => { - gAdmin = item.name + supArray.forEach(item => { + const symInfoObj = { + name: item.name, + identifier: item.identifier, + timestamp: item.updated ? item.updated : item.created + } + allSymKeys.push(symInfoObj) }) + let allSymKeysSorted = allSymKeys.sort(function(a, b) { + return b.timestamp - a.timestamp + }) + + gAdmin = allSymKeysSorted[0].name + const addressUrl = `${nodeUrl}/names/${gAdmin}` let addressObject = await fetch(addressUrl).then(response => { @@ -498,10 +514,28 @@ class Chat extends LitElement { } if (adminExists(gAddress)) { - const dataUrl = `${nodeUrl}/arbitrary/DOCUMENT_PRIVATE/${gAdmin}/${symIdentifier}?encoding=base64&rebuild=true&async=true` + const sleep = (t) => new Promise(r => setTimeout(r, t)) + const dataUrl = `${nodeUrl}/arbitrary/DOCUMENT_PRIVATE/${gAdmin}/${symIdentifier}?encoding=base64` const res = await fetch(dataUrl) - data = await res.text() + do { + counter++ + + if (!res.ok) { + parentEpml.request('showSnackBar', `${retryDownload}`) + await sleep(5000) + } else { + data = await res.text() + all_ok = true + } + + if (counter > 10) { + parentEpml.request('showSnackBar', `${failDownload}`) + this.activeChatHeadUrl = url + this.requestUpdate() + return + } + } while (!all_ok) const decryptedKey = await this.decryptGroupEncryption(data) diff --git a/plugins/plugins/core/qdn/browser/browser.src.js b/plugins/plugins/core/qdn/browser/browser.src.js index 4134a335..23fc00bf 100644 --- a/plugins/plugins/core/qdn/browser/browser.src.js +++ b/plugins/plugins/core/qdn/browser/browser.src.js @@ -12,6 +12,7 @@ import { uint8ArrayStartsWith, uint8ArrayToBase64 } from '../../components/qdn-action-encryption' +import { processTransactionVersion2 } from '../../../../../crypto/api/createTransaction' import { webBrowserStyles, webBrowserModalStyles } from '../../components/plugins-css' import * as actions from '../../components/qdn-action-types' import isElectron from 'is-electron' @@ -19,6 +20,8 @@ import ShortUniqueId from 'short-unique-id' import FileSaver from 'file-saver' import WebWorker from 'web-worker:./computePowWorkerFile.js' import WebWorkerChat from 'web-worker:./computePowWorker.js' +import Base58 from '../../../../../crypto/api/deps/Base58' +import nacl from '../../../../../crypto/api/deps/nacl-fast' import '@material/mwc-button' import '@material/mwc-icon' import '@material/mwc-checkbox' @@ -84,7 +87,7 @@ class WebBrowser extends LitElement { this.loader = new Loader() // Build initial display URL - let displayUrl = '' + let displayUrl if (this.dev === 'FRAMEWORK') { displayUrl = 'qortal://app/development' @@ -2271,6 +2274,140 @@ class WebBrowser extends LitElement { } } + case actions.SIGN_TRANSACTION: { + const signNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const signUrl = signNode.protocol + '://' + signNode.domain + ':' + signNode.port + const requiredFields = ['unsignedBytes'] + 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 shouldProcess = data.process || false + + let url = `${signUrl}/transactions/decode?ignoreValidityChecks=false` + let body = data.unsignedBytes + + const resp = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: body + }) + + if (!resp.ok) { + const errorMsg = "Failed to decode transaction" + let data = {} + data['error'] = errorMsg + response = JSON.stringify(data) + break + } + + const decodedData = await resp.json() + + const signRequest = await showModalAndWait( + actions.SIGN_TRANSACTION, + { + text1: `Do you give this application permission to ${ shouldProcess ? 'SIGN and PROCESS' : 'SIGN' } a transaction?`, + text2: "Read the transaction carefully before accepting!", + text3: `Tx type: ${decodedData.type}`, + json: decodedData + } + ) + + if (signRequest.action === 'accept') { + let urlConverted = `${signUrl}/transactions/convert` + + const responseConverted = await fetch(urlConverted, { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: data.unsignedBytes + }) + + const uint8PrivateKey = Base58.encode(window.parent.reduxStore.getState().app.wallet._addresses[0].keyPair.privateKey) + const uint8PublicKey = Base58.encode(window.parent.reduxStore.getState().app.wallet._addresses[0].keyPair.publicKey) + + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey, + } + + const convertedBytes = await responseConverted.text() + const txBytes = Base58.decode(data.unsignedBytes) + + const _arbitraryBytesBuffer = Object.keys(txBytes).map(function (key) { + return txBytes[key] + }) + + const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer) + const txByteSigned = Base58.decode(convertedBytes) + + const _bytesForSigningBuffer = Object.keys(txByteSigned).map(function (key) { + return txByteSigned[key] + }) + + const bytesForSigningBuffer = new Uint8Array(_bytesForSigningBuffer) + + const signature = nacl.sign.detached( + bytesForSigningBuffer, + keyPair.privateKey + ) + + const signedBytes = utils.appendBuffer(arbitraryBytesBuffer, signature) + const signedBytesToBase58 = Base58.encode(signedBytes) + + if(!shouldProcess) { + const errorMsg = "Process transaction was not requested! Signed bytes are: " + signedBytesToBase58 + let data = {} + data['error'] = errorMsg + response = JSON.stringify(data) + break + } + + try { + this.loader.show() + + const res = await processTransactionVersion2(signedBytesToBase58) + + if (!res.signature) { + const errorMsg = "Transaction was not able to be processed" + res.message + let data = {} + data['error'] = errorMsg + response = JSON.stringify(data) + break + } + + response = JSON.stringify(res) + } catch (error) { + console.error(error) + const data = {} + data['error'] = error.message || get("browserpage.bchange21") + response = JSON.stringify(data) + return + } finally { + this.loader.hide() + } + } else if (signRequest.action === 'reject') { + response = '{"error": "User declined request"}' + } + break + } + case actions.SEND_COIN: { const requiredFields = ['coin', 'destinationAddress', 'amount'] const missingFields = [] @@ -2356,7 +2493,6 @@ class WebBrowser extends LitElement { } ) if (processPayment.action === 'reject') { - let errorMsg = "User declined request" let myMsg1 = get("transactions.declined") let myMsg2 = get("walletpage.wchange44") await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) @@ -2516,7 +2652,6 @@ class WebBrowser extends LitElement { } ) if (processPayment.action === 'reject') { - let errorMsg = "User declined request" let myMsg1 = get("transactions.declined") let myMsg2 = get("walletpage.wchange44") await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) @@ -2614,7 +2749,6 @@ class WebBrowser extends LitElement { } ) if (processPayment.action === 'reject') { - let errorMsg = "User declined request" let myMsg1 = get("transactions.declined") let myMsg2 = get("walletpage.wchange44") await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) @@ -2712,7 +2846,6 @@ class WebBrowser extends LitElement { } ) if (processPayment.action === 'reject') { - let errorMsg = "User declined request" let myMsg1 = get("transactions.declined") let myMsg2 = get("walletpage.wchange44") await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) @@ -2810,7 +2943,6 @@ class WebBrowser extends LitElement { } ) if (processPayment.action === 'reject') { - let errorMsg = "User declined request" let myMsg1 = get("transactions.declined") let myMsg2 = get("walletpage.wchange44") await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) @@ -2908,7 +3040,6 @@ class WebBrowser extends LitElement { } ) if (processPayment.action === 'reject') { - let errorMsg = "User declined request" let myMsg1 = get("transactions.declined") let myMsg2 = get("walletpage.wchange44") await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) @@ -3006,7 +3137,6 @@ class WebBrowser extends LitElement { } ) if (processPayment.action === 'reject') { - let errorMsg = "User declined request" let myMsg1 = get("transactions.declined") let myMsg2 = get("walletpage.wchange44") await showErrorAndWait("DECLINED_REQUEST", myMsg1, myMsg2) @@ -4032,6 +4162,13 @@ async function showModalAndWait(type, data) { ` : ''} + + ${type === actions.SIGN_TRANSACTION ? ` + + + + + ` : ''}