REF: import procedure improvements

This commit is contained in:
Overtorment 2021-03-25 15:28:25 +00:00
parent c839c9a9dc
commit 7dbcf9c576
7 changed files with 287 additions and 86 deletions

View File

@ -116,9 +116,7 @@ function WalletImport() {
password = await prompt(loc.wallets.looks_like_bip38, loc.wallets.enter_bip38_password, false); password = await prompt(loc.wallets.looks_like_bip38, loc.wallets.enter_bip38_password, false);
} while (!password); } while (!password);
const decryptedKey = await bip38.decrypt(importText, password, status => { const decryptedKey = await bip38.decrypt(importText, password);
console.warn(status.percent + '%');
});
if (decryptedKey) { if (decryptedKey) {
importText = wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed); importText = wif.encode(0x80, decryptedKey.privateKey, decryptedKey.compressed);
@ -162,11 +160,33 @@ function WalletImport() {
const hd4 = new HDSegwitBech32Wallet(); const hd4 = new HDSegwitBech32Wallet();
hd4.setSecret(importText); hd4.setSecret(importText);
if (hd4.validateMnemonic()) { if (hd4.validateMnemonic()) {
await hd4.fetchBalance(); // OK its a valid BIP39 seed
if (hd4.getBalance() > 0) {
// await hd4.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later if (await hd4.wasEverUsed()) {
await hd4.fetchBalance(); // fetching balance for BIP84 only on purpose
return WalletImport._saveWallet(hd4); return WalletImport._saveWallet(hd4);
} }
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);
} }
const segwitWallet = new SegwitP2SHWallet(); const segwitWallet = new SegwitP2SHWallet();
@ -174,28 +194,24 @@ function WalletImport() {
if (segwitWallet.getAddress()) { if (segwitWallet.getAddress()) {
// ok its a valid WIF // ok its a valid WIF
const legacyWallet = new LegacyWallet();
legacyWallet.setSecret(importText);
const segwitBech32Wallet = new SegwitBech32Wallet(); const segwitBech32Wallet = new SegwitBech32Wallet();
segwitBech32Wallet.setSecret(importText); segwitBech32Wallet.setSecret(importText);
if (await segwitBech32Wallet.wasEverUsed()) {
await legacyWallet.fetchBalance();
await segwitBech32Wallet.fetchBalance();
if (legacyWallet.getBalance() > 0) {
// yep, its legacy we're importing
await legacyWallet.fetchTransactions();
return WalletImport._saveWallet(legacyWallet);
} else if (segwitBech32Wallet.getBalance() > 0) {
// yep, its single-address bech32 wallet // yep, its single-address bech32 wallet
await segwitBech32Wallet.fetchTransactions(); await segwitBech32Wallet.fetchBalance();
return WalletImport._saveWallet(segwitBech32Wallet); return WalletImport._saveWallet(segwitBech32Wallet);
} else { }
// by default, we import wif as Segwit P2SH
if (await segwitWallet.wasEverUsed()) {
// yep, its single-address bech32 wallet
await segwitWallet.fetchBalance(); await segwitWallet.fetchBalance();
await segwitWallet.fetchTransactions();
return WalletImport._saveWallet(segwitWallet); return WalletImport._saveWallet(segwitWallet);
} }
// default wallet is Legacy
const legacyWallet = new LegacyWallet();
legacyWallet.setSecret(importText);
return WalletImport._saveWallet(legacyWallet);
} }
// case - WIF is valid, just has uncompressed pubkey // case - WIF is valid, just has uncompressed pubkey
@ -210,20 +226,10 @@ function WalletImport() {
// if we're here - nope, its not a valid WIF // if we're here - nope, its not a valid WIF
const hd1 = new HDLegacyBreadwalletWallet();
hd1.setSecret(importText);
if (hd1.validateMnemonic()) {
await hd1.fetchBalance();
if (hd1.getBalance() > 0) {
// await hd1.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
return WalletImport._saveWallet(hd1);
}
}
try { try {
const hdElectrumSeedLegacy = new HDSegwitElectrumSeedP2WPKHWallet(); const hdElectrumSeedLegacy = new HDSegwitElectrumSeedP2WPKHWallet();
hdElectrumSeedLegacy.setSecret(importText); hdElectrumSeedLegacy.setSecret(importText);
if (await hdElectrumSeedLegacy.wasEverUsed()) { if (hdElectrumSeedLegacy.validateMnemonic()) {
// not fetching txs or balances, fuck it, yolo, life is too short // not fetching txs or balances, fuck it, yolo, life is too short
return WalletImport._saveWallet(hdElectrumSeedLegacy); return WalletImport._saveWallet(hdElectrumSeedLegacy);
} }
@ -232,7 +238,7 @@ function WalletImport() {
try { try {
const hdElectrumSeedLegacy = new HDLegacyElectrumSeedP2PKHWallet(); const hdElectrumSeedLegacy = new HDLegacyElectrumSeedP2PKHWallet();
hdElectrumSeedLegacy.setSecret(importText); hdElectrumSeedLegacy.setSecret(importText);
if (await hdElectrumSeedLegacy.wasEverUsed()) { if (hdElectrumSeedLegacy.validateMnemonic()) {
// not fetching txs or balances, fuck it, yolo, life is too short // not fetching txs or balances, fuck it, yolo, life is too short
return WalletImport._saveWallet(hdElectrumSeedLegacy); return WalletImport._saveWallet(hdElectrumSeedLegacy);
} }
@ -260,64 +266,11 @@ function WalletImport() {
} }
} catch (_) {} } catch (_) {}
const hd2 = new HDSegwitP2SHWallet();
hd2.setSecret(importText);
if (hd2.validateMnemonic()) {
await hd2.fetchBalance();
if (hd2.getBalance() > 0) {
// await hd2.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
return WalletImport._saveWallet(hd2);
}
}
const hd3 = new HDLegacyP2PKHWallet();
hd3.setSecret(importText);
if (hd3.validateMnemonic()) {
await hd3.fetchBalance();
if (hd3.getBalance() > 0) {
// await hd3.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
return WalletImport._saveWallet(hd3);
}
}
// no balances? how about transactions count?
if (hd1.validateMnemonic()) {
await hd1.fetchTransactions();
if (hd1.getTransactions().length !== 0) {
return WalletImport._saveWallet(hd1);
}
}
if (hd2.validateMnemonic()) {
await hd2.fetchTransactions();
if (hd2.getTransactions().length !== 0) {
return WalletImport._saveWallet(hd2);
}
}
if (hd3.validateMnemonic()) {
await hd3.fetchTransactions();
if (hd3.getTransactions().length !== 0) {
return WalletImport._saveWallet(hd3);
}
}
if (hd4.validateMnemonic()) {
await hd4.fetchTransactions();
if (hd4.getTransactions().length !== 0) {
return WalletImport._saveWallet(hd4);
}
}
// is it even valid? if yes we will import as:
if (hd4.validateMnemonic()) {
return WalletImport._saveWallet(hd4);
}
// not valid? maybe its a watch-only address? // not valid? maybe its a watch-only address?
const watchOnly = new WatchOnlyWallet(); const watchOnly = new WatchOnlyWallet();
watchOnly.setSecret(importText); watchOnly.setSecret(importText);
if (watchOnly.valid()) { if (watchOnly.valid()) {
// await watchOnly.fetchTransactions(); // experiment: dont fetch tx now. it will import faster. user can refresh his wallet later
await watchOnly.fetchBalance(); await watchOnly.fetchBalance();
return WalletImport._saveWallet(watchOnly, additionalProperties); return WalletImport._saveWallet(watchOnly, additionalProperties);
} }

View File

@ -534,4 +534,14 @@ export class LegacyWallet extends AbstractWallet {
// null, true so it can verify Electrum signatores without errors // null, true so it can verify Electrum signatores without errors
return bitcoinMessage.verify(message, address, signature, null, true); return bitcoinMessage.verify(message, address, signature, null, true);
} }
/**
* Probes address for transactions, if there are any returns TRUE
*
* @returns {Promise<boolean>}
*/
async wasEverUsed() {
const txs = await BlueElectrum.getTransactionsByAddress(this.getAddress());
return txs.length > 0;
}
} }

View File

@ -0,0 +1,180 @@
import {
HDSegwitElectrumSeedP2WPKHWallet,
HDLegacyBreadwalletWallet,
HDSegwitBech32Wallet,
HDLegacyElectrumSeedP2PKHWallet,
LegacyWallet,
SegwitP2SHWallet,
SegwitBech32Wallet,
HDLegacyP2PKHWallet,
HDSegwitP2SHWallet,
WatchOnlyWallet,
HDAezeedWallet,
} from '../../class';
import WalletImport from '../../class/wallet-import';
import React from 'react';
const assert = require('assert');
global.net = require('net'); // needed by Electrum client. For RN it is proviced in shim.js
global.tls = require('tls'); // needed by Electrum client. For RN it is proviced in shim.js
const BlueElectrum = require('../../blue_modules/BlueElectrum'); // so it connects ASAP
/** @type HDSegwitBech32Wallet */
let lastImportedWallet;
React.useContext = jest.fn(() => {
return {
wallets: [],
pendingWallets: [],
setPendingWallets: function () {},
saveToDisk: function () {},
addWallet: function (wallet) {
lastImportedWallet = wallet;
},
};
});
jest.mock('../../blue_modules/prompt', () => {
return jest.fn(() => {
return 'qwerty';
});
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
afterAll(async () => {
// after all tests we close socket so the test suite can actually terminate
BlueElectrum.forceDisconnect();
});
beforeAll(async () => {
// awaiting for Electrum to be connected. For RN Electrum would naturally connect
// while app starts up, but for tests we need to wait for it
await BlueElectrum.waitTillConnected();
WalletImport(); // wut
});
describe('import procedure', function () {
it('can import BIP84', async () => {
await WalletImport.processImportText(
'always direct find escape liar turn differ shy tool gap elder galaxy lawn wild movie fog moon spread casual inner box diagram outdoor tell',
);
assert.strictEqual(lastImportedWallet.type, HDSegwitBech32Wallet.type);
assert.strictEqual(lastImportedWallet._getExternalAddressByIndex(0), 'bc1qth9qxvwvdthqmkl6x586ukkq8zvumd38nxr08l');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported HD SegWit (BIP84 Bech32 Native)');
});
it('can import Legacy', async () => {
await WalletImport.processImportText('KztVRmc2EJJBHi599mCdXrxMTsNsGy3NUjc3Fb3FFDSMYyMDRjnv');
assert.strictEqual(lastImportedWallet.type, LegacyWallet.type);
assert.strictEqual(lastImportedWallet.getAddress(), '1AhcdMCzby4VXgqrexuMfh7eiSprRFtN78');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported Legacy (P2PKH)');
});
it('can import Legacy P2SH Segwit', async () => {
await WalletImport.processImportText('L3NxFnYoBGjJ5PhxrxV6jorvjnc8cerYJx71vXU6ta8BXQxHVZya');
assert.strictEqual(lastImportedWallet.type, SegwitP2SHWallet.type);
assert.strictEqual(lastImportedWallet.getAddress(), '3KM9VfdsDf9uT7uwZagoKgVn8z35m9CtSM');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported SegWit (P2SH)');
});
// todo:
it('can import Legacy Bech32 Segwit', async () => {
await WalletImport.processImportText('L1T6FfKpKHi8JE6eBKrsXkenw34d5FfFzJUZ6dLs2utxkSvsDfxZ');
assert.strictEqual(lastImportedWallet.type, SegwitBech32Wallet.type);
assert.strictEqual(lastImportedWallet.getAddress(), 'bc1q763rf54hzuncmf8dtlz558uqe4f247mq39rjvr');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported P2 WPKH');
});
it('can import BIP44', async () => {
await WalletImport.processImportText(
'sting museum endless duty nice riot because swallow brother depth weapon merge woman wish hold finish venture gauge stomach bomb device bracket agent parent',
);
assert.strictEqual(lastImportedWallet.type, HDLegacyP2PKHWallet.type);
assert.strictEqual(lastImportedWallet._getExternalAddressByIndex(0), '1EgDbwf5nXp9knoaWW6nV6N91EK3EFQ5vC');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported HD Legacy (BIP44 P2PKH)');
});
it('can import BIP49', async () => {
await WalletImport.processImportText(
'believe torch sport lizard absurd retreat scale layer song pen clump combine window staff dream filter latin bicycle vapor anchor put clean gain slush',
);
assert.strictEqual(lastImportedWallet.type, HDSegwitP2SHWallet.type);
assert.strictEqual(lastImportedWallet._getExternalAddressByIndex(0), '3EoqYYp7hQSHn5nHqRtWzkgqmK3caQ2SUu');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported HD SegWit (BIP49 P2SH)');
});
it('can import BIP49', async () => {
await WalletImport.processImportText(
'believe torch sport lizard absurd retreat scale layer song pen clump combine window staff dream filter latin bicycle vapor anchor put clean gain slush',
);
assert.strictEqual(lastImportedWallet.type, HDSegwitP2SHWallet.type);
assert.strictEqual(lastImportedWallet._getExternalAddressByIndex(0), '3EoqYYp7hQSHn5nHqRtWzkgqmK3caQ2SUu');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported HD SegWit (BIP49 P2SH)');
});
it('can import HD Legacy Electrum (BIP32 P2PKH)', async () => {
await WalletImport.processImportText('eight derive blast guide smoke piece coral burden lottery flower tomato flame');
assert.strictEqual(lastImportedWallet.type, HDLegacyElectrumSeedP2PKHWallet.type);
assert.strictEqual(lastImportedWallet._getExternalAddressByIndex(0), '1FgVfJ5D3HyKWKC4xk36Cio7MUaxxnXaVd');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported HD Legacy Electrum (BIP32 P2PKH)');
});
it('can import BreadWallet', async () => {
await WalletImport.processImportText(
'tired lesson alert attend giggle fancy nose enter ethics fashion fly dove dutch hidden toe argue save fish catch patient waste gift divorce whisper',
);
assert.strictEqual(lastImportedWallet.type, HDLegacyBreadwalletWallet.type);
assert.strictEqual(lastImportedWallet._getExternalAddressByIndex(0), '1j7TbbTv8adcZFr4RC7Cyr7GN9VGYTecu');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported HD Legacy Breadwallet (P2PKH)');
});
it('can import HD Electrum (BIP32 P2WPKH)', async () => {
await WalletImport.processImportText('noble mimic pipe merry knife screen enter dune crop bonus slice card');
assert.strictEqual(lastImportedWallet.type, HDSegwitElectrumSeedP2WPKHWallet.type);
assert.strictEqual(lastImportedWallet._getExternalAddressByIndex(0), 'bc1qzzanxnr3xv9a5ha264kpzpfq260qvuameslddu');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported HD Electrum (BIP32 P2WPKH)');
});
it('can import AEZEED', async () => {
await WalletImport.processImportText(
'abstract rhythm weird food attract treat mosquito sight royal actor surround ride strike remove guilt catch filter summer mushroom protect poverty cruel chaos pattern',
);
assert.strictEqual(lastImportedWallet.type, HDAezeedWallet.type);
});
it('importing empty BIP39 should yield BIP84', async () => {
const tempWallet = new HDSegwitBech32Wallet();
await tempWallet.generate();
await WalletImport.processImportText(tempWallet.getSecret());
assert.strictEqual(lastImportedWallet.type, HDSegwitBech32Wallet.type);
});
it('can import Legacy with uncompressed pubkey', async () => {
await WalletImport.processImportText('5KE6tf9vhYkzYSbgEL6M7xvkY69GMFHF3WxzYaCFMvwMxn3QgRS');
assert.strictEqual(lastImportedWallet.getSecret(), '5KE6tf9vhYkzYSbgEL6M7xvkY69GMFHF3WxzYaCFMvwMxn3QgRS');
assert.strictEqual(lastImportedWallet.type, LegacyWallet.type);
assert.strictEqual(lastImportedWallet.getAddress(), '1GsJDeD6fqS912egpjhdjrUTiCh1hhwBgQ');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported Legacy (P2PKH)');
});
it('can import BIP38 encrypted backup', async () => {
await WalletImport.processImportText('6PnU5voARjBBykwSddwCdcn6Eu9EcsK24Gs5zWxbJbPZYW7eiYQP8XgKbN');
assert.strictEqual(lastImportedWallet.getSecret(), 'KxqRtpd9vFju297ACPKHrGkgXuberTveZPXbRDiQ3MXZycSQYtjc');
assert.strictEqual(lastImportedWallet.type, LegacyWallet.type);
assert.strictEqual(lastImportedWallet.getAddress(), '1639W2kM6UY9PdavMQeLqG4SuUEae9NZfq');
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported Legacy (P2PKH)');
});
it('can import watch-only address', async () => {
await WalletImport.processImportText('1AhcdMCzby4VXgqrexuMfh7eiSprRFtN78');
assert.strictEqual(lastImportedWallet.type, WatchOnlyWallet.type);
await WalletImport.processImportText('3EoqYYp7hQSHn5nHqRtWzkgqmK3caQ2SUu');
assert.strictEqual(lastImportedWallet.type, WatchOnlyWallet.type);
await WalletImport.processImportText('bc1q8j4lk4qlhun0n7h5ahfslfldc8zhlxgynfpdj2');
assert.strictEqual(lastImportedWallet.type, WatchOnlyWallet.type);
await WalletImport.processImportText(
'zpub6r7jhKKm7BAVx3b3nSnuadY1WnshZYkhK8gKFoRLwK9rF3Mzv28BrGcCGA3ugGtawi1WLb2vyjQAX9ZTDGU5gNk2bLdTc3iEXr6tzR1ipNP',
);
assert.strictEqual(lastImportedWallet.type, WatchOnlyWallet.type);
});
});

View File

@ -109,3 +109,29 @@ jest.mock('realm', () => {
open: jest.fn(() => realmInstanceMock), open: jest.fn(() => realmInstanceMock),
}; };
}); });
jest.mock('react-native-idle-timer', () => {
return {
setIdleTimerDisabled: jest.fn(),
};
});
jest.mock('react-native-haptic-feedback', () => {
return {
trigger: jest.fn(),
};
});
jest.mock('../blue_modules/analytics', () => {
const ret = jest.fn();
ret.ENUM = { CREATED_WALLET: '' };
return ret;
});
jest.mock('../blue_modules/notifications', () => {
return {
majorTomToGroundControl: jest.fn(),
};
});
global.alert = () => {};

View File

@ -36,6 +36,12 @@ describe('HDAezeedWallet', () => {
assert.ok(await aezeed.validateMnemonicAsync()); assert.ok(await aezeed.validateMnemonicAsync());
assert.ok(!(await aezeed.mnemonicInvalidPassword())); assert.ok(!(await aezeed.mnemonicInvalidPassword()));
aezeed.setSecret(
'able concert slush lend olive cost wagon dawn board robot park snap dignity churn fiction quote shrimp hammer wing jump immune skill sunset west:aezeed',
);
assert.ok(await aezeed.validateMnemonicAsync());
assert.ok(!(await aezeed.mnemonicInvalidPassword()));
aezeed.setSecret( aezeed.setSecret(
'abstract rhythm weird food attract treat mosquito sight royal actor surround ride strike remove guilt catch filter summer mushroom protect poverty cruel chaos pattern', 'abstract rhythm weird food attract treat mosquito sight royal actor surround ride strike remove guilt catch filter summer mushroom protect poverty cruel chaos pattern',
); );

View File

@ -2,6 +2,20 @@ import { HDLegacyElectrumSeedP2PKHWallet } from '../../class';
const assert = require('assert'); const assert = require('assert');
describe('HDLegacyElectrumSeedP2PKHWallet', () => { describe('HDLegacyElectrumSeedP2PKHWallet', () => {
it('wont accept BIP39 seed', () => {
const hd = new HDLegacyElectrumSeedP2PKHWallet();
hd.setSecret(
'honey risk juice trip orient galaxy win situate shoot anchor bounce remind horse traffic exotic since escape mimic ramp skin judge owner topple erode',
);
assert.ok(!hd.validateMnemonic());
});
it('wont accept electrum seed, but SEGWIT seed', () => {
const hd = new HDLegacyElectrumSeedP2PKHWallet();
hd.setSecret('method goddess humble crumble output snake essay carpet monster barely trip betray ');
assert.ok(!hd.validateMnemonic());
});
it('can import mnemonics and generate addresses and WIFs', async function () { it('can import mnemonics and generate addresses and WIFs', async function () {
const hd = new HDLegacyElectrumSeedP2PKHWallet(); const hd = new HDLegacyElectrumSeedP2PKHWallet();
hd.setSecret('receive happy wash prosper update pet neck acid try profit proud hungry '); hd.setSecret('receive happy wash prosper update pet neck acid try profit proud hungry ');

View File

@ -2,6 +2,18 @@ import { HDSegwitElectrumSeedP2WPKHWallet } from '../../class';
const assert = require('assert'); const assert = require('assert');
describe('HDSegwitElectrumSeedP2WPKHWallet', () => { describe('HDSegwitElectrumSeedP2WPKHWallet', () => {
it('wont accept BIP39 seed', () => {
const hd = new HDSegwitElectrumSeedP2WPKHWallet();
hd.setSecret('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about');
assert.ok(!hd.validateMnemonic());
});
it('wont accept electrum seed, but STANDARD p2pkh seed', () => {
const hd = new HDSegwitElectrumSeedP2WPKHWallet();
hd.setSecret('receive happy wash prosper update pet neck acid try profit proud hungry ');
assert.ok(!hd.validateMnemonic());
});
it('can import mnemonics and generate addresses and WIFs', async function () { it('can import mnemonics and generate addresses and WIFs', async function () {
const hd = new HDSegwitElectrumSeedP2WPKHWallet(); const hd = new HDSegwitElectrumSeedP2WPKHWallet();
hd.setSecret('method goddess humble crumble output snake essay carpet monster barely trip betray '); hd.setSecret('method goddess humble crumble output snake essay carpet monster barely trip betray ');