BlueWallet/class/wallet-import.js

289 lines
9.5 KiB
JavaScript
Raw Normal View History

2019-12-27 03:21:07 +01:00
/* global alert */
import {
SegwitP2SHWallet,
LegacyWallet,
WatchOnlyWallet,
HDLegacyBreadwalletWallet,
HDSegwitP2SHWallet,
HDLegacyP2PKHWallet,
HDSegwitBech32Wallet,
LightningCustodianWallet,
PlaceholderWallet,
SegwitBech32Wallet,
HDLegacyElectrumSeedP2PKHWallet,
HDSegwitElectrumSeedP2WPKHWallet,
HDAezeedWallet,
2020-10-05 23:25:14 +02:00
MultisigHDWallet,
} from '.';
2019-12-27 03:21:07 +01:00
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import loc from '../loc';
import { useContext } from 'react';
import { BlueStorageContext } from '../blue_modules/storage-context';
import Notifications from '../blue_modules/notifications';
2021-01-29 21:26:10 +01:00
import IdleTimerManager from 'react-native-idle-timer';
const A = require('../blue_modules/analytics');
2020-04-07 16:25:56 +02:00
const bip38 = require('../blue_modules/bip38');
const wif = require('wif');
const prompt = require('../blue_modules/prompt');
2019-12-27 03:21:07 +01:00
function WalletImport() {
const { wallets, pendingWallets, setPendingWallets, saveToDisk, addWallet } = useContext(BlueStorageContext);
2020-02-25 15:42:11 +01:00
/**
*
* @param w {AbstractWallet}
2020-02-25 15:42:11 +01:00
* @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet
* @returns {Promise<void>}
* @private
*/
WalletImport._saveWallet = async (w, additionalProperties) => {
2021-01-29 21:26:10 +01:00
IdleTimerManager.setIdleTimerDisabled(false);
if (WalletImport.isWalletImported(w)) {
WalletImport.presentWalletAlreadyExistsAlert();
return;
}
const emptyWalletLabel = new LegacyWallet().getLabel();
ReactNativeHapticFeedback.trigger('notificationSuccess', { ignoreAndroidSystemSettings: false });
if (w.getLabel() === emptyWalletLabel) w.setLabel(loc.wallets.import_imported + ' ' + w.typeReadable);
w.setUserHasSavedExport(true);
if (additionalProperties) {
for (const [key, value] of Object.entries(additionalProperties)) {
w[key] = value;
2019-12-27 03:21:07 +01:00
}
2020-01-01 04:31:04 +01:00
}
addWallet(w);
await saveToDisk();
A(A.ENUM.CREATED_WALLET);
alert(loc.wallets.import_success);
Notifications.majorTomToGroundControl(w.getAllExternalAddresses(), [], []);
};
2019-12-27 03:21:07 +01:00
WalletImport.removePlaceholderWallet = () => {
setPendingWallets([]);
};
2019-12-27 03:21:07 +01:00
WalletImport.isWalletImported = w => {
const wallet = wallets.some(
wallet => (wallet.getSecret() === w.secret || wallet.getID() === w.getID()) && wallet.type !== PlaceholderWallet.type,
);
return !!wallet;
};
WalletImport.presentWalletAlreadyExistsAlert = () => {
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
alert('This wallet has been previously imported.');
};
WalletImport.addPlaceholderWallet = (importText, isFailure = false) => {
2019-12-27 03:21:07 +01:00
const wallet = new PlaceholderWallet();
wallet.setSecret(importText);
wallet.setIsFailure(isFailure);
setPendingWallets([...pendingWallets, wallet]);
2019-12-27 03:21:07 +01:00
return wallet;
};
2019-12-27 03:21:07 +01:00
WalletImport.isCurrentlyImportingWallet = () => {
return wallets.some(wallet => wallet.type === PlaceholderWallet.type);
};
2019-12-27 03:21:07 +01:00
2020-02-25 15:42:11 +01:00
/**
*
* @param importText
* @param additionalProperties key-values passed from outside. Used only to set up `masterFingerprint` property for watch-only wallet
* @returns {Promise<void>}
*/
WalletImport.processImportText = async (importText, additionalProperties) => {
2021-01-29 21:26:10 +01:00
IdleTimerManager.setIdleTimerDisabled(true);
2019-12-27 03:21:07 +01:00
// Plan:
2020-04-07 16:25:56 +02:00
// -2. check if BIP38 encrypted
2020-10-05 23:25:14 +02:00
// -1a. check if multisig
2020-04-07 16:25:56 +02:00
// -1. check lightning custodian
2019-12-27 03:21:07 +01:00
// 0. check if its HDSegwitBech32Wallet (BIP84)
// 1. check if its HDSegwitP2SHWallet (BIP49)
// 2. check if its HDLegacyP2PKHWallet (BIP44)
// 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0")
// 3.1 check HD Electrum legacy
// 3.2 check if its AEZEED
2019-12-27 03:21:07 +01:00
// 4. check if its Segwit WIF (P2SH)
// 5. check if its Legacy WIF
// 6. check if its address (watch-only wallet)
// 7. check if its private key (segwit address P2SH) TODO
// 7. check if its private key (legacy address) TODO
if (importText.startsWith('6P')) {
let password = false;
do {
password = await prompt(loc.wallets.looks_like_bip38, loc.wallets.enter_bip38_password, false);
} while (!password);
2020-04-07 16:25:56 +02:00
2021-03-25 16:28:25 +01:00
const decryptedKey = await bip38.decrypt(importText, password);
2020-10-05 23:25:14 +02:00
if (decryptedKey) {
importText = wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed);
2019-12-27 03:21:07 +01:00
}
}
2019-12-27 03:21:07 +01:00
// is it multisig?
try {
const ms = new MultisigHDWallet();
ms.setSecret(importText);
if (ms.getN() > 0 && ms.getM() > 0) {
await ms.fetchBalance();
return WalletImport._saveWallet(ms);
}
} catch (e) {
console.log(e);
}
2019-12-27 03:21:07 +01:00
// is it lightning custodian?
if (importText.indexOf('blitzhub://') !== -1 || importText.indexOf('lndhub://') !== -1) {
const lnd = new LightningCustodianWallet();
if (importText.includes('@')) {
const split = importText.split('@');
lnd.setBaseURI(split[1]);
lnd.setSecret(split[0]);
} else {
lnd.setBaseURI(LightningCustodianWallet.defaultBaseUri);
lnd.setSecret(importText);
2019-12-27 03:21:07 +01:00
}
lnd.init();
await lnd.authorize();
await lnd.fetchTransactions();
await lnd.fetchUserInvoices();
await lnd.fetchPendingTransactions();
await lnd.fetchBalance();
return WalletImport._saveWallet(lnd);
}
// trying other wallet types
2019-12-27 03:21:07 +01:00
const hd4 = new HDSegwitBech32Wallet();
hd4.setSecret(importText);
if (hd4.validateMnemonic()) {
2021-03-25 16:28:25 +01:00
// OK its a valid BIP39 seed
if (await hd4.wasEverUsed()) {
await hd4.fetchBalance(); // fetching balance for BIP84 only on purpose
return WalletImport._saveWallet(hd4);
2019-12-27 03:21:07 +01:00
}
2021-03-25 16:28:25 +01:00
const hd2 = new HDSegwitP2SHWallet();
hd2.setSecret(importText);
if (await hd2.wasEverUsed()) {
return WalletImport._saveWallet(hd2);
}
const hd3 = new HDLegacyP2PKHWallet();
hd3.setSecret(importText);
if (await hd3.wasEverUsed()) {
return WalletImport._saveWallet(hd3);
}
const hd1 = new HDLegacyBreadwalletWallet();
hd1.setSecret(importText);
if (await hd1.wasEverUsed()) {
return WalletImport._saveWallet(hd1);
}
// no scheme (BIP84/BIP49/BIP44/Bread) was ever used. lets import as default BIP84:
return WalletImport._saveWallet(hd4);
}
2019-12-27 03:21:07 +01:00
const segwitWallet = new SegwitP2SHWallet();
segwitWallet.setSecret(importText);
if (segwitWallet.getAddress()) {
// ok its a valid WIF
2019-12-27 03:21:07 +01:00
const segwitBech32Wallet = new SegwitBech32Wallet();
segwitBech32Wallet.setSecret(importText);
2021-03-25 16:28:25 +01:00
if (await segwitBech32Wallet.wasEverUsed()) {
// yep, its single-address bech32 wallet
2021-03-25 16:28:25 +01:00
await segwitBech32Wallet.fetchBalance();
return WalletImport._saveWallet(segwitBech32Wallet);
2021-03-25 16:28:25 +01:00
}
if (await segwitWallet.wasEverUsed()) {
// yep, its single-address bech32 wallet
await segwitWallet.fetchBalance();
return WalletImport._saveWallet(segwitWallet);
2019-12-27 03:21:07 +01:00
}
2021-03-25 16:28:25 +01:00
// default wallet is Legacy
const legacyWallet = new LegacyWallet();
legacyWallet.setSecret(importText);
return WalletImport._saveWallet(legacyWallet);
}
// case - WIF is valid, just has uncompressed pubkey
2019-12-27 03:21:07 +01:00
const legacyWallet = new LegacyWallet();
legacyWallet.setSecret(importText);
if (legacyWallet.getAddress()) {
await legacyWallet.fetchBalance();
await legacyWallet.fetchTransactions();
return WalletImport._saveWallet(legacyWallet);
}
// if we're here - nope, its not a valid WIF
2019-12-27 03:21:07 +01:00
try {
const hdElectrumSeedLegacy = new HDSegwitElectrumSeedP2WPKHWallet();
hdElectrumSeedLegacy.setSecret(importText);
2021-03-25 16:28:25 +01:00
if (hdElectrumSeedLegacy.validateMnemonic()) {
// not fetching txs or balances, fuck it, yolo, life is too short
return WalletImport._saveWallet(hdElectrumSeedLegacy);
2019-12-27 03:21:07 +01:00
}
} catch (_) {}
2019-12-27 03:21:07 +01:00
try {
const hdElectrumSeedLegacy = new HDLegacyElectrumSeedP2PKHWallet();
hdElectrumSeedLegacy.setSecret(importText);
2021-03-25 16:28:25 +01:00
if (hdElectrumSeedLegacy.validateMnemonic()) {
// not fetching txs or balances, fuck it, yolo, life is too short
return WalletImport._saveWallet(hdElectrumSeedLegacy);
2019-12-27 03:21:07 +01:00
}
} catch (_) {}
2019-12-27 03:21:07 +01:00
// is it AEZEED?
try {
const aezeed = new HDAezeedWallet();
aezeed.setSecret(importText);
if (await aezeed.validateMnemonicAsync()) {
// not fetching txs or balances, fuck it, yolo, life is too short
return WalletImport._saveWallet(aezeed);
} else {
// there is a chance that a password is required
if (await aezeed.mnemonicInvalidPassword()) {
const password = await prompt(loc.wallets.enter_bip38_password, '', false);
if (!password) {
// no passord is basically cancel whole aezeed import process
throw new Error(loc._.bad_password);
}
const mnemonics = importText.split(':')[0];
return WalletImport.processImportText(mnemonics + ':' + password);
}
}
} catch (_) {}
// not valid? maybe its a watch-only address?
2019-12-27 03:21:07 +01:00
const watchOnly = new WatchOnlyWallet();
watchOnly.setSecret(importText);
if (watchOnly.valid()) {
await watchOnly.fetchBalance();
return WalletImport._saveWallet(watchOnly, additionalProperties);
2019-12-27 03:21:07 +01:00
}
// nope?
// TODO: try a raw private key
2021-01-29 21:26:10 +01:00
IdleTimerManager.setIdleTimerDisabled(false);
throw new Error('Could not recognize format');
};
2021-01-29 21:26:10 +01:00
IdleTimerManager.setIdleTimerDisabled(false);
return null;
2019-12-27 03:21:07 +01:00
}
export default WalletImport;