package lighthouse.subwindows;
import com.google.common.primitives.Longs;
import com.google.protobuf.ByteString;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.PasswordField;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.HBox;
import lighthouse.Main;
import lighthouse.utils.KeyDerivationTasks;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import java.time.Duration;
import java.util.function.Consumer;
import static com.google.common.base.Preconditions.checkNotNull;
import static lighthouse.utils.GuiUtils.*;
/**
* User interface for entering a password on demand, e.g. to send money. Also used when encrypting a wallet. Shows a
* progress meter as we scrypt the password.
*/
public class WalletPasswordController {
private static final Logger log = LoggerFactory.getLogger(WalletPasswordController.class);
@FXML HBox buttonsBox;
@FXML PasswordField pass1;
@FXML ProgressIndicator progressMeter;
@FXML HBox widgetBox;
public Main.OverlayUI overlayUI;
private SimpleObjectProperty<KeyParameter> aesKey = new SimpleObjectProperty<>();
public void initialize() {
progressMeter.setOpacity(0);
Platform.runLater(pass1::requestFocus);
}
public static void requestPassword(Consumer<KeyParameter> keyConsumer) {
Main.OverlayUI<WalletPasswordController> pwd = Main.instance.overlayUI("subwindows/wallet_password.fxml", "Password");
pwd.controller.aesKeyProperty().addListener((observable, old, cur) -> {
// We only get here if the user found the right password. If they don't or they cancel, we end up back on
// the main UI screen.
checkGuiThread();
keyConsumer.accept(cur);
});
}
@FXML void confirmClicked(ActionEvent event) {
String password = pass1.getText();
if (password.isEmpty() || password.length() < 4) {
informationalAlert("Bad password", "The password you entered is empty or too short.");
return;
}
final KeyCrypterScrypt keyCrypter = (KeyCrypterScrypt) Main.bitcoin.wallet().getKeyCrypter();
checkNotNull(keyCrypter); // We should never arrive at this GUI if the wallet isn't actually encrypted.
KeyDerivationTasks tasks = new KeyDerivationTasks(keyCrypter, password, getTargetTime()) {
@Override
protected void onFinish(KeyParameter aesKey) {
super.onFinish(aesKey);
checkGuiThread();
if (Main.bitcoin.wallet().checkAESKey(aesKey)) {
WalletPasswordController.this.aesKey.set(aesKey);
} else {
log.warn("User entered incorrect password");
fadeOut(progressMeter);
fadeIn(widgetBox);
fadeIn(buttonsBox);
informationalAlert("Wrong password",
"Please try entering your password again, carefully checking for typos or spelling errors.");
}
}
};
progressMeter.progressProperty().bind(tasks.progress);
tasks.start();
fadeIn(progressMeter);
fadeOut(widgetBox);
fadeOut(buttonsBox);
}
@FXML
public void cancelClicked(ActionEvent event) {
overlayUI.done();
}
public ReadOnlyObjectProperty<KeyParameter> aesKeyProperty() {
return aesKey;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static final String TAG = WalletPasswordController.class.getName() + ".target-time";
// Writes the given time to the wallet as a tag so we can find it again in this class.
public static void setTargetTime(Duration targetTime) {
ByteString bytes = ByteString.copyFrom(Longs.toByteArray(targetTime.toMillis()));
Main.bitcoin.wallet().setTag(TAG, bytes);
}
// Reads target time or throws if not set yet (should never happen).
public static Duration getTargetTime() throws IllegalArgumentException {
return Duration.ofMillis(Longs.fromByteArray(Main.bitcoin.wallet().getTag(TAG).toByteArray()));
}
}