wallet load and save

This commit is contained in:
Craig Raw 2020-04-23 13:41:43 +02:00
parent 1f93d574eb
commit fb621963dd
11 changed files with 405 additions and 37 deletions

2
drongo

@ -1 +1 @@
Subproject commit 813781902b8914fbac20c5a36ef230a44639ecbc
Subproject commit 282628e4558b04dfa17c3f85247378204f8c82ff

View file

@ -3,7 +3,9 @@ package com.sparrowwallet.sparrow;
import com.google.common.base.Charsets;
import com.google.common.eventbus.Subscribe;
import com.google.common.io.ByteSource;
import com.google.gson.JsonSyntaxException;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.protocol.Transaction;
@ -11,15 +13,15 @@ import com.sparrowwallet.drongo.psbt.PSBT;
import com.sparrowwallet.drongo.psbt.PSBTParseException;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.control.TextAreaDialog;
import com.sparrowwallet.sparrow.control.WalletNameDialog;
import com.sparrowwallet.sparrow.event.TabEvent;
import com.sparrowwallet.sparrow.event.TransactionTabChangedEvent;
import com.sparrowwallet.sparrow.event.TransactionTabSelectedEvent;
import com.sparrowwallet.sparrow.storage.Storage;
import com.sparrowwallet.sparrow.transaction.TransactionController;
import com.sparrowwallet.sparrow.wallet.SettingsController;
import com.sparrowwallet.sparrow.wallet.WalletController;
import com.sparrowwallet.sparrow.wallet.WalletForm;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
@ -30,10 +32,6 @@ import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import org.controlsfx.validation.ValidationResult;
import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.Validator;
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
import java.io.*;
import java.net.URL;
@ -111,7 +109,8 @@ public class AppController implements Initializable {
fileChooser.setTitle("Open Transaction");
fileChooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("All Files", "*.*"),
new FileChooser.ExtensionFilter("PSBT", "*.psbt")
new FileChooser.ExtensionFilter("PSBT", "*.psbt"),
new FileChooser.ExtensionFilter("TXN", "*.txn")
);
File file = fileChooser.showOpenDialog(window);
@ -207,28 +206,12 @@ public class AppController implements Initializable {
}
public void newWallet(ActionEvent event) {
TextInputDialog dlg = new TextInputDialog("");
dlg.setTitle("New Wallet");
dlg.getDialogPane().setContentText("Wallet name:");
dlg.getDialogPane().getStylesheets().add(getClass().getResource("general.css").toExternalForm());
ValidationSupport validationSupport = new ValidationSupport();
validationSupport.registerValidator(dlg.getEditor(), Validator.combine(
Validator.createEmptyValidator("Wallet name is required"),
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Wallet name is not unique", Storage.getStorage().getWalletFile(newValue).exists())
));
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
Button okButton = (Button)dlg.getDialogPane().lookupButton(ButtonType.OK);
BooleanBinding isInvalid = Bindings.createBooleanBinding(() ->
dlg.getEditor().getText().length() == 0 || Storage.getStorage().getWalletFile(dlg.getEditor().getText()).exists(), dlg.getEditor().textProperty());
okButton.disableProperty().bind(isInvalid);
WalletNameDialog dlg = new WalletNameDialog();
Optional<String> walletName = dlg.showAndWait();
if(walletName.isPresent()) {
File walletFile = Storage.getStorage().getWalletFile(walletName.get());
Wallet wallet = new Wallet(PolicyType.SINGLE, ScriptType.P2WPKH);
Tab tab = addWalletTab(walletFile, wallet);
Tab tab = addWalletTab(walletFile, null, wallet);
tabs.getSelectionModel().select(tab);
}
}
@ -242,16 +225,31 @@ public class AppController implements Initializable {
File file = fileChooser.showOpenDialog(window);
if(file != null) {
try {
Wallet wallet = Storage.getStorage().loadWallet(file);
Tab tab = addWalletTab(file, wallet);
Wallet wallet;
ECKey encryptionPubKey = WalletForm.NO_PASSWORD_KEY;
try {
wallet = Storage.getStorage().loadWallet(file);
} catch(JsonSyntaxException e) {
Optional<ECKey> optionalFullKey = SettingsController.askForWalletPassword(null, true);
if(!optionalFullKey.isPresent()) {
return;
}
ECKey encryptionFullKey = optionalFullKey.get();
wallet = Storage.getStorage().loadWallet(file, encryptionFullKey);
encryptionPubKey = ECKey.fromPublicOnly(encryptionFullKey);
}
Tab tab = addWalletTab(file, encryptionPubKey, wallet);
tabs.getSelectionModel().select(tab);
} catch (IOException e) {
} catch (Exception e) {
e.printStackTrace();
showErrorDialog("Error opening wallet", e.getMessage());
}
}
}
public Tab addWalletTab(File walletFile, Wallet wallet) {
public Tab addWalletTab(File walletFile, ECKey encryptionPubKey, Wallet wallet) {
try {
Tab tab = new Tab(walletFile.getName());
TabData tabData = new TabData(TabData.TabType.WALLET);
@ -261,7 +259,7 @@ public class AppController implements Initializable {
FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml"));
tab.setContent(walletLoader.load());
WalletController controller = walletLoader.getController();
WalletForm walletForm = new WalletForm(walletFile, wallet);
WalletForm walletForm = new WalletForm(walletFile, encryptionPubKey, wallet);
controller.setWalletForm(walletForm);
tabs.getTabs().add(tab);

View file

@ -1,16 +1,20 @@
package com.sparrowwallet.sparrow;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.controlsfx.glyphfont.GlyphFontRegistry;
public class MainApp extends Application {
@Override
public void start(Stage stage) throws Exception {
GlyphFontRegistry.register(new FontAwesome5());
FXMLLoader transactionLoader = new FXMLLoader(getClass().getResource("app.fxml"));
Parent root = transactionLoader.load();
AppController appController = transactionLoader.getController();

View file

@ -0,0 +1,65 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.storage.Storage;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import org.controlsfx.control.textfield.CustomTextField;
import org.controlsfx.control.textfield.TextFields;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.glyphfont.GlyphFont;
import org.controlsfx.glyphfont.GlyphFontRegistry;
import org.controlsfx.validation.ValidationResult;
import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.Validator;
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
public class WalletNameDialog extends Dialog<String> {
private final CustomTextField name;
public WalletNameDialog() {
this.name = (CustomTextField)TextFields.createClearableTextField();
final DialogPane dialogPane = getDialogPane();
setTitle("Wallet Password");
dialogPane.setHeaderText("Enter a name for this wallet:");
dialogPane.getStylesheets().add(AppController.class.getResource("general.css").toExternalForm());
dialogPane.getButtonTypes().addAll(ButtonType.CANCEL);
dialogPane.setPrefWidth(380);
dialogPane.setPrefHeight(200);
Glyph wallet = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WALLET);
wallet.setFontSize(50);
dialogPane.setGraphic(wallet);
final VBox content = new VBox(10);
content.getChildren().add(name);
dialogPane.setContent(content);
ValidationSupport validationSupport = new ValidationSupport();
Platform.runLater( () -> {
validationSupport.registerValidator(name, Validator.combine(
Validator.createEmptyValidator("Wallet name is required"),
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Wallet name is not unique", Storage.getStorage().getWalletFile(newValue).exists())
));
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
});
final ButtonType okButtonType = new javafx.scene.control.ButtonType("New Wallet", ButtonBar.ButtonData.OK_DONE);
dialogPane.getButtonTypes().addAll(okButtonType);
Button okButton = (Button) dialogPane.lookupButton(okButtonType);
BooleanBinding isInvalid = Bindings.createBooleanBinding(() ->
name.getText().length() == 0 || Storage.getStorage().getWalletFile(name.getText()).exists(), name.textProperty());
okButton.disableProperty().bind(isInvalid);
name.setPromptText("Wallet Name");
setResultConverter(dialogButton -> dialogButton == okButtonType ? name.getText() : null);
}
}

View file

@ -0,0 +1,99 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.sparrow.AppController;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.scene.control.*;
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.controlsfx.validation.ValidationResult;
import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
public class WalletPasswordDialog extends Dialog<String> {
private final ButtonType okButtonType;
private final PasswordRequirement requirement;
private final CustomPasswordField password;
private final CustomPasswordField passwordConfirm;
public WalletPasswordDialog(PasswordRequirement requirement) {
this.requirement = requirement;
this.password = (CustomPasswordField)TextFields.createClearablePasswordField();
this.passwordConfirm = (CustomPasswordField)TextFields.createClearablePasswordField();
final DialogPane dialogPane = getDialogPane();
setTitle("Wallet Password");
dialogPane.setHeaderText(requirement.description);
dialogPane.getStylesheets().add(AppController.class.getResource("general.css").toExternalForm());
dialogPane.getButtonTypes().addAll(ButtonType.CANCEL);
dialogPane.setPrefWidth(380);
dialogPane.setPrefHeight(250);
Glyph lock = new Glyph("FontAwesome", FontAwesome.Glyph.LOCK);
lock.setFontSize(50);
dialogPane.setGraphic(lock);
final VBox content = new VBox(10);
content.setPrefHeight(100);
content.getChildren().add(password);
content.getChildren().add(passwordConfirm);
dialogPane.setContent(content);
ValidationSupport validationSupport = new ValidationSupport();
Platform.runLater( () -> {
validationSupport.registerValidator(passwordConfirm, (Control c, String newValue) -> ValidationResult.fromErrorIf(c, "Password confirmation does not match", !passwordConfirm.getText().equals(password.getText())));
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
});
okButtonType = new javafx.scene.control.ButtonType(requirement.okButtonText, ButtonBar.ButtonData.OK_DONE);
dialogPane.getButtonTypes().addAll(okButtonType);
Button okButton = (Button) dialogPane.lookupButton(okButtonType);
okButton.setPrefWidth(130);
BooleanBinding isInvalid = Bindings.createBooleanBinding(() -> passwordConfirm.isVisible() && !password.getText().equals(passwordConfirm.getText()), password.textProperty(), passwordConfirm.textProperty());
okButton.disableProperty().bind(isInvalid);
if(requirement != PasswordRequirement.UPDATE_NEW) {
passwordConfirm.setVisible(false);
passwordConfirm.setManaged(false);
}
if(requirement == PasswordRequirement.UPDATE_NEW || requirement == PasswordRequirement.UPDATE_EMPTY) {
password.textProperty().addListener((observable, oldValue, newValue) -> {
if(newValue.isEmpty()) {
okButton.setText("No Password");
passwordConfirm.setVisible(false);
passwordConfirm.setManaged(false);
} else {
okButton.setText("Set Password");
passwordConfirm.setVisible(true);
passwordConfirm.setManaged(true);
}
});
}
password.setPromptText("Password");
passwordConfirm.setPromptText("Password Confirmation");
setResultConverter(dialogButton -> dialogButton == okButtonType ? password.getText() : null);
}
public enum PasswordRequirement {
LOAD("Please enter the wallet password:", "Unlock"),
UPDATE_NEW("Add a password to the wallet?\nLeave empty for none:", "No Password"),
UPDATE_EMPTY("This wallet has no password.\nAdd a password to the wallet?\nLeave empty for none:", "No Password"),
UPDATE_SET("Please re-enter the wallet password:", "Verify Password");
private final String description;
private final String okButtonText;
PasswordRequirement(String description, String okButtonText) {
this.description = description;
this.okButtonText = okButtonText;
}
}
}

View file

@ -0,0 +1,56 @@
package com.sparrowwallet.sparrow.glyphfont;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.GlyphFont;
import org.controlsfx.glyphfont.GlyphFontRegistry;
import org.controlsfx.glyphfont.INamedCharacter;
import java.io.InputStream;
import java.util.Arrays;
public class FontAwesome5 extends GlyphFont {
public static String FONT_NAME = "Font Awesome 5 Free Solid";
/**
* The individual glyphs offered by the FontAwesome5 font.
*/
public static enum Glyph implements INamedCharacter {
WALLET('\uf555');
private final char ch;
/**
* Creates a named Glyph mapped to the given character
* @param ch
*/
Glyph( char ch ) {
this.ch = ch;
}
@Override
public char getChar() {
return ch;
}
}
/**
* Do not call this constructor directly - instead access the
* {@link FontAwesome5.Glyph} public static enumeration method to create the glyph nodes), or
* use the {@link GlyphFontRegistry} class to get access.
*
* Note: Do not remove this public constructor since it is used by the service loader!
*/
public FontAwesome5() {
this(FontAwesome5.class.getResourceAsStream("/font/fa-solid-900.ttf"));
}
/**
* Creates a new FontAwesome instance which uses the provided font source.
* @param is
*/
public FontAwesome5(InputStream is){
super(FONT_NAME, 14, is, true);
registerAll(Arrays.asList(FontAwesome.Glyph.values()));
registerAll(Arrays.asList(FontAwesome5.Glyph.values()));
}
}

View file

@ -1,11 +1,17 @@
package com.sparrowwallet.sparrow.storage;
import com.google.common.io.ByteStreams;
import com.google.gson.*;
import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.wallet.Wallet;
import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
public class Storage {
public static final String SPARROW_DIR = ".sparrow";
@ -38,6 +44,46 @@ public class Storage {
return wallet;
}
public static final void main(String[] args) throws Exception {
File file = new File("/Users/scy/.electrum-latest/wallets/scyone");
ECKey pubKey = ECKey.createKeyPbkdf2HmacSha512("ferSwogen");
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
byte[] encrypted = ByteStreams.toByteArray(inputStream);
byte[] decrypted = pubKey.decryptEcies(encrypted, getEncryptionMagic());
String jsonWallet = inflate(decrypted);
System.out.println(jsonWallet);
}
public Wallet loadWallet(File file, ECKey encryptionKey) throws IOException {
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
byte[] encrypted = ByteStreams.toByteArray(inputStream);
byte[] decrypted = encryptionKey.decryptEcies(encrypted, getEncryptionMagic());
String jsonWallet = inflate(decrypted);
return gson.fromJson(jsonWallet, Wallet.class);
}
private static String inflate(byte[] encryptedWallet) {
Inflater inflater = new Inflater();
inflater.setInput(encryptedWallet);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
byte[] buf = new byte[1024];
while(!inflater.finished()) {
int byteCount = inflater.inflate(buf);
baos.write(buf, 0, byteCount);
}
inflater.end();
} catch(DataFormatException e) {
throw new RuntimeException(e);
}
return baos.toString(StandardCharsets.UTF_8);
}
public void storeWallet(File file, Wallet wallet) throws IOException {
File parent = file.getParentFile();
if(!parent.exists() && !parent.mkdirs()) {
@ -49,6 +95,41 @@ public class Storage {
writer.close();
}
public void storeWallet(File file, ECKey encryptionKey, Wallet wallet) throws IOException {
File parent = file.getParentFile();
if(!parent.exists() && !parent.mkdirs()) {
throw new IOException("Could not create folder " + parent);
}
String jsonWallet = gson.toJson(wallet);
byte[] compressedWallet = deflate(jsonWallet);
byte[] encryptedWallet = encryptionKey.encryptEcies(compressedWallet, getEncryptionMagic());
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
outputStream.write(encryptedWallet);
outputStream.close();
}
private static byte[] deflate(String jsonWallet) {
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
deflater.setInput(jsonWallet.getBytes(StandardCharsets.UTF_8));
deflater.finish();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
while(!deflater.finished()) {
int byteCount = deflater.deflate(buf);
baos.write(buf, 0, byteCount);
}
deflater.end();
return baos.toByteArray();
}
private static byte[] getEncryptionMagic() {
return "BIE1".getBytes(StandardCharsets.UTF_8);
}
public File getWalletFile(String walletName) {
return new File(getWalletsDir(), walletName);
}

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.sparrow.wallet;
import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
@ -9,6 +10,7 @@ import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppController;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.CopyableLabel;
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
import com.sparrowwallet.sparrow.event.WalletChangedEvent;
import javafx.application.Platform;
@ -25,6 +27,7 @@ import tornadofx.control.Fieldset;
import java.io.IOException;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
@ -147,10 +150,14 @@ public class SettingsController extends WalletFormController implements Initiali
apply.setOnAction(event -> {
try {
walletForm.save();
revert.setDisable(true);
apply.setDisable(true);
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet()));
Optional<ECKey> optionalPubKey = askForWalletPassword(walletForm.getEncryptionPubKey(), false);
if(optionalPubKey.isPresent()) {
walletForm.setEncryptionPubKey(ECKey.fromPublicOnly(optionalPubKey.get()));
walletForm.save();
revert.setDisable(true);
apply.setDisable(true);
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet()));
}
} catch (IOException e) {
AppController.showErrorDialog("Error saving file", e.getMessage());
}
@ -241,4 +248,40 @@ public class SettingsController extends WalletFormController implements Initiali
revert.setDisable(false);
Platform.runLater(() -> apply.setDisable(!tabsValidate()));
}
public static Optional<ECKey> askForWalletPassword(ECKey existingPubKey, boolean required) {
WalletPasswordDialog.PasswordRequirement requirement;
if(existingPubKey == null && required) {
requirement = WalletPasswordDialog.PasswordRequirement.LOAD;
} else if(existingPubKey == null) {
requirement = WalletPasswordDialog.PasswordRequirement.UPDATE_NEW;
} else if(WalletForm.NO_PASSWORD_KEY.equals(existingPubKey)) {
requirement = WalletPasswordDialog.PasswordRequirement.UPDATE_EMPTY;
} else {
requirement = WalletPasswordDialog.PasswordRequirement.UPDATE_SET;
}
WalletPasswordDialog dlg = new WalletPasswordDialog(requirement);
Optional<String> password = dlg.showAndWait();
if(password.isPresent()) {
if(!required && password.get().isEmpty()) {
return Optional.of(WalletForm.NO_PASSWORD_KEY);
}
ECKey encryptionFullKey = ECKey.createKeyPbkdf2HmacSha512(password.get());
ECKey encryptionPubKey = ECKey.fromPublicOnly(encryptionFullKey);
if(existingPubKey != null) {
if(WalletForm.NO_PASSWORD_KEY.equals(existingPubKey) || existingPubKey.equals(encryptionPubKey)) {
return Optional.of(encryptionPubKey);
} else {
AppController.showErrorDialog("Incorrect Password", "The password was incorrect.");
return Optional.empty();
}
}
return Optional.of(encryptionFullKey);
}
return Optional.empty();
}
}

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.sparrow.wallet;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.storage.Storage;
@ -7,12 +8,16 @@ import java.io.File;
import java.io.IOException;
public class WalletForm {
public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(ECKey.createKeyPbkdf2HmacSha512(""));
private File walletFile;
private ECKey encryptionPubKey;
private Wallet oldWallet;
private Wallet wallet;
public WalletForm(File walletFile, Wallet currentWallet) {
public WalletForm(File walletFile, ECKey encryptionPubKey, Wallet currentWallet) {
this.walletFile = walletFile;
this.encryptionPubKey = encryptionPubKey;
this.oldWallet = currentWallet;
this.wallet = currentWallet.copy();
}
@ -21,12 +26,25 @@ public class WalletForm {
return wallet;
}
public ECKey getEncryptionPubKey() {
return encryptionPubKey;
}
public void setEncryptionPubKey(ECKey encryptionPubKey) {
this.encryptionPubKey = encryptionPubKey;
}
public void revert() {
this.wallet = oldWallet.copy();
}
public void save() throws IOException {
Storage.getStorage().storeWallet(walletFile, wallet);
if(encryptionPubKey.equals(NO_PASSWORD_KEY)) {
Storage.getStorage().storeWallet(walletFile, wallet);
} else {
Storage.getStorage().storeWallet(walletFile, encryptionPubKey, wallet);
}
oldWallet = wallet.copy();
}
}

View file

@ -46,3 +46,7 @@
-fx-effect: dropshadow(three-pass-box, gold, 14, 0, 0, 0);
}
.dialog-pane .header-panel {
-fx-padding: 20;
}

Binary file not shown.