diff --git a/drongo b/drongo index 81378190..282628e4 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 813781902b8914fbac20c5a36ef230a44639ecbc +Subproject commit 282628e4558b04dfa17c3f85247378204f8c82ff diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 9217fb33..760163d8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -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 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 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); diff --git a/src/main/java/com/sparrowwallet/sparrow/MainApp.java b/src/main/java/com/sparrowwallet/sparrow/MainApp.java index adf9d29e..0fd75d6a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/MainApp.java +++ b/src/main/java/com/sparrowwallet/sparrow/MainApp.java @@ -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(); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java new file mode 100644 index 00000000..0ad30324 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java @@ -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 { + 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); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletPasswordDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletPasswordDialog.java new file mode 100644 index 00000000..82db83e6 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletPasswordDialog.java @@ -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 { + 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; + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java new file mode 100644 index 00000000..02790583 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java @@ -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())); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/storage/Storage.java b/src/main/java/com/sparrowwallet/sparrow/storage/Storage.java index 1f96bd93..4fe3b65d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/storage/Storage.java +++ b/src/main/java/com/sparrowwallet/sparrow/storage/Storage.java @@ -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); } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java index ef420e16..eaa685b4 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java @@ -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 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 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 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(); + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index cec7645e..6e997b5f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -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(); } } diff --git a/src/main/resources/com/sparrowwallet/sparrow/general.css b/src/main/resources/com/sparrowwallet/sparrow/general.css index 9ede2508..86e4fdd7 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/general.css +++ b/src/main/resources/com/sparrowwallet/sparrow/general.css @@ -46,3 +46,7 @@ -fx-effect: dropshadow(three-pass-box, gold, 14, 0, 0, 0); } +.dialog-pane .header-panel { + -fx-padding: 20; +} + diff --git a/src/main/resources/font/fa-solid-900.ttf b/src/main/resources/font/fa-solid-900.ttf new file mode 100644 index 00000000..5b979039 Binary files /dev/null and b/src/main/resources/font/fa-solid-900.ttf differ