mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
handle password key derivation in separate tasks
This commit is contained in:
parent
b52a7137c3
commit
2acc922b06
7 changed files with 285 additions and 112 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit d2bd335e76a6a18634d033b49bf2d36c2494d9cf
|
Subproject commit 06de1d7e1458cb00cc242025c5e0d536633083a0
|
|
@ -11,6 +11,8 @@ import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
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.Keystore;
|
||||||
|
import com.sparrowwallet.drongo.wallet.MnemonicException;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.control.*;
|
import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
|
@ -20,6 +22,10 @@ import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.transaction.TransactionController;
|
import com.sparrowwallet.sparrow.transaction.TransactionController;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletController;
|
import com.sparrowwallet.sparrow.wallet.WalletController;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||||
|
import javafx.animation.Animation;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.KeyValue;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
|
@ -30,6 +36,8 @@ import javafx.scene.input.TransferMode;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
import org.controlsfx.control.StatusBar;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -52,11 +60,16 @@ public class AppController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
private TabPane tabs;
|
private TabPane tabs;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private StatusBar statusBar;
|
||||||
|
|
||||||
|
private Timeline statusTimeline;
|
||||||
|
|
||||||
public static boolean showTxHexProperty;
|
public static boolean showTxHexProperty;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
|
EventManager.get().register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void initializeView() {
|
void initializeView() {
|
||||||
|
@ -234,12 +247,12 @@ public class AppController implements Initializable {
|
||||||
File file = fileChooser.showOpenDialog(window);
|
File file = fileChooser.showOpenDialog(window);
|
||||||
if(file != null) {
|
if(file != null) {
|
||||||
try {
|
try {
|
||||||
Wallet wallet;
|
|
||||||
CharSequence password = null;
|
|
||||||
Storage storage = new Storage(file);
|
Storage storage = new Storage(file);
|
||||||
FileType fileType = IOUtils.getFileType(file);
|
FileType fileType = IOUtils.getFileType(file);
|
||||||
if(FileType.JSON.equals(fileType)) {
|
if(FileType.JSON.equals(fileType)) {
|
||||||
wallet = storage.loadWallet();
|
Wallet wallet = storage.loadWallet();
|
||||||
|
Tab tab = addWalletTab(storage, wallet);
|
||||||
|
tabs.getSelectionModel().select(tab);
|
||||||
} else if(FileType.BINARY.equals(fileType)) {
|
} else if(FileType.BINARY.equals(fileType)) {
|
||||||
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
|
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||||
Optional<SecureString> optionalPassword = dlg.showAndWait();
|
Optional<SecureString> optionalPassword = dlg.showAndWait();
|
||||||
|
@ -247,18 +260,79 @@ public class AppController implements Initializable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
password = optionalPassword.get();
|
SecureString password = optionalPassword.get();
|
||||||
wallet = storage.loadWallet(password);
|
Storage.LoadWalletService loadWalletService = new Storage.LoadWalletService(storage, password);
|
||||||
|
loadWalletService.setOnSucceeded(workerStateEvent -> {
|
||||||
|
EventManager.get().post(new TimedWorkerEvent("Done"));
|
||||||
|
Storage.WalletAndKey walletAndKey = loadWalletService.getValue();
|
||||||
|
try {
|
||||||
|
restorePublicKeysFromSeed(walletAndKey.wallet, walletAndKey.key);
|
||||||
|
Tab tab = addWalletTab(storage, walletAndKey.wallet);
|
||||||
|
tabs.getSelectionModel().select(tab);
|
||||||
|
} catch(MnemonicException e) {
|
||||||
|
showErrorDialog("Error Opening Wallet", e.getMessage());
|
||||||
|
} finally {
|
||||||
|
walletAndKey.key.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
loadWalletService.setOnFailed(workerStateEvent -> {
|
||||||
|
EventManager.get().post(new TimedWorkerEvent("Failed"));
|
||||||
|
Throwable exception = loadWalletService.getException();
|
||||||
|
if(exception instanceof InvalidPasswordException) {
|
||||||
|
showErrorDialog("Invalid Password", "The wallet password was invalid.");
|
||||||
|
} else {
|
||||||
|
showErrorDialog("Error Opening Wallet", exception.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
loadWalletService.start();
|
||||||
|
EventManager.get().post(new TimedWorkerEvent("Decrypting wallet...", 1000));
|
||||||
} else {
|
} else {
|
||||||
throw new IOException("Unsupported file type");
|
throw new IOException("Unsupported file type");
|
||||||
}
|
}
|
||||||
|
|
||||||
Tab tab = addWalletTab(storage, wallet);
|
|
||||||
tabs.getSelectionModel().select(tab);
|
|
||||||
} catch (InvalidPasswordException e) {
|
|
||||||
showErrorDialog("Invalid Password", "The password was invalid.");
|
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
showErrorDialog("Error opening wallet", e.getMessage());
|
showErrorDialog("Error Opening Wallet", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restorePublicKeysFromSeed(Wallet wallet, Key key) throws MnemonicException {
|
||||||
|
if(wallet.containsSeeds()) {
|
||||||
|
//Derive xpub and master fingerprint from seed, potentially with passphrase
|
||||||
|
Wallet copy = wallet.copy();
|
||||||
|
for(Keystore copyKeystore : copy.getKeystores()) {
|
||||||
|
if(copyKeystore.hasSeed()) {
|
||||||
|
if(copyKeystore.getSeed().needsPassphrase()) {
|
||||||
|
KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(copyKeystore);
|
||||||
|
Optional<String> optionalPassphrase = passphraseDialog.showAndWait();
|
||||||
|
if(optionalPassphrase.isPresent()) {
|
||||||
|
copyKeystore.getSeed().setPassphrase(optionalPassphrase.get());
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
copyKeystore.getSeed().setPassphrase("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(wallet.isEncrypted()) {
|
||||||
|
if(key == null) {
|
||||||
|
throw new IllegalStateException("Wallet was not encrypted, but seed is");
|
||||||
|
}
|
||||||
|
|
||||||
|
copy.decrypt(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < wallet.getKeystores().size(); i++) {
|
||||||
|
Keystore keystore = wallet.getKeystores().get(i);
|
||||||
|
if(keystore.hasSeed()) {
|
||||||
|
Keystore copyKeystore = copy.getKeystores().get(i);
|
||||||
|
Keystore derivedKeystore = Keystore.fromSeed(copyKeystore.getSeed(), copyKeystore.getKeyDerivation().getDerivation());
|
||||||
|
keystore.setKeyDerivation(derivedKeystore.getKeyDerivation());
|
||||||
|
keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey());
|
||||||
|
keystore.getSeed().setPassphrase(copyKeystore.getSeed().getPassphrase());
|
||||||
|
copyKeystore.getSeed().clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -418,7 +492,6 @@ public class AppController implements Initializable {
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void tabSelected(TabSelectedEvent event) {
|
public void tabSelected(TabSelectedEvent event) {
|
||||||
Tab selectedTab = event.getTab();
|
Tab selectedTab = event.getTab();
|
||||||
String tabType = (String)selectedTab.getUserData();
|
|
||||||
|
|
||||||
String tabName = selectedTab.getText();
|
String tabName = selectedTab.getText();
|
||||||
if(tabs.getScene() != null) {
|
if(tabs.getScene() != null) {
|
||||||
|
@ -426,4 +499,28 @@ public class AppController implements Initializable {
|
||||||
tabStage.setTitle("Sparrow - " + tabName);
|
tabStage.setTitle("Sparrow - " + tabName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void timedWorker(TimedWorkerEvent event) {
|
||||||
|
if(statusTimeline != null && statusTimeline.getStatus() == Animation.Status.RUNNING) {
|
||||||
|
if(event.getTimeMills() == 0) {
|
||||||
|
statusTimeline.stop();
|
||||||
|
statusBar.setText("");
|
||||||
|
statusBar.setProgress(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusBar.setText(event.getStatus());
|
||||||
|
statusTimeline = new Timeline(
|
||||||
|
new KeyFrame(Duration.ZERO, new KeyValue(statusBar.progressProperty(), 0)),
|
||||||
|
new KeyFrame(Duration.millis(event.getTimeMills()), e -> {
|
||||||
|
statusBar.setText("");
|
||||||
|
statusBar.setProgress(0);
|
||||||
|
}, new KeyValue(statusBar.progressProperty(), 1))
|
||||||
|
);
|
||||||
|
statusTimeline.setCycleCount(1);
|
||||||
|
statusTimeline.play();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,9 @@ package com.sparrowwallet.sparrow.control;
|
||||||
import com.sparrowwallet.drongo.SecureString;
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.event.TimedWorkerEvent;
|
||||||
import com.sparrowwallet.sparrow.event.WalletExportEvent;
|
import com.sparrowwallet.sparrow.event.WalletExportEvent;
|
||||||
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import com.sparrowwallet.sparrow.io.WalletExport;
|
import com.sparrowwallet.sparrow.io.WalletExport;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
@ -55,22 +57,31 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
||||||
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
|
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||||
Optional<SecureString> password = dlg.showAndWait();
|
Optional<SecureString> password = dlg.showAndWait();
|
||||||
if(password.isPresent()) {
|
if(password.isPresent()) {
|
||||||
copy.decrypt(password.get());
|
Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(copy, password.get());
|
||||||
} else {
|
decryptWalletService.setOnSucceeded(workerStateEvent -> {
|
||||||
return;
|
EventManager.get().post(new TimedWorkerEvent("Done"));
|
||||||
}
|
Wallet decryptedWallet = decryptWalletService.getValue();
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
OutputStream outputStream = new FileOutputStream(file);
|
OutputStream outputStream = new FileOutputStream(file);
|
||||||
exporter.exportWallet(copy, outputStream);
|
exporter.exportWallet(decryptedWallet, outputStream);
|
||||||
EventManager.get().post(new WalletExportEvent(copy));
|
EventManager.get().post(new WalletExportEvent(decryptedWallet));
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
String errorMessage = e.getMessage();
|
String errorMessage = e.getMessage();
|
||||||
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
||||||
errorMessage = e.getCause().getMessage();
|
errorMessage = e.getCause().getMessage();
|
||||||
}
|
}
|
||||||
setError("Export Error", errorMessage);
|
setError("Export Error", errorMessage);
|
||||||
|
} finally {
|
||||||
|
decryptedWallet.clearPrivate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
decryptWalletService.setOnFailed(workerStateEvent -> {
|
||||||
|
EventManager.get().post(new TimedWorkerEvent("Failed"));
|
||||||
|
setError("Export Error", decryptWalletService.getException().getMessage());
|
||||||
|
});
|
||||||
|
decryptWalletService.start();
|
||||||
|
EventManager.get().post(new TimedWorkerEvent("Decrypting wallet...", 1000));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.sparrowwallet.sparrow.event;
|
||||||
|
|
||||||
|
public class TimedWorkerEvent {
|
||||||
|
private final String status;
|
||||||
|
private final int timeMills;
|
||||||
|
|
||||||
|
public TimedWorkerEvent(String status) {
|
||||||
|
this.status = status;
|
||||||
|
this.timeMills = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimedWorkerEvent(String status, int timeMills) {
|
||||||
|
this.status = status;
|
||||||
|
this.timeMills = timeMills;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTimeMills() {
|
||||||
|
return timeMills;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,12 +2,12 @@ package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import com.sparrowwallet.drongo.ExtendedKey;
|
import com.sparrowwallet.drongo.ExtendedKey;
|
||||||
|
import com.sparrowwallet.drongo.SecureString;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
import com.sparrowwallet.drongo.crypto.*;
|
import com.sparrowwallet.drongo.crypto.*;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.MnemonicException;
|
import com.sparrowwallet.drongo.wallet.MnemonicException;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.control.KeystorePassphraseDialog;
|
|
||||||
import javafx.concurrent.Service;
|
import javafx.concurrent.Service;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.zip.*;
|
import java.util.zip.*;
|
||||||
|
|
||||||
import static com.sparrowwallet.drongo.crypto.Argon2KeyDeriver.SPRW1_PARAMETERS;
|
import static com.sparrowwallet.drongo.crypto.Argon2KeyDeriver.SPRW1_PARAMETERS;
|
||||||
|
@ -68,12 +67,10 @@ public class Storage {
|
||||||
Wallet wallet = gson.fromJson(reader, Wallet.class);
|
Wallet wallet = gson.fromJson(reader, Wallet.class);
|
||||||
reader.close();
|
reader.close();
|
||||||
|
|
||||||
restorePublicKeysFromSeed(wallet, null);
|
|
||||||
|
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Wallet loadWallet(CharSequence password) throws IOException, MnemonicException, StorageException {
|
public WalletAndKey loadWallet(CharSequence password) throws IOException, MnemonicException, StorageException {
|
||||||
InputStream fileStream = new FileInputStream(walletFile);
|
InputStream fileStream = new FileInputStream(walletFile);
|
||||||
ECKey encryptionKey = getEncryptionKey(password, fileStream);
|
ECKey encryptionKey = getEncryptionKey(password, fileStream);
|
||||||
|
|
||||||
|
@ -83,52 +80,9 @@ public class Storage {
|
||||||
reader.close();
|
reader.close();
|
||||||
|
|
||||||
Key key = new Key(encryptionKey.getPrivKeyBytes(), keyDeriver.getSalt(), EncryptionType.Deriver.ARGON2);
|
Key key = new Key(encryptionKey.getPrivKeyBytes(), keyDeriver.getSalt(), EncryptionType.Deriver.ARGON2);
|
||||||
restorePublicKeysFromSeed(wallet, key);
|
|
||||||
|
|
||||||
encryptionPubKey = ECKey.fromPublicOnly(encryptionKey);
|
encryptionPubKey = ECKey.fromPublicOnly(encryptionKey);
|
||||||
return wallet;
|
return new WalletAndKey(wallet, key);
|
||||||
}
|
|
||||||
|
|
||||||
private void restorePublicKeysFromSeed(Wallet wallet, Key key) throws MnemonicException {
|
|
||||||
if(wallet.containsSeeds()) {
|
|
||||||
//Derive xpub and master fingerprint from seed, potentially with passphrase
|
|
||||||
Wallet copy = wallet.copy();
|
|
||||||
for(Keystore copyKeystore : copy.getKeystores()) {
|
|
||||||
if(copyKeystore.hasSeed()) {
|
|
||||||
if(copyKeystore.getSeed().needsPassphrase()) {
|
|
||||||
KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(copyKeystore);
|
|
||||||
Optional<String> optionalPassphrase = passphraseDialog.showAndWait();
|
|
||||||
if(optionalPassphrase.isPresent()) {
|
|
||||||
copyKeystore.getSeed().setPassphrase(optionalPassphrase.get());
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
copyKeystore.getSeed().setPassphrase("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(wallet.isEncrypted()) {
|
|
||||||
if(key == null) {
|
|
||||||
throw new IllegalStateException("Wallet was not encrypted, but seed is");
|
|
||||||
}
|
|
||||||
|
|
||||||
copy.decrypt(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i = 0; i < wallet.getKeystores().size(); i++) {
|
|
||||||
Keystore keystore = wallet.getKeystores().get(i);
|
|
||||||
if(keystore.hasSeed()) {
|
|
||||||
Keystore copyKeystore = copy.getKeystores().get(i);
|
|
||||||
Keystore derivedKeystore = Keystore.fromSeed(copyKeystore.getSeed(), copyKeystore.getKeyDerivation().getDerivation());
|
|
||||||
keystore.setKeyDerivation(derivedKeystore.getKeyDerivation());
|
|
||||||
keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey());
|
|
||||||
keystore.getSeed().setPassphrase(copyKeystore.getSeed().getPassphrase());
|
|
||||||
copyKeystore.getSeed().clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void storeWallet(Wallet wallet) throws IOException {
|
public void storeWallet(Wallet wallet) throws IOException {
|
||||||
|
@ -316,11 +270,44 @@ public class Storage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class WalletAndKey {
|
||||||
|
public final Wallet wallet;
|
||||||
|
public final Key key;
|
||||||
|
|
||||||
|
public WalletAndKey(Wallet wallet, Key key) {
|
||||||
|
this.wallet = wallet;
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LoadWalletService extends Service<WalletAndKey> {
|
||||||
|
private final Storage storage;
|
||||||
|
private final SecureString password;
|
||||||
|
|
||||||
|
public LoadWalletService(Storage storage, SecureString password) {
|
||||||
|
this.storage = storage;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<WalletAndKey> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
protected WalletAndKey call() throws IOException, StorageException, MnemonicException {
|
||||||
|
try {
|
||||||
|
return storage.loadWallet(password);
|
||||||
|
} finally {
|
||||||
|
password.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static class KeyDerivationService extends Service<ECKey> {
|
public static class KeyDerivationService extends Service<ECKey> {
|
||||||
private final Storage storage;
|
private final Storage storage;
|
||||||
private final String password;
|
private final SecureString password;
|
||||||
|
|
||||||
public KeyDerivationService(Storage storage, String password) {
|
public KeyDerivationService(Storage storage, SecureString password) {
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
@ -329,7 +316,35 @@ public class Storage {
|
||||||
protected Task<ECKey> createTask() {
|
protected Task<ECKey> createTask() {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
protected ECKey call() throws IOException, StorageException {
|
protected ECKey call() throws IOException, StorageException {
|
||||||
|
try {
|
||||||
return storage.getEncryptionKey(password);
|
return storage.getEncryptionKey(password);
|
||||||
|
} finally {
|
||||||
|
password.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DecryptWalletService extends Service<Wallet> {
|
||||||
|
private final Wallet wallet;
|
||||||
|
private final SecureString password;
|
||||||
|
|
||||||
|
public DecryptWalletService(Wallet wallet, SecureString password) {
|
||||||
|
this.wallet = wallet;
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<Wallet> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
protected Wallet call() throws IOException, StorageException {
|
||||||
|
try {
|
||||||
|
wallet.decrypt(password);
|
||||||
|
return wallet;
|
||||||
|
} finally {
|
||||||
|
password.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
||||||
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
||||||
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
||||||
|
import com.sparrowwallet.sparrow.event.TimedWorkerEvent;
|
||||||
import com.sparrowwallet.sparrow.event.WalletChangedEvent;
|
import com.sparrowwallet.sparrow.event.WalletChangedEvent;
|
||||||
import com.sparrowwallet.sparrow.io.Storage;
|
import com.sparrowwallet.sparrow.io.Storage;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
@ -158,18 +159,9 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
});
|
});
|
||||||
|
|
||||||
apply.setOnAction(event -> {
|
apply.setOnAction(event -> {
|
||||||
try {
|
|
||||||
Optional<ECKey> optionalPubKey = requestEncryption(walletForm.getStorage().getEncryptionPubKey());
|
|
||||||
if(optionalPubKey.isPresent()) {
|
|
||||||
walletForm.getStorage().setEncryptionPubKey(optionalPubKey.get());
|
|
||||||
walletForm.save();
|
|
||||||
revert.setDisable(true);
|
revert.setDisable(true);
|
||||||
apply.setDisable(true);
|
apply.setDisable(true);
|
||||||
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet(), walletForm.getWalletFile()));
|
saveWallet();
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
AppController.showErrorDialog("Error saving file", e.getMessage());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setFieldsFromWallet(walletForm.getWallet());
|
setFieldsFromWallet(walletForm.getWallet());
|
||||||
|
@ -256,7 +248,9 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<ECKey> requestEncryption(ECKey existingPubKey) {
|
private void saveWallet() {
|
||||||
|
ECKey existingPubKey = walletForm.getStorage().getEncryptionPubKey();
|
||||||
|
|
||||||
WalletPasswordDialog.PasswordRequirement requirement;
|
WalletPasswordDialog.PasswordRequirement requirement;
|
||||||
if(existingPubKey == null) {
|
if(existingPubKey == null) {
|
||||||
requirement = WalletPasswordDialog.PasswordRequirement.UPDATE_NEW;
|
requirement = WalletPasswordDialog.PasswordRequirement.UPDATE_NEW;
|
||||||
|
@ -270,26 +264,58 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
Optional<SecureString> password = dlg.showAndWait();
|
Optional<SecureString> password = dlg.showAndWait();
|
||||||
if(password.isPresent()) {
|
if(password.isPresent()) {
|
||||||
if(password.get().length() == 0) {
|
if(password.get().length() == 0) {
|
||||||
return Optional.of(Storage.NO_PASSWORD_KEY);
|
try {
|
||||||
|
walletForm.getStorage().setEncryptionPubKey(Storage.NO_PASSWORD_KEY);
|
||||||
|
walletForm.save();
|
||||||
|
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet(), walletForm.getWalletFile()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
AppController.showErrorDialog("Error saving wallet", e.getMessage());
|
||||||
|
revert.setDisable(false);
|
||||||
|
apply.setDisable(false);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(walletForm.getStorage(), password.get());
|
||||||
|
keyDerivationService.setOnSucceeded(workerStateEvent -> {
|
||||||
|
EventManager.get().post(new TimedWorkerEvent("Done"));
|
||||||
|
ECKey encryptionFullKey = keyDerivationService.getValue();
|
||||||
|
Key key = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ECKey encryptionFullKey = walletForm.getStorage().getEncryptionKey(password.get());
|
|
||||||
ECKey encryptionPubKey = ECKey.fromPublicOnly(encryptionFullKey);
|
ECKey encryptionPubKey = ECKey.fromPublicOnly(encryptionFullKey);
|
||||||
|
|
||||||
if(existingPubKey != null && !Storage.NO_PASSWORD_KEY.equals(existingPubKey) && !existingPubKey.equals(encryptionPubKey)) {
|
if(existingPubKey != null && !Storage.NO_PASSWORD_KEY.equals(existingPubKey) && !existingPubKey.equals(encryptionPubKey)) {
|
||||||
AppController.showErrorDialog("Incorrect Password", "The password was incorrect.");
|
AppController.showErrorDialog("Incorrect Password", "The password was incorrect.");
|
||||||
return Optional.empty();
|
revert.setDisable(false);
|
||||||
|
apply.setDisable(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Key key = new Key(encryptionFullKey.getPrivKeyBytes(), walletForm.getStorage().getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2);
|
key = new Key(encryptionFullKey.getPrivKeyBytes(), walletForm.getStorage().getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2);
|
||||||
walletForm.getWallet().encrypt(key);
|
walletForm.getWallet().encrypt(key);
|
||||||
return Optional.of(encryptionPubKey);
|
|
||||||
} catch (Exception e) {
|
|
||||||
AppController.showErrorDialog("Wallet File Invalid", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.empty();
|
walletForm.getStorage().setEncryptionPubKey(encryptionPubKey);
|
||||||
|
walletForm.save();
|
||||||
|
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet(), walletForm.getWalletFile()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
AppController.showErrorDialog("Error saving wallet", e.getMessage());
|
||||||
|
revert.setDisable(false);
|
||||||
|
apply.setDisable(false);
|
||||||
|
} finally {
|
||||||
|
encryptionFullKey.clear();
|
||||||
|
if(key != null) {
|
||||||
|
key.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
keyDerivationService.setOnFailed(workerStateEvent -> {
|
||||||
|
EventManager.get().post(new TimedWorkerEvent("Failed"));
|
||||||
|
AppController.showErrorDialog("Error saving wallet", keyDerivationService.getException().getMessage());
|
||||||
|
revert.setDisable(false);
|
||||||
|
apply.setDisable(false);
|
||||||
|
});
|
||||||
|
keyDerivationService.start();
|
||||||
|
EventManager.get().post(new TimedWorkerEvent("Encrypting wallet...", 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,6 @@
|
||||||
<TabPane fx:id="tabs" />
|
<TabPane fx:id="tabs" />
|
||||||
</StackPane>
|
</StackPane>
|
||||||
|
|
||||||
<StatusBar text=""/>
|
<StatusBar fx:id="statusBar" text=""/>
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
Loading…
Reference in a new issue