attempt to import wallet file if open fails

This commit is contained in:
Craig Raw 2020-10-05 10:36:27 +02:00
parent 9e5937d2fe
commit f949bcc1cf
8 changed files with 146 additions and 35 deletions

2
drongo

@ -1 +1 @@
Subproject commit e912e8a5121f06b4a2ac913b74e51c0f7dcbb940 Subproject commit fee042679938316f034ceee137cfa056be566ffd

View file

@ -773,6 +773,9 @@ public class AppController implements Initializable {
Wallet wallet = storage.loadWallet(); Wallet wallet = storage.loadWallet();
checkWalletNetwork(wallet); checkWalletNetwork(wallet);
restorePublicKeysFromSeed(wallet, null); restorePublicKeysFromSeed(wallet, null);
if(!wallet.isValid()) {
throw new IllegalStateException("Wallet file is not valid.");
}
Tab tab = addWalletTab(storage, wallet); Tab tab = addWalletTab(storage, wallet);
tabs.getSelectionModel().select(tab); tabs.getSelectionModel().select(tab);
} else if(FileType.BINARY.equals(fileType)) { } else if(FileType.BINARY.equals(fileType)) {
@ -804,9 +807,12 @@ public class AppController implements Initializable {
if(exception instanceof InvalidPasswordException) { if(exception instanceof InvalidPasswordException) {
showErrorDialog("Invalid Password", "The wallet password was invalid."); showErrorDialog("Invalid Password", "The wallet password was invalid.");
} else { } else {
if(!attemptImportWallet(file, password)) {
log.error("Error Opening Wallet", exception); log.error("Error Opening Wallet", exception);
showErrorDialog("Error Opening Wallet", exception.getMessage()); showErrorDialog("Error Opening Wallet", exception.getMessage());
} }
password.clear();
}
}); });
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.START, "Decrypting wallet...")); EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.START, "Decrypting wallet..."));
loadWalletService.start(); loadWalletService.start();
@ -814,10 +820,12 @@ public class AppController implements Initializable {
throw new IOException("Unsupported file type"); throw new IOException("Unsupported file type");
} }
} catch(Exception e) { } catch(Exception e) {
if(!attemptImportWallet(file, null)) {
log.error("Error opening wallet", e); log.error("Error opening wallet", e);
showErrorDialog("Error Opening Wallet", e.getMessage()); showErrorDialog("Error Opening Wallet", e.getMessage());
} }
} }
}
private void checkWalletNetwork(Wallet wallet) { private void checkWalletNetwork(Wallet wallet) {
if(wallet.getNetwork() != null && wallet.getNetwork() != Network.get()) { if(wallet.getNetwork() != null && wallet.getNetwork() != Network.get()) {
@ -872,6 +880,37 @@ public class AppController implements Initializable {
Optional<Wallet> optionalWallet = dlg.showAndWait(); Optional<Wallet> optionalWallet = dlg.showAndWait();
if(optionalWallet.isPresent()) { if(optionalWallet.isPresent()) {
Wallet wallet = optionalWallet.get(); Wallet wallet = optionalWallet.get();
addImportedWallet(wallet);
}
}
private boolean attemptImportWallet(File file, SecureString password) {
List<WalletImport> walletImporters = List.of(new ColdcardSinglesig(), new ColdcardMultisig(), new Electrum(), new Specter());
for(WalletImport importer : walletImporters) {
try(FileInputStream inputStream = new FileInputStream(file)) {
if(importer.isEncrypted(file) && password == null) {
WalletPasswordDialog dlg = new WalletPasswordDialog(file.getName(), WalletPasswordDialog.PasswordRequirement.LOAD);
Optional<SecureString> optionalPassword = dlg.showAndWait();
if(optionalPassword.isPresent()) {
password = optionalPassword.get();
}
}
Wallet wallet = importer.importWallet(inputStream, password == null ? null: password.asString());
if(wallet.getName() == null) {
wallet.setName(file.getName());
}
addImportedWallet(wallet);
return true;
} catch(Exception e) {
//ignore
}
}
return false;
}
private void addImportedWallet(Wallet wallet) {
File walletFile = Storage.getWalletFile(wallet.getName()); File walletFile = Storage.getWalletFile(wallet.getName());
if(walletFile.exists()) { if(walletFile.exists()) {
@ -901,7 +940,6 @@ public class AppController implements Initializable {
Tab tab = addWalletTab(storage, wallet); Tab tab = addWalletTab(storage, wallet);
tabs.getSelectionModel().select(tab); tabs.getSelectionModel().select(tab);
} }
}
public void exportWallet(ActionEvent event) { public void exportWallet(ActionEvent event) {
Tab selectedTab = tabs.getSelectionModel().getSelectedItem(); Tab selectedTab = tabs.getSelectionModel().getSelectedItem();

View file

@ -5,9 +5,12 @@ import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.KeystoreSource; import com.sparrowwallet.drongo.wallet.KeystoreSource;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletModel; import com.sparrowwallet.drongo.wallet.WalletModel;
import java.io.File; import java.io.File;
@ -16,7 +19,7 @@ import java.io.InputStreamReader;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Map; import java.util.Map;
public class ColdcardSinglesig implements KeystoreFileImport { public class ColdcardSinglesig implements KeystoreFileImport, WalletImport {
@Override @Override
public String getName() { public String getName() {
return "Coldcard"; return "Coldcard";
@ -77,6 +80,29 @@ public class ColdcardSinglesig implements KeystoreFileImport {
throw new ImportException("Correct derivation not found for script type: " + scriptType); throw new ImportException("Correct derivation not found for script type: " + scriptType);
} }
@Override
public String getWalletImportDescription() {
return getKeystoreImportDescription();
}
@Override
public Wallet importWallet(InputStream inputStream, String password) throws ImportException {
//Use default of P2WPKH
Keystore keystore = getKeystore(ScriptType.P2WPKH, inputStream, "");
Wallet wallet = new Wallet();
wallet.setPolicyType(PolicyType.SINGLE);
wallet.setScriptType(ScriptType.P2WPKH);
wallet.getKeystores().add(keystore);
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, wallet.getKeystores(), null));
if(!wallet.isValid()) {
throw new ImportException("Wallet is in an inconsistent state.");
}
return wallet;
}
private static class ColdcardKeystore { private static class ColdcardKeystore {
public String deriv; public String deriv;
public String name; public String name;

View file

@ -81,7 +81,7 @@ public class Storage {
return gsonBuilder.setPrettyPrinting().disableHtmlEscaping().create(); return gsonBuilder.setPrettyPrinting().disableHtmlEscaping().create();
} }
public Wallet loadWallet() throws IOException, MnemonicException { public Wallet loadWallet() throws IOException {
Reader reader = new FileReader(walletFile); Reader reader = new FileReader(walletFile);
Wallet wallet = gson.fromJson(reader, Wallet.class); Wallet wallet = gson.fromJson(reader, Wallet.class);
reader.close(); reader.close();
@ -90,7 +90,7 @@ public class Storage {
return wallet; return wallet;
} }
public WalletAndKey loadWallet(CharSequence password) throws IOException, MnemonicException, StorageException { public WalletAndKey loadWallet(CharSequence password) throws IOException, StorageException {
InputStream fileStream = new FileInputStream(walletFile); InputStream fileStream = new FileInputStream(walletFile);
ECKey encryptionKey = getEncryptionKey(password, fileStream); ECKey encryptionKey = getEncryptionKey(password, fileStream);
@ -461,11 +461,9 @@ public class Storage {
protected Task<WalletAndKey> createTask() { protected Task<WalletAndKey> createTask() {
return new Task<>() { return new Task<>() {
protected WalletAndKey call() throws IOException, StorageException, MnemonicException { protected WalletAndKey call() throws IOException, StorageException, MnemonicException {
try { WalletAndKey walletAndKey = storage.loadWallet(password);
return storage.loadWallet(password);
} finally {
password.clear(); password.clear();
} return walletAndKey;
} }
}; };
} }

View file

@ -0,0 +1,39 @@
package com.sparrowwallet.sparrow.io;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Wallet;
import org.junit.Assert;
import org.junit.Test;
public class SpecterTest extends IoTest {
@Test
public void testImport() throws ImportException {
Specter specter = new Specter();
Wallet wallet = specter.importWallet(getInputStream("specter-wallet.json"), null);
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2SH_P2WPKH, wallet.getScriptType());
Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired());
Assert.assertEquals("sh(wpkh(keystore1))", wallet.getDefaultPolicy().getMiniscript().getScript());
Assert.assertEquals("4df18faa", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
Assert.assertEquals("m/49'/0'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
Assert.assertEquals("xpub6BgwyseZdeGJj2vB3FPHSGPxR1LLkr8AsAJqedrgjwBXKXXVWkH31fhwtQXgrM7uMrWjLwXhuDhhenNAh5eBdUSjrHkrKfaXutcJdAfgQ8D", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
Assert.assertTrue(wallet.isValid());
}
@Test
public void testMultisigImport() throws ImportException {
Specter specter = new Specter();
Wallet wallet = specter.importWallet(getInputStream("specter-multisig-wallet.json"), null);
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2WSH, wallet.getScriptType());
Assert.assertEquals(3, wallet.getDefaultPolicy().getNumSignaturesRequired());
Assert.assertEquals("wsh(sortedmulti(3,keystore1,keystore2,keystore3,keystore4))", wallet.getDefaultPolicy().getMiniscript().getScript());
Assert.assertEquals("ca9a2b19", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
Assert.assertEquals("m/48'/0'/0'/2'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath());
Assert.assertEquals("xpub6EhbRDNhmMX863W8RujJyAMw1vtM4MHXnsk14paK1ZBEH75k44gWqfaraXCrzg6w9pzC2yLc28vAdUfpB9ShuEB1HA9xMs6BjmRi4PKbt1K", wallet.getKeystores().get(0).getExtendedPublicKey().toString());
Assert.assertTrue(wallet.isValid());
}
}

Binary file not shown.

View file

@ -0,0 +1,5 @@
{
"label": "specter-multisig",
"blockheight": 646246,
"descriptor": "wsh(multi(3,[ca9a2b19/48'/0'/0'/2']xpub6EhbRDNhmMX863W8RujJyAMw1vtM4MHXnsk14paK1ZBEH75k44gWqfaraXCrzg6w9pzC2yLc28vAdUfpB9ShuEB1HA9xMs6BjmRi4PKbt1K,[046b9450/48'/0'/0'/2']xpub6Ej6a311KAYh6jUAnD4zHhgtgemM4DfMksQUNbAzXCQEkjKf5Ep2WLmMtJkmR4MHm8aEy648kzZo4cphwYhMpfwfYvHRQY4u2e4ijWzohwE,[747b698e/48'/0'/0'/2']xpub6Eb6Z1xtmWRiWKgRpHf6dHiEagGd6FLiBXrnma1nFK4PGRYqSVqVyJaxna5Mb8etSP4ATKVAvKnXG1a9HZauoAawuSDJT5RgH2HqEVHZVHY,[7bb026be/48'/0'/0'/2']xpub6FMGmJccz6dqLo9TMmXYPpZ7HUDm71RHHSTXqTUgkyP9TZmF2uexoB7qttEBtHaotopPwfAVfKfwmdEjCGabVpND1m7ix2AW2LxPuNfLZhi))"
}

View file

@ -0,0 +1,5 @@
{
"label": "specter-wallet",
"blockheight": 647153,
"descriptor": "sh(wpkh([4df18faa/49'/0'/0']xpub6BgwyseZdeGJj2vB3FPHSGPxR1LLkr8AsAJqedrgjwBXKXXVWkH31fhwtQXgrM7uMrWjLwXhuDhhenNAh5eBdUSjrHkrKfaXutcJdAfgQ8D))"
}