mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-24 12:46:45 +00:00
bip39 word list improvements #2
This commit is contained in:
parent
e878648587
commit
df15129f64
2 changed files with 89 additions and 18 deletions
|
@ -5,6 +5,7 @@ import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||||
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.io.*;
|
import com.sparrowwallet.sparrow.io.*;
|
||||||
import javafx.beans.property.SimpleListProperty;
|
import javafx.beans.property.SimpleListProperty;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
@ -21,6 +22,7 @@ import javafx.util.Callback;
|
||||||
import org.controlsfx.control.textfield.AutoCompletionBinding;
|
import org.controlsfx.control.textfield.AutoCompletionBinding;
|
||||||
import org.controlsfx.control.textfield.CustomTextField;
|
import org.controlsfx.control.textfield.CustomTextField;
|
||||||
import org.controlsfx.control.textfield.TextFields;
|
import org.controlsfx.control.textfield.TextFields;
|
||||||
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
import org.controlsfx.validation.ValidationResult;
|
import org.controlsfx.validation.ValidationResult;
|
||||||
import org.controlsfx.validation.ValidationSupport;
|
import org.controlsfx.validation.ValidationSupport;
|
||||||
import org.controlsfx.validation.Validator;
|
import org.controlsfx.validation.Validator;
|
||||||
|
@ -29,6 +31,7 @@ import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
|
@ -39,7 +42,10 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
private SplitMenuButton importButton;
|
private SplitMenuButton importButton;
|
||||||
|
|
||||||
private TilePane wordsPane;
|
private TilePane wordsPane;
|
||||||
private Button verifyButton;
|
private Button generateButton;
|
||||||
|
private Label validLabel;
|
||||||
|
private Label invalidLabel;
|
||||||
|
private Button calculateButton;
|
||||||
private Button backButton;
|
private Button backButton;
|
||||||
private Button confirmButton;
|
private Button confirmButton;
|
||||||
private List<String> generatedMnemonicCode;
|
private List<String> generatedMnemonicCode;
|
||||||
|
@ -155,14 +161,33 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
AnchorPane buttonPane = new AnchorPane();
|
AnchorPane buttonPane = new AnchorPane();
|
||||||
buttonPane.setPadding(new Insets(0, 32, 0, 10));
|
buttonPane.setPadding(new Insets(0, 32, 0, 10));
|
||||||
|
|
||||||
Button generateButton = new Button("Generate New");
|
generateButton = new Button("Generate New");
|
||||||
generateButton.setOnAction(event -> {
|
generateButton.setOnAction(event -> {
|
||||||
generateNew();
|
generateNew();
|
||||||
});
|
});
|
||||||
|
generateButton.managedProperty().bind(generateButton.visibleProperty());
|
||||||
generateButton.setTooltip(new Tooltip("Generate a unique set of words that provide the seed for your wallet"));
|
generateButton.setTooltip(new Tooltip("Generate a unique set of words that provide the seed for your wallet"));
|
||||||
buttonPane.getChildren().add(generateButton);
|
buttonPane.getChildren().add(generateButton);
|
||||||
AnchorPane.setLeftAnchor(generateButton, 0.0);
|
AnchorPane.setLeftAnchor(generateButton, 0.0);
|
||||||
|
|
||||||
|
validLabel = new Label("Valid checksum", getValidGlyph());
|
||||||
|
validLabel.setContentDisplay(ContentDisplay.LEFT);
|
||||||
|
validLabel.setGraphicTextGap(5.0);
|
||||||
|
validLabel.managedProperty().bind(validLabel.visibleProperty());
|
||||||
|
validLabel.setVisible(false);
|
||||||
|
buttonPane.getChildren().add(validLabel);
|
||||||
|
AnchorPane.setTopAnchor(validLabel, 5.0);
|
||||||
|
AnchorPane.setLeftAnchor(validLabel, 0.0);
|
||||||
|
|
||||||
|
invalidLabel = new Label("Invalid checksum", getInvalidGlyph());
|
||||||
|
invalidLabel.setContentDisplay(ContentDisplay.LEFT);
|
||||||
|
invalidLabel.setGraphicTextGap(5.0);
|
||||||
|
invalidLabel.managedProperty().bind(invalidLabel.visibleProperty());
|
||||||
|
invalidLabel.setVisible(false);
|
||||||
|
buttonPane.getChildren().add(invalidLabel);
|
||||||
|
AnchorPane.setTopAnchor(invalidLabel, 5.0);
|
||||||
|
AnchorPane.setLeftAnchor(invalidLabel, 0.0);
|
||||||
|
|
||||||
confirmButton = new Button("Confirm Written Backup");
|
confirmButton = new Button("Confirm Written Backup");
|
||||||
confirmButton.setOnAction(event -> {
|
confirmButton.setOnAction(event -> {
|
||||||
confirmBackup();
|
confirmBackup();
|
||||||
|
@ -174,14 +199,14 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
buttonPane.getChildren().add(confirmButton);
|
buttonPane.getChildren().add(confirmButton);
|
||||||
AnchorPane.setRightAnchor(confirmButton, 0.0);
|
AnchorPane.setRightAnchor(confirmButton, 0.0);
|
||||||
|
|
||||||
verifyButton = new Button("Verify");
|
calculateButton = new Button("Calculate Seed");
|
||||||
verifyButton.setDisable(true);
|
calculateButton.setDisable(true);
|
||||||
verifyButton.setDefaultButton(true);
|
calculateButton.setDefaultButton(true);
|
||||||
verifyButton.setOnAction(event -> {
|
calculateButton.setOnAction(event -> {
|
||||||
prepareImport();
|
prepareImport();
|
||||||
});
|
});
|
||||||
verifyButton.managedProperty().bind(verifyButton.visibleProperty());
|
calculateButton.managedProperty().bind(calculateButton.visibleProperty());
|
||||||
verifyButton.setTooltip(new Tooltip("Enter the words you have just written down to confirm the backup is correct"));
|
calculateButton.setTooltip(new Tooltip("Calculate the seed from the provided word list"));
|
||||||
|
|
||||||
backButton = new Button("Back");
|
backButton = new Button("Back");
|
||||||
backButton.setOnAction(event -> {
|
backButton.setOnAction(event -> {
|
||||||
|
@ -192,19 +217,37 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
backButton.setVisible(false);
|
backButton.setVisible(false);
|
||||||
|
|
||||||
wordEntriesProperty.addListener((ListChangeListener<String>) c -> {
|
wordEntriesProperty.addListener((ListChangeListener<String>) c -> {
|
||||||
|
boolean empty = true;
|
||||||
|
boolean validWords = true;
|
||||||
|
boolean validChecksum = false;
|
||||||
for(String word : wordEntryList) {
|
for(String word : wordEntryList) {
|
||||||
|
if(!word.isEmpty()) {
|
||||||
|
empty = false;
|
||||||
|
}
|
||||||
|
|
||||||
if(!WordEntry.isValid(word)) {
|
if(!WordEntry.isValid(word)) {
|
||||||
verifyButton.setDisable(true);
|
validWords = false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyButton.setDisable(false);
|
if(!empty && validWords) {
|
||||||
|
try {
|
||||||
|
importer.getKeystore(wallet.getScriptType().getDefaultDerivation(), wordEntriesProperty.get(), passphraseProperty.get());
|
||||||
|
validChecksum = true;
|
||||||
|
} catch(ImportException e) {
|
||||||
|
//ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateButton.setVisible(empty && generatedMnemonicCode == null);
|
||||||
|
calculateButton.setDisable(!validChecksum);
|
||||||
|
validLabel.setVisible(validChecksum);
|
||||||
|
invalidLabel.setVisible(!validChecksum && !empty);
|
||||||
});
|
});
|
||||||
|
|
||||||
HBox rightBox = new HBox();
|
HBox rightBox = new HBox();
|
||||||
rightBox.setSpacing(10);
|
rightBox.setSpacing(10);
|
||||||
rightBox.getChildren().addAll(backButton, verifyButton);
|
rightBox.getChildren().addAll(backButton, calculateButton);
|
||||||
|
|
||||||
buttonPane.getChildren().add(rightBox);
|
buttonPane.getChildren().add(rightBox);
|
||||||
AnchorPane.setRightAnchor(rightBox, 0.0);
|
AnchorPane.setRightAnchor(rightBox, 0.0);
|
||||||
|
@ -229,6 +272,10 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
setDescription("Write down word list to confirm backup");
|
setDescription("Write down word list to confirm backup");
|
||||||
showHideLink.setVisible(false);
|
showHideLink.setVisible(false);
|
||||||
|
|
||||||
|
calculateButton.setVisible(false);
|
||||||
|
confirmButton.setVisible(true);
|
||||||
|
backButton.setVisible(false);
|
||||||
|
|
||||||
if(generatedMnemonicCode.size() != wordsPane.getChildren().size()) {
|
if(generatedMnemonicCode.size() != wordsPane.getChildren().size()) {
|
||||||
throw new IllegalStateException("Generated mnemonic words list not same size as displayed words list");
|
throw new IllegalStateException("Generated mnemonic words list not same size as displayed words list");
|
||||||
}
|
}
|
||||||
|
@ -238,10 +285,6 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
wordEntry.getEditor().setText(generatedMnemonicCode.get(i));
|
wordEntry.getEditor().setText(generatedMnemonicCode.get(i));
|
||||||
wordEntry.getEditor().setEditable(false);
|
wordEntry.getEditor().setEditable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyButton.setVisible(false);
|
|
||||||
confirmButton.setVisible(true);
|
|
||||||
backButton.setVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmBackup() {
|
private void confirmBackup() {
|
||||||
|
@ -250,6 +293,7 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
setContent(getMnemonicWordsEntry(wordEntriesProperty.get().size(), false));
|
setContent(getMnemonicWordsEntry(wordEntriesProperty.get().size(), false));
|
||||||
setExpanded(true);
|
setExpanded(true);
|
||||||
backButton.setVisible(true);
|
backButton.setVisible(true);
|
||||||
|
generateButton.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareImport() {
|
private void prepareImport() {
|
||||||
|
@ -259,7 +303,7 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(importKeystore(wallet.getScriptType().getDefaultDerivation(), true)) {
|
if(importKeystore(wallet.getScriptType().getDefaultDerivation(), true)) {
|
||||||
setExpanded(false);
|
setExpanded(true);
|
||||||
enterMnemonicButton.setVisible(false);
|
enterMnemonicButton.setVisible(false);
|
||||||
importButton.setVisible(true);
|
importButton.setVisible(true);
|
||||||
importButton.setDisable(false);
|
importButton.setDisable(false);
|
||||||
|
@ -307,7 +351,8 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
wordField.setMaxWidth(100);
|
wordField.setMaxWidth(100);
|
||||||
|
|
||||||
wordList = Bip39MnemonicCode.INSTANCE.getWordList();
|
wordList = Bip39MnemonicCode.INSTANCE.getWordList();
|
||||||
TextFields.bindAutoCompletion(wordField, new WordlistSuggestionProvider(wordList));
|
AutoCompletionBinding<String> autoCompletionBinding = TextFields.bindAutoCompletion(wordField, new WordlistSuggestionProvider(wordList));
|
||||||
|
autoCompletionBinding.setDelay(50);
|
||||||
|
|
||||||
ValidationSupport validationSupport = new ValidationSupport();
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
validationSupport.registerValidator(wordField, Validator.combine(
|
validationSupport.registerValidator(wordField, Validator.combine(
|
||||||
|
@ -344,6 +389,10 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
List<String> suggestions = new ArrayList<>();
|
List<String> suggestions = new ArrayList<>();
|
||||||
if(!request.getUserText().isEmpty()) {
|
if(!request.getUserText().isEmpty()) {
|
||||||
for(String word : wordList) {
|
for(String word : wordList) {
|
||||||
|
if(word.equals(request.getUserText())) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
if(word.startsWith(request.getUserText())) {
|
if(word.startsWith(request.getUserText())) {
|
||||||
suggestions.add(word);
|
suggestions.add(word);
|
||||||
}
|
}
|
||||||
|
@ -420,4 +469,18 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
||||||
wordEntry.getEditor().setEditable(false);
|
wordEntry.getEditor().setEditable(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Glyph getValidGlyph() {
|
||||||
|
Glyph validGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.CHECK_CIRCLE);
|
||||||
|
validGlyph.getStyleClass().add("valid-checksum");
|
||||||
|
validGlyph.setFontSize(12);
|
||||||
|
return validGlyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Glyph getInvalidGlyph() {
|
||||||
|
Glyph invalidGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_CIRCLE);
|
||||||
|
invalidGlyph.getStyleClass().add("invalid-checksum");
|
||||||
|
invalidGlyph.setFontSize(12);
|
||||||
|
return invalidGlyph;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,5 +33,13 @@
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.valid-checksum {
|
||||||
|
-fx-text-fill: #50a14f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid-checksum {
|
||||||
|
-fx-text-fill: rgb(202, 18, 67);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue