wallet export, settings fixes

This commit is contained in:
Craig Raw 2020-05-15 12:56:38 +02:00
parent bb2ec1882d
commit d0e5da0ec8
21 changed files with 387 additions and 59 deletions

2
drongo

@ -1 +1 @@
Subproject commit 766a986abb72ea84317a405b93055abc3d1eaf22 Subproject commit f6414a447550eb87a871c54c7e376713cae8eb9c

View file

@ -13,13 +13,8 @@ import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.psbt.PSBTParseException; import com.sparrowwallet.drongo.psbt.PSBTParseException;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.control.TextAreaDialog; import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.control.WalletImportDialog; import com.sparrowwallet.sparrow.event.*;
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.io.FileType; import com.sparrowwallet.sparrow.io.FileType;
import com.sparrowwallet.sparrow.io.IOUtils; import com.sparrowwallet.sparrow.io.IOUtils;
import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.io.Storage;
@ -46,6 +41,9 @@ public class AppController implements Initializable {
private static final String TRANSACTION_TAB_TYPE = "transaction"; private static final String TRANSACTION_TAB_TYPE = "transaction";
public static final String DRAG_OVER_CLASS = "drag-over"; public static final String DRAG_OVER_CLASS = "drag-over";
@FXML
private MenuItem exportWallet;
@FXML @FXML
private CheckMenuItem showTxHex; private CheckMenuItem showTxHex;
@ -96,12 +94,19 @@ public class AppController implements Initializable {
TabData tabData = (TabData)selectedTab.getUserData(); TabData tabData = (TabData)selectedTab.getUserData();
if(tabData.getType() == TabData.TabType.TRANSACTION) { if(tabData.getType() == TabData.TabType.TRANSACTION) {
EventManager.get().post(new TransactionTabSelectedEvent(selectedTab)); 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); showTxHex.setSelected(true);
showTxHexProperty = true; showTxHexProperty = true;
exportWallet.setDisable(true);
//addWalletTab("newWallet", new Wallet(PolicyType.SINGLE, ScriptType.P2WPKH)); //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> wallet = dlg.showAndWait();
if(wallet.isPresent()) {
//Successful export
}
}
}
public Tab addWalletTab(File walletFile, ECKey encryptionPubKey, Wallet wallet) { public Tab addWalletTab(File walletFile, ECKey encryptionPubKey, Wallet wallet) {
try { try {
String name = walletFile.getName(); String name = walletFile.getName();
@ -276,7 +294,7 @@ public class AppController implements Initializable {
name = name.substring(0, name.lastIndexOf('.')); name = name.substring(0, name.lastIndexOf('.'));
} }
Tab tab = new Tab(name); 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.setUserData(tabData);
tab.setContextMenu(getTabContextMenu(tab)); tab.setContextMenu(getTabContextMenu(tab));
tab.setClosable(true); tab.setClosable(true);
@ -351,7 +369,7 @@ public class AppController implements Initializable {
} }
Tab tab = new Tab(tabName); Tab tab = new Tab(tabName);
TabData tabData = new TabData(TabData.TabType.TRANSACTION); TabData tabData = new TransactionTabData(TabData.TabType.TRANSACTION, transaction);
tab.setUserData(tabData); tab.setUserData(tabData);
tab.setContextMenu(getTabContextMenu(tab)); tab.setContextMenu(getTabContextMenu(tab));
tab.setClosable(true); tab.setClosable(true);
@ -397,7 +415,7 @@ public class AppController implements Initializable {
} }
@Subscribe @Subscribe
public void tabSelected(TabEvent event) { public void tabSelected(TabSelectedEvent event) {
Tab selectedTab = event.getTab(); Tab selectedTab = event.getTab();
String tabType = (String)selectedTab.getUserData(); String tabType = (String)selectedTab.getUserData();

View file

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

View file

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

View file

@ -51,7 +51,7 @@ public abstract class FileImportPane extends TitledDescriptionPane {
Stage window = new Stage(); Stage window = new Stage();
FileChooser fileChooser = new FileChooser(); FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " keystore"); fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " File");
fileChooser.getExtensionFilters().addAll( fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("All Files", "*.*"), new FileChooser.ExtensionFilter("All Files", "*.*"),
new FileChooser.ExtensionFilter("JSON", "*.json") new FileChooser.ExtensionFilter("JSON", "*.json")

View file

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

View file

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

View file

@ -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<Wallet> {
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<WalletExport> 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();
}
}

View file

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

View file

@ -2,7 +2,7 @@ package com.sparrowwallet.sparrow.event;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
public class TransactionTabSelectedEvent extends TabEvent { public class TransactionTabSelectedEvent extends TabSelectedEvent {
public TransactionTabSelectedEvent(Tab tab) { public TransactionTabSelectedEvent(Tab tab) {
super(tab); super(tab);
} }

View file

@ -2,14 +2,22 @@ package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
public class WalletChangedEvent { import java.io.File;
private Wallet wallet;
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.wallet = wallet;
this.walletFile = walletFile;
} }
public Wallet getWallet() { public Wallet getWallet() {
return wallet; return wallet;
} }
public File getWalletFile() {
return walletFile;
}
} }

View file

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

View file

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

View file

@ -18,6 +18,7 @@ public class FontAwesome5 extends GlyphFont {
CIRCLE('\uf111'), CIRCLE('\uf111'),
EXCLAMATION_CIRCLE('\uf06a'), EXCLAMATION_CIRCLE('\uf06a'),
EYE('\uf06e'), EYE('\uf06e'),
KEY('\uf084'),
LAPTOP('\uf109'), LAPTOP('\uf109'),
SD_CARD('\uf7c2'), SD_CARD('\uf7c2'),
WALLET('\uf555'); WALLET('\uf555');

View file

@ -157,15 +157,36 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
throw new ExportException("Could not export a wallet with a " + wallet.getPolicyType() + " policy"); 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; int index = 1;
for(Keystore keystore : wallet.getKeystores()) { for(Keystore keystore : wallet.getKeystores()) {
ElectrumKeystore ek = new ElectrumKeystore(); ElectrumKeystore ek = new ElectrumKeystore();
ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader);
if(keystore.getSource() == KeystoreSource.HW_USB || keystore.getSource() == KeystoreSource.HW_AIRGAPPED) {
ek.label = keystore.getLabel();
ek.derivation = keystore.getKeyDerivation().getDerivationPath(); ek.derivation = keystore.getKeyDerivation().getDerivationPath();
ek.root_fingerprint = keystore.getKeyDerivation().getMasterFingerprint(); ek.root_fingerprint = keystore.getKeyDerivation().getMasterFingerprint();
ek.label = keystore.getLabel(); 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)) { if(wallet.getPolicyType().equals(PolicyType.SINGLE)) {
ew.keystores.put("keystore", ek); ew.keystores.put("keystore", ek);
@ -179,6 +200,12 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
Gson gson = new Gson(); Gson gson = new Gson();
JsonObject eJson = gson.toJsonTree(ew.keystores).getAsJsonObject(); JsonObject eJson = gson.toJsonTree(ew.keystores).getAsJsonObject();
eJson.addProperty("wallet_type", ew.wallet_type); 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(); gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
String json = gson.toJson(eJson); String json = gson.toJson(eJson);
@ -203,6 +230,8 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
private static class ElectrumJsonWallet { private static class ElectrumJsonWallet {
public Map<String, ElectrumKeystore> keystores = new LinkedHashMap<>(); public Map<String, ElectrumKeystore> keystores = new LinkedHashMap<>();
public String wallet_type; public String wallet_type;
public String seed_type;
public Boolean use_encryption;
} }
public static class ElectrumKeystore { public static class ElectrumKeystore {
@ -216,5 +245,6 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
public String type; public String type;
public String derivation; public String derivation;
public String seed; public String seed;
public Integer pw_hash_version;
} }
} }

View file

@ -1,5 +1,8 @@
package com.sparrowwallet.sparrow.io; package com.sparrowwallet.sparrow.io;
import com.sparrowwallet.drongo.wallet.WalletModel;
public interface Export { public interface Export {
String getName(); String getName();
WalletModel getWalletModel();
} }

View file

@ -1,6 +1,6 @@
package com.sparrowwallet.sparrow.io; package com.sparrowwallet.sparrow.io;
public class ExportException extends Throwable { public class ExportException extends Exception {
public ExportException() { public ExportException() {
super(); super();
} }

View file

@ -4,7 +4,6 @@ import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.Utils;
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.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
@ -20,10 +19,8 @@ import org.controlsfx.validation.ValidationResult;
import org.controlsfx.validation.ValidationSupport; import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.Validator; import org.controlsfx.validation.Validator;
import org.controlsfx.validation.decoration.StyleClassValidationDecoration; import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
import tornadofx.control.Form;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.stream.Collectors; 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)); EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_FINGERPRINT));
}); });
derivation.textProperty().addListener((observable, oldValue, newValue) -> { 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)); keystore.setKeyDerivation(new KeyDerivation(keystore.getKeyDerivation().getMasterFingerprint(), newValue));
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_DERIVATION)); 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( validationSupport.registerValidator(derivation, Validator.combine(
Validator.createEmptyValidator("Derivation is required"), 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 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( validationSupport.registerValidator(fingerprint, Validator.combine(
@ -151,14 +148,6 @@ public class KeystoreController extends WalletFormController implements Initiali
validationSupport.setValidationDecorator(new StyleClassValidationDecoration()); 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() { private void updateType() {
type.setText(getTypeLabel(keystore)); type.setText(getTypeLabel(keystore));
@ -211,9 +200,9 @@ public class KeystoreController extends WalletFormController implements Initiali
@Subscribe @Subscribe
public void update(SettingsChangedEvent event) { 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(); String derivationPath = derivation.getText();
derivation.setText(""); derivation.setText(derivationPath + " ");
derivation.setText(derivationPath); derivation.setText(derivationPath);
} }
} }

View file

@ -165,7 +165,7 @@ public class SettingsController extends WalletFormController implements Initiali
walletForm.save(); walletForm.save();
revert.setDisable(true); revert.setDisable(true);
apply.setDisable(true); apply.setDisable(true);
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet())); EventManager.get().post(new WalletChangedEvent(walletForm.getWallet(), walletForm.getWalletFile()));
} }
} catch (IOException e) { } catch (IOException e) {
AppController.showErrorDialog("Error saving file", e.getMessage()); AppController.showErrorDialog("Error saving file", e.getMessage());
@ -226,13 +226,11 @@ public class SettingsController extends WalletFormController implements Initiali
if(result.getErrors().isEmpty()) { if(result.getErrors().isEmpty()) {
tab.getStyleClass().remove("tab-error"); tab.getStyleClass().remove("tab-error");
tab.setTooltip(null); tab.setTooltip(null);
apply.setDisable(false);
} else { } else {
if(!tab.getStyleClass().contains("tab-error")) { if(!tab.getStyleClass().contains("tab-error")) {
tab.getStyleClass().add("tab-error"); tab.getStyleClass().add("tab-error");
} }
tab.setTooltip(new Tooltip(result.getErrors().iterator().next().getText())); 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 @Subscribe
public void update(SettingsChangedEvent event) { public void update(SettingsChangedEvent event) {
Wallet wallet = event.getWallet(); Wallet wallet = event.getWallet();
if(walletForm.getWallet().equals(wallet)) {
if(wallet.getPolicyType() == PolicyType.SINGLE) { if(wallet.getPolicyType() == PolicyType.SINGLE) {
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), 1)); wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), 1));
} else if(wallet.getPolicyType() == PolicyType.MULTI) { } else if(wallet.getPolicyType() == PolicyType.MULTI) {
wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), (int)multisigControl.getLowValue())); wallet.setDefaultPolicy(Policy.getPolicy(wallet.getPolicyType(), wallet.getScriptType(), wallet.getKeystores(), (int)multisigControl.getLowValue()));
} }
spendingMiniscript.setText(event.getWallet().getDefaultPolicy().getMiniscript().getScript()); spendingMiniscript.setText(wallet.getDefaultPolicy().getMiniscript().getScript());
revert.setDisable(false); revert.setDisable(false);
Platform.runLater(() -> apply.setDisable(!tabsValidate())); apply.setDisable(!wallet.isValid());
}
} }
private Optional<ECKey> requestEncryption(ECKey existingPubKey) { private Optional<ECKey> requestEncryption(ECKey existingPubKey) {

View file

@ -11,7 +11,7 @@ import java.io.IOException;
public class WalletForm { public class WalletForm {
public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(ECIESKeyCrypter.deriveECKey("")); public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(ECIESKeyCrypter.deriveECKey(""));
private File walletFile; private final File walletFile;
private ECKey encryptionPubKey; private ECKey encryptionPubKey;
private Wallet oldWallet; private Wallet oldWallet;
private Wallet wallet; private Wallet wallet;
@ -27,6 +27,10 @@ public class WalletForm {
return wallet; return wallet;
} }
public File getWalletFile() {
return walletFile;
}
public ECKey getEncryptionPubKey() { public ECKey getEncryptionPubKey() {
return encryptionPubKey; return encryptionPubKey;
} }

View file

@ -22,6 +22,7 @@
</items> </items>
</Menu> </Menu>
<MenuItem mnemonicParsing="false" text="Import Wallet..." onAction="#importWallet"/> <MenuItem mnemonicParsing="false" text="Import Wallet..." onAction="#importWallet"/>
<MenuItem fx:id="exportWallet" mnemonicParsing="false" text="Export Wallet..." onAction="#exportWallet"/>
<MenuItem mnemonicParsing="false" text="Close" onAction="#closeTab"/> <MenuItem mnemonicParsing="false" text="Close" onAction="#closeTab"/>
</items> </items>
</Menu> </Menu>