mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2024-11-19 01:40:12 +01:00
Merge pull request #3406 from BlueWallet/limpbrains-passphrase2
ADD: UI for import wallet with passphrase
This commit is contained in:
commit
64aed0657c
@ -609,7 +609,7 @@ export class BlueCopyTextToClipboard extends Component {
|
||||
disabled={this.state.hasTappedText}
|
||||
testID="BlueCopyTextToClipboard"
|
||||
>
|
||||
<Animated.Text style={styleCopyTextToClipboard.address} numberOfLines={0}>
|
||||
<Animated.Text style={styleCopyTextToClipboard.address} numberOfLines={0} testID="AddressValue">
|
||||
{this.state.address}
|
||||
</Animated.Text>
|
||||
</TouchableOpacity>
|
||||
|
@ -60,10 +60,9 @@ function WalletImport() {
|
||||
return !!wallet;
|
||||
};
|
||||
|
||||
WalletImport.removePlaceholderWallet = ()=> {
|
||||
setIsImportingWallet(false)
|
||||
}
|
||||
|
||||
WalletImport.removePlaceholderWallet = () => {
|
||||
setIsImportingWallet(false);
|
||||
};
|
||||
|
||||
WalletImport.addPlaceholderWallet = (importText, isFailure = false) => {
|
||||
const placeholderWallet = new PlaceholderWallet();
|
||||
@ -81,8 +80,60 @@ function WalletImport() {
|
||||
*
|
||||
* @param importText
|
||||
* @returns {Promise<void>}
|
||||
* @returns {Promise<{text: string, password: string|void}>}
|
||||
*/
|
||||
WalletImport.processImportText = async importText => {
|
||||
WalletImport.askPasswordIfNeeded = async importText => {
|
||||
const text = importText.trim();
|
||||
let password;
|
||||
|
||||
// BIP38 password required
|
||||
if (text.startsWith('6P')) {
|
||||
do {
|
||||
password = await prompt(loc.wallets.looks_like_bip38, loc.wallets.enter_bip38_password);
|
||||
} while (!password);
|
||||
return { text, password };
|
||||
}
|
||||
|
||||
// HD BIP39 wallet password is optinal
|
||||
const hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(text);
|
||||
if (hd.validateMnemonic()) {
|
||||
password = await prompt(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message);
|
||||
return { text, password };
|
||||
}
|
||||
|
||||
// AEZEED password needs to be correct
|
||||
const aezeed = new HDAezeedWallet();
|
||||
aezeed.setSecret(text);
|
||||
if (await aezeed.mnemonicInvalidPassword()) {
|
||||
do {
|
||||
password = await prompt('', loc.wallets.enter_bip38_password);
|
||||
aezeed.setPassphrase(password);
|
||||
} while (await aezeed.mnemonicInvalidPassword());
|
||||
return { text, password };
|
||||
}
|
||||
|
||||
// SLIP39 wallet password is optinal
|
||||
if (text.includes('\n')) {
|
||||
const s1 = new SLIP39SegwitP2SHWallet();
|
||||
s1.setSecret(text);
|
||||
|
||||
if (s1.validateMnemonic()) {
|
||||
password = await prompt(loc.wallets.import_passphrase_title, loc.wallets.import_passphrase_message);
|
||||
return { text, password };
|
||||
}
|
||||
}
|
||||
|
||||
return { text, password };
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param importText
|
||||
* @param password
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
WalletImport.processImportText = async (importText, password) => {
|
||||
IdleTimerManager.setIdleTimerDisabled(true);
|
||||
// Plan:
|
||||
// -2. check if BIP38 encrypted
|
||||
@ -94,6 +145,7 @@ function WalletImport() {
|
||||
// 3. check if its HDLegacyBreadwalletWallet (no BIP, just "m/0")
|
||||
// 3.1 check HD Electrum legacy
|
||||
// 3.2 check if its AEZEED
|
||||
// 3.3 check if its SLIP39
|
||||
// 4. check if its Segwit WIF (P2SH)
|
||||
// 5. check if its Legacy WIF
|
||||
// 6. check if its address (watch-only wallet)
|
||||
@ -103,11 +155,6 @@ function WalletImport() {
|
||||
importText = importText.trim();
|
||||
|
||||
if (importText.startsWith('6P')) {
|
||||
let password = false;
|
||||
do {
|
||||
password = await prompt(loc.wallets.looks_like_bip38, loc.wallets.enter_bip38_password, false);
|
||||
} while (!password);
|
||||
|
||||
const decryptedKey = await bip38.decrypt(importText, password);
|
||||
|
||||
if (decryptedKey) {
|
||||
@ -148,8 +195,7 @@ function WalletImport() {
|
||||
const hd4 = new HDSegwitBech32Wallet();
|
||||
hd4.setSecret(importText);
|
||||
if (hd4.validateMnemonic()) {
|
||||
// OK its a valid BIP39 seed
|
||||
|
||||
hd4.setPassphrase(password);
|
||||
if (await hd4.wasEverUsed()) {
|
||||
await hd4.fetchBalance(); // fetching balance for BIP84 only on purpose
|
||||
return WalletImport._saveWallet(hd4);
|
||||
@ -157,18 +203,21 @@ function WalletImport() {
|
||||
|
||||
const hd2 = new HDSegwitP2SHWallet();
|
||||
hd2.setSecret(importText);
|
||||
hd2.setPassphrase(password);
|
||||
if (await hd2.wasEverUsed()) {
|
||||
return WalletImport._saveWallet(hd2);
|
||||
}
|
||||
|
||||
const hd3 = new HDLegacyP2PKHWallet();
|
||||
hd3.setSecret(importText);
|
||||
hd3.setPassphrase(password);
|
||||
if (await hd3.wasEverUsed()) {
|
||||
return WalletImport._saveWallet(hd3);
|
||||
}
|
||||
|
||||
const hd1 = new HDLegacyBreadwalletWallet();
|
||||
hd1.setSecret(importText);
|
||||
hd1.setPassphrase(password);
|
||||
if (await hd1.wasEverUsed()) {
|
||||
return WalletImport._saveWallet(hd1);
|
||||
}
|
||||
@ -245,21 +294,10 @@ function WalletImport() {
|
||||
try {
|
||||
const aezeed = new HDAezeedWallet();
|
||||
aezeed.setSecret(importText);
|
||||
aezeed.setPassphrase(password);
|
||||
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 (_) {}
|
||||
|
||||
@ -270,11 +308,13 @@ function WalletImport() {
|
||||
s1.setSecret(importText);
|
||||
|
||||
if (s1.validateMnemonic()) {
|
||||
s1.setPassphrase(password);
|
||||
if (await s1.wasEverUsed()) {
|
||||
return WalletImport._saveWallet(s1);
|
||||
}
|
||||
|
||||
const s2 = new SLIP39LegacyP2PKHWallet();
|
||||
s2.setPassphrase(password);
|
||||
s2.setSecret(importText);
|
||||
if (await s2.wasEverUsed()) {
|
||||
return WalletImport._saveWallet(s2);
|
||||
@ -282,6 +322,7 @@ function WalletImport() {
|
||||
|
||||
const s3 = new SLIP39SegwitBech32Wallet();
|
||||
s3.setSecret(importText);
|
||||
s3.setPassphrase(password);
|
||||
return WalletImport._saveWallet(s3);
|
||||
}
|
||||
}
|
||||
|
@ -425,6 +425,8 @@
|
||||
"enter_bip38_password": "Enter password to decrypt",
|
||||
"export_title": "Wallet Export",
|
||||
"import_do_import": "Import",
|
||||
"import_passphrase_title": "Passphrase",
|
||||
"import_passphrase_message": "Enter passphrase if you have used any",
|
||||
"import_error": "Failed to import. Please make sure that the provided data is valid.",
|
||||
"import_explanation": "Please enter your seed words, public key, WIF, or anything you’ve got. BlueWallet will do its best to guess the correct format and import your wallet.",
|
||||
"import_file": "Import File",
|
||||
|
@ -136,14 +136,6 @@ const WalletExport = () => {
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
{wallet.getPassphrase && wallet.getPassphrase() && (
|
||||
<>
|
||||
<BlueSpacing20 />
|
||||
<BlueText style={stylesHook.secret} testID="Passphrase">
|
||||
{wallet.getPassphrase()}
|
||||
</BlueText>
|
||||
</>
|
||||
)}
|
||||
</ScrollView>
|
||||
</SafeBlueArea>
|
||||
);
|
||||
|
@ -73,11 +73,20 @@ const WalletsImport = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
WalletImport.addPlaceholderWallet(importText);
|
||||
let res;
|
||||
try {
|
||||
res = await WalletImport.askPasswordIfNeeded(importText);
|
||||
} catch (e) {
|
||||
// prompt cancelled
|
||||
return;
|
||||
}
|
||||
const { text, password } = res;
|
||||
|
||||
WalletImport.addPlaceholderWallet(text);
|
||||
navigation.dangerouslyGetParent().pop();
|
||||
await new Promise(resolve => setTimeout(resolve, 500)); // giving some time to animations
|
||||
try {
|
||||
await WalletImport.processImportText(importText);
|
||||
await WalletImport.processImportText(text, password);
|
||||
WalletImport.removePlaceholderWallet();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
@ -1145,6 +1145,34 @@ describe('BlueWallet UI Tests', () => {
|
||||
|
||||
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
||||
});
|
||||
|
||||
it('can import HD wallet with a passphrase', async () => {
|
||||
const lockFile = '/tmp/travislock.' + hashIt(jasmine.currentTest.fullName);
|
||||
if (process.env.TRAVIS) {
|
||||
if (require('fs').existsSync(lockFile))
|
||||
return console.warn('skipping', JSON.stringify(jasmine.currentTest.fullName), 'as it previously passed on Travis');
|
||||
}
|
||||
|
||||
await helperImportWallet(
|
||||
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
|
||||
'Imported HD SegWit (BIP84 Bech32 Native)',
|
||||
'0 BTC',
|
||||
'BlueWallet',
|
||||
);
|
||||
|
||||
await element(by.id('ReceiveButton')).tap();
|
||||
try {
|
||||
// in case emulator has no google services and doesnt support pushes
|
||||
// we just dont show this popup
|
||||
await element(by.text(`No, and don’t ask me again`)).tap();
|
||||
} catch (_) {}
|
||||
await yo('BitcoinAddressQRCodeContainer');
|
||||
|
||||
// check if imported wallet has correct recive address
|
||||
await expect(element(by.id('AddressValue'))).toHaveText('bc1qe8q660wfj6uvqg7zyn86jcsux36natklqnfdrc');
|
||||
|
||||
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
|
||||
});
|
||||
});
|
||||
|
||||
async function sleep(ms) {
|
||||
@ -1183,7 +1211,7 @@ async function helperCreateWallet(walletName) {
|
||||
await expect(element(by.id(walletName || 'cr34t3d'))).toBeVisible();
|
||||
}
|
||||
|
||||
async function helperImportWallet(importText, expectedWalletLabel, expectedBalance) {
|
||||
async function helperImportWallet(importText, expectedWalletLabel, expectedBalance, passphrase) {
|
||||
await yo('WalletsList');
|
||||
|
||||
await element(by.id('WalletsList')).swipe('left', 'fast', 1); // in case emu screen is small and it doesnt fit
|
||||
@ -1198,6 +1226,21 @@ async function helperImportWallet(importText, expectedWalletLabel, expectedBalan
|
||||
try {
|
||||
await element(by.id('DoImport')).tap();
|
||||
} catch (_) {}
|
||||
|
||||
let passphraseAsked = false;
|
||||
try {
|
||||
await sup('Passphrase', 3000);
|
||||
passphraseAsked = true;
|
||||
} catch (e) {} // passphrase not asked
|
||||
|
||||
if (passphraseAsked) {
|
||||
// enter passphrase if needed
|
||||
if (passphrase) {
|
||||
await element(by.type('android.widget.EditText')).typeText(passphrase);
|
||||
}
|
||||
await element(by.text('OK')).tap();
|
||||
}
|
||||
|
||||
if (process.env.TRAVIS) await sleep(60000);
|
||||
|
||||
// waiting for import result
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
WatchOnlyWallet,
|
||||
HDAezeedWallet,
|
||||
SLIP39SegwitP2SHWallet,
|
||||
SLIP39SegwitBech32Wallet,
|
||||
} from '../../class';
|
||||
import WalletImport from '../../class/wallet-import';
|
||||
import React from 'react';
|
||||
@ -72,6 +73,16 @@ describe('import procedure', function () {
|
||||
assert.strictEqual(lastImportedWallet.getLabel(), 'Imported HD SegWit (BIP84 Bech32 Native)');
|
||||
});
|
||||
|
||||
it('can import BIP84 with passphrase', async () => {
|
||||
await WalletImport.processImportText(
|
||||
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
|
||||
'BlueWallet',
|
||||
);
|
||||
assert.strictEqual(lastImportedWallet.type, HDSegwitBech32Wallet.type);
|
||||
assert.strictEqual(lastImportedWallet._getExternalAddressByIndex(0), 'bc1qe8q660wfj6uvqg7zyn86jcsux36natklqnfdrc');
|
||||
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);
|
||||
@ -141,6 +152,14 @@ describe('import procedure', function () {
|
||||
assert.strictEqual(lastImportedWallet.type, HDAezeedWallet.type);
|
||||
});
|
||||
|
||||
it('can import AEZEED with password', async () => {
|
||||
await WalletImport.processImportText(
|
||||
'able mix price funny host express lawsuit congress antique float pig exchange vapor drip wide cup style apple tumble verb fix blush tongue market',
|
||||
'strongPassword',
|
||||
);
|
||||
assert.strictEqual(lastImportedWallet.type, HDAezeedWallet.type);
|
||||
});
|
||||
|
||||
it('importing empty BIP39 should yield BIP84', async () => {
|
||||
const tempWallet = new HDSegwitBech32Wallet();
|
||||
await tempWallet.generate();
|
||||
@ -157,7 +176,7 @@ describe('import procedure', function () {
|
||||
});
|
||||
|
||||
it('can import BIP38 encrypted backup', async () => {
|
||||
await WalletImport.processImportText('6PnU5voARjBBykwSddwCdcn6Eu9EcsK24Gs5zWxbJbPZYW7eiYQP8XgKbN');
|
||||
await WalletImport.processImportText('6PnU5voARjBBykwSddwCdcn6Eu9EcsK24Gs5zWxbJbPZYW7eiYQP8XgKbN', 'qwerty');
|
||||
assert.strictEqual(lastImportedWallet.getSecret(), 'KxqRtpd9vFju297ACPKHrGkgXuberTveZPXbRDiQ3MXZycSQYtjc');
|
||||
assert.strictEqual(lastImportedWallet.type, LegacyWallet.type);
|
||||
assert.strictEqual(lastImportedWallet.getAddress(), '1639W2kM6UY9PdavMQeLqG4SuUEae9NZfq');
|
||||
@ -189,6 +208,20 @@ describe('import procedure', function () {
|
||||
assert.strictEqual(lastImportedWallet.type, SLIP39SegwitP2SHWallet.type);
|
||||
});
|
||||
|
||||
it('can import slip39 wallet with password', async () => {
|
||||
// 2-of-3 slip39 wallet
|
||||
// crystal lungs academic acid corner infant satisfy spider alcohol laser golden equation fiscal epidemic infant scholar space findings tadpole belong
|
||||
// crystal lungs academic agency class payment actress avoid rebound ordinary exchange petition tendency mild mobile spine robin fancy shelter increase
|
||||
// crystal lungs academic always earth satoshi elbow satoshi that pants formal leaf rival texture romantic filter expand regular soul desert
|
||||
await WalletImport.processImportText(
|
||||
'crystal lungs academic acid corner infant satisfy spider alcohol laser golden equation fiscal epidemic infant scholar space findings tadpole belong\n' +
|
||||
'crystal lungs academic agency class payment actress avoid rebound ordinary exchange petition tendency mild mobile spine robin fancy shelter increase',
|
||||
'BlueWallet',
|
||||
);
|
||||
assert.strictEqual(lastImportedWallet.type, SLIP39SegwitBech32Wallet.type);
|
||||
assert.strictEqual(lastImportedWallet._getExternalAddressByIndex(0), 'bc1q5k23fle53w8a3982m82e9f6hqlnrh3mv5s9s6z');
|
||||
});
|
||||
|
||||
it('can import watch-only Cobo vault export', async () => {
|
||||
await WalletImport.processImportText(
|
||||
'{"ExtPubKey":"zpub6riZchHnrWzhhZ3Z4dhCJmesGyafMmZBRC9txhnidR313XJbcv4KiDubderKHhL7rMsqacYd82FQ38e4whgs8Dg7CpsxX3dSGWayXsEerF4","MasterFingerprint":"7D2F0272","AccountKeyPath":"84\'\\/0\'\\/0\'","CoboVaultFirmwareVersion":"2.6.1(BTC-Only)"}',
|
||||
|
@ -43,7 +43,6 @@ describe('HDAezeedWallet', () => {
|
||||
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.setPassphrase();
|
||||
aezeed.setPassphrase('aezeed');
|
||||
assert.ok(await aezeed.validateMnemonicAsync());
|
||||
assert.ok(!(await aezeed.mnemonicInvalidPassword()));
|
||||
|
Loading…
Reference in New Issue
Block a user