mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-05 05:46:44 +00:00
attempt to import wallet file if open fails
This commit is contained in:
parent
9e5937d2fe
commit
f949bcc1cf
8 changed files with 146 additions and 35 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit e912e8a5121f06b4a2ac913b74e51c0f7dcbb940
|
Subproject commit fee042679938316f034ceee137cfa056be566ffd
|
|
@ -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,8 +807,11 @@ 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 {
|
||||||
log.error("Error Opening Wallet", exception);
|
if(!attemptImportWallet(file, password)) {
|
||||||
showErrorDialog("Error Opening Wallet", exception.getMessage());
|
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..."));
|
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");
|
throw new IOException("Unsupported file type");
|
||||||
}
|
}
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
log.error("Error opening wallet", e);
|
if(!attemptImportWallet(file, null)) {
|
||||||
showErrorDialog("Error Opening Wallet", e.getMessage());
|
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();
|
Optional<Wallet> optionalWallet = dlg.showAndWait();
|
||||||
if(optionalWallet.isPresent()) {
|
if(optionalWallet.isPresent()) {
|
||||||
Wallet wallet = optionalWallet.get();
|
Wallet wallet = optionalWallet.get();
|
||||||
File walletFile = Storage.getWalletFile(wallet.getName());
|
addImportedWallet(wallet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(walletFile.exists()) {
|
private boolean attemptImportWallet(File file, SecureString password) {
|
||||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
List<WalletImport> walletImporters = List.of(new ColdcardSinglesig(), new ColdcardMultisig(), new Electrum(), new Specter());
|
||||||
alert.setTitle("Existing wallet found");
|
for(WalletImport importer : walletImporters) {
|
||||||
alert.setHeaderText("Replace existing wallet?");
|
try(FileInputStream inputStream = new FileInputStream(file)) {
|
||||||
alert.setContentText("Wallet file " + wallet.getName() + " already exists");
|
if(importer.isEncrypted(file) && password == null) {
|
||||||
Optional<ButtonType> result = alert.showAndWait();
|
WalletPasswordDialog dlg = new WalletPasswordDialog(file.getName(), WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||||
if(result.isPresent() && result.get() == ButtonType.CANCEL) {
|
Optional<SecureString> optionalPassword = dlg.showAndWait();
|
||||||
return;
|
if(optionalPassword.isPresent()) {
|
||||||
|
password = optionalPassword.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Close existing wallet first if open
|
Wallet wallet = importer.importWallet(inputStream, password == null ? null: password.asString());
|
||||||
for(Iterator<Tab> iter = tabs.getTabs().iterator(); iter.hasNext(); ) {
|
if(wallet.getName() == null) {
|
||||||
Tab tab = iter.next();
|
wallet.setName(file.getName());
|
||||||
TabData tabData = (TabData)tab.getUserData();
|
}
|
||||||
if(tabData.getType() == TabData.TabType.WALLET) {
|
addImportedWallet(wallet);
|
||||||
WalletTabData walletTabData = (WalletTabData) tabData;
|
return true;
|
||||||
if(walletTabData.getStorage().getWalletFile().equals(walletFile)) {
|
} catch(Exception e) {
|
||||||
iter.remove();
|
//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) {
|
public void exportWallet(ActionEvent event) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
password.clear();
|
||||||
} finally {
|
return walletAndKey;
|
||||||
password.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
39
src/test/java/com/sparrowwallet/sparrow/io/SpecterTest.java
Normal file
39
src/test/java/com/sparrowwallet/sparrow/io/SpecterTest.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
BIN
src/test/resources/.DS_Store
vendored
BIN
src/test/resources/.DS_Store
vendored
Binary file not shown.
|
@ -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))"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"label": "specter-wallet",
|
||||||
|
"blockheight": 647153,
|
||||||
|
"descriptor": "sh(wpkh([4df18faa/49'/0'/0']xpub6BgwyseZdeGJj2vB3FPHSGPxR1LLkr8AsAJqedrgjwBXKXXVWkH31fhwtQXgrM7uMrWjLwXhuDhhenNAh5eBdUSjrHkrKfaXutcJdAfgQ8D))"
|
||||||
|
}
|
Loading…
Reference in a new issue