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/qdn/browser/browser.src.js b/plugins/plugins/core/qdn/browser/browser.src.js index 4134a335..496be933 100644 --- a/plugins/plugins/core/qdn/browser/browser.src.js +++ b/plugins/plugins/core/qdn/browser/browser.src.js @@ -22,6 +22,7 @@ import WebWorkerChat from 'web-worker:./computePowWorker.js' import '@material/mwc-button' import '@material/mwc-icon' import '@material/mwc-checkbox' +import nacl from '../../../../../crypto/api/deps/nacl-fast' // Multi language support import { get, registerTranslateConfig, translate, use } from '../../../../../core/translate' @@ -2271,6 +2272,126 @@ 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) throw new Error("Failed to decode transaction") + + 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){ + return signedBytesToBase58 + } + + try { + this.loader.show() + + const res = await this.processTransactionVersion2(signedBytesToBase58) + + if (!res.signature) { + throw new Error(res.message || "Transaction was not able to be processed") + } + + 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 = [] @@ -4032,6 +4153,13 @@ async function showModalAndWait(type, data) { ` : ''} + + ${type === actions.SIGN_TRANSACTION ? ` + + + + + ` : ''}