mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-02 20:36:44 +00:00
add lock wallet functionality
This commit is contained in:
parent
8e0b9a3ea0
commit
ea03dece72
13 changed files with 307 additions and 25 deletions
|
@ -137,6 +137,9 @@ public class AppController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
private MenuItem minimizeToTray;
|
private MenuItem minimizeToTray;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private MenuItem lockWallet;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private MenuItem refreshWallet;
|
private MenuItem refreshWallet;
|
||||||
|
|
||||||
|
@ -283,6 +286,7 @@ public class AppController implements Initializable {
|
||||||
savePSBT.visibleProperty().bind(saveTransaction.visibleProperty().not());
|
savePSBT.visibleProperty().bind(saveTransaction.visibleProperty().not());
|
||||||
savePSBTBinary.disableProperty().bind(saveTransaction.visibleProperty());
|
savePSBTBinary.disableProperty().bind(saveTransaction.visibleProperty());
|
||||||
exportWallet.setDisable(true);
|
exportWallet.setDisable(true);
|
||||||
|
lockWallet.setDisable(true);
|
||||||
refreshWallet.disableProperty().bind(Bindings.or(exportWallet.disableProperty(), Bindings.or(serverToggle.disableProperty(), AppServices.onlineProperty().not())));
|
refreshWallet.disableProperty().bind(Bindings.or(exportWallet.disableProperty(), Bindings.or(serverToggle.disableProperty(), AppServices.onlineProperty().not())));
|
||||||
sendToMany.disableProperty().bind(exportWallet.disableProperty());
|
sendToMany.disableProperty().bind(exportWallet.disableProperty());
|
||||||
|
|
||||||
|
@ -1140,6 +1144,13 @@ public class AppController implements Initializable {
|
||||||
AppServices.get().minimizeStage((Stage)tabs.getScene().getWindow());
|
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) {
|
public void refreshWallet(ActionEvent event) {
|
||||||
WalletForm selectedWalletForm = getSelectedWalletForm();
|
WalletForm selectedWalletForm = getSelectedWalletForm();
|
||||||
if(selectedWalletForm != null) {
|
if(selectedWalletForm != null) {
|
||||||
|
@ -1189,7 +1200,6 @@ public class AppController implements Initializable {
|
||||||
tabLabel.setGraphic(glyph);
|
tabLabel.setGraphic(glyph);
|
||||||
tabLabel.setGraphicTextGap(5.0);
|
tabLabel.setGraphicTextGap(5.0);
|
||||||
tab.setGraphic(tabLabel);
|
tab.setGraphic(tabLabel);
|
||||||
tab.setContextMenu(getTabContextMenu(tab));
|
|
||||||
tab.setClosable(true);
|
tab.setClosable(true);
|
||||||
tab.setOnCloseRequest(event -> {
|
tab.setOnCloseRequest(event -> {
|
||||||
if(AppServices.getWhirlpoolServices().getWhirlpoolForMixToWallet(((WalletTabData)tab.getUserData()).getWalletForm().getWalletId()) != null) {
|
if(AppServices.getWhirlpoolServices().getWhirlpoolForMixToWallet(((WalletTabData)tab.getUserData()).getWalletForm().getWalletId()) != null) {
|
||||||
|
@ -1202,13 +1212,17 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
TabPane subTabs = new TabPane();
|
TabPane subTabs = new TabPane();
|
||||||
subTabs.setSide(Side.RIGHT);
|
subTabs.setSide(Side.RIGHT);
|
||||||
subTabs.getStyleClass().add("master-only");
|
setSubTabsVisible(subTabs, false);
|
||||||
subTabs.rotateGraphicProperty().set(true);
|
subTabs.rotateGraphicProperty().set(true);
|
||||||
tab.setContent(subTabs);
|
tab.setContent(subTabs);
|
||||||
|
|
||||||
WalletForm walletForm = addWalletSubTab(subTabs, storage, wallet, backupWallet);
|
WalletForm walletForm = addWalletSubTab(subTabs, storage, wallet, backupWallet);
|
||||||
TabData tabData = new WalletTabData(TabData.TabType.WALLET, walletForm);
|
TabData tabData = new WalletTabData(TabData.TabType.WALLET, walletForm);
|
||||||
tab.setUserData(tabData);
|
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) -> {
|
subTabs.getSelectionModel().selectedItemProperty().addListener((observable, old_val, selectedTab) -> {
|
||||||
if(selectedTab != null) {
|
if(selectedTab != null) {
|
||||||
|
@ -1236,8 +1250,7 @@ public class AppController implements Initializable {
|
||||||
Label masterLabel = (Label)masterTab.getGraphic();
|
Label masterLabel = (Label)masterTab.getGraphic();
|
||||||
masterLabel.setText(getAutomaticName(wallet.getMasterWallet()));
|
masterLabel.setText(getAutomaticName(wallet.getMasterWallet()));
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
subTabs.getStyleClass().remove("master-only");
|
setSubTabsVisible(subTabs, true);
|
||||||
subTabs.getStyleClass().add("wallet-subtabs");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1247,6 +1260,20 @@ public class AppController implements Initializable {
|
||||||
EventManager.get().post(new WalletOpenedEvent(storage, wallet));
|
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) {
|
public WalletForm addWalletSubTab(TabPane subTabs, Storage storage, Wallet wallet, Wallet backupWallet) {
|
||||||
try {
|
try {
|
||||||
Tab subTab = new Tab();
|
Tab subTab = new Tab();
|
||||||
|
@ -1485,6 +1512,18 @@ public class AppController implements Initializable {
|
||||||
private ContextMenu getTabContextMenu(Tab tab) {
|
private ContextMenu getTabContextMenu(Tab tab) {
|
||||||
ContextMenu contextMenu = new ContextMenu();
|
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");
|
MenuItem close = new MenuItem("Close");
|
||||||
close.setOnAction(event -> {
|
close.setOnAction(event -> {
|
||||||
tabs.getTabs().remove(tab);
|
tabs.getTabs().remove(tab);
|
||||||
|
@ -1670,6 +1709,7 @@ public class AppController implements Initializable {
|
||||||
} else {
|
} else {
|
||||||
saveTransaction.setVisible(false);
|
saveTransaction.setVisible(false);
|
||||||
}
|
}
|
||||||
|
lockWallet.setDisable(true);
|
||||||
exportWallet.setDisable(true);
|
exportWallet.setDisable(true);
|
||||||
showLoadingLog.setDisable(true);
|
showLoadingLog.setDisable(true);
|
||||||
showUtxosChart.setDisable(true);
|
showUtxosChart.setDisable(true);
|
||||||
|
@ -1679,6 +1719,7 @@ public class AppController implements Initializable {
|
||||||
WalletTabData walletTabData = walletTabEvent.getWalletTabData();
|
WalletTabData walletTabData = walletTabEvent.getWalletTabData();
|
||||||
saveTransaction.setVisible(true);
|
saveTransaction.setVisible(true);
|
||||||
saveTransaction.setDisable(true);
|
saveTransaction.setDisable(true);
|
||||||
|
lockWallet.setDisable(walletTabData.getWalletForm().lockedProperty().get());
|
||||||
exportWallet.setDisable(walletTabData.getWallet() == null || !walletTabData.getWallet().isValid());
|
exportWallet.setDisable(walletTabData.getWallet() == null || !walletTabData.getWallet().isValid());
|
||||||
showLoadingLog.setDisable(false);
|
showLoadingLog.setDisable(false);
|
||||||
showUtxosChart.setDisable(false);
|
showUtxosChart.setDisable(false);
|
||||||
|
@ -1714,6 +1755,20 @@ public class AppController implements Initializable {
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void newWalletTransactions(NewWalletTransactionsEvent event) {
|
public void newWalletTransactions(NewWalletTransactionsEvent event) {
|
||||||
if(Config.get().isNotifyNewTransactions() && getOpenWallets().containsKey(event.getWallet())) {
|
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<BlockTransaction> blockTransactions = new ArrayList<>(event.getBlockTransactions());
|
List<BlockTransaction> blockTransactions = new ArrayList<>(event.getBlockTransactions());
|
||||||
List<BlockTransaction> whirlpoolTransactions = event.getWhirlpoolMixTransactions();
|
List<BlockTransaction> whirlpoolTransactions = event.getWhirlpoolMixTransactions();
|
||||||
blockTransactions.removeAll(whirlpoolTransactions);
|
blockTransactions.removeAll(whirlpoolTransactions);
|
||||||
|
@ -2172,4 +2227,20 @@ public class AppController implements Initializable {
|
||||||
|
|
||||||
addWalletTab(storage, event.getChildWallet(), null);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,6 +62,10 @@ public class Storage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEncrypted() throws IOException {
|
public boolean isEncrypted() throws IOException {
|
||||||
|
if(!walletFile.exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return persistence.isEncrypted(walletFile);
|
return persistence.isEncrypted(walletFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -531,10 +535,18 @@ public class Storage {
|
||||||
public static class KeyDerivationService extends Service<ECKey> {
|
public static class KeyDerivationService extends Service<ECKey> {
|
||||||
private final Storage storage;
|
private final Storage storage;
|
||||||
private final SecureString password;
|
private final SecureString password;
|
||||||
|
private final boolean verifyPassword;
|
||||||
|
|
||||||
public KeyDerivationService(Storage storage, SecureString password) {
|
public KeyDerivationService(Storage storage, SecureString password) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
this.verifyPassword = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyDerivationService(Storage storage, SecureString password, boolean verifyPassword) {
|
||||||
|
this.storage = storage;
|
||||||
|
this.password = password;
|
||||||
|
this.verifyPassword = verifyPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -542,7 +554,12 @@ public class Storage {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
protected ECKey call() throws IOException, StorageException {
|
protected ECKey call() throws IOException, StorageException {
|
||||||
try {
|
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 {
|
} finally {
|
||||||
password.clear();
|
password.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
package com.sparrowwallet.sparrow.wallet;
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
public enum Function {
|
public enum Function {
|
||||||
TRANSACTIONS, SEND, RECEIVE, ADDRESSES, UTXOS, SETTINGS;
|
TRANSACTIONS, SEND, RECEIVE, ADDRESSES, UTXOS, SETTINGS, LOCK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.io.StorageException;
|
import com.sparrowwallet.sparrow.io.StorageException;
|
||||||
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
|
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolServices;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
|
@ -35,6 +36,8 @@ import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||||
|
|
||||||
public class SettingsController extends WalletFormController implements Initializable {
|
public class SettingsController extends WalletFormController implements Initializable {
|
||||||
private static final Logger log = LoggerFactory.getLogger(SettingsController.class);
|
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);
|
WalletPasswordDialog dlg = new WalletPasswordDialog(masterWallet.getName(), WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||||
Optional<SecureString> password = dlg.showAndWait();
|
Optional<SecureString> password = dlg.showAndWait();
|
||||||
if(password.isPresent()) {
|
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 -> {
|
keyDerivationService.setOnSucceeded(workerStateEvent -> {
|
||||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
|
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
|
||||||
ECKey encryptionFullKey = keyDerivationService.getValue();
|
ECKey encryptionFullKey = keyDerivationService.getValue();
|
||||||
|
@ -482,7 +485,14 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
});
|
});
|
||||||
keyDerivationService.setOnFailed(workerStateEvent -> {
|
keyDerivationService.setOnFailed(workerStateEvent -> {
|
||||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
|
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
|
||||||
AppServices.showErrorDialog("Incorrect Password", keyDerivationService.getException().getMessage());
|
if(keyDerivationService.getException() instanceof InvalidPasswordException) {
|
||||||
|
Optional<ButtonType> 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..."));
|
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet..."));
|
||||||
keyDerivationService.start();
|
keyDerivationService.start();
|
||||||
|
|
|
@ -30,6 +30,7 @@ import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.control.Tooltip;
|
import javafx.scene.control.Tooltip;
|
||||||
import javafx.scene.control.TreeItem;
|
import javafx.scene.control.TreeItem;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
@ -47,6 +48,8 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||||
|
|
||||||
public class UtxosController extends WalletFormController implements Initializable {
|
public class UtxosController extends WalletFormController implements Initializable {
|
||||||
private static final Logger log = LoggerFactory.getLogger(UtxosController.class);
|
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);
|
WalletPasswordDialog dlg = new WalletPasswordDialog(wallet.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||||
Optional<SecureString> password = dlg.showAndWait();
|
Optional<SecureString> password = dlg.showAndWait();
|
||||||
if(password.isPresent()) {
|
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 -> {
|
keyDerivationService.setOnSucceeded(workerStateEvent -> {
|
||||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
|
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
|
||||||
ECKey encryptionFullKey = keyDerivationService.getValue();
|
ECKey encryptionFullKey = keyDerivationService.getValue();
|
||||||
|
@ -259,7 +262,14 @@ public class UtxosController extends WalletFormController implements Initializab
|
||||||
});
|
});
|
||||||
keyDerivationService.setOnFailed(workerStateEvent -> {
|
keyDerivationService.setOnFailed(workerStateEvent -> {
|
||||||
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
|
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
|
||||||
AppServices.showErrorDialog("Incorrect Password", keyDerivationService.getException().getMessage());
|
if(keyDerivationService.getException() instanceof InvalidPasswordException) {
|
||||||
|
Optional<ButtonType> 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..."));
|
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet..."));
|
||||||
keyDerivationService.start();
|
keyDerivationService.start();
|
||||||
|
|
|
@ -1,33 +1,54 @@
|
||||||
package com.sparrowwallet.sparrow.wallet;
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
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.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.ReceiveActionEvent;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.event.SendActionEvent;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.event.WalletAddressesChangedEvent;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Toggle;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.ToggleButton;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.control.ToggleGroup;
|
|
||||||
import javafx.scene.layout.StackPane;
|
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.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
|
||||||
|
|
||||||
public class WalletController extends WalletFormController implements Initializable {
|
public class WalletController extends WalletFormController implements Initializable {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(WalletController.class);
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private StackPane walletPane;
|
private StackPane walletPane;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private VBox walletMenuBox;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private ToggleGroup walletMenu;
|
private ToggleGroup walletMenu;
|
||||||
|
|
||||||
|
private BorderPane lockPane;
|
||||||
|
|
||||||
|
private final BooleanProperty walletEncryptedProperty = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
|
@ -47,7 +68,7 @@ public class WalletController extends WalletFormController implements Initializa
|
||||||
if(walletFunction.getUserData().equals(function)) {
|
if(walletFunction.getUserData().equals(function)) {
|
||||||
existing = true;
|
existing = true;
|
||||||
walletFunction.setViewOrder(0);
|
walletFunction.setViewOrder(0);
|
||||||
} else {
|
} else if(function != Function.LOCK) {
|
||||||
walletFunction.setViewOrder(1);
|
walletFunction.setViewOrder(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,6 +99,9 @@ public class WalletController extends WalletFormController implements Initializa
|
||||||
toggleButton.managedProperty().bind(toggleButton.visibleProperty());
|
toggleButton.managedProperty().bind(toggleButton.visibleProperty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
walletMenuBox.managedProperty().bind(walletMenuBox.visibleProperty());
|
||||||
|
walletMenuBox.visibleProperty().bind(getWalletForm().lockedProperty().not());
|
||||||
|
|
||||||
configure(walletForm.getWallet());
|
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
|
@Subscribe
|
||||||
public void walletAddressesChanged(WalletAddressesChangedEvent event) {
|
public void walletAddressesChanged(WalletAddressesChangedEvent event) {
|
||||||
if(event.getWalletId().equals(walletForm.getWalletId())) {
|
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
|
@Subscribe
|
||||||
public void receiveAction(ReceiveActionEvent event) {
|
public void receiveAction(ReceiveActionEvent event) {
|
||||||
if(event.getWallet().equals(walletForm.getWallet())) {
|
if(event.getWallet().equals(walletForm.getWallet())) {
|
||||||
|
@ -131,4 +232,27 @@ public class WalletController extends WalletFormController implements Initializa
|
||||||
selectFunction(Function.SEND);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.net.ServerType;
|
import com.sparrowwallet.sparrow.net.ServerType;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.beans.property.BooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -37,6 +39,8 @@ public class WalletForm {
|
||||||
|
|
||||||
private ElectrumServer.TransactionMempoolService transactionMempoolService;
|
private ElectrumServer.TransactionMempoolService transactionMempoolService;
|
||||||
|
|
||||||
|
private final BooleanProperty lockedProperty = new SimpleBooleanProperty(false);
|
||||||
|
|
||||||
public WalletForm(Storage storage, Wallet currentWallet, Wallet backupWallet) {
|
public WalletForm(Storage storage, Wallet currentWallet, Wallet backupWallet) {
|
||||||
this(storage, currentWallet, backupWallet, true);
|
this(storage, currentWallet, backupWallet, true);
|
||||||
}
|
}
|
||||||
|
@ -58,6 +62,10 @@ public class WalletForm {
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Wallet getMasterWallet() {
|
||||||
|
return wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||||
|
}
|
||||||
|
|
||||||
public Storage getStorage() {
|
public Storage getStorage() {
|
||||||
return storage;
|
return storage;
|
||||||
}
|
}
|
||||||
|
@ -298,6 +306,14 @@ public class WalletForm {
|
||||||
return walletUtxosEntry;
|
return walletUtxosEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BooleanProperty lockedProperty() {
|
||||||
|
return lockedProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocked(boolean locked) {
|
||||||
|
this.lockedProperty.set(locked);
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void walletDataChanged(WalletDataChangedEvent event) {
|
public void walletDataChanged(WalletDataChangedEvent event) {
|
||||||
if(event.getWallet().equals(wallet)) {
|
if(event.getWallet().equals(wallet)) {
|
||||||
|
|
|
@ -177,12 +177,15 @@ public class SparrowDataSource extends WalletResponseDataSource {
|
||||||
static Wallet getWallet(String zpub) {
|
static Wallet getWallet(String zpub) {
|
||||||
return AppServices.get().getOpenWallets().keySet().stream()
|
return AppServices.get().getOpenWallets().keySet().stream()
|
||||||
.filter(wallet -> {
|
.filter(wallet -> {
|
||||||
List<ExtendedKey.Header> headers = ExtendedKey.Header.getHeaders(Network.get());
|
try {
|
||||||
ExtendedKey.Header header = headers.stream().filter(head -> head.getDefaultScriptType().equals(wallet.getScriptType()) && !head.isPrivateKey()).findFirst().orElse(ExtendedKey.Header.xpub);
|
List<ExtendedKey.Header> headers = ExtendedKey.Header.getHeaders(Network.get());
|
||||||
ExtendedKey extPubKey = wallet.getKeystores().get(0).getExtendedPublicKey();
|
ExtendedKey.Header header = headers.stream().filter(head -> head.getDefaultScriptType().equals(wallet.getScriptType()) && !head.isPrivateKey()).findFirst().orElse(ExtendedKey.Header.xpub);
|
||||||
return extPubKey.toString(header).equals(zpub);
|
ExtendedKey extPubKey = wallet.getKeystores().get(0).getExtendedPublicKey();
|
||||||
|
return extPubKey.toString(header).equals(zpub);
|
||||||
|
} catch(Exception e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.filter(Wallet::isValid)
|
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,7 @@
|
||||||
<CheckMenuItem fx:id="showTxHex" mnemonicParsing="false" text="Show Transaction Hex" onAction="#showTxHex"/>
|
<CheckMenuItem fx:id="showTxHex" mnemonicParsing="false" text="Show Transaction Hex" onAction="#showTxHex"/>
|
||||||
<SeparatorMenuItem />
|
<SeparatorMenuItem />
|
||||||
<MenuItem fx:id="minimizeToTray" mnemonicParsing="false" text="Minimize to System Tray" accelerator="Shortcut+Y" onAction="#minimizeToTray"/>
|
<MenuItem fx:id="minimizeToTray" mnemonicParsing="false" text="Minimize to System Tray" accelerator="Shortcut+Y" onAction="#minimizeToTray"/>
|
||||||
|
<MenuItem fx:id="lockWallet" mnemonicParsing="false" text="Lock Wallet" accelerator="Shortcut+L" onAction="#lockWallet"/>
|
||||||
<MenuItem fx:id="refreshWallet" mnemonicParsing="false" text="Refresh Wallet" accelerator="Shortcut+R" onAction="#refreshWallet"/>
|
<MenuItem fx:id="refreshWallet" mnemonicParsing="false" text="Refresh Wallet" accelerator="Shortcut+R" onAction="#refreshWallet"/>
|
||||||
</items>
|
</items>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
|
@ -88,7 +88,7 @@
|
||||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="EXCLAMATION_TRIANGLE" styleClass="future-warning" />
|
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="EXCLAMATION_TRIANGLE" styleClass="future-warning" />
|
||||||
</graphic>
|
</graphic>
|
||||||
<tooltip>
|
<tooltip>
|
||||||
<Tooltip text="Future block specified - transaction will not be mined until this block"/>
|
<Tooltip text="Future block specified - transaction cannot be broadcast until this block height"/>
|
||||||
</tooltip>
|
</tooltip>
|
||||||
</Label>
|
</Label>
|
||||||
</Field>
|
</Field>
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="EXCLAMATION_TRIANGLE" styleClass="future-warning" />
|
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="12" icon="EXCLAMATION_TRIANGLE" styleClass="future-warning" />
|
||||||
</graphic>
|
</graphic>
|
||||||
<tooltip>
|
<tooltip>
|
||||||
<Tooltip text="Future date specified - transaction will not be mined until this date"/>
|
<Tooltip text="Future date specified - transaction cannot be broadcast until this date"/>
|
||||||
</tooltip>
|
</tooltip>
|
||||||
</Label>
|
</Label>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<BorderPane stylesheets="@wallet.css, @../general.css" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.sparrowwallet.sparrow.wallet.WalletController">
|
<BorderPane stylesheets="@wallet.css, @../general.css" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.sparrowwallet.sparrow.wallet.WalletController">
|
||||||
<left>
|
<left>
|
||||||
<VBox styleClass="list-menu">
|
<VBox fx:id="walletMenuBox" styleClass="list-menu">
|
||||||
<ToggleButton VBox.vgrow="ALWAYS" text="Transactions" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity">
|
<ToggleButton VBox.vgrow="ALWAYS" text="Transactions" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity">
|
||||||
<toggleGroup>
|
<toggleGroup>
|
||||||
<ToggleGroup fx:id="walletMenu" />
|
<ToggleGroup fx:id="walletMenu" />
|
||||||
|
|
Loading…
Reference in a new issue