Merge pull request #3406 from BlueWallet/limpbrains-passphrase2

ADD: UI for import wallet with passphrase
This commit is contained in:
GLaDOS 2021-07-20 16:47:11 +01:00 committed by GitHub
commit 64aed0657c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 157 additions and 38 deletions

View File

@ -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>

View File

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

View File

@ -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 youve got. BlueWallet will do its best to guess the correct format and import your wallet.",
"import_file": "Import File",

View 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>
);

View File

@ -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);

View File

@ -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 dont 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

View File

@ -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)"}',

View File

@ -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()));