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');
}