From ea03dece72a50ea22051e8e82c307b9e2176b251 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Fri, 1 Oct 2021 15:47:01 +0200 Subject: [PATCH] add lock wallet functionality --- .../sparrowwallet/sparrow/AppController.java | 79 +++++++++- .../sparrow/event/WalletLockEvent.java | 15 ++ .../sparrow/event/WalletUnlockEvent.java | 15 ++ .../com/sparrowwallet/sparrow/io/Storage.java | 19 ++- .../sparrow/wallet/Function.java | 2 +- .../sparrow/wallet/SettingsController.java | 14 +- .../sparrow/wallet/UtxosController.java | 14 +- .../sparrow/wallet/WalletController.java | 138 +++++++++++++++++- .../sparrow/wallet/WalletForm.java | 16 ++ .../dataSource/SparrowDataSource.java | 13 +- .../com/sparrowwallet/sparrow/app.fxml | 1 + .../sparrow/transaction/headers.fxml | 4 +- .../sparrowwallet/sparrow/wallet/wallet.fxml | 2 +- 13 files changed, 307 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/WalletLockEvent.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/WalletUnlockEvent.java diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 70245ebc..7942efe9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -137,6 +137,9 @@ public class AppController implements Initializable { @FXML private MenuItem minimizeToTray; + @FXML + private MenuItem lockWallet; + @FXML private MenuItem refreshWallet; @@ -283,6 +286,7 @@ public class AppController implements Initializable { savePSBT.visibleProperty().bind(saveTransaction.visibleProperty().not()); savePSBTBinary.disableProperty().bind(saveTransaction.visibleProperty()); exportWallet.setDisable(true); + lockWallet.setDisable(true); refreshWallet.disableProperty().bind(Bindings.or(exportWallet.disableProperty(), Bindings.or(serverToggle.disableProperty(), AppServices.onlineProperty().not()))); sendToMany.disableProperty().bind(exportWallet.disableProperty()); @@ -1140,6 +1144,13 @@ public class AppController implements Initializable { AppServices.get().minimizeStage((Stage)tabs.getScene().getWindow()); } + public void lockWallet(ActionEvent event) { + WalletForm selectedWalletForm = getSelectedWalletForm(); + if(selectedWalletForm != null) { + EventManager.get().post(new WalletLockEvent(selectedWalletForm.getMasterWallet())); + } + } + public void refreshWallet(ActionEvent event) { WalletForm selectedWalletForm = getSelectedWalletForm(); if(selectedWalletForm != null) { @@ -1189,7 +1200,6 @@ public class AppController implements Initializable { tabLabel.setGraphic(glyph); tabLabel.setGraphicTextGap(5.0); tab.setGraphic(tabLabel); - tab.setContextMenu(getTabContextMenu(tab)); tab.setClosable(true); tab.setOnCloseRequest(event -> { if(AppServices.getWhirlpoolServices().getWhirlpoolForMixToWallet(((WalletTabData)tab.getUserData()).getWalletForm().getWalletId()) != null) { @@ -1202,13 +1212,17 @@ public class AppController implements Initializable { TabPane subTabs = new TabPane(); subTabs.setSide(Side.RIGHT); - subTabs.getStyleClass().add("master-only"); + setSubTabsVisible(subTabs, false); subTabs.rotateGraphicProperty().set(true); tab.setContent(subTabs); WalletForm walletForm = addWalletSubTab(subTabs, storage, wallet, backupWallet); TabData tabData = new WalletTabData(TabData.TabType.WALLET, walletForm); tab.setUserData(tabData); + tab.setContextMenu(getTabContextMenu(tab)); + walletForm.lockedProperty().addListener((observable, oldValue, newValue) -> { + setSubTabsVisible(subTabs, !newValue && subTabs.getTabs().size() > 1); + }); subTabs.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedTab) -> { if(selectedTab != null) { @@ -1236,8 +1250,7 @@ public class AppController implements Initializable { Label masterLabel = (Label)masterTab.getGraphic(); masterLabel.setText(getAutomaticName(wallet.getMasterWallet())); Platform.runLater(() -> { - subTabs.getStyleClass().remove("master-only"); - subTabs.getStyleClass().add("wallet-subtabs"); + setSubTabsVisible(subTabs, true); }); } } @@ -1247,6 +1260,20 @@ public class AppController implements Initializable { EventManager.get().post(new WalletOpenedEvent(storage, wallet)); } + private void setSubTabsVisible(TabPane subTabs, boolean visible) { + if(visible) { + subTabs.getStyleClass().remove("master-only"); + if(!subTabs.getStyleClass().contains("wallet-subtabs")) { + subTabs.getStyleClass().add("wallet-subtabs"); + } + } else { + if(!subTabs.getStyleClass().contains("master-only")) { + subTabs.getStyleClass().add("master-only"); + } + subTabs.getStyleClass().remove("wallet-subtabs"); + } + } + public WalletForm addWalletSubTab(TabPane subTabs, Storage storage, Wallet wallet, Wallet backupWallet) { try { Tab subTab = new Tab(); @@ -1485,6 +1512,18 @@ public class AppController implements Initializable { private ContextMenu getTabContextMenu(Tab tab) { ContextMenu contextMenu = new ContextMenu(); + if(tab.getUserData() instanceof WalletTabData walletTabData) { + MenuItem lock = new MenuItem("Lock"); + Glyph lockGlyph = new Glyph("FontAwesome", FontAwesome.Glyph.LOCK); + lockGlyph.setFontSize(12); + lock.setGraphic(lockGlyph); + lock.disableProperty().bind(walletTabData.getWalletForm().lockedProperty()); + lock.setOnAction(event -> { + EventManager.get().post(new WalletLockEvent(walletTabData.getWallet())); + }); + contextMenu.getItems().addAll(lock); + } + MenuItem close = new MenuItem("Close"); close.setOnAction(event -> { tabs.getTabs().remove(tab); @@ -1670,6 +1709,7 @@ public class AppController implements Initializable { } else { saveTransaction.setVisible(false); } + lockWallet.setDisable(true); exportWallet.setDisable(true); showLoadingLog.setDisable(true); showUtxosChart.setDisable(true); @@ -1679,6 +1719,7 @@ public class AppController implements Initializable { WalletTabData walletTabData = walletTabEvent.getWalletTabData(); saveTransaction.setVisible(true); saveTransaction.setDisable(true); + lockWallet.setDisable(walletTabData.getWalletForm().lockedProperty().get()); exportWallet.setDisable(walletTabData.getWallet() == null || !walletTabData.getWallet().isValid()); showLoadingLog.setDisable(false); showUtxosChart.setDisable(false); @@ -1714,6 +1755,20 @@ public class AppController implements Initializable { @Subscribe public void newWalletTransactions(NewWalletTransactionsEvent event) { if(Config.get().isNotifyNewTransactions() && getOpenWallets().containsKey(event.getWallet())) { + for(Tab tab : tabs.getTabs()) { + if(tab.getUserData() instanceof WalletTabData) { + TabPane subTabs = (TabPane)tab.getContent(); + for(Tab subTab : subTabs.getTabs()) { + TabData tabData = (TabData)subTab.getUserData(); + if(tabData instanceof WalletTabData walletTabData) { + if(walletTabData.getWallet().equals(event.getWallet()) && walletTabData.getWalletForm().lockedProperty().get()) { + return; + } + } + } + } + } + List blockTransactions = new ArrayList<>(event.getBlockTransactions()); List whirlpoolTransactions = event.getWhirlpoolMixTransactions(); blockTransactions.removeAll(whirlpoolTransactions); @@ -2172,4 +2227,20 @@ public class AppController implements Initializable { addWalletTab(storage, event.getChildWallet(), null); } + + @Subscribe + public void walletLock(WalletLockEvent event) { + WalletForm selectedWalletForm = getSelectedWalletForm(); + if(selectedWalletForm != null && selectedWalletForm.getMasterWallet().equals(event.getWallet())) { + lockWallet.setDisable(true); + } + } + + @Subscribe + public void walletUnlock(WalletUnlockEvent event) { + WalletForm selectedWalletForm = getSelectedWalletForm(); + if(selectedWalletForm != null && selectedWalletForm.getMasterWallet().equals(event.getWallet())) { + lockWallet.setDisable(false); + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletLockEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletLockEvent.java new file mode 100644 index 00000000..a568e5d0 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletLockEvent.java @@ -0,0 +1,15 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.wallet.Wallet; + +public class WalletLockEvent { + private final Wallet wallet; + + public WalletLockEvent(Wallet wallet) { + this.wallet = wallet; + } + + public Wallet getWallet() { + return wallet; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletUnlockEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletUnlockEvent.java new file mode 100644 index 00000000..29ce4871 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletUnlockEvent.java @@ -0,0 +1,15 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.wallet.Wallet; + +public class WalletUnlockEvent { + private final Wallet wallet; + + public WalletUnlockEvent(Wallet wallet) { + this.wallet = wallet; + } + + public Wallet getWallet() { + return wallet; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java index 19135404..4d9d043f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java @@ -62,6 +62,10 @@ public class Storage { } public boolean isEncrypted() throws IOException { + if(!walletFile.exists()) { + return false; + } + return persistence.isEncrypted(walletFile); } @@ -531,10 +535,18 @@ public class Storage { public static class KeyDerivationService extends Service { private final Storage storage; private final SecureString password; + private final boolean verifyPassword; public KeyDerivationService(Storage storage, SecureString password) { this.storage = storage; this.password = password; + this.verifyPassword = false; + } + + public KeyDerivationService(Storage storage, SecureString password, boolean verifyPassword) { + this.storage = storage; + this.password = password; + this.verifyPassword = verifyPassword; } @Override @@ -542,7 +554,12 @@ public class Storage { return new Task<>() { protected ECKey call() throws IOException, StorageException { try { - return storage.getEncryptionKey(password); + ECKey encryptionFullKey = storage.getEncryptionKey(password); + if(verifyPassword && !ECKey.fromPublicOnly(encryptionFullKey).equals(storage.getEncryptionPubKey())) { + throw new InvalidPasswordException("Derived pubkey does not match stored pubkey"); + } + + return encryptionFullKey; } finally { password.clear(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/Function.java b/src/main/java/com/sparrowwallet/sparrow/wallet/Function.java index 86b6d90f..9fc2af35 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/Function.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/Function.java @@ -1,5 +1,5 @@ package com.sparrowwallet.sparrow.wallet; public enum Function { - TRANSACTIONS, SEND, RECEIVE, ADDRESSES, UTXOS, SETTINGS; + TRANSACTIONS, SEND, RECEIVE, ADDRESSES, UTXOS, SETTINGS, LOCK; } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java index be8a6787..c2b048f7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java @@ -16,6 +16,7 @@ import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.io.StorageException; import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices; +import javafx.application.Platform; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; import javafx.event.ActionEvent; @@ -35,6 +36,8 @@ import java.net.URL; import java.util.*; import java.util.stream.Collectors; +import static com.sparrowwallet.sparrow.AppServices.showErrorDialog; + public class SettingsController extends WalletFormController implements Initializable { private static final Logger log = LoggerFactory.getLogger(SettingsController.class); @@ -459,7 +462,7 @@ public class SettingsController extends WalletFormController implements Initiali WalletPasswordDialog dlg = new WalletPasswordDialog(masterWallet.getName(), WalletPasswordDialog.PasswordRequirement.LOAD); Optional password = dlg.showAndWait(); if(password.isPresent()) { - Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(walletForm.getStorage(), password.get()); + Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(walletForm.getStorage(), password.get(), true); keyDerivationService.setOnSucceeded(workerStateEvent -> { EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done")); ECKey encryptionFullKey = keyDerivationService.getValue(); @@ -482,7 +485,14 @@ public class SettingsController extends WalletFormController implements Initiali }); keyDerivationService.setOnFailed(workerStateEvent -> { EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed")); - AppServices.showErrorDialog("Incorrect Password", keyDerivationService.getException().getMessage()); + if(keyDerivationService.getException() instanceof InvalidPasswordException) { + Optional optResponse = showErrorDialog("Invalid Password", "The wallet password was invalid. Try again?", ButtonType.CANCEL, ButtonType.OK); + if(optResponse.isPresent() && optResponse.get().equals(ButtonType.OK)) { + Platform.runLater(() -> addAccount(null)); + } + } else { + log.error("Error deriving wallet key", keyDerivationService.getException()); + } }); EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet...")); keyDerivationService.start(); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java index 6d0d491e..66eb7cf7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/UtxosController.java @@ -30,6 +30,7 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.Button; +import javafx.scene.control.ButtonType; import javafx.scene.control.Tooltip; import javafx.scene.control.TreeItem; import javafx.scene.layout.HBox; @@ -47,6 +48,8 @@ import java.nio.charset.StandardCharsets; import java.util.*; import java.util.stream.Collectors; +import static com.sparrowwallet.sparrow.AppServices.showErrorDialog; + public class UtxosController extends WalletFormController implements Initializable { private static final Logger log = LoggerFactory.getLogger(UtxosController.class); @@ -234,7 +237,7 @@ public class UtxosController extends WalletFormController implements Initializab WalletPasswordDialog dlg = new WalletPasswordDialog(wallet.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD); Optional password = dlg.showAndWait(); if(password.isPresent()) { - Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(walletForm.getStorage(), password.get()); + Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(walletForm.getStorage(), password.get(), true); keyDerivationService.setOnSucceeded(workerStateEvent -> { EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done")); ECKey encryptionFullKey = keyDerivationService.getValue(); @@ -259,7 +262,14 @@ public class UtxosController extends WalletFormController implements Initializab }); keyDerivationService.setOnFailed(workerStateEvent -> { EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed")); - AppServices.showErrorDialog("Incorrect Password", keyDerivationService.getException().getMessage()); + if(keyDerivationService.getException() instanceof InvalidPasswordException) { + Optional optResponse = showErrorDialog("Invalid Password", "The wallet password was invalid. Try again?", ButtonType.CANCEL, ButtonType.OK); + if(optResponse.isPresent() && optResponse.get().equals(ButtonType.OK)) { + Platform.runLater(() -> previewPremix(tx0Preview, utxoEntries)); + } + } else { + log.error("Error deriving wallet key", keyDerivationService.getException()); + } }); EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet...")); keyDerivationService.start(); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java index 825781aa..6e3d6ad3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java @@ -1,33 +1,54 @@ package com.sparrowwallet.sparrow.wallet; import com.google.common.eventbus.Subscribe; +import com.sparrowwallet.drongo.SecureString; +import com.sparrowwallet.drongo.crypto.InvalidPasswordException; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; -import com.sparrowwallet.sparrow.event.ReceiveActionEvent; -import com.sparrowwallet.sparrow.event.SendActionEvent; -import com.sparrowwallet.sparrow.event.WalletAddressesChangedEvent; +import com.sparrowwallet.sparrow.event.*; +import com.sparrowwallet.sparrow.io.Storage; import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; +import javafx.geometry.Pos; import javafx.scene.Node; -import javafx.scene.control.Toggle; -import javafx.scene.control.ToggleButton; -import javafx.scene.control.ToggleGroup; +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import org.controlsfx.control.textfield.CustomPasswordField; +import org.controlsfx.control.textfield.TextFields; +import org.controlsfx.glyphfont.FontAwesome; +import org.controlsfx.glyphfont.Glyph; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.URL; import java.util.ResourceBundle; +import static com.sparrowwallet.sparrow.AppServices.showErrorDialog; + public class WalletController extends WalletFormController implements Initializable { + private static final Logger log = LoggerFactory.getLogger(WalletController.class); + @FXML private StackPane walletPane; + @FXML + private VBox walletMenuBox; + @FXML private ToggleGroup walletMenu; + private BorderPane lockPane; + + private final BooleanProperty walletEncryptedProperty = new SimpleBooleanProperty(false); + @Override public void initialize(URL location, ResourceBundle resources) { EventManager.get().register(this); @@ -47,7 +68,7 @@ public class WalletController extends WalletFormController implements Initializa if(walletFunction.getUserData().equals(function)) { existing = true; walletFunction.setViewOrder(0); - } else { + } else if(function != Function.LOCK) { walletFunction.setViewOrder(1); } } @@ -78,6 +99,9 @@ public class WalletController extends WalletFormController implements Initializa toggleButton.managedProperty().bind(toggleButton.visibleProperty()); } + walletMenuBox.managedProperty().bind(walletMenuBox.visibleProperty()); + walletMenuBox.visibleProperty().bind(getWalletForm().lockedProperty().not()); + configure(walletForm.getWallet()); } @@ -111,6 +135,76 @@ public class WalletController extends WalletFormController implements Initializa }); } + private void initializeLockScreen() { + lockPane = new BorderPane(); + lockPane.setUserData(Function.LOCK); + lockPane.getStyleClass().add("wallet-pane"); + VBox vBox = new VBox(20); + vBox.setAlignment(Pos.CENTER); + Glyph lock = new Glyph("FontAwesome", FontAwesome.Glyph.LOCK); + lock.setFontSize(80); + vBox.getChildren().add(lock); + Label label = new Label("Enter password to unlock:"); + label.managedProperty().bind(label.visibleProperty()); + label.visibleProperty().bind(walletEncryptedProperty); + CustomPasswordField passwordField = (CustomPasswordField)TextFields.createClearablePasswordField(); + passwordField.setMaxWidth(300); + passwordField.managedProperty().bind(passwordField.visibleProperty()); + passwordField.visibleProperty().bind(walletEncryptedProperty); + passwordField.setOnAction(event -> { + unlockWallet(passwordField); + }); + Button unlockButton = new Button("Unlock"); + unlockButton.setPrefWidth(300); + unlockButton.setOnAction(event -> { + unlockWallet(passwordField); + }); + vBox.getChildren().addAll(label, passwordField, unlockButton); + StackPane stackPane = new StackPane(); + stackPane.getChildren().add(vBox); + lockPane.setCenter(stackPane); + walletPane.getChildren().add(lockPane); + } + + private void unlockWallet(CustomPasswordField passwordField) { + if(walletEncryptedProperty.get()) { + String walletId = walletForm.getWalletId(); + SecureString password = new SecureString(passwordField.getText()); + Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(walletForm.getStorage(), password, true); + keyDerivationService.setOnSucceeded(workerStateEvent -> { + passwordField.clear(); + password.clear(); + EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done")); + unlockWallet(); + }); + keyDerivationService.setOnFailed(workerStateEvent -> { + EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed")); + if(keyDerivationService.getException() instanceof InvalidPasswordException) { + showErrorDialog("Invalid Password", "The wallet password was invalid."); + } else { + log.error("Error deriving wallet key", keyDerivationService.getException()); + } + }); + EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet...")); + keyDerivationService.start(); + } else { + unlockWallet(); + } + } + + private void unlockWallet() { + Wallet masterWallet = getWalletForm().getWallet().isMasterWallet() ? getWalletForm().getWallet() : getWalletForm().getWallet().getMasterWallet(); + EventManager.get().post(new WalletUnlockEvent(masterWallet)); + } + + private void updateWalletEncryptedStatus() { + try { + walletEncryptedProperty.set(getWalletForm().getStorage().isEncrypted()); + } catch(IOException e) { + log.warn("Error determining if wallet is locked", e); + } + } + @Subscribe public void walletAddressesChanged(WalletAddressesChangedEvent event) { if(event.getWalletId().equals(walletForm.getWalletId())) { @@ -118,6 +212,13 @@ public class WalletController extends WalletFormController implements Initializa } } + @Subscribe + public void walletSettingsChanged(WalletSettingsChangedEvent event) { + if(event.getWalletId().equals(walletForm.getWalletId())) { + Platform.runLater(this::updateWalletEncryptedStatus); + } + } + @Subscribe public void receiveAction(ReceiveActionEvent event) { if(event.getWallet().equals(walletForm.getWallet())) { @@ -131,4 +232,27 @@ public class WalletController extends WalletFormController implements Initializa selectFunction(Function.SEND); } } + + @Subscribe + public void walletLock(WalletLockEvent event) { + if(event.getWallet().equals(walletForm.getMasterWallet())) { + if(lockPane == null) { + updateWalletEncryptedStatus(); + initializeLockScreen(); + } + + getWalletForm().setLocked(true); + lockPane.setViewOrder(-1); + } + } + + @Subscribe + public void walletUnlock(WalletUnlockEvent event) { + if(event.getWallet().equals(walletForm.getMasterWallet())) { + getWalletForm().setLocked(false); + if(lockPane != null) { + lockPane.setViewOrder(2); + } + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index 9b99f727..ff22d8b3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -14,6 +14,8 @@ import com.sparrowwallet.sparrow.net.ElectrumServer; import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.net.ServerType; import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.util.Duration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +39,8 @@ public class WalletForm { private ElectrumServer.TransactionMempoolService transactionMempoolService; + private final BooleanProperty lockedProperty = new SimpleBooleanProperty(false); + public WalletForm(Storage storage, Wallet currentWallet, Wallet backupWallet) { this(storage, currentWallet, backupWallet, true); } @@ -58,6 +62,10 @@ public class WalletForm { return wallet; } + public Wallet getMasterWallet() { + return wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); + } + public Storage getStorage() { return storage; } @@ -298,6 +306,14 @@ public class WalletForm { return walletUtxosEntry; } + public BooleanProperty lockedProperty() { + return lockedProperty; + } + + public void setLocked(boolean locked) { + this.lockedProperty.set(locked); + } + @Subscribe public void walletDataChanged(WalletDataChangedEvent event) { if(event.getWallet().equals(wallet)) { diff --git a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java index 42e28182..c3ee29a3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java +++ b/src/main/java/com/sparrowwallet/sparrow/whirlpool/dataSource/SparrowDataSource.java @@ -177,12 +177,15 @@ public class SparrowDataSource extends WalletResponseDataSource { static Wallet getWallet(String zpub) { return AppServices.get().getOpenWallets().keySet().stream() .filter(wallet -> { - List headers = ExtendedKey.Header.getHeaders(Network.get()); - ExtendedKey.Header header = headers.stream().filter(head -> head.getDefaultScriptType().equals(wallet.getScriptType()) && !head.isPrivateKey()).findFirst().orElse(ExtendedKey.Header.xpub); - ExtendedKey extPubKey = wallet.getKeystores().get(0).getExtendedPublicKey(); - return extPubKey.toString(header).equals(zpub); + try { + List headers = ExtendedKey.Header.getHeaders(Network.get()); + ExtendedKey.Header header = headers.stream().filter(head -> head.getDefaultScriptType().equals(wallet.getScriptType()) && !head.isPrivateKey()).findFirst().orElse(ExtendedKey.Header.xpub); + ExtendedKey extPubKey = wallet.getKeystores().get(0).getExtendedPublicKey(); + return extPubKey.toString(header).equals(zpub); + } catch(Exception e) { + return false; + } }) - .filter(Wallet::isValid) .findFirst() .orElse(null); } diff --git a/src/main/resources/com/sparrowwallet/sparrow/app.fxml b/src/main/resources/com/sparrowwallet/sparrow/app.fxml index 2944267c..d9053c70 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/app.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/app.fxml @@ -100,6 +100,7 @@ + diff --git a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml index 7e034f0f..52cee8cf 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/transaction/headers.fxml @@ -88,7 +88,7 @@ - + @@ -99,7 +99,7 @@ - + diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.fxml index da9e2a14..0cbdeeb2 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.fxml @@ -10,7 +10,7 @@ - +