diff --git a/drongo b/drongo index be0c4d11..0f008b89 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit be0c4d1176da41671c2629e65f812fd28fab202b +Subproject commit 0f008b8985253c4fd0025c136d0a23ac3082537f diff --git a/src/main/java/com/sparrowwallet/sparrow/MainApp.java b/src/main/java/com/sparrowwallet/sparrow/MainApp.java index 25be1815..8f01bec8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/MainApp.java +++ b/src/main/java/com/sparrowwallet/sparrow/MainApp.java @@ -40,10 +40,10 @@ public class MainApp extends Application { wallet.setPolicyType(PolicyType.SINGLE); wallet.setScriptType(ScriptType.P2WPKH); -// KeystoreImportDialog dlg = new KeystoreImportDialog(wallet); -// dlg.showAndWait(); + KeystoreImportDialog dlg = new KeystoreImportDialog(wallet); + dlg.showAndWait(); - stage.show(); +// stage.show(); } public static void main(String[] args) { diff --git a/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java index 7c2e37c0..95e307be 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/MnemonicKeystoreImportPane.java @@ -2,17 +2,15 @@ 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.Bip39MnemonicCode; +import com.sparrowwallet.drongo.wallet.DeterministicSeed; 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; @@ -24,7 +22,6 @@ 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; @@ -32,6 +29,7 @@ import org.controlsfx.validation.ValidationSupport; import org.controlsfx.validation.Validator; import org.controlsfx.validation.decoration.StyleClassValidationDecoration; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -42,6 +40,11 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane { private SplitMenuButton enterMnemonicButton; private SplitMenuButton importButton; + private TilePane wordsPane; + private Button verifyButton; + private Button confirmButton; + private List generatedMnemonicCode; + private SimpleListProperty wordEntriesProperty; private final SimpleStringProperty passphraseProperty = new SimpleStringProperty(); @@ -107,6 +110,7 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane { } private void enterMnemonic(int numWords) { + generatedMnemonicCode = null; setDescription("Enter mnemonic word list"); showHideLink.setVisible(false); setContent(getMnemonicWordsEntry(numWords)); @@ -117,11 +121,11 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane { 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); + wordsPane = new TilePane(); + wordsPane.setPrefRows(numWords/3); + wordsPane.setHgap(10); + wordsPane.setVgap(10); + wordsPane.setOrientation(Orientation.VERTICAL); List words = new ArrayList<>(); for(int i = 0; i < numWords; i++) { @@ -132,44 +136,95 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane { wordEntriesProperty = new SimpleListProperty<>(wordEntryList); for(int i = 0; i < numWords; i++) { WordEntry wordEntry = new WordEntry(i, wordEntryList); - tilePane.getChildren().add(wordEntry); + wordsPane.getChildren().add(wordEntry); } - vBox.getChildren().add(tilePane); - - AnchorPane anchorPane = new AnchorPane(); - anchorPane.setPadding(new Insets(0, 32, 0, 10)); + vBox.getChildren().add(wordsPane); PassphraseEntry passphraseEntry = new PassphraseEntry(); - AnchorPane.setLeftAnchor(passphraseEntry, 0.0); + passphraseEntry.setPadding(new Insets(0, 32, 0, 10)); + vBox.getChildren().add(passphraseEntry); - Button okButton = new Button("Ok"); - okButton.setPrefWidth(70); - okButton.setDisable(true); - okButton.setOnAction(event -> { + AnchorPane buttonPane = new AnchorPane(); + buttonPane.setPadding(new Insets(0, 32, 0, 10)); + + Button generateButton = new Button("Generate New"); + generateButton.setOnAction(event -> { + generateNew(); + }); + buttonPane.getChildren().add(generateButton); + AnchorPane.setLeftAnchor(generateButton, 0.0); + + confirmButton = new Button("Confirm Backup"); + confirmButton.setOnAction(event -> { + confirmBackup(); + }); + confirmButton.managedProperty().bind(confirmButton.visibleProperty()); + confirmButton.setVisible(false); + buttonPane.getChildren().add(confirmButton); + AnchorPane.setRightAnchor(confirmButton, 0.0); + + verifyButton = new Button("Verify"); + verifyButton.setDisable(true); + verifyButton.setOnAction(event -> { prepareImport(); }); + verifyButton.managedProperty().bind(verifyButton.visibleProperty()); wordEntriesProperty.addListener((ListChangeListener) c -> { for(String word : wordEntryList) { if(!WordEntry.isValid(word)) { - okButton.setDisable(true); + verifyButton.setDisable(true); return; } } - okButton.setDisable(false); + verifyButton.setDisable(false); }); + buttonPane.getChildren().add(verifyButton); + AnchorPane.setRightAnchor(verifyButton, 0.0); - AnchorPane.setRightAnchor(okButton, 0.0); - - anchorPane.getChildren().addAll(passphraseEntry, okButton); - vBox.getChildren().add(anchorPane); + vBox.getChildren().add(buttonPane); return vBox; } + private void generateNew() { + setDescription("Write down word list to confirm backup"); + showHideLink.setVisible(false); + + int mnemonicSeedLength = wordEntriesProperty.get().size() * 11; + int entropyLength = mnemonicSeedLength - (mnemonicSeedLength/33); + + DeterministicSeed deterministicSeed = new DeterministicSeed(new SecureRandom(), entropyLength, ""); + generatedMnemonicCode = deterministicSeed.getMnemonicCode(); + if(generatedMnemonicCode.size() != wordsPane.getChildren().size()) { + throw new IllegalStateException("Generated mnemonic words list not same size as displayed words list"); + } + + for (int i = 0; i < wordsPane.getChildren().size(); i++) { + WordEntry wordEntry = (WordEntry)wordsPane.getChildren().get(i); + wordEntry.getEditor().setText(generatedMnemonicCode.get(i)); + wordEntry.getEditor().setEditable(false); + } + + verifyButton.setVisible(false); + confirmButton.setVisible(true); + } + + private void confirmBackup() { + setDescription("Confirm backup by re-entering words"); + showHideLink.setVisible(false); + setContent(getMnemonicWordsEntry(wordEntriesProperty.get().size())); + setExpanded(true); + } + private void prepareImport() { + if(generatedMnemonicCode != null && !generatedMnemonicCode.equals(wordEntriesProperty.get())) { + setError("Import Error", "Confirmation words did not match generated mnemonic"); + return; + } + if(importKeystore(wallet.getScriptType().getDefaultDerivation(), true)) { setExpanded(false); enterMnemonicButton.setVisible(false); @@ -203,6 +258,7 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane { private static class WordEntry extends HBox { private static List wordList; + private final TextField wordField; public WordEntry(int wordNumber, ObservableList wordEntryList) { super(); @@ -212,11 +268,10 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane { Label label = new Label((wordNumber+1) + "."); label.setPrefWidth(20); label.setAlignment(Pos.CENTER_RIGHT); - TextField wordField = new TextField(); + wordField = new TextField(); wordField.setMaxWidth(100); - Bip39Calculator bip39Calculator = new Bip39Calculator(); - wordList = bip39Calculator.getWordList(); + wordList = Bip39MnemonicCode.INSTANCE.getWordList(); TextFields.bindAutoCompletion(wordField, new WordlistSuggestionProvider(wordList)); ValidationSupport validationSupport = new ValidationSupport(); @@ -233,6 +288,10 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane { this.getChildren().addAll(label, wordField); } + public TextField getEditor() { + return wordField; + } + public static boolean isValid(String word) { return wordList.contains(word); } @@ -264,11 +323,12 @@ public class MnemonicKeystoreImportPane extends KeystoreImportPane { public PassphraseEntry() { super(); - setAlignment(Pos.CENTER_RIGHT); + setAlignment(Pos.CENTER_LEFT); setSpacing(10); Label passphraseLabel = new Label("Passphrase:"); CustomTextField passphraseField = (CustomTextField) TextFields.createClearableTextField(); passphraseProperty.bind(passphraseField.textProperty()); + passphraseField.setPromptText("Leave blank for none"); getChildren().addAll(passphraseLabel, passphraseField); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Bip39.java b/src/main/java/com/sparrowwallet/sparrow/io/Bip39.java index 423567f2..85f9f944 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Bip39.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Bip39.java @@ -1,7 +1,7 @@ package com.sparrowwallet.sparrow.io; import com.sparrowwallet.drongo.crypto.ChildNumber; -import com.sparrowwallet.drongo.wallet.Bip39Calculator; +import com.sparrowwallet.drongo.wallet.Bip39MnemonicCode; import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.WalletModel; @@ -26,8 +26,8 @@ public class Bip39 implements KeystoreMnemonicImport { @Override public Keystore getKeystore(List derivation, List mnemonicWords, String passphrase) throws ImportException { try { - Bip39Calculator bip39Calculator = new Bip39Calculator(); - byte[] seed = bip39Calculator.getSeed(mnemonicWords, passphrase); + Bip39MnemonicCode.INSTANCE.check(mnemonicWords); + byte[] seed = Bip39MnemonicCode.toSeed(mnemonicWords, passphrase); return Keystore.fromSeed(seed, derivation); } catch (Exception e) { throw new ImportException(e);