mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-04 21:36:45 +00:00
wallet load and save
This commit is contained in:
parent
1f93d574eb
commit
fb621963dd
11 changed files with 405 additions and 37 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit 813781902b8914fbac20c5a36ef230a44639ecbc
|
Subproject commit 282628e4558b04dfa17c3f85247378204f8c82ff
|
|
@ -3,7 +3,9 @@ package com.sparrowwallet.sparrow;
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
import com.sparrowwallet.drongo.Utils;
|
import com.sparrowwallet.drongo.Utils;
|
||||||
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
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.psbt.PSBTParseException;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.control.TextAreaDialog;
|
import com.sparrowwallet.sparrow.control.TextAreaDialog;
|
||||||
|
import com.sparrowwallet.sparrow.control.WalletNameDialog;
|
||||||
import com.sparrowwallet.sparrow.event.TabEvent;
|
import com.sparrowwallet.sparrow.event.TabEvent;
|
||||||
import com.sparrowwallet.sparrow.event.TransactionTabChangedEvent;
|
import com.sparrowwallet.sparrow.event.TransactionTabChangedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.TransactionTabSelectedEvent;
|
import com.sparrowwallet.sparrow.event.TransactionTabSelectedEvent;
|
||||||
import com.sparrowwallet.sparrow.storage.Storage;
|
import com.sparrowwallet.sparrow.storage.Storage;
|
||||||
import com.sparrowwallet.sparrow.transaction.TransactionController;
|
import com.sparrowwallet.sparrow.transaction.TransactionController;
|
||||||
|
import com.sparrowwallet.sparrow.wallet.SettingsController;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletController;
|
import com.sparrowwallet.sparrow.wallet.WalletController;
|
||||||
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
import com.sparrowwallet.sparrow.wallet.WalletForm;
|
||||||
import javafx.beans.binding.Bindings;
|
|
||||||
import javafx.beans.binding.BooleanBinding;
|
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
|
@ -30,10 +32,6 @@ import javafx.scene.input.TransferMode;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
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.io.*;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -111,7 +109,8 @@ public class AppController implements Initializable {
|
||||||
fileChooser.setTitle("Open Transaction");
|
fileChooser.setTitle("Open Transaction");
|
||||||
fileChooser.getExtensionFilters().addAll(
|
fileChooser.getExtensionFilters().addAll(
|
||||||
new FileChooser.ExtensionFilter("All Files", "*.*"),
|
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);
|
File file = fileChooser.showOpenDialog(window);
|
||||||
|
@ -207,28 +206,12 @@ public class AppController implements Initializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void newWallet(ActionEvent event) {
|
public void newWallet(ActionEvent event) {
|
||||||
TextInputDialog dlg = new TextInputDialog("");
|
WalletNameDialog dlg = new WalletNameDialog();
|
||||||
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);
|
|
||||||
|
|
||||||
Optional<String> walletName = dlg.showAndWait();
|
Optional<String> walletName = dlg.showAndWait();
|
||||||
if(walletName.isPresent()) {
|
if(walletName.isPresent()) {
|
||||||
File walletFile = Storage.getStorage().getWalletFile(walletName.get());
|
File walletFile = Storage.getStorage().getWalletFile(walletName.get());
|
||||||
Wallet wallet = new Wallet(PolicyType.SINGLE, ScriptType.P2WPKH);
|
Wallet wallet = new Wallet(PolicyType.SINGLE, ScriptType.P2WPKH);
|
||||||
Tab tab = addWalletTab(walletFile, wallet);
|
Tab tab = addWalletTab(walletFile, null, wallet);
|
||||||
tabs.getSelectionModel().select(tab);
|
tabs.getSelectionModel().select(tab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,16 +225,31 @@ public class AppController implements Initializable {
|
||||||
File file = fileChooser.showOpenDialog(window);
|
File file = fileChooser.showOpenDialog(window);
|
||||||
if(file != null) {
|
if(file != null) {
|
||||||
try {
|
try {
|
||||||
Wallet wallet = Storage.getStorage().loadWallet(file);
|
Wallet wallet;
|
||||||
Tab tab = addWalletTab(file, 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);
|
tabs.getSelectionModel().select(tab);
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
showErrorDialog("Error opening wallet", e.getMessage());
|
showErrorDialog("Error opening wallet", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Tab addWalletTab(File walletFile, Wallet wallet) {
|
public Tab addWalletTab(File walletFile, ECKey encryptionPubKey, Wallet wallet) {
|
||||||
try {
|
try {
|
||||||
Tab tab = new Tab(walletFile.getName());
|
Tab tab = new Tab(walletFile.getName());
|
||||||
TabData tabData = new TabData(TabData.TabType.WALLET);
|
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"));
|
FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml"));
|
||||||
tab.setContent(walletLoader.load());
|
tab.setContent(walletLoader.load());
|
||||||
WalletController controller = walletLoader.getController();
|
WalletController controller = walletLoader.getController();
|
||||||
WalletForm walletForm = new WalletForm(walletFile, wallet);
|
WalletForm walletForm = new WalletForm(walletFile, encryptionPubKey, wallet);
|
||||||
controller.setWalletForm(walletForm);
|
controller.setWalletForm(walletForm);
|
||||||
|
|
||||||
tabs.getTabs().add(tab);
|
tabs.getTabs().add(tab);
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
package com.sparrowwallet.sparrow;
|
package com.sparrowwallet.sparrow;
|
||||||
|
|
||||||
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.controlsfx.glyphfont.GlyphFontRegistry;
|
||||||
|
|
||||||
public class MainApp extends Application {
|
public class MainApp extends Application {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) throws Exception {
|
public void start(Stage stage) throws Exception {
|
||||||
|
GlyphFontRegistry.register(new FontAwesome5());
|
||||||
|
|
||||||
FXMLLoader transactionLoader = new FXMLLoader(getClass().getResource("app.fxml"));
|
FXMLLoader transactionLoader = new FXMLLoader(getClass().getResource("app.fxml"));
|
||||||
Parent root = transactionLoader.load();
|
Parent root = transactionLoader.load();
|
||||||
AppController appController = transactionLoader.getController();
|
AppController appController = transactionLoader.getController();
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,17 @@
|
||||||
package com.sparrowwallet.sparrow.storage;
|
package com.sparrowwallet.sparrow.storage;
|
||||||
|
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import com.sparrowwallet.drongo.ExtendedPublicKey;
|
import com.sparrowwallet.drongo.ExtendedPublicKey;
|
||||||
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.Type;
|
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 class Storage {
|
||||||
public static final String SPARROW_DIR = ".sparrow";
|
public static final String SPARROW_DIR = ".sparrow";
|
||||||
|
@ -38,6 +44,46 @@ public class Storage {
|
||||||
return wallet;
|
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 {
|
public void storeWallet(File file, Wallet wallet) throws IOException {
|
||||||
File parent = file.getParentFile();
|
File parent = file.getParentFile();
|
||||||
if(!parent.exists() && !parent.mkdirs()) {
|
if(!parent.exists() && !parent.mkdirs()) {
|
||||||
|
@ -49,6 +95,41 @@ public class Storage {
|
||||||
writer.close();
|
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) {
|
public File getWalletFile(String walletName) {
|
||||||
return new File(getWalletsDir(), walletName);
|
return new File(getWalletsDir(), walletName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.sparrowwallet.sparrow.wallet;
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
import com.sparrowwallet.drongo.policy.Policy;
|
import com.sparrowwallet.drongo.policy.Policy;
|
||||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
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.AppController;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
import com.sparrowwallet.sparrow.control.CopyableLabel;
|
||||||
|
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
||||||
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.WalletChangedEvent;
|
import com.sparrowwallet.sparrow.event.WalletChangedEvent;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
@ -25,6 +27,7 @@ import tornadofx.control.Fieldset;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -147,10 +150,14 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
|
|
||||||
apply.setOnAction(event -> {
|
apply.setOnAction(event -> {
|
||||||
try {
|
try {
|
||||||
|
Optional<ECKey> optionalPubKey = askForWalletPassword(walletForm.getEncryptionPubKey(), false);
|
||||||
|
if(optionalPubKey.isPresent()) {
|
||||||
|
walletForm.setEncryptionPubKey(ECKey.fromPublicOnly(optionalPubKey.get()));
|
||||||
walletForm.save();
|
walletForm.save();
|
||||||
revert.setDisable(true);
|
revert.setDisable(true);
|
||||||
apply.setDisable(true);
|
apply.setDisable(true);
|
||||||
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet()));
|
EventManager.get().post(new WalletChangedEvent(walletForm.getWallet()));
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
AppController.showErrorDialog("Error saving file", e.getMessage());
|
AppController.showErrorDialog("Error saving file", e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -241,4 +248,40 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||||
revert.setDisable(false);
|
revert.setDisable(false);
|
||||||
Platform.runLater(() -> apply.setDisable(!tabsValidate()));
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.wallet;
|
package com.sparrowwallet.sparrow.wallet;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.storage.Storage;
|
import com.sparrowwallet.sparrow.storage.Storage;
|
||||||
|
|
||||||
|
@ -7,12 +8,16 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class WalletForm {
|
public class WalletForm {
|
||||||
|
public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(ECKey.createKeyPbkdf2HmacSha512(""));
|
||||||
|
|
||||||
private File walletFile;
|
private File walletFile;
|
||||||
|
private ECKey encryptionPubKey;
|
||||||
private Wallet oldWallet;
|
private Wallet oldWallet;
|
||||||
private Wallet wallet;
|
private Wallet wallet;
|
||||||
|
|
||||||
public WalletForm(File walletFile, Wallet currentWallet) {
|
public WalletForm(File walletFile, ECKey encryptionPubKey, Wallet currentWallet) {
|
||||||
this.walletFile = walletFile;
|
this.walletFile = walletFile;
|
||||||
|
this.encryptionPubKey = encryptionPubKey;
|
||||||
this.oldWallet = currentWallet;
|
this.oldWallet = currentWallet;
|
||||||
this.wallet = currentWallet.copy();
|
this.wallet = currentWallet.copy();
|
||||||
}
|
}
|
||||||
|
@ -21,12 +26,25 @@ public class WalletForm {
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ECKey getEncryptionPubKey() {
|
||||||
|
return encryptionPubKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEncryptionPubKey(ECKey encryptionPubKey) {
|
||||||
|
this.encryptionPubKey = encryptionPubKey;
|
||||||
|
}
|
||||||
|
|
||||||
public void revert() {
|
public void revert() {
|
||||||
this.wallet = oldWallet.copy();
|
this.wallet = oldWallet.copy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void save() throws IOException {
|
public void save() throws IOException {
|
||||||
|
if(encryptionPubKey.equals(NO_PASSWORD_KEY)) {
|
||||||
Storage.getStorage().storeWallet(walletFile, wallet);
|
Storage.getStorage().storeWallet(walletFile, wallet);
|
||||||
|
} else {
|
||||||
|
Storage.getStorage().storeWallet(walletFile, encryptionPubKey, wallet);
|
||||||
|
}
|
||||||
|
|
||||||
oldWallet = wallet.copy();
|
oldWallet = wallet.copy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,3 +46,7 @@
|
||||||
-fx-effect: dropshadow(three-pass-box, gold, 14, 0, 0, 0);
|
-fx-effect: dropshadow(three-pass-box, gold, 14, 0, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-pane .header-panel {
|
||||||
|
-fx-padding: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
BIN
src/main/resources/font/fa-solid-900.ttf
Normal file
BIN
src/main/resources/font/fa-solid-900.ttf
Normal file
Binary file not shown.
Loading…
Reference in a new issue