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/src/App.tsx b/src/App.tsx index 690558d..457fb40 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) + } + /> + + + Authenticate + + + {walletToBeDecryptedError} + + + + )} {extState === "download-wallet" && ( <> @@ -1010,7 +1139,7 @@ function App() { Confirm password - + {walletToBeDownloadedError} @@ -1095,7 +1224,7 @@ function App() { Create Account - + {walletToBeDownloadedError} diff --git a/src/background.ts b/src/background.ts index 178af2b..9d0c8b4 100644 --- a/src/background.ts +++ b/src/background.ts @@ -80,6 +80,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) { @@ -222,15 +231,68 @@ async function getNameOrAddress(receiver) { 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(); @@ -240,7 +302,7 @@ async function sendCoin({ password, amount, receiver }) { lastRef, amount, fee, - wallet2._addresses[0].keyPair, + keyPair, validApi ); return { res, validApi }; @@ -263,13 +325,10 @@ function fetchMessages(apiCall) { try { const response = await fetch(apiCall); - console.log({response}) const data = await response.json(); - console.log({data}) if (data && data.length > 0) { resolve(data[0]); // Resolve the promise when data is found } else { - console.log("No items found, retrying in", retryDelay / 1000, "seconds..."); setTimeout(attemptFetch, retryDelay); retryDelay = Math.min(retryDelay * 2, 360000); // Ensure delay does not exceed 6 minutes } @@ -299,15 +358,19 @@ async function listenForChatMessage({ nodeBaseUrl, senderAddress, senderPublicKe 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) - console.log({encodedMessageObj}) - const response = await decryptStoredWallet('1234567890', wallet); - console.log({response}) - const wallet2 = new PhraseWallet(response, walletVersion); - console.log({wallet2}) - const decodedMessage = decryptChatMessage(encodedMessageObj.data, wallet2._addresses[0].keyPair.privateKey, senderPublicKey, encodedMessageObj.reference) - console.log({decodedMessage}) + + 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); } } @@ -329,15 +392,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() @@ -366,6 +435,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) => { @@ -392,7 +477,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { case "oauth": { const { nodeBaseUrl, senderAddress, senderPublicKey, timestamp } = request.payload; - console.log('sup', nodeBaseUrl, senderAddress, senderPublicKey, timestamp) + listenForChatMessage({ nodeBaseUrl, senderAddress, senderPublicKey, timestamp }) .then(({ secretCode }) => { sendResponse(secretCode); @@ -699,7 +784,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { sendResponse(false); return; } - sendCoin({ password, amount, receiver }) + sendCoin({ password, amount, receiver }, true) .then((res) => { sendResponse(true); // Use the sendResponse callback to respond to the original message @@ -721,7 +806,7 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { break; case "logout": { - chrome.storage.local.remove("walletInfo", () => { + chrome.storage.local.remove(["keyPair", "walletInfo"], () => { if (chrome.runtime.lastError) { // Handle error console.error(chrome.runtime.lastError.message); 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 +}