diff --git a/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java index 5b47cb7e..3e61cfd8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java @@ -5,6 +5,7 @@ import com.sparrowwallet.drongo.crypto.ChildNumber; import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.KeystoreImportEvent; +import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.io.*; import javafx.beans.property.SimpleListProperty; import javafx.beans.property.SimpleStringProperty; @@ -21,6 +22,7 @@ import javafx.util.Callback; import org.controlsfx.control.textfield.AutoCompletionBinding; import org.controlsfx.control.textfield.CustomTextField; import org.controlsfx.control.textfield.TextFields; +import org.controlsfx.glyphfont.Glyph; import org.controlsfx.validation.ValidationResult; import org.controlsfx.validation.ValidationSupport; import org.controlsfx.validation.Validator; @@ -29,6 +31,7 @@ import org.controlsfx.validation.decoration.StyleClassValidationDecoration; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; public class MnemonicKeystoreImportPane extends TitledDescriptionPane { @@ -39,7 +42,10 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { private SplitMenuButton importButton; private TilePane wordsPane; - private Button verifyButton; + private Button generateButton; + private Label validLabel; + private Label invalidLabel; + private Button calculateButton; private Button backButton; private Button confirmButton; private List generatedMnemonicCode; @@ -155,14 +161,33 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { AnchorPane buttonPane = new AnchorPane(); buttonPane.setPadding(new Insets(0, 32, 0, 10)); - Button generateButton = new Button("Generate New"); + generateButton = new Button("Generate New"); generateButton.setOnAction(event -> { generateNew(); }); + generateButton.managedProperty().bind(generateButton.visibleProperty()); generateButton.setTooltip(new Tooltip("Generate a unique set of words that provide the seed for your wallet")); buttonPane.getChildren().add(generateButton); 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.setOnAction(event -> { confirmBackup(); @@ -174,14 +199,14 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { buttonPane.getChildren().add(confirmButton); AnchorPane.setRightAnchor(confirmButton, 0.0); - verifyButton = new Button("Verify"); - verifyButton.setDisable(true); - verifyButton.setDefaultButton(true); - verifyButton.setOnAction(event -> { + calculateButton = new Button("Calculate Seed"); + calculateButton.setDisable(true); + calculateButton.setDefaultButton(true); + calculateButton.setOnAction(event -> { prepareImport(); }); - verifyButton.managedProperty().bind(verifyButton.visibleProperty()); - verifyButton.setTooltip(new Tooltip("Enter the words you have just written down to confirm the backup is correct")); + calculateButton.managedProperty().bind(calculateButton.visibleProperty()); + calculateButton.setTooltip(new Tooltip("Calculate the seed from the provided word list")); backButton = new Button("Back"); backButton.setOnAction(event -> { @@ -192,19 +217,37 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { backButton.setVisible(false); wordEntriesProperty.addListener((ListChangeListener) c -> { + boolean empty = true; + boolean validWords = true; + boolean validChecksum = false; for(String word : wordEntryList) { + if(!word.isEmpty()) { + empty = false; + } + if(!WordEntry.isValid(word)) { - verifyButton.setDisable(true); - return; + validWords = false; } } - 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(); rightBox.setSpacing(10); - rightBox.getChildren().addAll(backButton, verifyButton); + rightBox.getChildren().addAll(backButton, calculateButton); buttonPane.getChildren().add(rightBox); AnchorPane.setRightAnchor(rightBox, 0.0); @@ -229,6 +272,10 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { setDescription("Write down word list to confirm backup"); showHideLink.setVisible(false); + calculateButton.setVisible(false); + confirmButton.setVisible(true); + backButton.setVisible(false); + if(generatedMnemonicCode.size() != wordsPane.getChildren().size()) { 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().setEditable(false); } - - verifyButton.setVisible(false); - confirmButton.setVisible(true); - backButton.setVisible(false); } private void confirmBackup() { @@ -250,6 +293,7 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { setContent(getMnemonicWordsEntry(wordEntriesProperty.get().size(), false)); setExpanded(true); backButton.setVisible(true); + generateButton.setVisible(false); } private void prepareImport() { @@ -259,7 +303,7 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { } if(importKeystore(wallet.getScriptType().getDefaultDerivation(), true)) { - setExpanded(false); + setExpanded(true); enterMnemonicButton.setVisible(false); importButton.setVisible(true); importButton.setDisable(false); @@ -307,7 +351,8 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { wordField.setMaxWidth(100); wordList = Bip39MnemonicCode.INSTANCE.getWordList(); - TextFields.bindAutoCompletion(wordField, new WordlistSuggestionProvider(wordList)); + AutoCompletionBinding autoCompletionBinding = TextFields.bindAutoCompletion(wordField, new WordlistSuggestionProvider(wordList)); + autoCompletionBinding.setDelay(50); ValidationSupport validationSupport = new ValidationSupport(); validationSupport.registerValidator(wordField, Validator.combine( @@ -344,6 +389,10 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { List suggestions = new ArrayList<>(); if(!request.getUserText().isEmpty()) { for(String word : wordList) { + if(word.equals(request.getUserText())) { + return Collections.emptyList(); + } + if(word.startsWith(request.getUserText())) { suggestions.add(word); } @@ -420,4 +469,18 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane { 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; + } } diff --git a/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css b/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css index 921d74ad..3cec5987 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css +++ b/src/main/resources/com/sparrowwallet/sparrow/keystoreimport/keystoreimport.css @@ -33,5 +33,13 @@ -fx-background-color: transparent; } +.valid-checksum { + -fx-text-fill: #50a14f; +} + +.invalid-checksum { + -fx-text-fill: rgb(202, 18, 67); +} +