bip39 keystore import

This commit is contained in:
Craig Raw 2020-05-08 14:37:19 +02:00
parent 910dfcdeb1
commit a409c28b20
17 changed files with 613 additions and 248 deletions

View file

@ -48,7 +48,7 @@ dependencies {
mainClassName = 'com.sparrowwallet.sparrow/com.sparrowwallet.sparrow.MainApp' mainClassName = 'com.sparrowwallet.sparrow/com.sparrowwallet.sparrow.MainApp'
run { 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 { jlink {
@ -63,7 +63,7 @@ jlink {
options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages', '--ignore-signing-information', '--exclude-files', '**.png'] options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages', '--ignore-signing-information', '--exclude-files', '**.png']
launcher { launcher {
name = 'sparrow' 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") addExtraDependencies("javafx")
jpackage { jpackage {

2
drongo

@ -1 +1 @@
Subproject commit d394c25a3c05c02d984b1f709623a311c2afb7a1 Subproject commit be0c4d1176da41671c2629e65f812fd28fab202b

View file

@ -41,9 +41,9 @@ public class MainApp extends Application {
wallet.setScriptType(ScriptType.P2WPKH); wallet.setScriptType(ScriptType.P2WPKH);
KeystoreImportDialog dlg = new KeystoreImportDialog(wallet); KeystoreImportDialog dlg = new KeystoreImportDialog(wallet);
//dlg.showAndWait(); dlg.showAndWait();
stage.show(); //stage.show();
} }
public static void main(String[] args) { public static void main(String[] args) {

View file

@ -128,7 +128,8 @@ public class DevicePane extends TitledPane {
listItem.getChildren().add(buttonBox); listItem.getChildren().add(buttonBox);
this.layoutBoundsProperty().addListener((observable, oldValue, newValue) -> { 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; return listItem;

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -16,12 +16,12 @@ public class KeystoreImportAccordion extends Accordion {
this.importers = importers; this.importers = importers;
for(KeystoreImport importer : importers) { for(KeystoreImport importer : importers) {
KeystoreFileImportPane importPane = null; KeystoreImportPane importPane = null;
if(importer instanceof KeystoreFileImport) { if(importer instanceof KeystoreFileImport) {
importPane = new KeystoreFileImportPane(this, wallet, (KeystoreFileImport)importer); importPane = new FileKeystoreImportPane(this, wallet, (KeystoreFileImport)importer);
} else if(importer instanceof KeystoreMnemonicImport) { } else if(importer instanceof KeystoreMnemonicImport) {
//TODO: Import from the new Bip39KeystoreImport importPane = new MnemonicKeystoreImportPane(this, wallet, (KeystoreMnemonicImport)importer);
} else { } else {
throw new IllegalArgumentException("Could not create ImportPane for importer of type " + importer.getClass()); throw new IllegalArgumentException("Could not create ImportPane for importer of type " + importer.getClass());
} }

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -15,7 +15,7 @@ public class Bip39 implements KeystoreMnemonicImport {
@Override @Override
public WalletModel getWalletModel() { public WalletModel getWalletModel() {
return WalletModel.SPARROW; return WalletModel.SEED;
} }
@Override @Override
@ -25,8 +25,12 @@ public class Bip39 implements KeystoreMnemonicImport {
@Override @Override
public Keystore getKeystore(List<ChildNumber> derivation, List<String> mnemonicWords, String passphrase) throws ImportException { public Keystore getKeystore(List<ChildNumber> derivation, List<String> mnemonicWords, String passphrase) throws ImportException {
try {
Bip39Calculator bip39Calculator = new Bip39Calculator(); Bip39Calculator bip39Calculator = new Bip39Calculator();
byte[] seed = bip39Calculator.getSeed(mnemonicWords, passphrase); byte[] seed = bip39Calculator.getSeed(mnemonicWords, passphrase);
return Keystore.fromSeed(seed, derivation); return Keystore.fromSeed(seed, derivation);
} catch (Exception e) {
throw new ImportException(e);
}
} }
} }

View file

@ -37,8 +37,12 @@ public class KeystoreImportController implements Initializable {
public void initializeView(Wallet wallet) { public void initializeView(Wallet wallet) {
this.wallet = wallet; this.wallet = wallet;
importMenu.selectedToggleProperty().addListener((observable, oldValue, selectedToggle) -> { importMenu.selectedToggleProperty().addListener((observable, oldValue, selectedToggle) -> {
if(selectedToggle == null) {
oldValue.setSelected(true);
return;
}
KeystoreSource importType = (KeystoreSource) selectedToggle.getUserData(); KeystoreSource importType = (KeystoreSource) selectedToggle.getUserData();
System.out.println(importType);
String fxmlName = importType.toString().toLowerCase(); String fxmlName = importType.toString().toLowerCase();
if(importType == KeystoreSource.SW_SEED || importType == KeystoreSource.SW_WATCH) { if(importType == KeystoreSource.SW_SEED || importType == KeystoreSource.SW_WATCH) {
fxmlName = "sw"; fxmlName = "sw";

View file

@ -31,8 +31,8 @@ public class KeystoreImportDialog extends Dialog<Keystore> {
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
dialogPane.getButtonTypes().addAll(cancelButtonType); dialogPane.getButtonTypes().addAll(cancelButtonType);
dialogPane.setPrefWidth(620); dialogPane.setPrefWidth(650);
dialogPane.setPrefHeight(500); dialogPane.setPrefHeight(600);
setResultConverter(dialogButton -> dialogButton != cancelButtonType ? keystore : null); setResultConverter(dialogButton -> dialogButton != cancelButtonType ? keystore : null);
} catch(IOException e) { } catch(IOException e) {

View file

@ -1,6 +1,7 @@
package com.sparrowwallet.sparrow.keystoreimport; package com.sparrowwallet.sparrow.keystoreimport;
import com.sparrowwallet.sparrow.control.KeystoreImportAccordion; import com.sparrowwallet.sparrow.control.KeystoreImportAccordion;
import com.sparrowwallet.sparrow.io.Bip39;
import com.sparrowwallet.sparrow.io.Electrum; import com.sparrowwallet.sparrow.io.Electrum;
import com.sparrowwallet.sparrow.io.KeystoreImport; import com.sparrowwallet.sparrow.io.KeystoreImport;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@ -13,7 +14,7 @@ public class SwController extends KeystoreImportDetailController {
private KeystoreImportAccordion importAccordion; private KeystoreImportAccordion importAccordion;
public void initializeView() { 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)); importAccordion.setKeystoreImporters(getMasterController().getWallet(), FXCollections.observableList(importers));
} }
} }

View file

@ -31,6 +31,11 @@ public class WalletController extends WalletFormController implements Initializa
public void initializeView() { public void initializeView() {
walletMenu.selectedToggleProperty().addListener((observable, oldValue, selectedToggle) -> { walletMenu.selectedToggleProperty().addListener((observable, oldValue, selectedToggle) -> {
if(selectedToggle == null) {
oldValue.setSelected(true);
return;
}
Function function = (Function)selectedToggle.getUserData(); Function function = (Function)selectedToggle.getUserData();
boolean existing = false; boolean existing = false;

View file

@ -3,12 +3,12 @@
} }
.list-menu { .list-menu {
-fx-pref-width: 130; -fx-pref-width: 160;
-fx-background-color: #3da0e3; -fx-background-color: #3da0e3;
} }
.list-item { .list-item {
-fx-pref-width: 130; -fx-pref-width: 160;
-fx-padding: 0 20 0 20; -fx-padding: 0 20 0 20;
-fx-background-color: #3da0e3; -fx-background-color: #3da0e3;
} }
@ -36,6 +36,8 @@
.titled-pane > .title { .titled-pane > .title {
-fx-background-color: white; -fx-background-color: white;
-fx-padding: 0; -fx-padding: 0;
-fx-border-color: #e5e5e6;
/*-fx-border-width: 1;*/
} }
.titled-pane > .title > .arrow-button { .titled-pane > .title > .arrow-button {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB