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();
checkWalletNetwork(wallet);
restorePublicKeysFromSeed(wallet, null);
if(!wallet.isValid()) {
throw new IllegalStateException("Wallet file is not valid.");
}
Tab tab = addWalletTab(storage, wallet);
tabs.getSelectionModel().select(tab);
} else if(FileType.BINARY.equals(fileType)) {
@ -804,8 +807,11 @@ public class AppController implements Initializable {
if(exception instanceof InvalidPasswordException) {
showErrorDialog("Invalid Password", "The wallet password was invalid.");
} else {
log.error("Error Opening Wallet", exception);
showErrorDialog("Error Opening Wallet", exception.getMessage());
if(!attemptImportWallet(file, password)) {
log.error("Error Opening Wallet", exception);
showErrorDialog("Error Opening Wallet", exception.getMessage());
}
password.clear();
}
});
EventManager.get().post(new StorageEvent(storage.getWalletFile(), TimedEvent.Action.START, "Decrypting wallet..."));
@ -814,8 +820,10 @@ public class AppController implements Initializable {
throw new IOException("Unsupported file type");
}
} catch(Exception e) {
log.error("Error opening wallet", e);
showErrorDialog("Error Opening Wallet", e.getMessage());
if(!attemptImportWallet(file, null)) {
log.error("Error opening wallet", e);
showErrorDialog("Error Opening Wallet", e.getMessage());
}
}
}
@ -872,35 +880,65 @@ public class AppController implements Initializable {
Optional<Wallet> optionalWallet = dlg.showAndWait();
if(optionalWallet.isPresent()) {
Wallet wallet = optionalWallet.get();
File walletFile = Storage.getWalletFile(wallet.getName());
addImportedWallet(wallet);
}
}
if(walletFile.exists()) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Existing wallet found");
alert.setHeaderText("Replace existing wallet?");
alert.setContentText("Wallet file " + wallet.getName() + " already exists");
Optional<ButtonType> result = alert.showAndWait();
if(result.isPresent() && result.get() == ButtonType.CANCEL) {
return;
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();
}
}
//Close existing wallet first if open
for(Iterator<Tab> iter = tabs.getTabs().iterator(); iter.hasNext(); ) {
Tab tab = iter.next();
TabData tabData = (TabData)tab.getUserData();
if(tabData.getType() == TabData.TabType.WALLET) {
WalletTabData walletTabData = (WalletTabData) tabData;
if(walletTabData.getStorage().getWalletFile().equals(walletFile)) {
iter.remove();
}
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());
if(walletFile.exists()) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Existing wallet found");
alert.setHeaderText("Replace existing wallet?");
alert.setContentText("Wallet file " + wallet.getName() + " already exists");
Optional<ButtonType> result = alert.showAndWait();
if(result.isPresent() && result.get() == ButtonType.CANCEL) {
return;
}
//Close existing wallet first if open
for(Iterator<Tab> iter = tabs.getTabs().iterator(); iter.hasNext(); ) {
Tab tab = iter.next();
TabData tabData = (TabData)tab.getUserData();
if(tabData.getType() == TabData.TabType.WALLET) {
WalletTabData walletTabData = (WalletTabData) tabData;
if(walletTabData.getStorage().getWalletFile().equals(walletFile)) {
iter.remove();
}
}
}
Storage storage = new Storage(walletFile);
Tab tab = addWalletTab(storage, wallet);
tabs.getSelectionModel().select(tab);
}
Storage storage = new Storage(walletFile);
Tab tab = addWalletTab(storage, wallet);
tabs.getSelectionModel().select(tab);
}
public void exportWallet(ActionEvent event) {

View file

@ -5,9 +5,12 @@ import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import com.sparrowwallet.drongo.ExtendedKey;
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.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.KeystoreSource;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletModel;
import java.io.File;
@ -16,7 +19,7 @@ import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.Map;
public class ColdcardSinglesig implements KeystoreFileImport {
public class ColdcardSinglesig implements KeystoreFileImport, WalletImport {
@Override
public String getName() {
return "Coldcard";
@ -77,6 +80,29 @@ public class ColdcardSinglesig implements KeystoreFileImport {
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 {
public String deriv;
public String name;

View file

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