From b3d4d992893cf591158996c2440b320b38de2981 Mon Sep 17 00:00:00 2001 From: JSKitty Date: Fri, 1 Sep 2023 19:48:25 +0100 Subject: [PATCH 1/7] Improve Mnemonic handling + UX, Add Advanced Mode --- index.template.html | 14 ++++- locale/de/translation.js | 11 +++- locale/en/translation.js | 14 ++++- locale/fr/translation.js | 11 +++- locale/ph/translation.js | 11 +++- locale/pt-br/translation.js | 11 +++- locale/pt-pt/translation.js | 11 +++- locale/template/translation.js | 11 +++- locale/uwu/translation.js | 14 ++++- scripts/global.js | 12 +++- scripts/index.js | 9 ++- scripts/settings.js | 41 +++++++++++++ scripts/wallet.js | 109 ++++++++++++++++++++++++++------- 13 files changed, 241 insertions(+), 38 deletions(-) diff --git a/index.template.html b/index.template.html index cf5cbea8f..2767ea5b4 100644 --- a/index.template.html +++ b/index.template.html @@ -780,7 +780,7 @@

diff --git a/locale/de/translation.js b/locale/de/translation.js index e91678548..daf683fa5 100644 --- a/locale/de/translation.js +++ b/locale/de/translation.js @@ -62,9 +62,16 @@ export const de_translation = { 'Jeder mit einer Kopie der Phrase hat Zugriff auf deine Brieftasche.', //Anyone with a copy of it can access all of your funds. doNotShare: 'Teile Sie unter keinen Umständen mit dritten.', //Do NOT share it with anybody. digitalStoreNotAdvised: 'Es wird empfohlen diese nicht digital zu sichern', //It is NOT advised to store this digitally. - optionalPassphrase: 'Optionale Pass Phrase', //Optional Passphrase + optionalPassphrase: 'Optionale Pass Phrase (BIP39)', //Optional Passphrase (BIP39) writtenDown: 'Ich bestätige, dass ich die Seed Phrase notiert habe', //I have written down my seed phrase + // Seed Phrase Import + importSeedValid: '', //Seed Phrase is valid! + importSeedError: '', //Seed Phrase is invalid! + importSeedErrorSize: '', //A Seed Phrase should be 12 or 24 words long! + importSeedErrorTypo: '', //Seed Phrase contains typing errors! Check your input carefully + importSeedErrorSkip: '', //Seed Phrase appears invalid, but the warning was skipped by the user + // Wallet Dashboard gettingStarted: 'Erste Schritte', //Getting Started secureYourWallet: 'Verschlüssel deine Geldbörse', //Secure your wallet @@ -178,6 +185,8 @@ export const de_translation = { settingsAnalytics: 'Wähle die verwendeten Analysedaten dieser Sitzung', //Choose your analytics contribution level: settingsToggleDebug: 'Debug Modus', //Debug Mode settingsToggleTestnet: 'Testnet Modus', //Testnet Mode + settingsToggleAdvancedMode: '', //Advanced Mode + settingsToggleAdvancedModeSubtext: '', //This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! // Transparency Report transparencyReport: 'Transparenz-Bericht', //Transparency Report diff --git a/locale/en/translation.js b/locale/en/translation.js index ef291f09a..a9fd96e47 100644 --- a/locale/en/translation.js +++ b/locale/en/translation.js @@ -59,9 +59,18 @@ export const en_translation = { 'Anyone with a copy of it can access all of your funds.', // doNotShare: 'Do NOT share it with anyone.', // digitalStoreNotAdvised: 'It is NOT advised to store this digitally.', // - optionalPassphrase: 'Optional Passphrase', // + optionalPassphrase: 'Optional Passphrase (BIP39)', // writtenDown: 'I have written down my seed phrase', // + // Seed Phrase Import + importSeedValid: 'Seed Phrase is valid!', // + importSeedError: 'Seed Phrase is invalid!', // + importSeedErrorSize: 'A Seed Phrase should be 12 or 24 words long!', // + importSeedErrorTypo: + 'Seed Phrase contains typing errors! Check your input carefully', // + importSeedErrorSkip: + 'Seed Phrase appears invalid, but the warning was skipped by the user', // + // Wallet Dashboard gettingStarted: 'Getting Started', // secureYourWallet: 'Secure your wallet', // @@ -179,6 +188,9 @@ export const en_translation = { settingsAnalytics: 'Choose your analytics contribution level:', // settingsToggleDebug: 'Debug Mode', // settingsToggleTestnet: 'Testnet Mode', // + settingsToggleAdvancedMode: 'Advanced Mode', // + settingsToggleAdvancedModeSubtext: + 'This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users!', // // Network switching (mainnet <---> testnet) netSwitchUnsavedWarningTitle: "Your {network} wallet isn't saved!", // diff --git a/locale/fr/translation.js b/locale/fr/translation.js index 42c7aed40..77783d596 100644 --- a/locale/fr/translation.js +++ b/locale/fr/translation.js @@ -62,9 +62,16 @@ export const fr_translation = { doNotShare: 'Ne le partagez avec personne.', //Do NOT share it with anybody. digitalStoreNotAdvised: 'NON il est conseillé de les stocker sous forme numérique.', //It is NOT advised to store this digitally. - optionalPassphrase: 'Phrase mot de passe Facultatif', //Optional Passphrase + optionalPassphrase: 'Phrase mot de passe Facultatif (BIP39)', //Optional Passphrase writtenDown: "J'ai écrit ma phrase d'introduction", //I have written down my seed phrase + // Seed Phrase Import + importSeedValid: '', //Seed Phrase is valid! + importSeedError: '', //Seed Phrase is invalid! + importSeedErrorSize: '', //A Seed Phrase should be 12 or 24 words long! + importSeedErrorTypo: '', //Seed Phrase contains typing errors! Check your input carefully + importSeedErrorSkip: '', //Seed Phrase appears invalid, but the warning was skipped by the user + // Wallet Dashboard gettingStarted: 'Démarrer', //Getting Started secureYourWallet: 'Protégez votre portefeuille', //Secure your wallet @@ -178,6 +185,8 @@ export const fr_translation = { settingsAnalytics: "Choisissez votre niveau d'analyse :", //Choose your analytics contribution level: settingsToggleDebug: 'Mode de débogage', //Debug Mode settingsToggleTestnet: 'Mode testnet', //Testnet Mode + settingsToggleAdvancedMode: '', //Advanced Mode + settingsToggleAdvancedModeSubtext: '', //This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! // Network switching (mainnet <---> testnet) netSwitchUnsavedWarningTitle: '', //Your {network} wallet isn\'t saved! diff --git a/locale/ph/translation.js b/locale/ph/translation.js index 762354a8d..df6a36780 100644 --- a/locale/ph/translation.js +++ b/locale/ph/translation.js @@ -63,9 +63,16 @@ export const ph_translation = { doNotShare: 'WAG mo itong ibibigay kahit kanino', //Do NOT share it with anybody. digitalStoreNotAdvised: 'Ito ay HINDI payo upang itago ito digitally', //It is NOT advised to store this digitally. - optionalPassphrase: 'Optional Passphrase', //Optional Passphrase + optionalPassphrase: 'Optional Passphrase (BIP39)', //Optional Passphrase writtenDown: 'Isinulat ko na ang aking seed phrase', //I have written down my seed phrase + // Seed Phrase Import + importSeedValid: '', //Seed Phrase is valid! + importSeedError: '', //Seed Phrase is invalid! + importSeedErrorSize: '', //A Seed Phrase should be 12 or 24 words long! + importSeedErrorTypo: '', //Seed Phrase contains typing errors! Check your input carefully + importSeedErrorSkip: '', //Seed Phrase appears invalid, but the warning was skipped by the user + // Wallet Dashboard gettingStarted: 'Magsimula', //Getting Started secureYourWallet: 'I-secure ang iyong wallet', //Secure your wallet @@ -179,6 +186,8 @@ export const ph_translation = { settingsAnalytics: 'Pumili ng iyong analytics contribution level:', //Choose your analytics contribution level: settingsToggleDebug: 'Debug Mode', //Debug Mode settingsToggleTestnet: 'Testnet Mode', //Testnet Mode + settingsToggleAdvancedMode: '', //Advanced Mode + settingsToggleAdvancedModeSubtext: '', //This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! // Network switching (mainnet <---> testnet) netSwitchUnsavedWarningTitle: '', //Your {network} wallet isn\'t saved! diff --git a/locale/pt-br/translation.js b/locale/pt-br/translation.js index 32d96e76d..c18d19b56 100644 --- a/locale/pt-br/translation.js +++ b/locale/pt-br/translation.js @@ -62,9 +62,16 @@ export const pt_br_translation = { doNotShare: 'NÃO a compartilhe com ninguém.', //Do NOT share it with anybody. digitalStoreNotAdvised: 'NÃO é aconselhável armazená-la digitalmente.', //It is NOT advised to store this digitally. - optionalPassphrase: 'Frasse-Passe Opcional', //Optional Passphrase + optionalPassphrase: 'Frasse-Passe Opcional (BIP39)', //Optional Passphrase writtenDown: 'Eu escrevi a minha Seed Phrase', //I have written down my seed phrase + // Seed Phrase Import + importSeedValid: '', //Seed Phrase is valid! + importSeedError: '', //Seed Phrase is invalid! + importSeedErrorSize: '', //A Seed Phrase should be 12 or 24 words long! + importSeedErrorTypo: '', //Seed Phrase contains typing errors! Check your input carefully + importSeedErrorSkip: '', //Seed Phrase appears invalid, but the warning was skipped by the user + // Wallet Dashboard gettingStarted: 'Começar', //Getting Started secureYourWallet: 'Proteja a sua carteira', //Secure your wallet @@ -179,6 +186,8 @@ export const pt_br_translation = { settingsAnalytics: 'Escolha o seu nível de contribuição analítica:', //Choose your analytics contribution level: settingsToggleDebug: 'Modo de depuração', //Debug Mode settingsToggleTestnet: 'Modo Testnet', //Testnet Mode + settingsToggleAdvancedMode: '', //Advanced Mode + settingsToggleAdvancedModeSubtext: '', //This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! // Network switching (mainnet <---> testnet) netSwitchUnsavedWarningTitle: '', //Your {network} wallet isn\'t saved! diff --git a/locale/pt-pt/translation.js b/locale/pt-pt/translation.js index 76ec654e4..c0f7d606a 100644 --- a/locale/pt-pt/translation.js +++ b/locale/pt-pt/translation.js @@ -61,9 +61,16 @@ export const pt_pt_translation = { doNotShare: 'NÃO a compartilhe com ninguém.', //Do NOT share it with anybody. digitalStoreNotAdvised: 'NÃO é aconselhável armazená-lo digitalmente.', //It is NOT advised to store this digitally. - optionalPassphrase: 'Frase Senha Opcional', //Optional Passphrase + optionalPassphrase: 'Frase Senha Opcional (BIP39)', //Optional Passphrase writtenDown: 'Eu escrevi a minha frase-inicial', //I have written down my seed phrase + // Seed Phrase Import + importSeedValid: '', //Seed Phrase is valid! + importSeedError: '', //Seed Phrase is invalid! + importSeedErrorSize: '', //A Seed Phrase should be 12 or 24 words long! + importSeedErrorTypo: '', //Seed Phrase contains typing errors! Check your input carefully + importSeedErrorSkip: '', //Seed Phrase appears invalid, but the warning was skipped by the user + // Wallet Dashboard gettingStarted: 'A Começar', //Getting Started secureYourWallet: 'Proteja a sua carteira', //Secure your wallet @@ -178,6 +185,8 @@ export const pt_pt_translation = { settingsAnalytics: 'Escolha o seu nível de contribuição analítica:', //Choose your analytics contribution level: settingsToggleDebug: 'Modo de depuração', //Debug Mode settingsToggleTestnet: 'Modo Testnet', //Testnet Mode + settingsToggleAdvancedMode: '', //Advanced Mode + settingsToggleAdvancedModeSubtext: '', //This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! // Network switching (mainnet <---> testnet) netSwitchUnsavedWarningTitle: '', //Your {network} wallet isn\'t saved! diff --git a/locale/template/translation.js b/locale/template/translation.js index 02d105807..1246164ef 100644 --- a/locale/template/translation.js +++ b/locale/template/translation.js @@ -74,9 +74,16 @@ var translation = { doNotShareWarning: '', //Anyone with a copy of it can access all of your funds. doNotShare: '', //Do NOT share it with anybody. digitalStoreNotAdvised: '', //It is NOT advised to store this digitally. - optionalPassphrase: '', //Optional Passphrase + optionalPassphrase: '', //Optional Passphrase (BIP39) writtenDown: '', //I have written down my seed phrase + // Seed Phrase Import + importSeedValid: '', //Seed Phrase is valid! + importSeedError: '', //Seed Phrase is invalid! + importSeedErrorSize: '', //A Seed Phrase should be 12 or 24 words long! + importSeedErrorTypo: '', //Seed Phrase contains typing errors! Check your input carefully + importSeedErrorSkip: '', //Seed Phrase appears invalid, but the warning was skipped by the user + // Wallet Dashboard gettingStarted: '', //Getting Started secureYourWallet: '', //Secure your wallet @@ -185,6 +192,8 @@ var translation = { settingsAnalytics: '', //Choose your analytics contribution level: settingsToggleDebug: '', //Debug Mode settingsToggleTestnet: '', //Testnet Mode + settingsToggleAdvancedMode: '', //Advanced Mode + settingsToggleAdvancedModeSubtext: '', //This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! // Network switching (mainnet <---> testnet) netSwitchUnsavedWarningTitle: '', //Your {network} wallet isn\'t saved! diff --git a/locale/uwu/translation.js b/locale/uwu/translation.js index 2f726475c..93e3f9d18 100644 --- a/locale/uwu/translation.js +++ b/locale/uwu/translation.js @@ -61,9 +61,18 @@ export const uwu_translation = { doNotShare: 'Do NOT share it with anyuwu.', //Do NOT share it with anybody. digitalStoreNotAdvised: 'It is NAWT advised to store this digitally.', //It is NOT advised to store this digitally. - optionalPassphrase: 'Optional Passphwase', //Optional Passphrase + optionalPassphrase: 'Optional Passphwase (BIP39)', //Optional Passphrase writtenDown: 'I haz written down my seed phrase', //I have written down my seed phrase + // Seed Phrase Import + importSeedValid: 'Seed Phwase iz valid!', //Seed Phrase is valid! + importSeedError: 'Seed Phwase iz invalid!', //Seed Phrase is invalid! + importSeedErrorSize: 'A Seed Phwase shwould be 12 or 24 words long!', //A Seed Phrase should be 12 or 24 words long! + importSeedErrorTypo: + 'Seed Phwase contains typing ewrrors! Check ur input carefully', //Seed Phrase contains typing errors! Check your input carefully + importSeedErrorSkip: + 'Seed Phwase appears invalid, but da warning was skipped by da user', //Seed Phrase appears invalid, but the warning was skipped by the user + // Wallet Dashboard gettingStarted: 'Getting Stwarted', //Getting Started secureYourWallet: 'Secure ur wawwet', //Secure your wallet @@ -182,6 +191,9 @@ export const uwu_translation = { settingsAutoSelectNet: 'Auto-select Expwowers and Nowodes', // Auto-select Explorers and Nodes settingsToggleDebug: 'Debug Mowode', //Debug Mode settingsToggleTestnet: 'Testnet Mowode', //Testnet Mode + settingsToggleAdvancedMode: 'Advwanced Mowode', //Advanced Mode + settingsToggleAdvancedModeSubtext: + 'Dis unlocks deeper fwunctionality and cuwustomisatwion, but may be oveuhwhelming and potentially dangerwus for unexperienced bakas!', //This unlocks deeper functionality and customisation, but may be overwhelming and potentially dangerous for unexperienced users! // Network switching (mainnet <---> testnet) netSwitchUnsavedWarningTitle: "Ur {network} wawwet isn't saved!", //Your {network} wallet isn\'t saved! diff --git a/scripts/global.js b/scripts/global.js index d3a6f345c..4993b5dad 100644 --- a/scripts/global.js +++ b/scripts/global.js @@ -22,6 +22,7 @@ import { setColdStakingAddress, strColdStakingAddress, nDisplayDecimals, + fAdvancedMode, } from './settings.js'; import { createAndSendTransaction, signTransaction } from './transactions.js'; import { @@ -319,6 +320,7 @@ export async function start() { domVersion: document.getElementById('version'), domFlipdown: document.getElementById('flipdown'), domTestnetToggler: document.getElementById('testnetToggler'), + domAdvancedModeToggler: document.getElementById('advancedModeToggler'), }; await i18nStart(); await loadImages(); @@ -1529,15 +1531,19 @@ export async function accessOrImportWallet() { * * Useful for adjusting the input types or displaying password prompts depending on the import scheme */ -export async function onPrivateKeyChanged() { +export async function guiUpdateImportInput() { if (await hasEncryptedWallet()) return; // Check whether the string is Base64 (would likely be an MPW-encrypted import) // and it doesn't have any spaces (would be a mnemonic seed) - const fContainsSpaces = doms.domPrivKey.value.includes(' '); + const fContainsSpaces = doms.domPrivKey.value.trim().includes(' '); + + // If this could require a Seed Passphrase (BIP39 Passphrase) and Advanced Mode is enabled + // ...or if this is an Encrypted Import (Encrypted Base64 MPW key) + const fBIP39Passphrase = fContainsSpaces && fAdvancedMode; doms.domPrivKeyPassword.hidden = (doms.domPrivKey.value.length < 128 || !isBase64(doms.domPrivKey.value)) && - !fContainsSpaces; + !fBIP39Passphrase; doms.domPrivKeyPassword.placeholder = fContainsSpaces ? translation.optionalPassphrase diff --git a/scripts/index.js b/scripts/index.js index 9187e943d..6610efcee 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -19,7 +19,7 @@ export { accessOrImportWallet, guiImportWallet, guiSetColdStakingAddress, - onPrivateKeyChanged, + guiUpdateImportInput, toClipboard, toggleExportUI, wipePrivateData, @@ -46,7 +46,12 @@ export { govVote, } from './global.js'; export { generateWallet, getNewAddress, importWallet } from './wallet.js'; -export { toggleTestnet, toggleDebug, toggleAutoSwitch } from './settings.js'; +export { + toggleTestnet, + toggleDebug, + toggleAutoSwitch, + toggleAdvancedMode, +} from './settings.js'; export { createTxGUI, undelegateGUI, diff --git a/scripts/settings.js b/scripts/settings.js index 23b8ed361..66bad79a3 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -2,6 +2,7 @@ import { doms, getBalance, getStakingBalance, + guiUpdateImportInput, mempool, refreshChainData, renderActivityGUI, @@ -52,6 +53,8 @@ export let fAutoSwitch = true; export let strColdStakingAddress = 'SdgQDpS8jDRJDX8yK8m9KnTMarsE84zdsy'; /** The decimals to display for the wallet balance */ export let nDisplayDecimals = 2; +/** A mode which configures MPW towards Advanced users, with low-level feature access and less restrictions (Potentially dangerous) */ +export let fAdvancedMode = false; let transparencyReport; @@ -88,6 +91,10 @@ export class Settings { * @type {number} The decimals to display for the wallet balance */ displayDecimals; + /** + * @type {Boolean} Whether Advanced Mode is enabled or disabled + */ + advancedMode; constructor({ analytics, explorer, @@ -97,6 +104,7 @@ export class Settings { translation = '', displayCurrency = 'usd', displayDecimals = nDisplayDecimals, + advancedMode = false, } = {}) { this.analytics = analytics; this.explorer = explorer; @@ -106,6 +114,7 @@ export class Settings { this.translation = translation; this.displayCurrency = displayCurrency; this.displayDecimals = displayDecimals; + this.advancedMode = advancedMode; } } @@ -196,15 +205,22 @@ export async function start() { coldAddress, displayCurrency, displayDecimals, + advancedMode, } = await database.getSettings(); // Set the Cold Staking address strColdStakingAddress = coldAddress; // Set any Toggles to their default or DB state + // Network Auto-Switch fAutoSwitch = autoswitch; doms.domAutoSwitchToggle.checked = fAutoSwitch; + // Advanced Mode + fAdvancedMode = advancedMode; + doms.domAdvancedModeToggler.checked = fAdvancedMode; + await configureAdvancedMode(); + // Set the display currency strCurrency = doms.domCurrencySelect.value = displayCurrency; @@ -620,3 +636,28 @@ async function fillNodeSelect() { // And update the UI to reflect them doms.domNodeSelect.value = cNode.url; } + +/** + * Toggle Advanced Mode at runtime and in DB + */ +export async function toggleAdvancedMode() { + fAdvancedMode = !fAdvancedMode; + + // Configure the app accordingly + await configureAdvancedMode(); + + // Update the setting in the DB + const database = await Database.getInstance(); + await database.setSettings({ advancedMode: fAdvancedMode }); +} + +/** + * Configure the app functionality and UI for the current mode + */ +async function configureAdvancedMode() { + // Re-render the Import Input UI + await guiUpdateImportInput(); + + // Hide or Show the "Mnemonic Passphrase" in the Seed Creation modal + doms.domMnemonicModalPassphrase.hidden = !fAdvancedMode; +} diff --git a/scripts/wallet.js b/scripts/wallet.js index 0c1d52727..23591f02e 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -38,6 +38,7 @@ import * as jdenticon from 'jdenticon'; import { Database } from './database.js'; import { guiRenderCurrentReceiveModal } from './contacts-book.js'; import { Account } from './accounts.js'; +import { debug, fAdvancedMode } from './settings.js'; export let fWalletLoaded = false; @@ -657,39 +658,57 @@ export async function importWallet({ doms.domPrivKey.value = ''; doms.domPrivKeyPassword.value = ''; - if (await verifyMnemonic(privateImportValue)) { - // Generate our masterkey via Mnemonic Phrase + // Clean and verify the Seed Phrase (if one exists) + const cPhraseValidator = await cleanAndVerifySeedPhrase( + privateImportValue, + true + ); + + // If Debugging is enabled, show what the validator returned + if (debug) { + const fnLog = cPhraseValidator.ok ? console.log : console.warn; + fnLog('Seed Import Validator: ' + cPhraseValidator.msg); + } + + // If the Seed is OK, proceed + if (cPhraseValidator.ok) { + // Generate our HD MasterKey with the cleaned (Mnemonic) Seed Phrase const seed = await mnemonicToSeed( - privateImportValue, + cPhraseValidator.phrase, passphrase ); await setMasterKey(new HdMasterKey({ seed })); + } else if (cPhraseValidator.phrase.includes(' ')) { + // The Phrase Validator failed, but the input contains at least one space; possibly a Seed Typo? + return createAlert('warning', cPhraseValidator.msg, 5000); } else { - // Public Key Derivation + // The input definitely isn't a seed, so we'll try every other import method try { + // XPub import (HD view only) if (isXPub(privateImportValue)) { await setMasterKey( new HdMasterKey({ xpub: privateImportValue, }) ); + // XPrv import (HD full access) } else if (privateImportValue.startsWith('xprv')) { await setMasterKey( new HdMasterKey({ xpriv: privateImportValue, }) ); + // Pubkey import (non-HD view only) } else if (isStandardAddress(privateImportValue)) { await setMasterKey( new LegacyMasterKey({ address: privateImportValue, }) ); + // WIF import (non-HD full access) } else { - // Lastly, attempt to parse as a WIF private key + // Attempt to import a raw WIF private key const pkBytes = parseWIF(privateImportValue); - - // Import the raw private key await setMasterKey(new LegacyMasterKey({ pkBytes })); } } catch (e) { @@ -813,37 +832,79 @@ export async function generateWallet(noUI = false) { return masterKey; } -export async function verifyMnemonic(strMnemonic = '', fPopupConfirm = true) { - const nWordCount = strMnemonic.trim().split(/\s+/g).length; +export async function cleanAndVerifySeedPhrase( + strPhraseInput = '', + fPopupConfirm = true +) { + // Clean the phrase (removing unnecessary spaces) and force to lowercase + const strPhrase = strPhraseInput.trim().replace(/\s+/g, ' ').toLowerCase(); - // Sanity check: Convert to lowercase - strMnemonic = strMnemonic.toLowerCase(); + // Count the Words + const nWordCount = strPhrase.trim().split(' ').length; // Ensure it's a word count that makes sense - if (nWordCount >= 12 && nWordCount <= 24) { - if (!validateMnemonic(strMnemonic)) { + if (nWordCount === 12 || nWordCount === 24) { + if (!validateMnemonic(strPhrase)) { + // If a popup is allowed and Advanced Mode is enabled, warn the user that the + // ... seed phrase is potentially bad, and ask for confirmation to proceed + if (!fPopupConfirm || !fAdvancedMode) + return { + ok: false, + msg: translation.importSeedErrorTypo, + phrase: strPhrase, + }; + // The reason we want to ask the user for confirmation is that the mnemonic - // Could have been generated with another app that has a different dictionary - return ( - fPopupConfirm && - (await confirmPopup({ - title: translation.popupSeedPhraseBad, - html: translation.popupSeedPhraseBadNote, - })) - ); + // could have been generated with another app that has a different dictionary + const fSkipWarning = await confirmPopup({ + title: translation.popupSeedPhraseBad, + html: translation.popupSeedPhraseBadNote, + }); + + if (fSkipWarning) { + // User is probably an Arch Linux user and used `-f` + return { + ok: true, + msg: translation.importSeedErrorSkip, + phrase: strPhrase, + }; + } else { + // User heeded the warning and rejected the phrase + return { + ok: false, + msg: translation.importSeedError, + phrase: strPhrase, + }; + } } else { // Valid count and mnemonic - return true; + return { + ok: true, + msg: translation.importSeedValid, + phrase: strPhrase, + }; } } else { // Invalid count - return false; + return { + ok: false, + msg: translation.importSeedErrorSize, + phrase: strPhrase, + }; } } +/** + * Display a Seed Phrase popup to the user and optionally wait for a Seed Passphrase + * @param {String} mnemonic - The Seed Phrase to display to the user + * @returns {Promise} - The Mnemonic Passphrase (empty string if omitted by user) + */ function informUserOfMnemonic(mnemonic) { return new Promise((res, _) => { + // Configure the modal $('#mnemonicModal').modal({ keyboard: false }); + + // Render the Seed Phrase and configure the button doms.domMnemonicModalContent.innerText = mnemonic; doms.domMnemonicModalButton.onclick = () => { res(doms.domMnemonicModalPassphrase.value); @@ -853,6 +914,8 @@ function informUserOfMnemonic(mnemonic) { doms.domMnemonicModalContent.innerText = ''; doms.domMnemonicModalPassphrase.value = ''; }; + + // Display the modal $('#mnemonicModal').modal('show'); }); } From 252b4d7c26f219de1b0b307cf255fb36b2bd2920 Mon Sep 17 00:00:00 2001 From: JSKitty Date: Fri, 1 Sep 2023 22:00:32 +0100 Subject: [PATCH 2/7] Fleek pls build From 2e90bd76c50ec8f890d6d358fbe7e1e2efc38375 Mon Sep 17 00:00:00 2001 From: JSKitty Date: Sat, 2 Sep 2023 15:01:10 +0100 Subject: [PATCH 3/7] Add JSDocs for cleanAndVerifySeedPhrase() --- scripts/wallet.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/wallet.js b/scripts/wallet.js index 23591f02e..0c209cb70 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -832,6 +832,13 @@ export async function generateWallet(noUI = false) { return masterKey; } +/** + * Clean a Seed Phrase string and verify it's integrity + * + * This returns an object of the validation status and the cleaned Seed Phrase for safe low-level usage. + * @param {String} strPhraseInput - The Seed Phrase string + * @param {Boolean} fPopupConfirm - Allow a warning bypass popup if the Seed Phrase is unusual + */ export async function cleanAndVerifySeedPhrase( strPhraseInput = '', fPopupConfirm = true From c43bb8d36cba8f80a2804f6ec67f28defa201f7f Mon Sep 17 00:00:00 2001 From: JSKitty Date: Mon, 4 Sep 2023 15:28:08 +0100 Subject: [PATCH 4/7] Fix type primitive Co-authored-by: Duddino --- scripts/settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/settings.js b/scripts/settings.js index 66bad79a3..e451c327b 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -92,7 +92,7 @@ export class Settings { */ displayDecimals; /** - * @type {Boolean} Whether Advanced Mode is enabled or disabled + * @type {boolean} Whether Advanced Mode is enabled or disabled */ advancedMode; constructor({ From f1b4e61cba5a0a4f9c54c9fc98c77eefcb904162 Mon Sep 17 00:00:00 2001 From: JSKitty Date: Mon, 4 Sep 2023 15:28:25 +0100 Subject: [PATCH 5/7] Fix type primitive Co-authored-by: Duddino --- scripts/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index 0c209cb70..7d6ed35b1 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -903,7 +903,7 @@ export async function cleanAndVerifySeedPhrase( /** * Display a Seed Phrase popup to the user and optionally wait for a Seed Passphrase - * @param {String} mnemonic - The Seed Phrase to display to the user + * @param {string} mnemonic - The Seed Phrase to display to the user * @returns {Promise} - The Mnemonic Passphrase (empty string if omitted by user) */ function informUserOfMnemonic(mnemonic) { From 85fb95d6f78b872594326d3352644443209d0b1c Mon Sep 17 00:00:00 2001 From: JSKitty Date: Mon, 4 Sep 2023 15:28:39 +0100 Subject: [PATCH 6/7] Fix type primitive Co-authored-by: Duddino --- scripts/wallet.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/wallet.js b/scripts/wallet.js index 7d6ed35b1..fc91fbe76 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -904,7 +904,7 @@ export async function cleanAndVerifySeedPhrase( /** * Display a Seed Phrase popup to the user and optionally wait for a Seed Passphrase * @param {string} mnemonic - The Seed Phrase to display to the user - * @returns {Promise} - The Mnemonic Passphrase (empty string if omitted by user) + * @returns {Promise} - The Mnemonic Passphrase (empty string if omitted by user) */ function informUserOfMnemonic(mnemonic) { return new Promise((res, _) => { From 762243cb63a32dcbfd88d09441c4b3efebfe0ad0 Mon Sep 17 00:00:00 2001 From: JSKitty Date: Mon, 4 Sep 2023 15:41:39 +0100 Subject: [PATCH 7/7] Reset Mnemonic inputs on Advanced Mode switch --- scripts/global.js | 5 +++++ scripts/settings.js | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/global.js b/scripts/global.js index 4993b5dad..777445bea 100644 --- a/scripts/global.js +++ b/scripts/global.js @@ -1548,6 +1548,11 @@ export async function guiUpdateImportInput() { doms.domPrivKeyPassword.placeholder = fContainsSpaces ? translation.optionalPassphrase : translation.password; + + // If the "Import Password/Passphrase" is hidden, we'll also wipe it's input, in the + // ... edge-case that a passphrase was entered, then the import key had changed. + if (doms.domPrivKeyPassword.hidden) doms.domPrivKeyPassword.value = ''; + // Uncloak the private input IF spaces are detected, to make Seed Phrases easier to input and verify doms.domPrivKey.setAttribute('type', fContainsSpaces ? 'text' : 'password'); } diff --git a/scripts/settings.js b/scripts/settings.js index e451c327b..9beaf8bc7 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -658,6 +658,7 @@ async function configureAdvancedMode() { // Re-render the Import Input UI await guiUpdateImportInput(); - // Hide or Show the "Mnemonic Passphrase" in the Seed Creation modal + // Hide or Show the "Mnemonic Passphrase" in the Seed Creation modal, and reset it's input + doms.domMnemonicModalPassphrase.value = ''; doms.domMnemonicModalPassphrase.hidden = !fAdvancedMode; }