mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
bip39 keystore import
This commit is contained in:
parent
910dfcdeb1
commit
a409c28b20
17 changed files with 613 additions and 248 deletions
|
@ -48,7 +48,7 @@ dependencies {
|
|||
mainClassName = 'com.sparrowwallet.sparrow/com.sparrowwallet.sparrow.MainApp'
|
||||
|
||||
run {
|
||||
applicationDefaultJvmArgs = ["-Xdock:name=Sparrow", "-Xdock:icon=/Users/scy/git/sparrow/src/main/resources/sparrow.png", "--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls", "--add-opens=javafx.graphics/javafx.scene=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls", "--add-opens=javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls"]
|
||||
applicationDefaultJvmArgs = ["-Xdock:name=Sparrow", "-Xdock:icon=/Users/scy/git/sparrow/src/main/resources/sparrow.png", "--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls", "--add-opens=javafx.graphics/javafx.scene=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls", "--add-opens=javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls", "--add-opens=javafx.base/com.sun.javafx.event=org.controlsfx.controls"]
|
||||
}
|
||||
|
||||
jlink {
|
||||
|
@ -63,7 +63,7 @@ jlink {
|
|||
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages', '--ignore-signing-information', '--exclude-files', '**.png']
|
||||
launcher {
|
||||
name = 'sparrow'
|
||||
jvmArgs = ["--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls", "--add-opens=javafx.graphics/javafx.scene=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls", "--add-opens=javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls"]
|
||||
jvmArgs = ["--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls", "--add-opens=javafx.graphics/javafx.scene=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls", "--add-opens=javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls", "--add-opens=javafx.base/com.sun.javafx.event=org.controlsfx.controls"]
|
||||
}
|
||||
addExtraDependencies("javafx")
|
||||
jpackage {
|
||||
|
|
2
drongo
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit d394c25a3c05c02d984b1f709623a311c2afb7a1
|
||||
Subproject commit be0c4d1176da41671c2629e65f812fd28fab202b
|
|
@ -41,9 +41,9 @@ public class MainApp extends Application {
|
|||
wallet.setScriptType(ScriptType.P2WPKH);
|
||||
|
||||
KeystoreImportDialog dlg = new KeystoreImportDialog(wallet);
|
||||
//dlg.showAndWait();
|
||||
dlg.showAndWait();
|
||||
|
||||
stage.show();
|
||||
//stage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
|
|
@ -128,7 +128,8 @@ public class DevicePane extends TitledPane {
|
|||
listItem.getChildren().add(buttonBox);
|
||||
|
||||
this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
|
||||
listItem.setPrefWidth(newValue.getWidth());
|
||||
//Hack to force listItem to expand to full available width less border
|
||||
listItem.setPrefWidth(newValue.getWidth() - 2);
|
||||
});
|
||||
|
||||
return listItem;
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||
import com.sparrowwallet.sparrow.io.KeystoreFileImport;
|
||||
import com.sparrowwallet.sparrow.io.KeystoreImport;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.controlsfx.control.textfield.CustomPasswordField;
|
||||
import org.controlsfx.control.textfield.TextFields;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class FileKeystoreImportPane extends KeystoreImportPane {
|
||||
private final KeystoreFileImport importer;
|
||||
private Button importButton;
|
||||
private final SimpleStringProperty password = new SimpleStringProperty("");
|
||||
|
||||
public FileKeystoreImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreFileImport importer) {
|
||||
super(importAccordion, wallet, importer);
|
||||
this.importer = importer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node getTitle(KeystoreImport importer) {
|
||||
Node title = super.getTitle(importer);
|
||||
|
||||
setDescription("Keystore file import");
|
||||
|
||||
importButton = new Button("Import File...");
|
||||
importButton.setAlignment(Pos.CENTER_RIGHT);
|
||||
importButton.setOnAction(event -> {
|
||||
importFile();
|
||||
});
|
||||
buttonBox.getChildren().add(importButton);
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
private void importFile() {
|
||||
Stage window = new Stage();
|
||||
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " keystore");
|
||||
fileChooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("All Files", "*.*"),
|
||||
new FileChooser.ExtensionFilter("JSON", "*.json")
|
||||
);
|
||||
|
||||
File file = fileChooser.showOpenDialog(window);
|
||||
if(file != null) {
|
||||
importFile(file, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void importFile(File file, String password) {
|
||||
if(file.exists()) {
|
||||
try {
|
||||
if(importer.isEncrypted(file) && password == null) {
|
||||
setDescription("Password Required");
|
||||
showHideLink.setVisible(false);
|
||||
setContent(getPasswordEntry(file));
|
||||
importButton.setDisable(true);
|
||||
setExpanded(true);
|
||||
} else {
|
||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
|
||||
Keystore keystore = importer.getKeystore(wallet.getScriptType(), inputStream, password);
|
||||
EventManager.get().post(new KeystoreImportEvent(keystore));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String errorMessage = e.getMessage();
|
||||
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
||||
errorMessage = e.getCause().getMessage();
|
||||
}
|
||||
if(e instanceof ECKey.InvalidPasswordException || e.getCause() instanceof ECKey.InvalidPasswordException) {
|
||||
errorMessage = "Invalid wallet password";
|
||||
}
|
||||
if(e instanceof JsonParseException || e.getCause() instanceof JsonParseException) {
|
||||
errorMessage = "File was not in JSON format";
|
||||
}
|
||||
setError("Import Error", errorMessage);
|
||||
importButton.setDisable(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Node getPasswordEntry(File file) {
|
||||
CustomPasswordField passwordField = (CustomPasswordField) TextFields.createClearablePasswordField();
|
||||
passwordField.setPromptText("Wallet password");
|
||||
password.bind(passwordField.textProperty());
|
||||
HBox.setHgrow(passwordField, Priority.ALWAYS);
|
||||
|
||||
Button importEncryptedButton = new Button("Import");
|
||||
importEncryptedButton.setOnAction(event -> {
|
||||
showHideLink.setVisible(true);
|
||||
setExpanded(false);
|
||||
importFile(file, password.get());
|
||||
});
|
||||
|
||||
HBox contentBox = new HBox();
|
||||
contentBox.setAlignment(Pos.TOP_RIGHT);
|
||||
contentBox.setSpacing(20);
|
||||
contentBox.getChildren().add(passwordField);
|
||||
contentBox.getChildren().add(importEncryptedButton);
|
||||
contentBox.setPadding(new Insets(10, 30, 10, 30));
|
||||
contentBox.setPrefHeight(60);
|
||||
|
||||
return contentBox;
|
||||
}
|
||||
}
|
|
@ -1,229 +0,0 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||
import com.sparrowwallet.sparrow.io.KeystoreFileImport;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
import org.controlsfx.control.textfield.CustomPasswordField;
|
||||
import org.controlsfx.control.textfield.TextFields;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class KeystoreFileImportPane extends TitledPane {
|
||||
private final KeystoreImportAccordion importAccordion;
|
||||
private final Wallet wallet;
|
||||
private final KeystoreFileImport importer;
|
||||
|
||||
private Label mainLabel;
|
||||
private Label descriptionLabel;
|
||||
private Hyperlink showHideLink;
|
||||
private Button importButton;
|
||||
|
||||
private final SimpleStringProperty password = new SimpleStringProperty("");
|
||||
|
||||
public KeystoreFileImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreFileImport importer) {
|
||||
this.importAccordion = importAccordion;
|
||||
this.wallet = wallet;
|
||||
this.importer = importer;
|
||||
|
||||
setPadding(Insets.EMPTY);
|
||||
|
||||
setGraphic(getTitle());
|
||||
getStyleClass().add("importpane");
|
||||
setContent(getContentBox(importer.getKeystoreImportDescription()));
|
||||
|
||||
removeArrow();
|
||||
}
|
||||
|
||||
private void removeArrow() {
|
||||
Platform.runLater(() -> {
|
||||
Node arrow = this.lookup(".arrow");
|
||||
if (arrow != null) {
|
||||
arrow.setVisible(false);
|
||||
arrow.setManaged(false);
|
||||
} else {
|
||||
removeArrow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Node getTitle() {
|
||||
HBox listItem = new HBox();
|
||||
listItem.setPadding(new Insets(10, 20, 10, 10));
|
||||
listItem.setSpacing(10);
|
||||
|
||||
HBox imageBox = new HBox();
|
||||
imageBox.setMinWidth(50);
|
||||
imageBox.setMinHeight(50);
|
||||
listItem.getChildren().add(imageBox);
|
||||
|
||||
Image image = new Image("image/" + importer.getWalletModel().getType() + ".png", 50, 50, true, true);
|
||||
if (!image.isError()) {
|
||||
ImageView imageView = new ImageView();
|
||||
imageView.setImage(image);
|
||||
imageBox.getChildren().add(imageView);
|
||||
}
|
||||
|
||||
VBox labelsBox = new VBox();
|
||||
labelsBox.setSpacing(5);
|
||||
labelsBox.setAlignment(Pos.CENTER_LEFT);
|
||||
this.mainLabel = new Label();
|
||||
mainLabel.setText(importer.getName());
|
||||
mainLabel.getStyleClass().add("main-label");
|
||||
labelsBox.getChildren().add(mainLabel);
|
||||
|
||||
HBox descriptionBox = new HBox();
|
||||
descriptionBox.setSpacing(7);
|
||||
labelsBox.getChildren().add(descriptionBox);
|
||||
|
||||
descriptionLabel = new Label("Keystore file import");
|
||||
descriptionLabel.getStyleClass().add("description-label");
|
||||
showHideLink = new Hyperlink("View Details...");
|
||||
showHideLink.managedProperty().bind(showHideLink.visibleProperty());
|
||||
showHideLink.setOnAction(event -> {
|
||||
if(showHideLink.getText().contains("View")) {
|
||||
setExpanded(true);
|
||||
showHideLink.setText("Hide Details...");
|
||||
} else {
|
||||
setExpanded(false);
|
||||
showHideLink.setText("View Details...");
|
||||
}
|
||||
});
|
||||
descriptionBox.getChildren().addAll(descriptionLabel, showHideLink);
|
||||
|
||||
listItem.getChildren().add(labelsBox);
|
||||
HBox.setHgrow(labelsBox, Priority.ALWAYS);
|
||||
|
||||
HBox buttonBox = new HBox();
|
||||
buttonBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
importButton = new Button("Import File...");
|
||||
importButton.setAlignment(Pos.CENTER_RIGHT);
|
||||
importButton.setOnAction(event -> {
|
||||
importFile();
|
||||
});
|
||||
|
||||
buttonBox.getChildren().add(importButton);
|
||||
listItem.getChildren().add(buttonBox);
|
||||
|
||||
this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
|
||||
listItem.setPrefWidth(newValue.getWidth());
|
||||
});
|
||||
|
||||
return listItem;
|
||||
}
|
||||
|
||||
private void importFile() {
|
||||
Stage window = new Stage();
|
||||
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Open " + importer.getWalletModel().toDisplayString() + " keystore");
|
||||
fileChooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("All Files", "*.*"),
|
||||
new FileChooser.ExtensionFilter("JSON", "*.json")
|
||||
);
|
||||
|
||||
File file = fileChooser.showOpenDialog(window);
|
||||
if(file != null) {
|
||||
importFile(file, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void importFile(File file, String password) {
|
||||
if(file.exists()) {
|
||||
try {
|
||||
if(importer.isEncrypted(file) && password == null) {
|
||||
descriptionLabel.getStyleClass().remove("description-error");
|
||||
descriptionLabel.getStyleClass().add("description-label");
|
||||
descriptionLabel.setText("Password Required");
|
||||
showHideLink.setVisible(false);
|
||||
setContent(getPasswordEntry(file));
|
||||
importButton.setDisable(true);
|
||||
setExpanded(true);
|
||||
} else {
|
||||
InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
|
||||
Keystore keystore = importer.getKeystore(wallet.getScriptType(), inputStream, password);
|
||||
EventManager.get().post(new KeystoreImportEvent(keystore));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
descriptionLabel.getStyleClass().remove("description-label");
|
||||
descriptionLabel.getStyleClass().add("description-error");
|
||||
descriptionLabel.setText("Import Error");
|
||||
String errorMessage = e.getMessage();
|
||||
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
||||
errorMessage = e.getCause().getMessage();
|
||||
}
|
||||
if(e instanceof ECKey.InvalidPasswordException || e.getCause() instanceof ECKey.InvalidPasswordException) {
|
||||
errorMessage = "Invalid wallet password";
|
||||
}
|
||||
if(e instanceof JsonParseException || e.getCause() instanceof JsonParseException) {
|
||||
errorMessage = "File was not in JSON format";
|
||||
}
|
||||
setContent(getContentBox(errorMessage));
|
||||
setExpanded(true);
|
||||
showHideLink.setText("Hide Details...");
|
||||
importButton.setDisable(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Node getContentBox(String message) {
|
||||
Label details = new Label(message);
|
||||
details.setWrapText(true);
|
||||
|
||||
HBox contentBox = new HBox();
|
||||
contentBox.setAlignment(Pos.TOP_LEFT);
|
||||
contentBox.getChildren().add(details);
|
||||
contentBox.setPadding(new Insets(10, 30, 10, 30));
|
||||
|
||||
double width = TextUtils.computeTextWidth(details.getFont(), message, 0.0D);
|
||||
double numLines = Math.max(1, width / 400);
|
||||
double height = Math.max(60, numLines * 40);
|
||||
contentBox.setPrefHeight(height);
|
||||
|
||||
return contentBox;
|
||||
}
|
||||
|
||||
private Node getPasswordEntry(File file) {
|
||||
CustomPasswordField passwordField = (CustomPasswordField) TextFields.createClearablePasswordField();
|
||||
passwordField.setPromptText("Wallet password");
|
||||
password.bind(passwordField.textProperty());
|
||||
HBox.setHgrow(passwordField, Priority.ALWAYS);
|
||||
|
||||
Button importEncryptedButton = new Button("Import");
|
||||
importEncryptedButton.setOnAction(event -> {
|
||||
showHideLink.setVisible(true);
|
||||
setExpanded(false);
|
||||
importFile(file, password.get());
|
||||
});
|
||||
|
||||
HBox contentBox = new HBox();
|
||||
contentBox.setAlignment(Pos.TOP_RIGHT);
|
||||
contentBox.setSpacing(20);
|
||||
contentBox.getChildren().add(passwordField);
|
||||
contentBox.getChildren().add(importEncryptedButton);
|
||||
contentBox.setPadding(new Insets(10, 30, 10, 30));
|
||||
contentBox.setPrefHeight(60);
|
||||
|
||||
return contentBox;
|
||||
}
|
||||
}
|
|
@ -16,12 +16,12 @@ public class KeystoreImportAccordion extends Accordion {
|
|||
this.importers = importers;
|
||||
|
||||
for(KeystoreImport importer : importers) {
|
||||
KeystoreFileImportPane importPane = null;
|
||||
KeystoreImportPane importPane = null;
|
||||
|
||||
if(importer instanceof KeystoreFileImport) {
|
||||
importPane = new KeystoreFileImportPane(this, wallet, (KeystoreFileImport)importer);
|
||||
importPane = new FileKeystoreImportPane(this, wallet, (KeystoreFileImport)importer);
|
||||
} else if(importer instanceof KeystoreMnemonicImport) {
|
||||
//TODO: Import from the new Bip39KeystoreImport
|
||||
importPane = new MnemonicKeystoreImportPane(this, wallet, (KeystoreMnemonicImport)importer);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Could not create ImportPane for importer of type " + importer.getClass());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.io.KeystoreImport;
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
public abstract class KeystoreImportPane extends TitledPane {
|
||||
protected final KeystoreImportAccordion importAccordion;
|
||||
protected final Wallet wallet;
|
||||
|
||||
private Label mainLabel;
|
||||
private Label descriptionLabel;
|
||||
protected Hyperlink showHideLink;
|
||||
protected HBox buttonBox;
|
||||
|
||||
public KeystoreImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreImport importer) {
|
||||
this.importAccordion = importAccordion;
|
||||
this.wallet = wallet;
|
||||
|
||||
setPadding(Insets.EMPTY);
|
||||
setGraphic(getTitle(importer));
|
||||
getStyleClass().add("importpane");
|
||||
setContent(getContentBox(importer.getKeystoreImportDescription()));
|
||||
removeArrow();
|
||||
}
|
||||
|
||||
private void removeArrow() {
|
||||
Platform.runLater(() -> {
|
||||
Node arrow = this.lookup(".arrow");
|
||||
if (arrow != null) {
|
||||
arrow.setVisible(false);
|
||||
arrow.setManaged(false);
|
||||
} else {
|
||||
removeArrow();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected Node getTitle(KeystoreImport importer) {
|
||||
HBox listItem = new HBox();
|
||||
listItem.setPadding(new Insets(10, 20, 10, 10));
|
||||
listItem.setSpacing(10);
|
||||
|
||||
HBox imageBox = new HBox();
|
||||
imageBox.setMinWidth(50);
|
||||
imageBox.setMinHeight(50);
|
||||
listItem.getChildren().add(imageBox);
|
||||
|
||||
Image image = new Image("image/" + importer.getWalletModel().getType() + ".png", 50, 50, true, true);
|
||||
if (!image.isError()) {
|
||||
ImageView imageView = new ImageView();
|
||||
imageView.setImage(image);
|
||||
imageBox.getChildren().add(imageView);
|
||||
}
|
||||
|
||||
VBox labelsBox = new VBox();
|
||||
labelsBox.setSpacing(5);
|
||||
labelsBox.setAlignment(Pos.CENTER_LEFT);
|
||||
this.mainLabel = new Label();
|
||||
mainLabel.setText(importer.getName());
|
||||
mainLabel.getStyleClass().add("main-label");
|
||||
labelsBox.getChildren().add(mainLabel);
|
||||
|
||||
HBox descriptionBox = new HBox();
|
||||
descriptionBox.setSpacing(7);
|
||||
labelsBox.getChildren().add(descriptionBox);
|
||||
|
||||
descriptionLabel = new Label("Keystore Import");
|
||||
descriptionLabel.getStyleClass().add("description-label");
|
||||
showHideLink = new Hyperlink("Show Details...");
|
||||
showHideLink.managedProperty().bind(showHideLink.visibleProperty());
|
||||
showHideLink.setOnAction(event -> {
|
||||
if(this.isExpanded()) {
|
||||
setExpanded(false);
|
||||
} else {
|
||||
setExpanded(true);
|
||||
}
|
||||
});
|
||||
this.expandedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(newValue) {
|
||||
showHideLink.setText(showHideLink.getText().replace("Show", "Hide"));
|
||||
} else {
|
||||
showHideLink.setText(showHideLink.getText().replace("Hide", "Show"));
|
||||
}
|
||||
});
|
||||
descriptionBox.getChildren().addAll(descriptionLabel, showHideLink);
|
||||
|
||||
listItem.getChildren().add(labelsBox);
|
||||
HBox.setHgrow(labelsBox, Priority.ALWAYS);
|
||||
|
||||
buttonBox = new HBox();
|
||||
buttonBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
listItem.getChildren().add(buttonBox);
|
||||
|
||||
this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> {
|
||||
//Hack to force listItem to expand to full available width less border
|
||||
listItem.setPrefWidth(newValue.getWidth() - 2);
|
||||
});
|
||||
|
||||
return listItem;
|
||||
}
|
||||
|
||||
protected void setDescription(String text) {
|
||||
descriptionLabel.getStyleClass().remove("description-error");
|
||||
descriptionLabel.getStyleClass().add("description-label");
|
||||
descriptionLabel.setText(text);
|
||||
}
|
||||
|
||||
protected void setError(String title, String detail) {
|
||||
descriptionLabel.getStyleClass().remove("description-label");
|
||||
descriptionLabel.getStyleClass().add("description-error");
|
||||
descriptionLabel.setText(title);
|
||||
setContent(getContentBox(detail));
|
||||
setExpanded(true);
|
||||
}
|
||||
|
||||
protected Node getContentBox(String message) {
|
||||
Label details = new Label(message);
|
||||
details.setWrapText(true);
|
||||
|
||||
HBox contentBox = new HBox();
|
||||
contentBox.setAlignment(Pos.TOP_LEFT);
|
||||
contentBox.getChildren().add(details);
|
||||
contentBox.setPadding(new Insets(10, 30, 10, 30));
|
||||
|
||||
double width = TextUtils.computeTextWidth(details.getFont(), message, 0.0D);
|
||||
double numLines = Math.max(1, width / 400);
|
||||
double height = Math.max(60, numLines * 40);
|
||||
contentBox.setPrefHeight(height);
|
||||
|
||||
return contentBox;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||
import com.sparrowwallet.drongo.wallet.Bip39Calculator;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||
import com.sparrowwallet.sparrow.io.*;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.util.Callback;
|
||||
import org.controlsfx.control.textfield.AutoCompletionBinding;
|
||||
import org.controlsfx.control.textfield.CustomPasswordField;
|
||||
import org.controlsfx.control.textfield.CustomTextField;
|
||||
import org.controlsfx.control.textfield.TextFields;
|
||||
import org.controlsfx.validation.ValidationResult;
|
||||
import org.controlsfx.validation.ValidationSupport;
|
||||
import org.controlsfx.validation.Validator;
|
||||
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class MnemonicKeystoreImportPane extends KeystoreImportPane {
|
||||
private final KeystoreMnemonicImport importer;
|
||||
|
||||
private SplitMenuButton enterMnemonicButton;
|
||||
private SplitMenuButton importButton;
|
||||
|
||||
private SimpleListProperty<String> wordEntriesProperty;
|
||||
private final SimpleStringProperty passphraseProperty = new SimpleStringProperty();
|
||||
|
||||
public MnemonicKeystoreImportPane(KeystoreImportAccordion importAccordion, Wallet wallet, KeystoreMnemonicImport importer) {
|
||||
super(importAccordion, wallet, importer);
|
||||
this.importer = importer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node getTitle(KeystoreImport importer) {
|
||||
Node title = super.getTitle(importer);
|
||||
setDescription("Keystore file import");
|
||||
|
||||
createEnterMnemonicButton();
|
||||
createImportButton();
|
||||
buttonBox.getChildren().addAll(enterMnemonicButton, importButton);
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
private void createEnterMnemonicButton() {
|
||||
enterMnemonicButton = new SplitMenuButton();
|
||||
enterMnemonicButton.setAlignment(Pos.CENTER_RIGHT);
|
||||
enterMnemonicButton.setText("Enter Mnemonic");
|
||||
enterMnemonicButton.setOnAction(event -> {
|
||||
enterMnemonic(24);
|
||||
});
|
||||
int[] numberWords = new int[] {24, 21, 18, 15, 12};
|
||||
for(int i = 0; i < numberWords.length; i++) {
|
||||
MenuItem item = new MenuItem(numberWords[i] + " words");
|
||||
final int words = numberWords[i];
|
||||
item.setOnAction(event -> {
|
||||
enterMnemonic(words);
|
||||
});
|
||||
enterMnemonicButton.getItems().add(item);
|
||||
}
|
||||
enterMnemonicButton.managedProperty().bind(enterMnemonicButton.visibleProperty());
|
||||
}
|
||||
|
||||
private void createImportButton() {
|
||||
importButton = new SplitMenuButton();
|
||||
importButton.setAlignment(Pos.CENTER_RIGHT);
|
||||
importButton.setText("Import Keystore");
|
||||
importButton.setOnAction(event -> {
|
||||
importButton.setDisable(true);
|
||||
importKeystore(wallet.getScriptType().getDefaultDerivation(), false);
|
||||
});
|
||||
String[] accounts = new String[] {"Default Account #0", "Account #1", "Account #2", "Account #3", "Account #4", "Account #5", "Account #6", "Account #7", "Account #8", "Account #9"};
|
||||
for(int i = 0; i < accounts.length; i++) {
|
||||
MenuItem item = new MenuItem(accounts[i]);
|
||||
final List<ChildNumber> derivation = wallet.getScriptType().getDefaultDerivation(i);
|
||||
item.setOnAction(event -> {
|
||||
importButton.setDisable(true);
|
||||
importKeystore(derivation, false);
|
||||
});
|
||||
importButton.getItems().add(item);
|
||||
}
|
||||
|
||||
|
||||
|
||||
importButton.managedProperty().bind(importButton.visibleProperty());
|
||||
importButton.setVisible(false);
|
||||
}
|
||||
|
||||
private void enterMnemonic(int numWords) {
|
||||
setDescription("Enter mnemonic word list");
|
||||
showHideLink.setVisible(false);
|
||||
setContent(getMnemonicWordsEntry(numWords));
|
||||
setExpanded(true);
|
||||
}
|
||||
|
||||
private Node getMnemonicWordsEntry(int numWords) {
|
||||
VBox vBox = new VBox();
|
||||
vBox.setSpacing(10);
|
||||
|
||||
TilePane tilePane = new TilePane();
|
||||
tilePane.setPrefRows(numWords/3);
|
||||
tilePane.setHgap(10);
|
||||
tilePane.setVgap(10);
|
||||
tilePane.setOrientation(Orientation.VERTICAL);
|
||||
|
||||
List<String> words = new ArrayList<>();
|
||||
for(int i = 0; i < numWords; i++) {
|
||||
words.add("");
|
||||
}
|
||||
|
||||
ObservableList<String> wordEntryList = FXCollections.observableArrayList(words);
|
||||
wordEntriesProperty = new SimpleListProperty<>(wordEntryList);
|
||||
for(int i = 0; i < numWords; i++) {
|
||||
WordEntry wordEntry = new WordEntry(i, wordEntryList);
|
||||
tilePane.getChildren().add(wordEntry);
|
||||
}
|
||||
|
||||
vBox.getChildren().add(tilePane);
|
||||
|
||||
AnchorPane anchorPane = new AnchorPane();
|
||||
anchorPane.setPadding(new Insets(0, 32, 0, 10));
|
||||
|
||||
PassphraseEntry passphraseEntry = new PassphraseEntry();
|
||||
AnchorPane.setLeftAnchor(passphraseEntry, 0.0);
|
||||
|
||||
Button okButton = new Button("Ok");
|
||||
okButton.setPrefWidth(70);
|
||||
okButton.setDisable(true);
|
||||
okButton.setOnAction(event -> {
|
||||
prepareImport();
|
||||
});
|
||||
|
||||
wordEntriesProperty.addListener((ListChangeListener<String>) c -> {
|
||||
for(String word : wordEntryList) {
|
||||
if(!WordEntry.isValid(word)) {
|
||||
okButton.setDisable(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
okButton.setDisable(false);
|
||||
});
|
||||
|
||||
AnchorPane.setRightAnchor(okButton, 0.0);
|
||||
|
||||
anchorPane.getChildren().addAll(passphraseEntry, okButton);
|
||||
vBox.getChildren().add(anchorPane);
|
||||
|
||||
return vBox;
|
||||
}
|
||||
|
||||
private void prepareImport() {
|
||||
if(importKeystore(wallet.getScriptType().getDefaultDerivation(), true)) {
|
||||
setExpanded(false);
|
||||
enterMnemonicButton.setVisible(false);
|
||||
importButton.setVisible(true);
|
||||
importButton.setDisable(false);
|
||||
setDescription("Ready to import");
|
||||
showHideLink.setText("Show Derivation...");
|
||||
showHideLink.setVisible(true);
|
||||
setContent(getDerivationEntry(wallet.getScriptType().getDefaultDerivation()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean importKeystore(List<ChildNumber> derivation, boolean dryrun) {
|
||||
importButton.setDisable(true);
|
||||
try {
|
||||
Keystore keystore = importer.getKeystore(derivation, wordEntriesProperty.get(), passphraseProperty.get());
|
||||
if(!dryrun) {
|
||||
EventManager.get().post(new KeystoreImportEvent(keystore));
|
||||
}
|
||||
return true;
|
||||
} catch (ImportException e) {
|
||||
String errorMessage = e.getMessage();
|
||||
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
||||
errorMessage = e.getCause().getMessage();
|
||||
}
|
||||
setError("Import Error", errorMessage);
|
||||
importButton.setDisable(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class WordEntry extends HBox {
|
||||
private static List<String> wordList;
|
||||
|
||||
public WordEntry(int wordNumber, ObservableList<String> wordEntryList) {
|
||||
super();
|
||||
setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
setSpacing(10);
|
||||
Label label = new Label((wordNumber+1) + ".");
|
||||
label.setPrefWidth(20);
|
||||
label.setAlignment(Pos.CENTER_RIGHT);
|
||||
TextField wordField = new TextField();
|
||||
wordField.setMaxWidth(100);
|
||||
|
||||
Bip39Calculator bip39Calculator = new Bip39Calculator();
|
||||
wordList = bip39Calculator.getWordList();
|
||||
TextFields.bindAutoCompletion(wordField, new WordlistSuggestionProvider(wordList));
|
||||
|
||||
ValidationSupport validationSupport = new ValidationSupport();
|
||||
validationSupport.registerValidator(wordField, Validator.combine(
|
||||
Validator.createEmptyValidator("Word is required"),
|
||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid word", !wordList.contains(newValue))
|
||||
));
|
||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||
|
||||
wordField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
wordEntryList.set(wordNumber, newValue);
|
||||
});
|
||||
|
||||
this.getChildren().addAll(label, wordField);
|
||||
}
|
||||
|
||||
public static boolean isValid(String word) {
|
||||
return wordList.contains(word);
|
||||
}
|
||||
}
|
||||
|
||||
private static class WordlistSuggestionProvider implements Callback<AutoCompletionBinding.ISuggestionRequest, Collection<String>> {
|
||||
private final List<String> wordList;
|
||||
|
||||
public WordlistSuggestionProvider(List<String> wordList) {
|
||||
this.wordList = wordList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> call(AutoCompletionBinding.ISuggestionRequest request) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
if(!request.getUserText().isEmpty()) {
|
||||
for(String word : wordList) {
|
||||
if(word.startsWith(request.getUserText())) {
|
||||
suggestions.add(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
}
|
||||
|
||||
private class PassphraseEntry extends HBox {
|
||||
public PassphraseEntry() {
|
||||
super();
|
||||
|
||||
setAlignment(Pos.CENTER_RIGHT);
|
||||
setSpacing(10);
|
||||
Label passphraseLabel = new Label("Passphrase:");
|
||||
CustomTextField passphraseField = (CustomTextField) TextFields.createClearableTextField();
|
||||
passphraseProperty.bind(passphraseField.textProperty());
|
||||
|
||||
getChildren().addAll(passphraseLabel, passphraseField);
|
||||
}
|
||||
}
|
||||
|
||||
private Node getDerivationEntry(List<ChildNumber> derivation) {
|
||||
TextField derivationField = new TextField();
|
||||
derivationField.setPromptText("Derivation path");
|
||||
derivationField.setText(KeyDerivation.writePath(derivation));
|
||||
HBox.setHgrow(derivationField, Priority.ALWAYS);
|
||||
|
||||
ValidationSupport validationSupport = new ValidationSupport();
|
||||
validationSupport.registerValidator(derivationField, Validator.combine(
|
||||
Validator.createEmptyValidator("Derivation is required"),
|
||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid derivation", !KeyDerivation.isValid(newValue))
|
||||
));
|
||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||
|
||||
Button importDerivationButton = new Button("Import");
|
||||
importDerivationButton.setOnAction(event -> {
|
||||
showHideLink.setVisible(true);
|
||||
setExpanded(false);
|
||||
List<ChildNumber> importDerivation = KeyDerivation.parsePath(derivationField.getText());
|
||||
importKeystore(importDerivation, false);
|
||||
});
|
||||
|
||||
derivationField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
importDerivationButton.setDisable(newValue.isEmpty() || !KeyDerivation.isValid(newValue));
|
||||
});
|
||||
|
||||
HBox contentBox = new HBox();
|
||||
contentBox.setAlignment(Pos.TOP_RIGHT);
|
||||
contentBox.setSpacing(20);
|
||||
contentBox.getChildren().add(derivationField);
|
||||
contentBox.getChildren().add(importDerivationButton);
|
||||
contentBox.setPadding(new Insets(10, 30, 10, 30));
|
||||
contentBox.setPrefHeight(60);
|
||||
|
||||
return contentBox;
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ public class Bip39 implements KeystoreMnemonicImport {
|
|||
|
||||
@Override
|
||||
public WalletModel getWalletModel() {
|
||||
return WalletModel.SPARROW;
|
||||
return WalletModel.SEED;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -25,8 +25,12 @@ public class Bip39 implements KeystoreMnemonicImport {
|
|||
|
||||
@Override
|
||||
public Keystore getKeystore(List<ChildNumber> derivation, List<String> mnemonicWords, String passphrase) throws ImportException {
|
||||
try {
|
||||
Bip39Calculator bip39Calculator = new Bip39Calculator();
|
||||
byte[] seed = bip39Calculator.getSeed(mnemonicWords, passphrase);
|
||||
return Keystore.fromSeed(seed, derivation);
|
||||
} catch (Exception e) {
|
||||
throw new ImportException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,8 +37,12 @@ public class KeystoreImportController implements Initializable {
|
|||
public void initializeView(Wallet wallet) {
|
||||
this.wallet = wallet;
|
||||
importMenu.selectedToggleProperty().addListener((observable, oldValue, selectedToggle) -> {
|
||||
if(selectedToggle == null) {
|
||||
oldValue.setSelected(true);
|
||||
return;
|
||||
}
|
||||
|
||||
KeystoreSource importType = (KeystoreSource) selectedToggle.getUserData();
|
||||
System.out.println(importType);
|
||||
String fxmlName = importType.toString().toLowerCase();
|
||||
if(importType == KeystoreSource.SW_SEED || importType == KeystoreSource.SW_WATCH) {
|
||||
fxmlName = "sw";
|
||||
|
|
|
@ -31,8 +31,8 @@ public class KeystoreImportDialog extends Dialog<Keystore> {
|
|||
|
||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||
dialogPane.getButtonTypes().addAll(cancelButtonType);
|
||||
dialogPane.setPrefWidth(620);
|
||||
dialogPane.setPrefHeight(500);
|
||||
dialogPane.setPrefWidth(650);
|
||||
dialogPane.setPrefHeight(600);
|
||||
|
||||
setResultConverter(dialogButton -> dialogButton != cancelButtonType ? keystore : null);
|
||||
} catch(IOException e) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.sparrowwallet.sparrow.keystoreimport;
|
||||
|
||||
import com.sparrowwallet.sparrow.control.KeystoreImportAccordion;
|
||||
import com.sparrowwallet.sparrow.io.Bip39;
|
||||
import com.sparrowwallet.sparrow.io.Electrum;
|
||||
import com.sparrowwallet.sparrow.io.KeystoreImport;
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -13,7 +14,7 @@ public class SwController extends KeystoreImportDetailController {
|
|||
private KeystoreImportAccordion importAccordion;
|
||||
|
||||
public void initializeView() {
|
||||
List<KeystoreImport> importers = List.of(new Electrum());
|
||||
List<KeystoreImport> importers = List.of(new Bip39(), new Electrum());
|
||||
importAccordion.setKeystoreImporters(getMasterController().getWallet(), FXCollections.observableList(importers));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,11 @@ public class WalletController extends WalletFormController implements Initializa
|
|||
|
||||
public void initializeView() {
|
||||
walletMenu.selectedToggleProperty().addListener((observable, oldValue, selectedToggle) -> {
|
||||
if(selectedToggle == null) {
|
||||
oldValue.setSelected(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Function function = (Function)selectedToggle.getUserData();
|
||||
|
||||
boolean existing = false;
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
}
|
||||
|
||||
.list-menu {
|
||||
-fx-pref-width: 130;
|
||||
-fx-pref-width: 160;
|
||||
-fx-background-color: #3da0e3;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
-fx-pref-width: 130;
|
||||
-fx-pref-width: 160;
|
||||
-fx-padding: 0 20 0 20;
|
||||
-fx-background-color: #3da0e3;
|
||||
}
|
||||
|
@ -36,6 +36,8 @@
|
|||
.titled-pane > .title {
|
||||
-fx-background-color: white;
|
||||
-fx-padding: 0;
|
||||
-fx-border-color: #e5e5e6;
|
||||
/*-fx-border-width: 1;*/
|
||||
}
|
||||
|
||||
.titled-pane > .title > .arrow-button {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3 KiB |
BIN
src/main/resources/image/seed.png
Normal file
BIN
src/main/resources/image/seed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
Loading…
Reference in a new issue