package wallettemplate;
import javafx.application.Platform;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import com.google.common.primitives.Longs;
import com.google.protobuf.ByteString;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.ImageView;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import wallettemplate.utils.KeyDerivationTasks;
import java.time.Duration;
import static com.google.common.base.Preconditions.checkNotNull;
import static wallettemplate.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 ImageView padlockImage;
@FXML ProgressIndicator progressMeter;
@FXML GridPane widgetGrid;
@FXML Label explanationLabel;
public Main.OverlayUI overlayUI;
private SimpleObjectProperty<KeyParameter> aesKey = new SimpleObjectProperty<>();
public void initialize() {
progressMeter.setOpacity(0);
Platform.runLater(pass1::requestFocus);
}
@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, int timeTakenMsec) {
checkGuiThread();
if (Main.bitcoin.wallet().checkAESKey(aesKey)) {
WalletPasswordController.this.aesKey.set(aesKey);
} else {
log.warn("User entered incorrect password");
fadeOut(progressMeter);
fadeIn(widgetGrid);
fadeIn(explanationLabel);
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(widgetGrid);
fadeOut(explanationLabel);
fadeOut(buttonsBox);
}
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()));
}
}