diff --git a/locale/de/translation.js b/locale/de/translation.js index 8eb169108..5e58e6dcc 100644 --- a/locale/de/translation.js +++ b/locale/de/translation.js @@ -302,6 +302,7 @@ export default { INTERNAL_ERROR: 'Interner Fehler, bitte versuche es später erneut', //Internal error, please try again later FAILED_TO_IMPORT: 'Import fehlgeschlagen! Falsches Passwort', //Failed to import! Invalid password + FAILED_TO_IMPORT_HARDWARE: '', // Failed to import Hardware Wallet. UNSUPPORTED_CHARACTER: 'Das Zeichen {char} ist nicht erlaubt in der Adresse! (Nicht Base58 kompatibel)', //The character '{char}' is unsupported in addresses! (Not Base58 compatible) UNSUPPORTED_WEBWORKERS: @@ -435,6 +436,9 @@ export default { WALLET_CONFIRM_L: 'Bestätige den Import auf deinem Ledger', //Confirm the import on your Ledger WALLET_NO_HARDWARE: 'Kein Gerät verfügbar
Es wurde keine Hardware-Geldbörse gefunden, bitte stecke es ein und entsperre es!', //No device available
Couldn't find a hardware wallet; please plug it in and unlock! + WALLET_HARDWARE_UDEV: '', // The OS denied access Did you add the udev rules? + WALLET_HARDWARE_NO_ACCESS: '', // The OS denied access Please check your Operating System settings. + WALLET_HARDWARE_CONNECTION_LOST: 'Verbindung zum {hardwareWallet} verloren
So wie es scheint, wurde das {hardwareWalletProductionName} im Prozess abgezogen, oops!', //Lost connection to {hardwareWallet}
It seems the {hardwareWalletProductionName} was unplugged mid-operation, oops! WALLET_HARDWARE_BUSY: diff --git a/locale/en/translation.js b/locale/en/translation.js index 8433caa9b..3a5416a3b 100644 --- a/locale/en/translation.js +++ b/locale/en/translation.js @@ -299,6 +299,7 @@ export default { INTERNAL_ERROR: 'Internal error, please try again later', FAILED_TO_IMPORT: 'Failed to import! Invalid password', + FAILED_TO_IMPORT_HARDWARE: ' Failed to import Hardware Wallet.', TESTNET_ENCRYPTION_DISABLED: 'Testnet Mode is ON!
Wallet encryption disabled', PASSWORD_TOO_SMALL: @@ -424,6 +425,10 @@ export default { WALLET_CONFIRM_L: 'Confirm the import on your Ledger', WALLET_NO_HARDWARE: "No device available
Couldn't find a hardware wallet; please plug it in and unlock!", + WALLET_HARDWARE_UDEV: + 'The OS denied access Did you add the udev rules?', + WALLET_HARDWARE_NO_ACCESS: + 'The OS denied access Please check your Operating System settings.', WALLET_HARDWARE_CONNECTION_LOST: 'Lost connection to {hardwareWallet}
It seems the {hardwareWallet} was unplugged mid-operation, oops!', WALLET_HARDWARE_BUSY: diff --git a/locale/es-mx/translation.js b/locale/es-mx/translation.js index 4d8351db4..2324bcd48 100644 --- a/locale/es-mx/translation.js +++ b/locale/es-mx/translation.js @@ -306,6 +306,7 @@ export default { INTERNAL_ERROR: 'Error interno, vuelve a intentarlo más tarde', //Internal error, please try again later FAILED_TO_IMPORT: '¡No se ha podido importar! Contraseña inválida', //Failed to import! Invalid password + FAILED_TO_IMPORT_HARDWARE: '', // Failed to import Hardware Wallet. UNSUPPORTED_CHARACTER: '¡El carácter {char} no está soportado en las direcciones! (No compatible con Base58)', //The character '{char}' is unsupported in addresses! (Not Base58 compatible) UNSUPPORTED_WEBWORKERS: @@ -422,6 +423,8 @@ export default { WALLET_CONFIRM_L: 'Confirma la importación en tu Ledger', //Confirm the import on your Ledger WALLET_NO_HARDWARE: 'No hay ningún dispositivo disponible
No se pudo encontrar una wallet de hardware; ¡Conéctala y desbloquéala!', //No device available
Couldn't find a hardware wallet; please plug it in and unlock! + WALLET_HARDWARE_UDEV: '', // The OS denied access Did you add the udev rules? + WALLET_HARDWARE_NO_ACCESS: '', // The OS denied access Please check your Operating System settings. WALLET_HARDWARE_CONNECTION_LOST: 'Se perdió la conexión con {hardwareWallet}
Parece que {hardwareWalletProductionName} se desconectó en mitad de la operación, ¡ups!', //Lost connection to {hardwareWallet}
It seems the {hardwareWalletProductionName} was unplugged mid-operation, oops! WALLET_HARDWARE_BUSY: diff --git a/locale/fr/translation.js b/locale/fr/translation.js index 4d2793d9d..5bf4b2ec1 100644 --- a/locale/fr/translation.js +++ b/locale/fr/translation.js @@ -308,6 +308,7 @@ export default { INTERNAL_ERROR: 'Erreur interne, veuillez réessayer plus tard', //Internal error, please try again later FAILED_TO_IMPORT: "Échec de l'importation ! Mot de passe invalide", //Failed to import! Invalid password + FAILED_TO_IMPORT_HARDWARE: '', // Failed to import Hardware Wallet. UNSUPPORTED_CHARACTER: "Le caractère {char} n'est pas pris en charge dans les adresses ! (Non compatible avec Base58)", //The character '{char}' is unsupported in addresses! (Not Base58 compatible) UNSUPPORTED_WEBWORKERS: @@ -426,6 +427,8 @@ export default { WALLET_CONFIRM_L: "Confirmez l'importation dans votre Ledger", //Confirm the import on your Ledger WALLET_NO_HARDWARE: "Aucun dispositif disponible
Il n'a pas été possible de trouver un portefeuille de hardware; brancher et déverrouiller!", //No device available
Couldn't find a hardware wallet; please plug it in and unlock! + WALLET_HARDWARE_UDEV: '', // The OS denied access Did you add the udev rules? + WALLET_HARDWARE_NO_ACCESS: '', // The OS denied access Please check your Operating System settings. WALLET_HARDWARE_CONNECTION_LOST: "Perte de connexion avec le {hardwareWallet}
Oops! Il semble que {hardwareWalletProductionName} a été déconnecté au milieu de l'opération.", //Lost connection to {hardwareWallet}
It seems the {hardwareWalletProductionName} was unplugged mid-operation, oops! WALLET_HARDWARE_BUSY: diff --git a/locale/it/translation.js b/locale/it/translation.js index 6e758871c..e7cf47c2d 100644 --- a/locale/it/translation.js +++ b/locale/it/translation.js @@ -288,6 +288,8 @@ export default { INTERNAL_ERROR: 'Errore interno, rirova più tardi', //Internal error, please try again later FAILED_TO_IMPORT: 'Impossibile importare! Password non valida', //Failed to import! Invalid password + FAILED_TO_IMPORT_HARDWARE: + 'Impossibile importare il wallet Hardware!', // Failed to import Hardware Wallet. UNSUPPORTED_CHARACTER: "Il carattere '{char}' non è supportato negli indirizzi! (Non compatibile con Base58)", //The character '{char}' is unsupported in addresses! (Not Base58 compatible) UNSUPPORTED_WEBWORKERS: @@ -400,6 +402,11 @@ export default { WALLET_CONFIRM_L: "Conferma l'importo sulla tua Ledger", //Confirm the import on your Ledger WALLET_NO_HARDWARE: 'Nessun dispositivo disponibile
Impossibile trovare un wallet hardware; per favore collegalo e sbloccalo!', //No device available
Couldn't find a hardware wallet; please plug it in and unlock! + WALLET_HARDWARE_UDEV: + " Il Sistema operativo ha negato l'accesso Hai aggiunto le regole udev?", // The OS denied access Did you add the udev rules? + WALLET_HARDWARE_NO_ACCESS: + " Il Sistema Operativo ha negato l'accesso Perfavore, controlla le impostazioni del tuo sistema operativo.", // The OS denied access Please check your Operating System settings. + WALLET_HARDWARE_CONNECTION_LOST: "Connessione a {hardwareWallet} persa
Sembra che {hardwareWalletProductionName} sia stato scollegato durante l'operazione, ops!", //Lost connection to {hardwareWallet}
It seems the {hardwareWalletProductionName} was unplugged mid-operation, oops! WALLET_HARDWARE_BUSY: diff --git a/locale/ph/translation.js b/locale/ph/translation.js index 58f3fe837..28b3c8daf 100644 --- a/locale/ph/translation.js +++ b/locale/ph/translation.js @@ -431,6 +431,8 @@ export default { WALLET_CONFIRM_L: 'Kumpirmahin ang import sa iyong Ledger', //Confirm the import on your Ledger WALLET_NO_HARDWARE: 'Walang pwedeng magamit na device
Hindi makahanap ng hardware wallet; pakiusap i-plug in ito at buksan!', //No device available
Couldn't find a hardware wallet; please plug it in and unlock! + WALLET_HARDWARE_UDEV: '', // The OS denied access Did you add the udev rules? + WALLET_HARDWARE_NO_ACCESS: '', // The OS denied access Please check your Operating System settings. WALLET_HARDWARE_CONNECTION_LOST: 'Nawala ang koneksyon sa {hardwareWallet}
Parang ang {hardwareWalletProductionName} ay na-unplug sa kalagitnaan ng operasyon, oops!', //Lost connection to {hardwareWallet}
It seems the {hardwareWalletProductionName} was unplugged mid-operation, oops! WALLET_HARDWARE_BUSY: diff --git a/locale/pt-br/translation.js b/locale/pt-br/translation.js index 37e9bb94f..45ea5ef8e 100644 --- a/locale/pt-br/translation.js +++ b/locale/pt-br/translation.js @@ -434,6 +434,8 @@ export default { WALLET_CONFIRM_L: 'Confirme a importação na sua Ledger', //Confirm the import on your Ledger", WALLET_NO_HARDWARE: 'Nenhum dispositivo disponível
Não foi possível encontrar uma carteira de hardware; conecte-a e desbloqueie-a!', //No device available
Couldn't find a hardware wallet; please plug it in and unlock! + WALLET_HARDWARE_UDEV: '', // The OS denied access Did you add the udev rules? + WALLET_HARDWARE_NO_ACCESS: '', // The OS denied access Please check your Operating System settings. WALLET_HARDWARE_CONNECTION_LOST: 'Conexão perdida com a {hardwareWallet}
Oops! Parece que a {hardwareWalletProductionName} foi desconectada no meio da operação.', //Lost connection to {hardwareWallet}
It seems the {hardwareWalletProductionName} was unplugged mid-operation, oops! WALLET_HARDWARE_BUSY: diff --git a/locale/pt-pt/translation.js b/locale/pt-pt/translation.js index 5f28ae6c1..4e448bb7c 100644 --- a/locale/pt-pt/translation.js +++ b/locale/pt-pt/translation.js @@ -434,6 +434,8 @@ export default { WALLET_CONFIRM_L: 'Confirme a importação na sua Ledger', //Confirm the import on your Ledger", WALLET_NO_HARDWARE: 'Nenhum dispositivo disponível
Não foi possível encontrar uma carteira de hardware; conecte-a e desbloqueie-a!', //No device available
Couldn't find a hardware wallet; please plug it in and unlock! + WALLET_HARDWARE_UDEV: '', // The OS denied access Did you add the udev rules? + WALLET_HARDWARE_NO_ACCESS: '', // The OS denied access Please check your Operating System settings. WALLET_HARDWARE_CONNECTION_LOST: 'Conexão perdida com a {hardwareWallet}
Oops! Parece que a {hardwareWalletProductionName} foi desconectado no meio da operação.', //Lost connection to {hardwareWallet}
It seems the {hardwareWalletProductionName} was unplugged mid-operation, oops! WALLET_HARDWARE_BUSY: diff --git a/locale/template/translation.js b/locale/template/translation.js index 103c89015..5a49df470 100644 --- a/locale/template/translation.js +++ b/locale/template/translation.js @@ -292,6 +292,7 @@ export default { INTERNAL_ERROR: '', //Internal error, please try again later FAILED_TO_IMPORT: '', //Failed to import! Invalid password + FAILED_TO_IMPORT_HARDWARE: '', // Failed to import Hardware Wallet. UNSUPPORTED_CHARACTER: '', //The character '{char}' is unsupported in addresses! (Not Base58 compatible) UNSUPPORTED_WEBWORKERS: '', //This browser doesn\'t support Web Workers (multi-threaded JS), unfortunately you cannot generate Vanity wallets! INVALID_ADDRESS: '', //Invalid PIVX address!
{address} @@ -369,6 +370,8 @@ export default { WALLET_CONFIRM_L: '', //Confirm the import on your Ledger WALLET_NO_HARDWARE: '', //No device available
Couldn't find a hardware wallet; please plug it in and unlock! WALLET_HARDWARE_CONNECTION_LOST: '', //Lost connection to {hardwareWallet}
It seems the {hardwareWalletProductionName} was unplugged mid-operation, oops! + WALLET_HARDWARE_UDEV: '', // The OS denied access Did you add the udev rules? + WALLET_HARDWARE_NO_ACCESS: '', // The OS denied access Please check your Operating System settings. WALLET_HARDWARE_BUSY: '', //{hardwareWallet} is waiting
Please unlock your {hardwareWalletProductionName} or finish it's current prompt WALLET_HARDWARE_ERROR: '', // {hardwareWallet}
{error} diff --git a/locale/uwu/translation.js b/locale/uwu/translation.js index 6c180d0b2..64d234775 100644 --- a/locale/uwu/translation.js +++ b/locale/uwu/translation.js @@ -430,6 +430,10 @@ export default { "No device avaiwable ☹
Couldn't find a hawdware wawwet; pwease pwug it in and unwock!", //"No device available
Couldn't find a hardware wallet; please plug it in and unlock!", WALLET_HARDWARE_CONNECTION_LOST: 'Wost connection to da {hardwareWallet}
It seems da {hardwareWalletProductionName} was unpwugged mid-opewation, oops!!', // "Lost connection to {hardwareWallet}
It seems the {hardwareWalletProductionName} was unplugged mid-operation, oops!", + WALLET_HARDWARE_UDEV: + 'Onii-chan noticed the OS denied access~ OwO Did you add the udev rules? UwU', // The OS denied access Did you add the udev rules? + WALLET_HARDWARE_NO_ACCESS: + 'Nyaa~ The OS denied access, nya~ Please purr-retty please check your Operating System settings, nya~ UwU', // The OS denied access Please check your Operating System settings. WALLET_HARDWARE_BUSY: "{hardwareWallet} is waiting!
Pwease unwock yowour {hardwareWalletProductionName} or finish it's cuwwent pwompt", //"{hardwareWallet} is waiting
Please unlock your {hardwareWalletProductionName} or finish it's current prompt", WALLET_HARDWARE_ERROR: ' {hardwareWallet}
{error}', //" {hardwareWallet}
{error}" diff --git a/scripts/global.js b/scripts/global.js index 8a50d263c..47f1d201a 100644 --- a/scripts/global.js +++ b/scripts/global.js @@ -1610,6 +1610,7 @@ export async function wipePrivateData() { * @returns {Promise} - If the unlock was successful or rejected */ export async function restoreWallet(strReason = '') { + if (wallet.isHardwareWallet()) return true; // Build up the UI elements based upon conditions for the unlock prompt let strHTML = ''; diff --git a/scripts/ledger.js b/scripts/ledger.js index f64b390a5..21d015786 100644 --- a/scripts/ledger.js +++ b/scripts/ledger.js @@ -2,17 +2,23 @@ import createXpub from 'create-xpub'; import { ALERTS, tr } from './i18n.js'; import AppBtc from '@ledgerhq/hw-app-btc'; import TransportWebUSB from '@ledgerhq/hw-transport-webusb'; -import { createAlert, sleep } from './misc.js'; +import { createAlert } from './misc.js'; +/** + * @type{TransportWebUSB} + */ let transport; +/** + * @type {AppBtc?} + */ export let cHardwareWallet = null; export let strHardwareName = ''; -export async function getHardwareWalletKeys( - path, - xpub = false, - verify = false, - _attempts = 0 -) { +/** + * Get hardware wallet keys. + * @param {string} path - bip32 path to the key + * @returns {Promise} + */ +export async function getHardwareWalletKeys(path, xpub = false, verify = true) { try { // Check if we haven't setup a connection yet OR the previous connection disconnected if (!cHardwareWallet || transport._disconnectEmitted) { @@ -46,13 +52,13 @@ export async function getHardwareWalletKeys( } catch (e) { if (e.message.includes('denied by the user')) { // User denied an operation - return false; + return null; } // If there's no device, nudge the user to plug it in. if (e.message.toLowerCase().includes('no device selected')) { createAlert('info', ALERTS.WALLET_NO_HARDWARE, 10000); - return false; + return null; } // If the device is unplugged, or connection lost through other means (such as spontanious device explosion) @@ -66,19 +72,7 @@ export async function getHardwareWalletKeys( ]), 10000 ); - return false; - } - if (_attempts < 10) { - // This is an ugly hack :( - // in the event where multiple parts of the code decide to ask for an address, just - // Retry at most 10 times waiting 200ms each time - await sleep(200); - return await getHardwareWalletKeys( - path, - xpub, - verify, - _attempts + 1 - ); + return null; } // If the ledger is busy, just nudge the user. @@ -92,7 +86,20 @@ export async function getHardwareWalletKeys( ]), 7500 ); - return false; + return null; + } + + // This is when the OS denies access to the WebUSB + // It's likely caused by faulty udev rules on linux + if (e instanceof DOMException && e.message.includes('Access Denied')) { + if (navigator.userAgent.toLowerCase().includes('linux')) { + createAlert('warning', ALERTS.WALLET_HARDWARE_UDEV, 5500); + } else { + createAlert('warning', ALERTS.WALLET_HARDWARE_NO_ACCESS, 5500); + } + + console.error(e); + return; } // Check if this is an expected error @@ -100,7 +107,7 @@ export async function getHardwareWalletKeys( console.error( 'MISSING LEDGER ERROR-CODE TRANSLATION! - Please report this below error on our GitHub so we can handle it more nicely!' ); - console.error(e); + throw e; } // Translate the error to a user-friendly string (if possible) @@ -117,7 +124,7 @@ export async function getHardwareWalletKeys( 5500 ); - return false; + return null; } } diff --git a/scripts/masterkey.js b/scripts/masterkey.js index b87b2a89a..4dc6828b1 100644 --- a/scripts/masterkey.js +++ b/scripts/masterkey.js @@ -21,38 +21,37 @@ export class MasterKey { /** * @param {String} [path] - BIP32 path pointing to the private key. - * @return {Promise>} Array of bytes containing private key + * @return {number[]} array of bytes containing private key * @abstract */ - async getPrivateKeyBytes(_path) { + getPrivateKeyBytes(_path) { throw new Error('Not implemented'); } /** * @param {String} [path] - BIP32 path pointing to the private key. - * @return {Promise} encoded private key + * @return {string} encoded private key * @abstract */ - async getPrivateKey(path) { - return generateOrEncodePrivkey(await this.getPrivateKeyBytes(path)) - .strWIF; + getPrivateKey(path) { + return generateOrEncodePrivkey(this.getPrivateKeyBytes(path)).strWIF; } /** * @param {String} [path] - BIP32 path pointing to the address - * @return {Promise} Address + * @return {string} Address * @abstract */ - async getAddress(path) { - return deriveAddress({ pkBytes: await this.getPrivateKeyBytes(path) }); + getAddress(path) { + return deriveAddress({ pkBytes: this.getPrivateKeyBytes(path) }); } /** * @param {String} path - BIP32 path pointing to the xpub - * @return {Promise} xpub + * @return {string} xpub * @abstract */ - async getxpub(_path) { + getxpub(_path) { throw new Error('Not implemented'); } @@ -103,15 +102,8 @@ export class MasterKey { } // Construct a full BIP44 pubkey derivation path from it's parts - getDerivationPath(nAccount, nReceiving, nIndex) { - // Coin-Type is different on Ledger, as such, for local wallets; we modify it if we're using a Ledger to derive a key - const strCoinType = this.isHardwareWallet - ? cChainParams.current.BIP44_TYPE_LEDGER - : cChainParams.current.BIP44_TYPE; - if (!this.isHD && !this.isHardwareWallet) { - return `:)//${strCoinType}'`; - } - return `m/44'/${strCoinType}'/${nAccount}'/${nReceiving}/${nIndex}`; + getDerivationPath(_nAccount, _nReceiving, _nIndex) { + throw new Error('Not implemented'); } } @@ -129,7 +121,7 @@ export class HdMasterKey extends MasterKey { this._isHardwareWallet = false; } - async getPrivateKeyBytes(path) { + getPrivateKeyBytes(path) { if (this.isViewOnly) { throw new Error( 'Trying to get private key bytes from a view only key' @@ -145,7 +137,7 @@ export class HdMasterKey extends MasterKey { return this._hdKey.privateExtendedKey; } - async getxpub(path) { + getxpub(path) { if (this.isViewOnly) return this._hdKey.publicExtendedKey; return this._hdKey.derive(path).publicExtendedKey; } @@ -183,54 +175,71 @@ export class HdMasterKey extends MasterKey { .join('/') ).publicExtendedKey; } -} -export class HardwareWalletMasterKey extends MasterKey { - constructor() { - super(); - this._isHD = true; - this._isHardwareWallet = true; - } - async getPrivateKeyBytes(_path) { - throw new Error('Hardware wallets cannot export private keys'); + getDerivationPath(nAccount, nReceiving, nIndex) { + return `m/44'/${cChainParams.current.BIP44_TYPE}'/${nAccount}'/${nReceiving}/${nIndex}`; } +} - async getAddress(path, { verify } = {}) { - return deriveAddress({ - publicKey: await this.getPublicKey(path, { verify }), +export class HardwareWalletMasterKey extends HdMasterKey { + /** + * Trick to get private constructors + */ + static #initializing = false; + constructor(xpub) { + if (!HardwareWalletMasterKey.#initializing) { + throw new Error( + 'Hardware wallet master keys must be created with create' + ); + } + HardwareWalletMasterKey.#initializing = false; + super({ + xpub, }); + this._isHardwareWallet = true; + this._isViewOnly = true; } - async getPublicKey(path, { verify } = {}) { - return deriveAddress({ - publicKey: await getHardwareWalletKeys(path, false, verify), - output: 'COMPRESSED_HEX', - }); + /** + * @param {number} nAccount + * @returns {Promise} + */ + static async create(nAccount = 0) { + const path = this.getDerivationPath(nAccount, 0, 0) + .split('/') + .slice(0, 4) + .join('/'); + const xpub = await getHardwareWalletKeys(path, true, false); + if (!xpub) throw new Error('Failed to get hardware wallet keys.'); + HardwareWalletMasterKey.#initializing = true; + return new HardwareWalletMasterKey(xpub); } - get keyToBackup() { - throw new Error("Hardware wallets don't have keys to backup"); - } + /** + * Verifies that the address is correct by asking the ledger + * directly and then the user. + * This is considerably slower than deriving the key ourselves + * with `getAddress`, but should be used every time we want to be sure + * the address cannot be tampered with + * @param {string} path - bip32 path + * @returns {Promise} address or null if the user rejected the verification + */ + async verifyAddress(path) { + const publicKey = await getHardwareWalletKeys(path); - async getxpub(path) { - if (!this.xpub) { - this.xpub = await getHardwareWalletKeys(path, true); - } - return this.xpub; + return deriveAddress({ publicKey }); } - // Hardware Wallets don't have exposed private data - wipePrivateData(_nAccount) {} - - get isViewOnly() { - return false; + getDerivationPath(nAccount, nReceiving, nIndex) { + return HardwareWalletMasterKey.getDerivationPath( + nAccount, + nReceiving, + nIndex + ); } - getKeyToExport(nAccount) { - const derivationPath = this.getDerivationPath(nAccount, 0, 0) - .split('/') - .slice(0, 4) - .join('/'); - return this.getxpub(derivationPath); + + static getDerivationPath(nAccount, nReceiving, nIndex) { + return `m/44'/${cChainParams.current.BIP44_TYPE_LEDGER}'/${nAccount}'/${nReceiving}/${nIndex}`; } } @@ -252,7 +261,7 @@ export class LegacyMasterKey extends MasterKey { return this._address; } - async getPrivateKeyBytes(_path) { + getPrivateKeyBytes(_path) { if (this.isViewOnly) { throw new Error( 'Trying to get private key bytes from a view only key' @@ -265,7 +274,7 @@ export class LegacyMasterKey extends MasterKey { return generateOrEncodePrivkey(this._pkBytes).strWIF; } - async getxpub(_path) { + getxpub(_path) { throw new Error( 'Trying to get an extended public key from a legacy address' ); @@ -275,4 +284,13 @@ export class LegacyMasterKey extends MasterKey { this._pkBytes = null; this._isViewOnly = true; } + + /** + * This is a bit of a hack: + * Legacy master keys don't need derivation paths. + * We're going to make one nonetheless, to generalize things a bit + */ + getDerivationPath(_nAccount, _nReceiving, _nIndex) { + return `:)//${cChainParams.current.BIP44_TYPE}'`; + } } diff --git a/scripts/transactions.js b/scripts/transactions.js index 24d52d944..776ef85c4 100644 --- a/scripts/transactions.js +++ b/scripts/transactions.js @@ -11,7 +11,7 @@ import { guiSetColdStakingAddress, } from './global.js'; import { cHardwareWallet, strHardwareName } from './ledger.js'; -import { wallet } from './wallet.js'; +import { wallet, getNewAddress } from './wallet.js'; import { HdMasterKey } from './masterkey.js'; import { Mempool, UTXO } from './mempool.js'; import { getNetwork } from './network.js'; @@ -226,7 +226,7 @@ export async function undelegateGUI() { if (!validateAmount(nAmount)) return; // Generate a new address to undelegate towards - const [address] = await wallet.getNewAddress(); + const [address] = wallet.getNewAddress(); // Perform the TX const cTxRes = await createAndSendTransaction({ @@ -296,7 +296,7 @@ export async function createAndSendTransaction({ // Compute change (or lack thereof) const nChange = cCoinControl.nValue - (nFee + amount); - const [changeAddress, changeAddressPath] = await wallet.getNewAddress({ + const [changeAddress, changeAddressPath] = await getNewAddress({ verify: wallet.isHardwareWallet(), }); @@ -419,7 +419,7 @@ export async function createAndSendTransaction({ } if (!isDelegation && !isProposal) { - const path = await wallet.isOwnAddress(address); + const path = wallet.isOwnAddress(address); // If the tx was sent to yourself, add it to the mempool if (path) { @@ -452,7 +452,7 @@ export async function createMasternode() { return; // Generate the Masternode collateral - const [address] = await wallet.getNewAddress(); + const [address] = getNewAddress({ verify: wallet.isHardwareWallet() }); const result = await createAndSendTransaction({ amount: cChainParams.current.collateralInSats, address, diff --git a/scripts/wallet.js b/scripts/wallet.js index 351cf0d87..2a9b7486c 100644 --- a/scripts/wallet.js +++ b/scripts/wallet.js @@ -28,6 +28,7 @@ import { Database } from './database.js'; import { guiRenderCurrentReceiveModal } from './contacts-book.js'; import { Account } from './accounts.js'; import { debug, fAdvancedMode } from './settings.js'; + import { strHardwareName, getHardwareWalletKeys } from './ledger.js'; import { getEventEmitter } from './event_bus.js'; export let fWalletLoaded = false; @@ -123,7 +124,7 @@ export class Wallet { /** * Set or replace the active Master Key with a new Master Key - * @param {Promise} mk - The new Master Key to set active + * @param {import('./masterkey.js').MasterKey} mk - The new Master Key to set active */ async setMasterKey(mk) { this.#masterKey = mk; @@ -133,36 +134,33 @@ export class Wallet { /** * Derive the current address (by internal index) - * @return {Promise} Address + * @return {string} Address * */ - async getCurrentAddress() { - return await this.getAddress(0, this.#addressIndex); + getCurrentAddress() { + return this.getAddress(0, this.#addressIndex); } /** * Derive a generic address (given nReceiving and nIndex) - * @return {Promise} Address + * @return {string} Address */ - async getAddress(nReceiving = 0, nIndex = 0) { + getAddress(nReceiving = 0, nIndex = 0) { const path = this.getDerivationPath(nReceiving, nIndex); - return await this.#masterKey.getAddress(path); + return this.#masterKey.getAddress(path); } /** * Derive xpub (given nReceiving and nIndex) - * @return {Promise} Address + * @return {string} Address */ - async getXPub(nReceiving = 0, nIndex = 0) { - if (this.isHD()) { - // Get our current wallet XPub - const derivationPath = this.getDerivationPath(nReceiving, nIndex) - .split('/') - .slice(0, 4) - .join('/'); - return await this.#masterKey.getxpub(derivationPath); - } - throw new Error('Legacy wallet does not have a xpub'); + getXPub(nReceiving = 0, nIndex = 0) { + // Get our current wallet XPub + const derivationPath = this.getDerivationPath(nReceiving, nIndex) + .split('/') + .slice(0, 4) + .join('/'); + return this.#masterKey.getxpub(derivationPath); } /** @@ -183,7 +181,7 @@ export class Wallet { // Prepare to Add/Update an account in the DB const cAccount = new Account({ - publicKey: await this.getKeyToExport(), + publicKey: this.getKeyToExport(), encWif: strEncWIF, }); @@ -204,9 +202,9 @@ export class Wallet { } /** - * @return Promise<[string, string]> Address and its BIP32 derivation path + * @return [string, string] Address and its BIP32 derivation path */ - async getNewAddress() { + getNewAddress() { const last = getNetwork().lastWallet; this.#addressIndex = (this.#addressIndex > last ? this.#addressIndex : last) + 1; @@ -215,20 +213,19 @@ export class Wallet { this.#addressIndex = last; } const path = this.getDerivationPath(0, this.#addressIndex); - const address = await this.getAddress(0, this.#addressIndex); + const address = this.getAddress(0, this.#addressIndex); return [address, path]; } - // If the privateKey is null then the user connected a hardware wallet + isHardwareWallet() { - if (!this.#masterKey) return false; - return this.#masterKey.isHardwareWallet == true; + return this.#masterKey?.isHardwareWallet === true; } /** * @param {string} address - address to check - * @return {Promise} BIP32 path or null if it's not your address + * @return {string?} BIP32 path or null if it's not your address */ - async isOwnAddress(address) { + isOwnAddress(address) { if (this.#ownAddresses.has(address)) { return this.#ownAddresses.get(address); } @@ -238,15 +235,14 @@ export class Wallet { if (this.isHD()) { for (let i = 0; i <= this.#addressIndex + MAX_ACCOUNT_GAP; i++) { const path = this.getDerivationPath(0, i); - const testAddress = await this.#masterKey.getAddress(path); + const testAddress = this.#masterKey.getAddress(path); if (address === testAddress) { this.#ownAddresses.set(address, path); return path; } } } else { - const value = - address === (await this.getKeyToExport()) ? ':)' : null; + const value = address === this.getKeyToExport() ? ':)' : null; this.#ownAddresses.set(address, value); return value; } @@ -266,8 +262,8 @@ export class Wallet { ); } - async getKeyToExport() { - return await this.#masterKey?.getKeyToExport(this.#nAccount); + getKeyToExport() { + return this.#masterKey?.getKeyToExport(this.#nAccount); } } @@ -307,14 +303,25 @@ export async function importWallet({ ); } // Derive our hardware address and import! - await wallet.setMasterKey(new HardwareWalletMasterKey()); - const publicKey = await getHardwareWalletKeys( - wallet.getDerivationPath() - ); - // Errors are handled within the above function, so there's no need for an 'else' here, just silent ignore. - if (!publicKey) { - await wallet.setMasterKey(null); - return; + try { + const key = await HardwareWalletMasterKey.create(0); + await wallet.setMasterKey(key); + } catch (e) { + // Display a properly translated error if it's a ledger error + if ( + e instanceof Error && + e.message === 'Failed to get hardware wallet keys.' + ) { + // console.error so we get a backtrace if needed + console.error(e); + return createAlert( + 'warning', + translation.FAILED_TO_IMPORT_HARDWARE, + 5000 + ); + } else { + throw e; + } } createAlert( @@ -427,7 +434,7 @@ export async function importWallet({ doms.domDashboard.click(); // Update identicon - doms.domIdenticon.dataset.jdenticonValue = await wallet.getAddress(); + doms.domIdenticon.dataset.jdenticonValue = wallet.getAddress(); jdenticon.update('#identicon'); // Hide the encryption prompt if the user is using @@ -490,7 +497,7 @@ export async function generateWallet(noUI = false) { setDisplayForAllWalletOptions('none'); // Update identicon - doms.domIdenticon.dataset.jdenticonValue = await wallet.getAddress(); + doms.domIdenticon.dataset.jdenticonValue = wallet.getAddress(); jdenticon.update('#identicon'); await getNewAddress({ updateGUI: true }); @@ -631,14 +638,15 @@ export async function getNewAddress({ updateGUI = false, verify = false, } = {}) { - const [address, path] = await wallet.getNewAddress(); + const [address, path] = wallet.getNewAddress(); if (verify && wallet.isHardwareWallet()) { // Generate address to present to the user without asking to verify const confAddress = await confirmPopup({ title: ALERTS.CONFIRM_POPUP_VERIFY_ADDR, html: createAddressConfirmation(address), - resolvePromise: wallet.getMasterKey().getAddress(path, { verify }), + resolvePromise: wallet.getMasterKey().verifyAddress(path), }); + console.log(address, confAddress); if (address !== confAddress) { throw new Error('User did not verify address'); }