diff --git a/package-lock.json b/package-lock.json index 5f7d749..8d577ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "asmcrypto.js": "2.3.2", "bcryptjs": "2.4.3", "buffer": "6.0.3", + "file-saver": "^2.0.5", "jssha": "3.3.1", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", @@ -3439,6 +3440,11 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, "node_modules/file-selector": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", diff --git a/package.json b/package.json index 0f43afd..67930ed 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "asmcrypto.js": "2.3.2", "bcryptjs": "2.4.3", "buffer": "6.0.3", + "file-saver": "^2.0.5", "jssha": "3.3.1", "react": "^18.2.0", "react-copy-to-clipboard": "^5.1.0", diff --git a/public/content-script.js b/public/content-script.js index 3b74c85..ff844c6 100644 --- a/public/content-script.js +++ b/public/content-script.js @@ -62,6 +62,36 @@ document.addEventListener('qortalExtensionRequests', async (event) => { })); } }); + } else if (type === 'REQUEST_OAUTH') { + const hostname = window.location.hostname + const res = await connection(hostname) + if(!res){ + document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { + detail: { type: "OAUTH", data: { + error: "Not authorized" + }, requestId } + })); + return + } + + chrome.runtime.sendMessage({ action: "oauth", payload: { + nodeBaseUrl: payload.nodeBaseUrl, + senderAddress: payload.senderAddress, + senderPublicKey: payload.senderPublicKey, timestamp: payload.timestamp + }}, (response) => { + if (response.error) { + document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { + detail: { type: "OAUTH", data: { + error: response.error + }, requestId } + })); + } else { + // Include the requestId in the detail when dispatching the response + document.dispatchEvent(new CustomEvent('qortalExtensionResponses', { + detail: { type: "OAUTH", data: response, requestId } + })); + } + }); } else if (type === 'REQUEST_AUTHENTICATION') { const hostname = window.location.hostname const res = await connection(hostname) @@ -148,3 +178,14 @@ document.addEventListener('qortalExtensionRequests', async (event) => { } // Handle other request types as needed... }); + + +chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) { + if (message.type === "LOGOUT") { + // Notify the web page + window.postMessage({ + type: "LOGOUT", + from: 'qortal' + }, "*"); + } +}); \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 690558d..7af3d2a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -53,7 +53,9 @@ type extStates = | "download-wallet" | "create-wallet" | "transfer-success-regular" - | "transfer-success-request"; + | "transfer-success-request" + | "wallet-dropped" + ; function App() { const [extState, setExtstate] = useState("not-authenticated"); @@ -73,6 +75,8 @@ function App() { const [walletToBeDownloaded, setWalletToBeDownloaded] = useState(null); const [walletToBeDownloadedPassword, setWalletToBeDownloadedPassword] = useState(""); + const [authenticatePassword, setAuthenticatePassword] = + useState(""); const [sendqortState, setSendqortState] = useState(null); const [isLoading, setIsLoading] = useState(false) const [ @@ -81,7 +85,8 @@ function App() { ] = useState(""); const [walletToBeDownloadedError, setWalletToBeDownloadedError] = useState(""); - + const [walletToBeDecryptedError, setWalletToBeDecryptedError] = + useState(""); const holdRefExtState = useRef("not-authenticated") useEffect(()=> { if(extState){ @@ -135,10 +140,10 @@ function App() { for (const field of requiredFields) { if (!(field in pf)) throw new Error(field + " not found in JSON"); } - // setBackupjson(pf) - storeWalletInfo(pf); + // storeWalletInfo(pf); setRawWallet(pf); - setExtstate("authenticated"); + // setExtstate("authenticated"); + setExtstate("wallet-dropped"); setdecryptedWallet(null); } catch (e) { console.log(e); @@ -296,10 +301,10 @@ function App() { ); return; } - if (!paymentPassword) { - setSendPaymentError("Please enter your wallet password"); - return; - } + // if (!paymentPassword) { + // setSendPaymentError("Please enter your wallet password"); + // return; + // } setIsLoading(true) chrome.runtime.sendMessage( { @@ -351,7 +356,6 @@ function App() { // rawWalletRef.current = rawWallet // }, [rawWallet]) - useEffect(() => { try { setIsLoading(true) @@ -447,18 +451,35 @@ function App() { crypto.kdfThreads, () => {} ); - setRawWallet(wallet); - storeWalletInfo(wallet); - setWalletToBeDownloaded({ - wallet, - qortAddress: wallet.address0, + chrome.runtime.sendMessage({ action: "decryptWallet", payload: { + password: walletToBeDownloadedPassword, + wallet + } }, (response) => { + if (response && !response?.error) { + setRawWallet(wallet); + setWalletToBeDownloaded({ + wallet, + qortAddress: wallet.address0, + }); + chrome.runtime.sendMessage({ action: "userInfo" }, (response2) => { + setIsLoading(false) + if (response2 && !response2.error) { + setUserInfo(response); + } + }); + getBalanceFunc(); + } else if(response?.error){ + setIsLoading(false) + setWalletToBeDecryptedError(response.error) + } }); + + + } catch (error: any) { setWalletToBeDownloadedError(error?.message); - } finally { setIsLoading(false) - - } + } }; const logoutFunc = () => { @@ -505,6 +526,40 @@ function App() { setSendqortState(null); }; + const authenticateWallet = async()=> { + try { + setIsLoading(true) + setWalletToBeDecryptedError('') + await new Promise((res)=> { + setTimeout(()=> { + res() + }, 250) + }) + chrome.runtime.sendMessage({ action: "decryptWallet", payload: { + password: authenticatePassword, + wallet: rawWallet + } }, (response) => { + if (response && !response?.error) { + setAuthenticatePassword(""); + setExtstate("authenticated"); + setWalletToBeDecryptedError('') + chrome.runtime.sendMessage({ action: "userInfo" }, (response) => { + setIsLoading(false) + if (response && !response.error) { + setUserInfo(response); + } + }); + getBalanceFunc(); + } else if(response?.error){ + setIsLoading(false) + setWalletToBeDecryptedError(response.error) + } + }); + } catch (error) { + setWalletToBeDecryptedError('Unable to authenticate. Wrong password') + } + } + return ( {extState === "not-authenticated" && ( @@ -802,7 +857,7 @@ function App() { > {sendqortState?.amount} QORT - + {/* Confirm Wallet Password @@ -812,7 +867,7 @@ function App() { id="standard-adornment-password" value={paymentPassword} onChange={(e) => setPaymentPassword(e.target.value)} - /> + /> */} )} + {rawWallet && extState === 'wallet-dropped' && ( + <> + + + { + setRawWallet(null); + setExtstate("not-authenticated"); + }} + src={Return} + /> + + +
+ + +
+ + + + Authenticate + + + + + <> + + Wallet Password + + + + setAuthenticatePassword(e.target.value) + } + onKeyDown={(e) => { + if (e.key === "Enter") { + authenticateWallet(); + } + }} + /> + + + Authenticate + + + {walletToBeDecryptedError} + + + + )} {extState === "download-wallet" && ( <> @@ -1010,7 +1144,7 @@ function App() { Confirm password - + {walletToBeDownloadedError} @@ -1095,7 +1229,7 @@ function App() { Create Account - + {walletToBeDownloadedError} diff --git a/src/background.ts b/src/background.ts index 9e71175..f523d0e 100644 --- a/src/background.ts +++ b/src/background.ts @@ -1,6 +1,7 @@ // @ts-nocheck import Base58 from "./deps/Base58"; import { createTransaction } from "./transactions/transactions"; +import { decryptChatMessage } from "./utils/decryptChatMessage"; import { decryptStoredWallet } from "./utils/decryptWallet"; import PhraseWallet from "./utils/generateWallet/phrase-wallet"; import { validateAddress } from "./utils/validateAddress"; @@ -18,9 +19,8 @@ export const walletVersion = 2; // List of your API endpoints const apiEndpoints = [ "https://api.qortal.org", - "https://webapi.qortal.online", - "https://web-api.qortal.online", - "https://api.qortal.online", + "https://api2.qortal.org", + "https://appnode.qortal.org", "https://apinode.qortalnodes.live", "https://apinode1.qortalnodes.live", "https://apinode2.qortalnodes.live", @@ -79,6 +79,15 @@ async function getAddressInfo(address) { return data; } +async function getKeyPair() { + const res = await chrome.storage.local.get(["keyPair"]); + if (res?.keyPair) { + return res.keyPair; + } else { + throw new Error("Wallet not authenticated"); + } +} + async function getSaveWallet() { const res = await chrome.storage.local.get(["walletInfo"]); if (res?.walletInfo) { @@ -132,8 +141,10 @@ const processTransactionVersion2 = async (body: any, validApi: string) => { }); }; -const transaction = async ({ type, params, apiVersion, keyPair }: any, validApi) => { - +const transaction = async ( + { type, params, apiVersion, keyPair }: any, + validApi +) => { const tx = createTransaction(type, keyPair, params); let res; @@ -155,20 +166,22 @@ const makeTransactionRequest = async ( keyPair, validApi ) => { - - const myTxnrequest = await transaction({ - nonce: 0, - type: 2, - params: { - recipient: receiver, - // recipientName: recipientName, - amount: amount, - lastReference: lastRef, - fee: fee, + const myTxnrequest = await transaction( + { + nonce: 0, + type: 2, + params: { + recipient: receiver, + // recipientName: recipientName, + amount: amount, + lastReference: lastRef, + fee: fee, + }, + apiVersion: 2, + keyPair, }, - apiVersion: 2, - keyPair, - }, validApi); + validApi + ); return myTxnrequest; }; @@ -214,18 +227,71 @@ async function getNameOrAddress(receiver) { if (!response?.ok) throw new Error("Cannot fetch name"); return { error: "cannot validate address or name" }; } catch (error) { - throw new Error(error?.message || "cannot validate address or name") + throw new Error(error?.message || "cannot validate address or name"); } } -async function sendCoin({ password, amount, receiver }) { + +async function decryptWallet({password, wallet, walletVersion}) { + try { + const response = await decryptStoredWallet(password, wallet); + const wallet2 = new PhraseWallet(response, walletVersion); + const keyPair = wallet2._addresses[0].keyPair; + const toSave = { + privateKey: Base58.encode(keyPair.privateKey), + publicKey: Base58.encode(keyPair.publicKey) + } + const dataString = JSON.stringify(toSave) + await new Promise((resolve, reject) => { + chrome.storage.local.set({ keyPair: dataString }, () => { + if (chrome.runtime.lastError) { + reject(new Error(chrome.runtime.lastError.message)); + } else { + resolve(true); + } + }); + }); + + await new Promise((resolve, reject) => { + chrome.storage.local.set({ walletInfo: wallet }, () => { + if (chrome.runtime.lastError) { + reject(new Error(chrome.runtime.lastError.message)); + } else { + resolve(true); + } + }); + }); + + return true; + } catch (error) { + console.log({error}) + throw new Error(error.message); + } +} + +async function sendCoin({ password, amount, receiver }, skipConfirmPassword) { try { const confirmReceiver = await getNameOrAddress(receiver); if (confirmReceiver.error) throw new Error("Invalid receiver address or name"); const wallet = await getSaveWallet(); - const response = await decryptStoredWallet(password, wallet); + let keyPair = '' + if(skipConfirmPassword){ + const resKeyPair = await getKeyPair() + const parsedData = JSON.parse(resKeyPair) + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey + }; + } else { + const response = await decryptStoredWallet(password, wallet); const wallet2 = new PhraseWallet(response, walletVersion); + keyPair = wallet2._addresses[0].keyPair + } + + const lastRef = await getLastRef(); const fee = await sendQortFee(); const validApi = await findUsableApi(); @@ -235,15 +301,79 @@ async function sendCoin({ password, amount, receiver }) { lastRef, amount, fee, - wallet2._addresses[0].keyPair, + keyPair, validApi ); - return {res, validApi}; + return { res, validApi }; } catch (error) { throw new Error(error.message); } } +function fetchMessages(apiCall) { + let retryDelay = 2000; // Start with a 2-second delay + const maxDuration = 360000; // Maximum duration set to 6 minutes + const startTime = Date.now(); // Record the start time + + // Promise to handle polling logic + return new Promise((resolve, reject) => { + const attemptFetch = async () => { + if (Date.now() - startTime > maxDuration) { + return reject(new Error("Maximum polling time exceeded")); + } + + try { + const response = await fetch(apiCall); + const data = await response.json(); + if (data && data.length > 0) { + resolve(data[0]); // Resolve the promise when data is found + } else { + setTimeout(attemptFetch, retryDelay); + retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes + } + } catch (error) { + reject(error); // Reject the promise on error + } + }; + + attemptFetch(); // Initial call to start the polling + }); +} + +async function listenForChatMessage({ nodeBaseUrl, senderAddress, senderPublicKey, timestamp }) { + try { + let validApi = ""; + const checkIfNodeBaseUrlIsAcceptable = apiEndpoints.find( + (item) => item === nodeBaseUrl + ); + if (checkIfNodeBaseUrlIsAcceptable) { + validApi = checkIfNodeBaseUrlIsAcceptable; + } else { + validApi = await findUsableApi(); + } + const wallet = await getSaveWallet(); + const address = wallet.address0; + const before = timestamp + 5000 + const after = timestamp - 5000 + const apiCall = `${validApi}/chat/messages?involving=${senderAddress}&involving=${address}&reverse=true&limit=1&before=${before}&after=${after}`; + const encodedMessageObj = await fetchMessages(apiCall) + + const resKeyPair = await getKeyPair() + const parsedData = JSON.parse(resKeyPair) + const uint8PrivateKey = Base58.decode(parsedData.privateKey); + const uint8PublicKey = Base58.decode(parsedData.publicKey); + const keyPair = { + privateKey: uint8PrivateKey, + publicKey: uint8PublicKey + }; + const decodedMessage = decryptChatMessage(encodedMessageObj.data, keyPair.privateKey, senderPublicKey, encodedMessageObj.reference) + return { secretCode: decodedMessage }; + } catch (error) { + console.error(error) + throw new Error(error.message); + } +} + chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request) { switch (request.action) { @@ -261,15 +391,21 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { }); break; case "getWalletInfo": - chrome.storage.local.get(["walletInfo"], (result) => { - if (chrome.runtime.lastError) { - sendResponse({ error: chrome.runtime.lastError.message }); - } else if (result.walletInfo) { - sendResponse({ walletInfo: result.walletInfo }); - } else { - sendResponse({ error: "No wallet info found" }); - } - }); + + getKeyPair().then(()=> { + chrome.storage.local.get(["walletInfo"], (result) => { + if (chrome.runtime.lastError) { + sendResponse({ error: chrome.runtime.lastError.message }); + } else if (result.walletInfo) { + sendResponse({ walletInfo: result.walletInfo }); + } else { + sendResponse({ error: "No wallet info found" }); + } + }); + }).catch((error)=> { + sendResponse({ error: error.message }); + }) + break; case "validApi": findUsableApi() @@ -298,6 +434,22 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { console.error(error.message); }); break; + case "decryptWallet": { + const { password, wallet } = request.payload; + + decryptWallet({ + password, wallet, walletVersion + }) + .then((hasDecrypted) => { + sendResponse(hasDecrypted); + }) + .catch((error) => { + sendResponse({ error: error?.message }); + console.error(error.message); + }); + } + + break; case "balance": getBalanceInfo() .then((balance) => { @@ -321,6 +473,21 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { } break; + + case "oauth": { + const { nodeBaseUrl, senderAddress, senderPublicKey, timestamp } = request.payload; + + listenForChatMessage({ nodeBaseUrl, senderAddress, senderPublicKey, timestamp }) + .then(({ secretCode }) => { + sendResponse(secretCode); + }) + .catch((error) => { + sendResponse({ error: error.message }); + console.error(error.message); + }); + + break; + } case "authentication": { getSaveWallet() @@ -333,7 +500,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { chrome.windows.getAll( { populate: true, windowTypes: ["popup"] }, (windows) => { - // Attempt to find an existing popup window that has a tab with the correct URL const existingPopup = windows.find( (w) => @@ -396,7 +562,9 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { let intervalId = null; const startTime = Date.now(); const checkInterval = 3000; // Check every 3 seconds - const timeout = request.timeout ? 0.75 * (request.timeout * 1000) : 60000; // Stop after 15 seconds + const timeout = request.timeout + ? 0.75 * (request.timeout * 1000) + : 60000; // Stop after 15 seconds const checkFunction = () => { getSaveWallet() @@ -439,7 +607,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { chrome.windows.getAll( { populate: true, windowTypes: ["popup"] }, (windows) => { - // Attempt to find an existing popup window that has a tab with the correct URL const existingPopup = windows.find( (w) => @@ -515,7 +682,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { chrome.windows.getAll( { populate: true, windowTypes: ["popup"] }, (windows) => { - // Attempt to find an existing popup window that has a tab with the correct URL const existingPopup = windows.find( (w) => @@ -557,7 +723,6 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { const interactionId = Date.now().toString(); // Simple example; consider a better unique ID - setTimeout(() => { chrome.runtime.sendMessage({ action: "SET_COUNTDOWN", @@ -602,13 +767,14 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { sendResponse(true); } } + + pendingResponses.delete(interactionId3); } break; case "sendQortConfirmation": const { password, amount, receiver, isDecline } = request.payload; const interactionId2 = request.payload.interactionId; - // Retrieve the stored sendResponse callback const originalSendResponse = pendingResponses.get(interactionId2); @@ -616,13 +782,16 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (isDecline) { originalSendResponse({ error: "User has declined" }); sendResponse(false); + pendingResponses.delete(interactionId2); return; } - sendCoin({ password, amount, receiver }) + sendCoin({ password, amount, receiver }, true) .then((res) => { sendResponse(true); // Use the sendResponse callback to respond to the original message originalSendResponse(res); + // Remove the callback from the Map as it's no longer needed + pendingResponses.delete(interactionId2); // chrome.runtime.sendMessage({ // action: "closePopup", // }); @@ -633,38 +802,38 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { originalSendResponse({ error: error.message }); }); - // Remove the callback from the Map as it's no longer needed - pendingResponses.delete(interactionId2); } break; - case "logout" : { - chrome.storage.local.remove('walletInfo', () => { - if (chrome.runtime.lastError) { - // Handle error - console.error(chrome.runtime.lastError.message); - } else { - // Data removed successfully - sendResponse(true) - } - }); - + case "logout": + { + chrome.storage.local.remove(["keyPair", "walletInfo"], () => { + if (chrome.runtime.lastError) { + // Handle error + console.error(chrome.runtime.lastError.message); + } else { + chrome.tabs.query({}, function(tabs) { + tabs.forEach(tab => { + chrome.tabs.sendMessage(tab.id, { type: "LOGOUT" }); + }); + }); + // Data removed successfully + sendResponse(true); + } + }); + } - } - - break; + break; } } return true; }); - chrome.action.onClicked.addListener((tab) => { const popupUrl = chrome.runtime.getURL("index.html"); chrome.windows.getAll( { populate: true, windowTypes: ["popup"] }, (windows) => { - // Attempt to find an existing popup window that has a tab with the correct URL const existingPopup = windows.find( (w) => diff --git a/src/deps/ed2curve.ts b/src/deps/ed2curve.ts new file mode 100644 index 0000000..3bf851e --- /dev/null +++ b/src/deps/ed2curve.ts @@ -0,0 +1,266 @@ +// @ts-nocheck + +/* + * ed2curve: convert Ed25519 signing key pair into Curve25519 + * key pair suitable for Diffie-Hellman key exchange. + * + * Written by Dmitry Chestnykh in 2014. Public domain. + */ +/* jshint newcap: false */ + +/* +Change to es6 import/export +*/ + +import nacl from './nacl-fast' + +// (function(root, f) { +// 'use strict'; +// if (typeof module !== 'undefined' && module.exports) module.exports = f(require('tweetnacl')); +// else root.ed2curve = f(root.nacl); +// }(this, function(nacl) { +// 'use strict'; +// if (!nacl) throw new Error('tweetnacl not loaded'); + + // -- Operations copied from TweetNaCl.js. -- + + var gf = function(init) { + var i, r = new Float64Array(16); + if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; + return r; + }; + + var gf0 = gf(), + gf1 = gf([1]), + D = gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]), + I = gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]); + + function car25519(o) { + var c; + var i; + for (i = 0; i < 16; i++) { + o[i] += 65536; + c = Math.floor(o[i] / 65536); + o[(i+1)*(i<15?1:0)] += c - 1 + 37 * (c-1) * (i===15?1:0); + o[i] -= (c * 65536); + } + } + + function sel25519(p, q, b) { + var t, c = ~(b-1); + for (var i = 0; i < 16; i++) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } + } + + function unpack25519(o, n) { + var i; + for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8); + o[15] &= 0x7fff; + } + + // addition + function A(o, a, b) { + var i; + for (i = 0; i < 16; i++) o[i] = (a[i] + b[i])|0; + } + + // subtraction + function Z(o, a, b) { + var i; + for (i = 0; i < 16; i++) o[i] = (a[i] - b[i])|0; + } + + // multiplication + function M(o, a, b) { + var i, j, t = new Float64Array(31); + for (i = 0; i < 31; i++) t[i] = 0; + for (i = 0; i < 16; i++) { + for (j = 0; j < 16; j++) { + t[i+j] += a[i] * b[j]; + } + } + for (i = 0; i < 15; i++) { + t[i] += 38 * t[i+16]; + } + for (i = 0; i < 16; i++) o[i] = t[i]; + car25519(o); + car25519(o); + } + + // squaring + function S(o, a) { + M(o, a, a); + } + + // inversion + function inv25519(o, i) { + var c = gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 253; a >= 0; a--) { + S(c, c); + if(a !== 2 && a !== 4) M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; + } + + function pack25519(o, n) { + var i, j, b; + var m = gf(), t = gf(); + for (i = 0; i < 16; i++) t[i] = n[i]; + car25519(t); + car25519(t); + car25519(t); + for (j = 0; j < 2; j++) { + m[0] = t[0] - 0xffed; + for (i = 1; i < 15; i++) { + m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1); + m[i-1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1); + b = (m[15]>>16) & 1; + m[14] &= 0xffff; + sel25519(t, m, 1-b); + } + for (i = 0; i < 16; i++) { + o[2*i] = t[i] & 0xff; + o[2*i+1] = t[i] >> 8; + } + } + + function par25519(a) { + var d = new Uint8Array(32); + pack25519(d, a); + return d[0] & 1; + } + + function vn(x, xi, y, yi, n) { + var i, d = 0; + for (i = 0; i < n; i++) d |= x[xi + i] ^ y[yi + i]; + return (1 & ((d - 1) >>> 8)) - 1; + } + + function crypto_verify_32(x, xi, y, yi) { + return vn(x, xi, y, yi, 32); + } + + function neq25519(a, b) { + var c = new Uint8Array(32), d = new Uint8Array(32); + pack25519(c, a); + pack25519(d, b); + return crypto_verify_32(c, 0, d, 0); + } + + function pow2523(o, i) { + var c = gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 250; a >= 0; a--) { + S(c, c); + if (a !== 1) M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; + } + + function set25519(r, a) { + var i; + for (i = 0; i < 16; i++) r[i] = a[i] | 0; + } + + function unpackneg(r, p) { + var t = gf(), chk = gf(), num = gf(), + den = gf(), den2 = gf(), den4 = gf(), + den6 = gf(); + + set25519(r[2], gf1); + unpack25519(r[1], p); + S(num, r[1]); + M(den, num, D); + Z(num, num, r[2]); + A(den, r[2], den); + + S(den2, den); + S(den4, den2); + M(den6, den4, den2); + M(t, den6, num); + M(t, t, den); + + pow2523(t, t); + M(t, t, num); + M(t, t, den); + M(t, t, den); + M(r[0], t, den); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) M(r[0], r[0], I); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) return -1; + + if (par25519(r[0]) === (p[31] >> 7)) Z(r[0], gf0, r[0]); + + M(r[3], r[0], r[1]); + return 0; + } + + // ---- + + // Converts Ed25519 public key to Curve25519 public key. + // montgomeryX = (edwardsY + 1)*inverse(1 - edwardsY) mod p + function convertPublicKey(pk) { + var z = new Uint8Array(32), + q = [gf(), gf(), gf(), gf()], + a = gf(), b = gf(); + + if (unpackneg(q, pk)) return null; // reject invalid key + + var y = q[1]; + + A(a, gf1, y); + Z(b, gf1, y); + inv25519(b, b); + M(a, a, b); + + pack25519(z, a); + return z; + } + + // Converts Ed25519 secret key to Curve25519 secret key. + function convertSecretKey(sk) { + var d = new Uint8Array(64), o = new Uint8Array(32), i; + nacl.lowlevel.crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + for (i = 0; i < 32; i++) o[i] = d[i]; + for (i = 0; i < 64; i++) d[i] = 0; + return o; + } + + function convertKeyPair(edKeyPair) { + var publicKey = convertPublicKey(edKeyPair.publicKey); + if (!publicKey) return null; + return { + publicKey: publicKey, + secretKey: convertSecretKey(edKeyPair.secretKey) + }; + } + +// return { +// convertPublicKey: convertPublicKey, +// convertSecretKey: convertSecretKey, +// convertKeyPair: convertKeyPair, +// }; + +export default { + convertPublicKey: convertPublicKey, + convertSecretKey: convertSecretKey, + convertKeyPair: convertKeyPair, +} + +// })); diff --git a/src/utils/decryptChatMessage.ts b/src/utils/decryptChatMessage.ts new file mode 100644 index 0000000..bb8a104 --- /dev/null +++ b/src/utils/decryptChatMessage.ts @@ -0,0 +1,29 @@ +// @ts-nocheck + +import Base58 from '../deps/Base58' +import ed2curve from '../deps/ed2curve' +import nacl from '../deps/nacl-fast' +import {Sha256} from 'asmcrypto.js' + + +export const decryptChatMessage = (encryptedMessage, privateKey, recipientPublicKey, lastReference) => { + const test = encryptedMessage + let _encryptedMessage = Base58.decode(encryptedMessage) + const _base58RecipientPublicKey = recipientPublicKey instanceof Uint8Array ? Base58.encode(recipientPublicKey) : recipientPublicKey + const _recipientPublicKey = Base58.decode(_base58RecipientPublicKey) + + const _lastReference = lastReference instanceof Uint8Array ? lastReference : Base58.decode(lastReference) + + const convertedPrivateKey = ed2curve.convertSecretKey(privateKey) + const convertedPublicKey = ed2curve.convertPublicKey(_recipientPublicKey) + const sharedSecret = new Uint8Array(32); + nacl.lowlevel.crypto_scalarmult(sharedSecret, convertedPrivateKey, convertedPublicKey) + + const _chatEncryptionSeed = new Sha256().process(sharedSecret).finish().result + const _decryptedMessage = nacl.secretbox.open(_encryptedMessage, _lastReference.slice(0, 24), _chatEncryptionSeed) + + let decryptedMessage = '' + + _decryptedMessage === false ? decryptedMessage : decryptedMessage = new TextDecoder('utf-8').decode(_decryptedMessage) + return decryptedMessage +} \ No newline at end of file diff --git a/src/utils/generateWallet/generateWallet.ts b/src/utils/generateWallet/generateWallet.ts index f00814e..678b983 100644 --- a/src/utils/generateWallet/generateWallet.ts +++ b/src/utils/generateWallet/generateWallet.ts @@ -4,6 +4,7 @@ import { crypto, walletVersion } from '../../constants/decryptWallet'; import { doInitWorkers, kdf } from '../../deps/kdf'; import PhraseWallet from './phrase-wallet'; import * as WORDLISTS from './wordlists'; +import { saveAs } from 'file-saver'; export function generateRandomSentence(template = 'adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun', maxWordLength = 0, capitalize = true) { const partsOfSpeechMap = { @@ -83,63 +84,19 @@ export const createAccount = async()=> { } - export const saveFileToDisk= async(data, qortAddress) => { + export const saveFileToDisk = async (data, qortAddress) => { try { - const dataString = JSON.stringify(data) - const blob = new Blob([dataString], { type: 'text/plain;charset=utf-8' }) - const fileName = "qortal_backup_" + qortAddress + ".json" - // Feature detection. The API needs to be supported - // and the app not run in an iframe. - const supportsFileSystemAccess = - 'showSaveFilePicker' in window && - (() => { - try { - return window.self === window.top - } catch { - return false - } - })() - // If the File System Access API is supported... - if (supportsFileSystemAccess) { - try { - // Show the file save dialog. - const fileHandle = await window.showSaveFilePicker({ - suggestedName: fileName, - types: [{ - description: "File", - }] - }) - // Write the blob to the file. - const writable = await fileHandle.createWritable() - await writable.write(blob) - await writable.close() - console.log("FILE SAVED") - return - } catch (err) { - // Fail silently if the user has simply canceled the dialog. - if (err.name === 'AbortError') { - console.error(err.name, err.message) - return - } - } - } - // Fallback if the File System Access API is not supported... - // Create the blob URL. - const blobURL = URL.createObjectURL(blob) - // Create the `` element and append it invisibly. - const a = document.createElement('a') - a.href = blobURL - a.download = fileName - a.style.display = 'none' - document.body.append(a) - // Programmatically click the element. - a.click() - // Revoke the blob URL and remove the element. - setTimeout(() => { - URL.revokeObjectURL(blobURL); - a.remove(); - }, 1000); + const dataString = JSON.stringify(data); + const blob = new Blob([dataString], { type: 'application/json' }); + const fileName = "qortal_backup_" + qortAddress + ".json"; + + saveAs(blob, fileName); } catch (error) { - console.log({error}) + console.log({ error }); + if (error.name === 'AbortError') { + return; + } + // This fallback will only be executed if the `showSaveFilePicker` method fails. + FileSaver.saveAs(blob, fileName); // Ensure FileSaver is properly imported or available in your environment. } -} \ No newline at end of file +}