diff --git a/drongo b/drongo index 766a986a..f6414a44 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 766a986abb72ea84317a405b93055abc3d1eaf22 +Subproject commit f6414a447550eb87a871c54c7e376713cae8eb9c diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 609dff12..d2d3eb63 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -13,13 +13,8 @@ import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBTParseException; import com.sparrowwallet.drongo.wallet.Wallet; -import com.sparrowwallet.sparrow.control.TextAreaDialog; -import com.sparrowwallet.sparrow.control.WalletImportDialog; -import com.sparrowwallet.sparrow.control.WalletNameDialog; -import com.sparrowwallet.sparrow.control.WalletPasswordDialog; -import com.sparrowwallet.sparrow.event.TabEvent; -import com.sparrowwallet.sparrow.event.TransactionTabChangedEvent; -import com.sparrowwallet.sparrow.event.TransactionTabSelectedEvent; +import com.sparrowwallet.sparrow.control.*; +import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.io.FileType; import com.sparrowwallet.sparrow.io.IOUtils; import com.sparrowwallet.sparrow.io.Storage; @@ -46,6 +41,9 @@ public class AppController implements Initializable { private static final String TRANSACTION_TAB_TYPE = "transaction"; public static final String DRAG_OVER_CLASS = "drag-over"; + @FXML + private MenuItem exportWallet; + @FXML private CheckMenuItem showTxHex; @@ -96,12 +94,19 @@ public class AppController implements Initializable { TabData tabData = (TabData)selectedTab.getUserData(); if(tabData.getType() == TabData.TabType.TRANSACTION) { EventManager.get().post(new TransactionTabSelectedEvent(selectedTab)); + exportWallet.setDisable(true); + showTxHex.setDisable(false); + } else if(tabData.getType() == TabData.TabType.WALLET) { + EventManager.get().post(new WalletTabSelectedEvent(selectedTab)); + exportWallet.setDisable(false); + showTxHex.setDisable(true); } } }); showTxHex.setSelected(true); showTxHexProperty = true; + exportWallet.setDisable(true); //addWalletTab("newWallet", new Wallet(PolicyType.SINGLE, ScriptType.P2WPKH)); } @@ -269,6 +274,19 @@ public class AppController implements Initializable { } } + public void exportWallet(ActionEvent event) { + Tab selectedTab = tabs.getSelectionModel().getSelectedItem(); + TabData tabData = (TabData)selectedTab.getUserData(); + if(tabData.getType() == TabData.TabType.WALLET) { + WalletTabData walletTabData = (WalletTabData)tabData; + WalletExportDialog dlg = new WalletExportDialog(walletTabData.getWallet()); + Optional wallet = dlg.showAndWait(); + if(wallet.isPresent()) { + //Successful export + } + } + } + public Tab addWalletTab(File walletFile, ECKey encryptionPubKey, Wallet wallet) { try { String name = walletFile.getName(); @@ -276,7 +294,7 @@ public class AppController implements Initializable { name = name.substring(0, name.lastIndexOf('.')); } Tab tab = new Tab(name); - TabData tabData = new TabData(TabData.TabType.WALLET); + TabData tabData = new WalletTabData(TabData.TabType.WALLET, wallet, walletFile); tab.setUserData(tabData); tab.setContextMenu(getTabContextMenu(tab)); tab.setClosable(true); @@ -351,7 +369,7 @@ public class AppController implements Initializable { } Tab tab = new Tab(tabName); - TabData tabData = new TabData(TabData.TabType.TRANSACTION); + TabData tabData = new TransactionTabData(TabData.TabType.TRANSACTION, transaction); tab.setUserData(tabData); tab.setContextMenu(getTabContextMenu(tab)); tab.setClosable(true); @@ -397,7 +415,7 @@ public class AppController implements Initializable { } @Subscribe - public void tabSelected(TabEvent event) { + public void tabSelected(TabSelectedEvent event) { Tab selectedTab = event.getTab(); String tabType = (String)selectedTab.getUserData(); diff --git a/src/main/java/com/sparrowwallet/sparrow/TransactionTabData.java b/src/main/java/com/sparrowwallet/sparrow/TransactionTabData.java new file mode 100644 index 00000000..7a7137be --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/TransactionTabData.java @@ -0,0 +1,16 @@ +package com.sparrowwallet.sparrow; + +import com.sparrowwallet.drongo.protocol.Transaction; + +public class TransactionTabData extends TabData { + private Transaction transaction; + + public TransactionTabData(TabType type, Transaction transaction) { + super(type); + this.transaction = transaction; + } + + public Transaction getTransaction() { + return transaction; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/WalletTabData.java b/src/main/java/com/sparrowwallet/sparrow/WalletTabData.java new file mode 100644 index 00000000..370bb554 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/WalletTabData.java @@ -0,0 +1,35 @@ +package com.sparrowwallet.sparrow; + +import com.google.common.eventbus.Subscribe; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.event.WalletChangedEvent; + +import java.io.File; + +public class WalletTabData extends TabData { + private Wallet wallet; + private final File walletFile; + + public WalletTabData(TabType type, Wallet wallet, File walletFile) { + super(type); + this.wallet = wallet; + this.walletFile = walletFile; + + EventManager.get().register(this); + } + + public Wallet getWallet() { + return wallet; + } + + public File getWalletFile() { + return walletFile; + } + + @Subscribe + public void walletChanged(WalletChangedEvent event) { + if(event.getWalletFile().equals(walletFile)) { + wallet = event.getWallet(); + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java index dd95e0e6..1b7ec051 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java @@ -51,7 +51,7 @@ public abstract class FileImportPane extends TitledDescriptionPane { Stage window = new Stage(); FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " keystore"); + fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " File"); fileChooser.getExtensionFilters().addAll( new FileChooser.ExtensionFilter("All Files", "*.*"), new FileChooser.ExtensionFilter("JSON", "*.json") diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java new file mode 100644 index 00000000..afb88f35 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java @@ -0,0 +1,88 @@ +package com.sparrowwallet.sparrow.control; + +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.WalletExportEvent; +import com.sparrowwallet.sparrow.io.WalletExport; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Control; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Optional; + +public class FileWalletExportPane extends TitledDescriptionPane { + private final Wallet wallet; + private final WalletExport exporter; + + public FileWalletExportPane(Wallet wallet, WalletExport exporter) { + super(exporter.getName(), "Wallet file export", exporter.getWalletExportDescription(), "image/" + exporter.getWalletModel().getType() + ".png"); + this.wallet = wallet; + this.exporter = exporter; + } + + @Override + protected Control createButton() { + Button exportButton = new Button("Export Wallet..."); + exportButton.setAlignment(Pos.CENTER_RIGHT); + exportButton.setOnAction(event -> { + exportWallet(); + }); + return exportButton; + } + + private void exportWallet() { + Stage window = new Stage(); + + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Export " + exporter.getWalletModel().toDisplayString() + " File"); + + File file = fileChooser.showSaveDialog(window); + if(file != null) { + exportWallet(file); + } + } + + private void exportWallet(File file) { + Wallet copy = wallet.copy(); + + if(copy.isEncrypted()) { + WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD); + Optional password = dlg.showAndWait(); + if(password.isPresent()) { + copy.decrypt(password.get(), ""); + + for(Keystore keystore : copy.getKeystores()) { + if(keystore.hasSeed() && keystore.getSeed().needPassphrase()) { + KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(keystore); + Optional passphrase = passphraseDialog.showAndWait(); + if(passphrase.isPresent()) { + keystore.setPassphrase(passphrase.get()); + } else { + return; + } + } + } + } else { + return; + } + } + + try { + OutputStream outputStream = new FileOutputStream(file); + exporter.exportWallet(copy, outputStream); + EventManager.get().post(new WalletExportEvent(copy)); + } catch(Exception e) { + String errorMessage = e.getMessage(); + if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) { + errorMessage = e.getCause().getMessage(); + } + setError("Export Error", errorMessage); + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/KeystorePassphraseDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/KeystorePassphraseDialog.java new file mode 100644 index 00000000..9cf066ff --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/KeystorePassphraseDialog.java @@ -0,0 +1,41 @@ +package com.sparrowwallet.sparrow.control; + +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.sparrow.AppController; +import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.DialogPane; +import javafx.scene.layout.VBox; +import org.controlsfx.control.textfield.CustomPasswordField; +import org.controlsfx.control.textfield.TextFields; +import org.controlsfx.glyphfont.Glyph; + +public class KeystorePassphraseDialog extends Dialog { + private final CustomPasswordField passphrase; + + public KeystorePassphraseDialog(Keystore keystore) { + this.passphrase = (CustomPasswordField) TextFields.createClearablePasswordField(); + + final DialogPane dialogPane = getDialogPane(); + setTitle("Keystore Passphrase"); + dialogPane.setHeaderText("Please enter the passphrase for keystore: " + keystore.getLabel()); + dialogPane.getStylesheets().add(AppController.class.getResource("general.css").toExternalForm()); + dialogPane.getButtonTypes().addAll(ButtonType.CANCEL, ButtonType.OK); + dialogPane.setPrefWidth(380); + dialogPane.setPrefHeight(200); + + Glyph lock = new Glyph("FontAwesome5", FontAwesome5.Glyph.KEY); + lock.setFontSize(50); + dialogPane.setGraphic(lock); + + final VBox content = new VBox(10); + content.setPrefHeight(50); + content.getChildren().add(passphrase); + + dialogPane.setContent(content); + passphrase.requestFocus(); + + setResultConverter(dialogButton -> dialogButton == ButtonType.OK ? passphrase.getText() : null); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java new file mode 100644 index 00000000..8e54937d --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java @@ -0,0 +1,71 @@ +package com.sparrowwallet.sparrow.control; + +import com.google.common.eventbus.Subscribe; +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.WalletExportEvent; +import com.sparrowwallet.sparrow.event.WalletImportEvent; +import com.sparrowwallet.sparrow.io.ColdcardMultisig; +import com.sparrowwallet.sparrow.io.Electrum; +import com.sparrowwallet.sparrow.io.WalletExport; +import com.sparrowwallet.sparrow.io.WalletImport; +import javafx.scene.control.*; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.StackPane; + +import java.util.List; + +public class WalletExportDialog extends Dialog { + private Wallet wallet; + + public WalletExportDialog(Wallet wallet) { + EventManager.get().register(this); + + final DialogPane dialogPane = getDialogPane(); + + StackPane stackPane = new StackPane(); + dialogPane.setContent(stackPane); + + AnchorPane anchorPane = new AnchorPane(); + stackPane.getChildren().add(anchorPane); + + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setPrefHeight(280); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + anchorPane.getChildren().add(scrollPane); + scrollPane.setFitToWidth(true); + AnchorPane.setLeftAnchor(scrollPane, 0.0); + AnchorPane.setRightAnchor(scrollPane, 0.0); + + List exporters; + if(wallet.getPolicyType() == PolicyType.SINGLE) { + exporters = List.of(new Electrum()); + } else if(wallet.getPolicyType() == PolicyType.MULTI) { + exporters = List.of(new ColdcardMultisig(), new Electrum()); + } else { + throw new UnsupportedOperationException("Cannot export wallet with policy type " + wallet.getPolicyType()); + } + + Accordion exportAccordion = new Accordion(); + for (WalletExport exporter : exporters) { + FileWalletExportPane exportPane = new FileWalletExportPane(wallet, exporter); + exportAccordion.getPanes().add(exportPane); + } + scrollPane.setContent(exportAccordion); + + final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + dialogPane.getButtonTypes().addAll(cancelButtonType); + dialogPane.setPrefWidth(500); + dialogPane.setPrefHeight(360); + + setResultConverter(dialogButton -> dialogButton != cancelButtonType ? wallet : null); + } + + @Subscribe + public void walletExported(WalletExportEvent event) { + wallet = event.getWallet(); + setResult(wallet); + this.close(); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/TabSelectedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/TabSelectedEvent.java new file mode 100644 index 00000000..deb4af68 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/TabSelectedEvent.java @@ -0,0 +1,9 @@ +package com.sparrowwallet.sparrow.event; + +import javafx.scene.control.Tab; + +public class TabSelectedEvent extends TabEvent { + public TabSelectedEvent(Tab tab) { + super(tab); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/TransactionTabSelectedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/TransactionTabSelectedEvent.java index 1233e80d..3810fc39 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/TransactionTabSelectedEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/TransactionTabSelectedEvent.java @@ -2,7 +2,7 @@ package com.sparrowwallet.sparrow.event; import javafx.scene.control.Tab; -public class TransactionTabSelectedEvent extends TabEvent { +public class TransactionTabSelectedEvent extends TabSelectedEvent { public TransactionTabSelectedEvent(Tab tab) { super(tab); } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletChangedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletChangedEvent.java index 5814aae6..60202e8d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/WalletChangedEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletChangedEvent.java @@ -2,14 +2,22 @@ package com.sparrowwallet.sparrow.event; import com.sparrowwallet.drongo.wallet.Wallet; -public class WalletChangedEvent { - private Wallet wallet; +import java.io.File; - public WalletChangedEvent(Wallet wallet) { +public class WalletChangedEvent { + private final Wallet wallet; + private final File walletFile; + + public WalletChangedEvent(Wallet wallet, File walletFile) { this.wallet = wallet; + this.walletFile = walletFile; } public Wallet getWallet() { return wallet; } + + public File getWalletFile() { + return walletFile; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletExportEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletExportEvent.java new file mode 100644 index 00000000..7a445685 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletExportEvent.java @@ -0,0 +1,15 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.wallet.Wallet; + +public class WalletExportEvent { + private Wallet wallet; + + public WalletExportEvent(Wallet wallet) { + this.wallet = wallet; + } + + public Wallet getWallet() { + return wallet; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletTabSelectedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletTabSelectedEvent.java new file mode 100644 index 00000000..67460b92 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletTabSelectedEvent.java @@ -0,0 +1,9 @@ +package com.sparrowwallet.sparrow.event; + +import javafx.scene.control.Tab; + +public class WalletTabSelectedEvent extends TabSelectedEvent { + public WalletTabSelectedEvent(Tab tab) { + super(tab); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java index 9cf13995..4e66cca6 100644 --- a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java +++ b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java @@ -18,6 +18,7 @@ public class FontAwesome5 extends GlyphFont { CIRCLE('\uf111'), EXCLAMATION_CIRCLE('\uf06a'), EYE('\uf06e'), + KEY('\uf084'), LAPTOP('\uf109'), SD_CARD('\uf7c2'), WALLET('\uf555'); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java b/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java index 99636003..7fd4eeb0 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java @@ -157,15 +157,36 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport throw new ExportException("Could not export a wallet with a " + wallet.getPolicyType() + " policy"); } - ExtendedKey.Header xpubHeader = ExtendedKey.Header.fromScriptType(wallet.getScriptType()); + ExtendedKey.Header xpubHeader = ExtendedKey.Header.fromScriptType(wallet.getScriptType(), false); + ExtendedKey.Header xprvHeader = ExtendedKey.Header.fromScriptType(wallet.getScriptType(), true); int index = 1; for(Keystore keystore : wallet.getKeystores()) { ElectrumKeystore ek = new ElectrumKeystore(); - ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader); - ek.derivation = keystore.getKeyDerivation().getDerivationPath(); - ek.root_fingerprint = keystore.getKeyDerivation().getMasterFingerprint(); - ek.label = keystore.getLabel(); + + if(keystore.getSource() == KeystoreSource.HW_USB || keystore.getSource() == KeystoreSource.HW_AIRGAPPED) { + ek.label = keystore.getLabel(); + ek.derivation = keystore.getKeyDerivation().getDerivationPath(); + ek.root_fingerprint = keystore.getKeyDerivation().getMasterFingerprint(); + ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader); + ek.type = "hardware"; + ek.hw_type = keystore.getWalletModel().getType(); + ew.use_encryption = false; + } else if(keystore.getSource() == KeystoreSource.SW_SEED) { + ek.type = "bip32"; + ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader); + ek.xprv = keystore.getExtendedPrivateKey().toString(xprvHeader); + ek.pw_hash_version = 1; + ew.seed_type = "bip39"; + ew.use_encryption = false; + } else if(keystore.getSource() == KeystoreSource.SW_WATCH) { + ek.type = "bip32"; + ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader); + ek.pw_hash_version = 1; + ew.use_encryption = false; + } else { + throw new ExportException("Cannot export a keystore of source " + keystore.getSource()); + } if(wallet.getPolicyType().equals(PolicyType.SINGLE)) { ew.keystores.put("keystore", ek); @@ -179,6 +200,12 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport Gson gson = new Gson(); JsonObject eJson = gson.toJsonTree(ew.keystores).getAsJsonObject(); eJson.addProperty("wallet_type", ew.wallet_type); + if(ew.use_encryption != null) { + eJson.addProperty("use_encryption", ew.use_encryption); + } + if(ew.seed_type != null) { + eJson.addProperty("seed_type", ew.seed_type); + } gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); String json = gson.toJson(eJson); @@ -203,6 +230,8 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport private static class ElectrumJsonWallet { public Map keystores = new LinkedHashMap<>(); public String wallet_type; + public String seed_type; + public Boolean use_encryption; } public static class ElectrumKeystore { @@ -216,5 +245,6 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport public String type; public String derivation; public String seed; + public Integer pw_hash_version; } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Export.java b/src/main/java/com/sparrowwallet/sparrow/io/Export.java index 2166bcb7..8265e966 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Export.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Export.java @@ -1,5 +1,8 @@ package com.sparrowwallet.sparrow.io; +import com.sparrowwallet.drongo.wallet.WalletModel; + public interface Export { String getName(); + WalletModel getWalletModel(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ExportException.java b/src/main/java/com/sparrowwallet/sparrow/io/ExportException.java index fa17cdf2..42492aab 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ExportException.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ExportException.java @@ -1,6 +1,6 @@ package com.sparrowwallet.sparrow.io; -public class ExportException extends Throwable { +public class ExportException extends Exception { public ExportException() { super(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java index 5fd3fa76..75c21ce2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java @@ -4,7 +4,6 @@ import com.google.common.eventbus.Subscribe; import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.Utils; -import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.KeystoreSource; import com.sparrowwallet.sparrow.EventManager; @@ -20,10 +19,8 @@ import org.controlsfx.validation.ValidationResult; import org.controlsfx.validation.ValidationSupport; import org.controlsfx.validation.Validator; import org.controlsfx.validation.decoration.StyleClassValidationDecoration; -import tornadofx.control.Form; import java.net.URL; -import java.util.Arrays; import java.util.Optional; import java.util.ResourceBundle; import java.util.stream.Collectors; @@ -94,7 +91,7 @@ public class KeystoreController extends WalletFormController implements Initiali EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_FINGERPRINT)); }); derivation.textProperty().addListener((observable, oldValue, newValue) -> { - if(KeyDerivation.isValid(newValue) && !matchesAnotherScriptType(newValue)) { + if(KeyDerivation.isValid(newValue) && !walletForm.getWallet().derivationMatchesAnotherScriptType(newValue)) { keystore.setKeyDerivation(new KeyDerivation(keystore.getKeyDerivation().getMasterFingerprint(), newValue)); EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_DERIVATION)); } @@ -140,7 +137,7 @@ public class KeystoreController extends WalletFormController implements Initiali validationSupport.registerValidator(derivation, Validator.combine( Validator.createEmptyValidator("Derivation is required"), (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Derivation is invalid", !KeyDerivation.isValid(newValue)), - (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Derivation matches another script type", matchesAnotherScriptType(newValue)) + (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Derivation matches another script type", walletForm.getWallet().derivationMatchesAnotherScriptType(newValue)) )); validationSupport.registerValidator(fingerprint, Validator.combine( @@ -151,14 +148,6 @@ public class KeystoreController extends WalletFormController implements Initiali validationSupport.setValidationDecorator(new StyleClassValidationDecoration()); } - private boolean matchesAnotherScriptType(String derivationPath) { - if(walletForm.getWallet().getScriptType() != null && walletForm.getWallet().getScriptType().getAccount(derivationPath) > -1) { - return false; - } - - return Arrays.stream(ScriptType.values()).anyMatch(scriptType -> !scriptType.equals(walletForm.getWallet().getScriptType()) && scriptType.getAccount(derivationPath) > -1); - } - private void updateType() { type.setText(getTypeLabel(keystore)); @@ -211,9 +200,9 @@ public class KeystoreController extends WalletFormController implements Initiali @Subscribe public void update(SettingsChangedEvent event) { - if(event.getType().equals(SettingsChangedEvent.Type.SCRIPT_TYPE) && !derivation.getText().isEmpty()) { + if(walletForm.getWallet().equals(event.getWallet()) && event.getType().equals(SettingsChangedEvent.Type.SCRIPT_TYPE) && !derivation.getText().isEmpty()) { String derivationPath = derivation.getText(); - derivation.setText(""); + derivation.setText(derivationPath + " "); derivation.setText(derivationPath); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java index 8ece1e06..9ee4d041 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java @@ -165,7 +165,7 @@ public class SettingsController extends WalletFormController implements Initiali walletForm.save(); revert.setDisable(true); apply.setDisable(true); - EventManager.get().post(new WalletChangedEvent(walletForm.getWallet())); + EventManager.get().post(new WalletChangedEvent(walletForm.getWallet(), walletForm.getWalletFile())); } } catch (IOException e) { AppController.showErrorDialog("Error saving file", e.getMessage()); @@ -226,13 +226,11 @@ public class SettingsController extends WalletFormController implements Initiali if(result.getErrors().isEmpty()) { tab.getStyleClass().remove("tab-error"); tab.setTooltip(null); - apply.setDisable(false); } else { if(!tab.getStyleClass().contains("tab-error")) { tab.getStyleClass().add("tab-error"); } tab.setTooltip(new Tooltip(result.getErrors().iterator().next().getText())); - apply.setDisable(true); } }); @@ -242,28 +240,20 @@ public class SettingsController extends WalletFormController implements Initiali } } - private boolean tabsValidate() { - for(Tab tab : keystoreTabs.getTabs()) { - if(tab.getStyleClass().contains("tab-error")) { - return false; - } - } - - return true; - } - @Subscribe public void update(SettingsChangedEvent event) { Wallet wallet = event.getWallet(); - if(wallet.getPolicyType() == PolicyType.SINGLE) { - wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), 1)); - } else if(wallet.getPolicyType() == PolicyType.MULTI) { - wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), (int)multisigControl.getLowValue())); - } + if(walletForm.getWallet().equals(wallet)) { + if(wallet.getPolicyType() == PolicyType.SINGLE) { + wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), 1)); + } else if(wallet.getPolicyType() == PolicyType.MULTI) { + wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), (int)multisigControl.getLowValue())); + } - spendingMiniscript.setText(event.getWallet().getDefaultPolicy().getMiniscript().getScript()); - revert.setDisable(false); - Platform.runLater(() -> apply.setDisable(!tabsValidate())); + spendingMiniscript.setText(wallet.getDefaultPolicy().getMiniscript().getScript()); + revert.setDisable(false); + apply.setDisable(!wallet.isValid()); + } } private Optional requestEncryption(ECKey existingPubKey) { diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index caf94770..4d7c5616 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -11,7 +11,7 @@ import java.io.IOException; public class WalletForm { public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(ECIESKeyCrypter.deriveECKey("")); - private File walletFile; + private final File walletFile; private ECKey encryptionPubKey; private Wallet oldWallet; private Wallet wallet; @@ -27,6 +27,10 @@ public class WalletForm { return wallet; } + public File getWalletFile() { + return walletFile; + } + public ECKey getEncryptionPubKey() { return encryptionPubKey; } diff --git a/src/main/resources/com/sparrowwallet/sparrow/app.fxml b/src/main/resources/com/sparrowwallet/sparrow/app.fxml index 1f264fad..a0e41d88 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/app.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/app.fxml @@ -22,6 +22,7 @@ +