mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-24 23:18:17 +01:00
Add wallet restore to pw popup
This commit is contained in:
parent
a823c0360e
commit
97c54eb58e
3 changed files with 245 additions and 37 deletions
|
@ -396,7 +396,7 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||
model.p2PNetworkIconId.addListener(splashP2PNetworkIconIdListener);
|
||||
|
||||
splashP2PNetworkProgressListener = (ov, oldValue, newValue) -> {
|
||||
if ((double) newValue >= 1) {
|
||||
if ((double) newValue != -1) {
|
||||
splashP2PNetworkIndicator.setVisible(false);
|
||||
splashP2PNetworkIndicator.setManaged(false);
|
||||
}
|
||||
|
|
|
@ -17,36 +17,71 @@
|
|||
|
||||
package io.bitsquare.gui.main.overlays.windows;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import io.bitsquare.app.BitsquareApp;
|
||||
import io.bitsquare.btc.WalletService;
|
||||
import io.bitsquare.common.UserThread;
|
||||
import io.bitsquare.common.util.Tuple2;
|
||||
import io.bitsquare.crypto.ScryptUtil;
|
||||
import io.bitsquare.gui.components.PasswordTextField;
|
||||
import io.bitsquare.gui.main.overlays.Overlay;
|
||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||
import io.bitsquare.gui.util.Transitions;
|
||||
import io.bitsquare.gui.util.validation.PasswordValidator;
|
||||
import io.bitsquare.locale.BSResources;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.layout.ColumnConstraints;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.bitcoinj.core.Wallet;
|
||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
import org.bitcoinj.crypto.MnemonicCode;
|
||||
import org.bitcoinj.crypto.MnemonicException;
|
||||
import org.bitcoinj.wallet.DeterministicSeed;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.google.inject.internal.util.$Preconditions.checkArgument;
|
||||
import static io.bitsquare.gui.util.FormBuilder.*;
|
||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
|
||||
public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
||||
private static final Logger log = LoggerFactory.getLogger(WalletPasswordWindow.class);
|
||||
private final WalletService walletService;
|
||||
private Button unlockButton;
|
||||
private AesKeyHandler aesKeyHandler;
|
||||
private PasswordTextField passwordTextField;
|
||||
private Button forgotPasswordButton;
|
||||
private Button restoreButton;
|
||||
private TextArea restoreSeedWordsTextArea;
|
||||
private DatePicker restoreDatePicker;
|
||||
private SimpleBooleanProperty seedWordsValid = new SimpleBooleanProperty(false);
|
||||
private SimpleBooleanProperty dateValid = new SimpleBooleanProperty(false);
|
||||
private BooleanProperty seedWordsEdited = new SimpleBooleanProperty();
|
||||
private ChangeListener<String> changeListener;
|
||||
private ChangeListener<String> seedWordsTextAreaChangeListener;
|
||||
private ChangeListener<Boolean> datePickerChangeListener;
|
||||
private ChangeListener<Boolean> seedWordsValidChangeListener;
|
||||
private ChangeListener<LocalDate> dateChangeListener;
|
||||
private LocalDate walletCreationDate;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -57,23 +92,19 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
|||
void onAesKey(KeyParameter aesKey);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public WalletPasswordWindow(WalletService walletService) {
|
||||
this.walletService = walletService;
|
||||
type = Type.Attention;
|
||||
width = 800;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Public API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void cleanup() {
|
||||
if (passwordTextField != null)
|
||||
passwordTextField.textProperty().addListener(changeListener);
|
||||
}
|
||||
|
||||
@Inject
|
||||
public WalletPasswordWindow(WalletService walletService) {
|
||||
this.walletService = walletService;
|
||||
type = Type.Attention;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (gridPane != null) {
|
||||
rowIndex = -1;
|
||||
|
@ -97,11 +128,42 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup() {
|
||||
if (passwordTextField != null)
|
||||
passwordTextField.textProperty().removeListener(changeListener);
|
||||
|
||||
if (seedWordsValidChangeListener != null) {
|
||||
seedWordsValid.removeListener(seedWordsValidChangeListener);
|
||||
dateValid.removeListener(datePickerChangeListener);
|
||||
restoreSeedWordsTextArea.textProperty().removeListener(seedWordsTextAreaChangeListener);
|
||||
restoreDatePicker.valueProperty().removeListener(dateChangeListener);
|
||||
restoreButton.disableProperty().unbind();
|
||||
restoreButton.setOnAction(null);
|
||||
restoreSeedWordsTextArea.setText("");
|
||||
restoreDatePicker.setValue(null);
|
||||
restoreSeedWordsTextArea.getStyleClass().remove("validation_error");
|
||||
restoreDatePicker.getStyleClass().remove("validation_error");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void setupKeyHandler(Scene scene) {
|
||||
if (!hideCloseButton) {
|
||||
scene.setOnKeyPressed(e -> {
|
||||
if (e.getCode() == KeyCode.ESCAPE) {
|
||||
e.consume();
|
||||
doClose();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void addInputFields() {
|
||||
Label label = new Label("Enter password:");
|
||||
label.setWrapText(true);
|
||||
|
@ -113,9 +175,7 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
|||
GridPane.setRowIndex(passwordTextField, rowIndex);
|
||||
GridPane.setColumnIndex(passwordTextField, 1);
|
||||
PasswordValidator passwordValidator = new PasswordValidator();
|
||||
changeListener = (observable, oldValue, newValue) -> {
|
||||
unlockButton.setDisable(!passwordValidator.validate(newValue).isValid);
|
||||
};
|
||||
changeListener = (observable, oldValue, newValue) -> unlockButton.setDisable(!passwordValidator.validate(newValue).isValid);
|
||||
passwordTextField.textProperty().addListener(changeListener);
|
||||
gridPane.getChildren().addAll(label, passwordTextField);
|
||||
}
|
||||
|
@ -124,7 +184,36 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
|||
unlockButton = new Button("Unlock");
|
||||
unlockButton.setDefaultButton(true);
|
||||
unlockButton.setDisable(true);
|
||||
unlockButton.setOnAction(e -> checkPassword());
|
||||
unlockButton.setOnAction(e -> {
|
||||
String password = passwordTextField.getText();
|
||||
checkArgument(password.length() < 50, "Password must be less then 50 characters.");
|
||||
Wallet wallet = walletService.getWallet();
|
||||
KeyCrypterScrypt keyCrypterScrypt = (KeyCrypterScrypt) wallet.getKeyCrypter();
|
||||
if (keyCrypterScrypt != null) {
|
||||
ScryptUtil.deriveKeyWithScrypt(keyCrypterScrypt, password, aesKey -> {
|
||||
if (wallet.checkAESKey(aesKey)) {
|
||||
if (aesKeyHandler != null)
|
||||
aesKeyHandler.onAesKey(aesKey);
|
||||
|
||||
hide();
|
||||
} else {
|
||||
UserThread.runAfter(() -> new Popup()
|
||||
.warning("You entered the wrong password.\n\n" +
|
||||
"Please try entering your password again, carefully checking for typos or spelling errors.")
|
||||
.onClose(this::blurAgain).show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log.error("wallet.getKeyCrypter() is null, than must not happen.");
|
||||
}
|
||||
});
|
||||
|
||||
forgotPasswordButton = new Button("Forgot password?");
|
||||
forgotPasswordButton.setOnAction(e -> {
|
||||
forgotPasswordButton.setDisable(true);
|
||||
unlockButton.setDefaultButton(false);
|
||||
showRestoreScreen();
|
||||
});
|
||||
|
||||
Button cancelButton = new Button("Cancel");
|
||||
cancelButton.setOnAction(event -> {
|
||||
|
@ -137,32 +226,150 @@ public class WalletPasswordWindow extends Overlay<WalletPasswordWindow> {
|
|||
GridPane.setRowIndex(hBox, ++rowIndex);
|
||||
GridPane.setColumnIndex(hBox, 1);
|
||||
if (hideCloseButton)
|
||||
hBox.getChildren().add(unlockButton);
|
||||
hBox.getChildren().addAll(unlockButton, forgotPasswordButton);
|
||||
else
|
||||
hBox.getChildren().addAll(unlockButton, cancelButton);
|
||||
gridPane.getChildren().add(hBox);
|
||||
|
||||
|
||||
ColumnConstraints columnConstraints1 = new ColumnConstraints();
|
||||
columnConstraints1.setHalignment(HPos.RIGHT);
|
||||
columnConstraints1.setHgrow(Priority.SOMETIMES);
|
||||
ColumnConstraints columnConstraints2 = new ColumnConstraints();
|
||||
columnConstraints2.setHgrow(Priority.ALWAYS);
|
||||
gridPane.getColumnConstraints().addAll(columnConstraints1, columnConstraints2);
|
||||
}
|
||||
|
||||
private void checkPassword() {
|
||||
String password = passwordTextField.getText();
|
||||
Wallet wallet = walletService.getWallet();
|
||||
KeyCrypterScrypt keyCrypterScrypt = (KeyCrypterScrypt) wallet.getKeyCrypter();
|
||||
if (keyCrypterScrypt != null) {
|
||||
ScryptUtil.deriveKeyWithScrypt(keyCrypterScrypt, password, aesKey -> {
|
||||
if (wallet.checkAESKey(aesKey)) {
|
||||
if (aesKeyHandler != null)
|
||||
aesKeyHandler.onAesKey(aesKey);
|
||||
private void showRestoreScreen() {
|
||||
Label headLine2Label = new Label(BSResources.get("Restore wallet from seed words"));
|
||||
headLine2Label.setId("popup-headline");
|
||||
headLine2Label.setMouseTransparent(true);
|
||||
GridPane.setHalignment(headLine2Label, HPos.LEFT);
|
||||
GridPane.setRowIndex(headLine2Label, ++rowIndex);
|
||||
GridPane.setColumnSpan(headLine2Label, 2);
|
||||
GridPane.setMargin(headLine2Label, new Insets(30, 0, 0, 0));
|
||||
gridPane.getChildren().add(headLine2Label);
|
||||
|
||||
hide();
|
||||
} else {
|
||||
UserThread.runAfter(() -> new Popup()
|
||||
.warning("You entered the wrong password.\n\n" +
|
||||
"Please try entering your password again, carefully checking for typos or spelling errors.")
|
||||
.onClose(this::blurAgain).show(), Transitions.DEFAULT_DURATION, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
});
|
||||
Separator separator = new Separator();
|
||||
separator.setMouseTransparent(true);
|
||||
separator.setOrientation(Orientation.HORIZONTAL);
|
||||
separator.setStyle("-fx-background: #ccc;");
|
||||
GridPane.setHalignment(separator, HPos.CENTER);
|
||||
GridPane.setRowIndex(separator, ++rowIndex);
|
||||
GridPane.setColumnSpan(separator, 2);
|
||||
|
||||
gridPane.getChildren().add(separator);
|
||||
|
||||
Tuple2<Label, TextArea> tuple = addLabelTextArea(gridPane, ++rowIndex, "Wallet seed words:", "", 5);
|
||||
restoreSeedWordsTextArea = tuple.second;
|
||||
restoreSeedWordsTextArea.setPrefHeight(60);
|
||||
restoreSeedWordsTextArea.setStyle("-fx-border-color: #ddd;");
|
||||
Tuple2<Label, DatePicker> labelDatePickerTuple2 = addLabelDatePicker(gridPane, ++rowIndex, "Creation Date:");
|
||||
restoreDatePicker = labelDatePickerTuple2.second;
|
||||
restoreButton = addButton(gridPane, ++rowIndex, "Restore wallet");
|
||||
restoreButton.setDefaultButton(true);
|
||||
stage.setHeight(340);
|
||||
|
||||
DeterministicSeed keyChainSeed = walletService.getWallet().getKeyChainSeed();
|
||||
// wallet creation date is not encrypted
|
||||
walletCreationDate = Instant.ofEpochSecond(keyChainSeed.getCreationTimeSeconds()).atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
|
||||
|
||||
restoreButton.disableProperty().bind(createBooleanBinding(() -> !seedWordsValid.get() || !dateValid.get() || !seedWordsEdited.get(),
|
||||
seedWordsValid, dateValid, seedWordsEdited));
|
||||
|
||||
|
||||
seedWordsValidChangeListener = (observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
restoreSeedWordsTextArea.getStyleClass().remove("validation_error");
|
||||
} else {
|
||||
restoreSeedWordsTextArea.getStyleClass().add("validation_error");
|
||||
}
|
||||
};
|
||||
|
||||
seedWordsTextAreaChangeListener = (observable, oldValue, newValue) -> {
|
||||
seedWordsEdited.set(true);
|
||||
try {
|
||||
MnemonicCode codec = new MnemonicCode();
|
||||
codec.check(Splitter.on(" ").splitToList(newValue));
|
||||
seedWordsValid.set(true);
|
||||
} catch (IOException | MnemonicException e) {
|
||||
seedWordsValid.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
datePickerChangeListener = (observable, oldValue, newValue) -> {
|
||||
if (newValue)
|
||||
restoreDatePicker.getStyleClass().remove("validation_error");
|
||||
else
|
||||
restoreDatePicker.getStyleClass().add("validation_error");
|
||||
};
|
||||
dateChangeListener = (observable, oldValue, newValue) -> {
|
||||
dateValid.set(walletCreationDate.equals(newValue));
|
||||
};
|
||||
|
||||
seedWordsValid.addListener(seedWordsValidChangeListener);
|
||||
dateValid.addListener(datePickerChangeListener);
|
||||
restoreSeedWordsTextArea.textProperty().addListener(seedWordsTextAreaChangeListener);
|
||||
restoreDatePicker.valueProperty().addListener(dateChangeListener);
|
||||
restoreButton.disableProperty().bind(createBooleanBinding(() -> !seedWordsValid.get() || !dateValid.get() || !seedWordsEdited.get(),
|
||||
seedWordsValid, dateValid, seedWordsEdited));
|
||||
|
||||
restoreButton.setOnAction(e -> onRestore());
|
||||
|
||||
restoreSeedWordsTextArea.getStyleClass().remove("validation_error");
|
||||
restoreDatePicker.getStyleClass().remove("validation_error");
|
||||
|
||||
layout();
|
||||
}
|
||||
|
||||
private void onRestore() {
|
||||
Wallet wallet = walletService.getWallet();
|
||||
if (wallet.getBalance(Wallet.BalanceType.AVAILABLE).value > 0) {
|
||||
new Popup()
|
||||
.warning("Your bitcoin wallet is not empty.\n\n" +
|
||||
"You must empty this wallet before attempting to restore an older one, as mixing wallets " +
|
||||
"together can lead to invalidated backups.\n\n" +
|
||||
"Please finalize your trades, close all your open offers and go to the Funds section to withdraw your bitcoin.\n" +
|
||||
"In case you cannot access your bitcoin you can use the emergency tool to empty the wallet.\n" +
|
||||
"To open that emergency tool press \"cmd + e\".")
|
||||
.show();
|
||||
} else if (wallet.isEncrypted()) {
|
||||
new Popup()
|
||||
.warning("Your bitcoin wallet is encrypted.\n\n" +
|
||||
"After restore, the wallet will no longer be encrypted and you must set a new password.\n\n" +
|
||||
"Do you want to proceed?")
|
||||
.closeButtonText("No")
|
||||
.actionButtonText("Yes")
|
||||
.onAction(this::doRestore)
|
||||
.show();
|
||||
} else {
|
||||
log.error("wallet.getKeyCrypter() is null, than must not happen.");
|
||||
doRestore();
|
||||
}
|
||||
}
|
||||
|
||||
private void doRestore() {
|
||||
log.info("Attempting wallet restore using seed '{}' from date {}", restoreSeedWordsTextArea.getText(), restoreDatePicker.getValue());
|
||||
long date = restoreDatePicker.getValue().atStartOfDay().toEpochSecond(ZoneOffset.UTC);
|
||||
DeterministicSeed seed = new DeterministicSeed(Splitter.on(" ").splitToList(restoreSeedWordsTextArea.getText()), null, "", date);
|
||||
walletService.restoreSeedWords(seed,
|
||||
() -> UserThread.execute(() -> {
|
||||
log.debug("Wallet restored with seed words");
|
||||
|
||||
new Popup()
|
||||
.feedback("Wallet restored successfully with the new seed words.\n\n" +
|
||||
"You need to shut down and restart the application.")
|
||||
.closeButtonText("Shut down")
|
||||
.onClose(BitsquareApp.shutDownHandler::run)
|
||||
.show();
|
||||
}),
|
||||
throwable -> UserThread.execute(() -> {
|
||||
log.error(throwable.getMessage());
|
||||
new Popup()
|
||||
.error("An error occurred when restoring the wallet with seed words.\n" +
|
||||
"Error message: " + throwable.getMessage())
|
||||
.show();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ validation.fiat.toLarge=Input larger as maximum possible amount is not allowed.
|
|||
validation.btc.toSmall=Input results in a bitcoin value with a fraction of the smallest unit (Satoshi).
|
||||
validation.btc.toLarge=Input larger as maximum trading amount of {0} is not allowed.
|
||||
validation.passwordTooShort=The password you entered is too short. It needs to have min. 8 characters.
|
||||
validation.passwordTooLong=The password you entered is too long. It cannot be longer as 50 characters.
|
||||
|
||||
# Create offer
|
||||
createOffer.amount.prompt=Enter amount in BTC
|
||||
|
|
Loading…
Add table
Reference in a new issue