mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-02 20:36:44 +00:00
recover slip39 shares to keystore seed and store as single slip39 share
This commit is contained in:
parent
33d23e9ea5
commit
041b5aa34c
10 changed files with 499 additions and 52 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit f066b5b608c4cff8873c776ef9c89e9194ccc332
|
Subproject commit ebcee47771bfb4b81dc19d0159a168d3bd8a824d
|
|
@ -1,5 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.control;
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.DeterministicSeed;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
import javafx.beans.property.SimpleListProperty;
|
import javafx.beans.property.SimpleListProperty;
|
||||||
|
@ -15,10 +16,13 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MnemonicKeystoreDisplayPane extends MnemonicKeystorePane {
|
public class MnemonicKeystoreDisplayPane extends MnemonicKeystorePane {
|
||||||
|
private final DeterministicSeed.Type type;
|
||||||
|
|
||||||
public MnemonicKeystoreDisplayPane(Keystore keystore) {
|
public MnemonicKeystoreDisplayPane(Keystore keystore) {
|
||||||
super(keystore.getSeed().getType().getName(), keystore.getSeed().needsPassphrase() && (keystore.getSeed().getPassphrase() == null || keystore.getSeed().getPassphrase().length() > 0) ? "Passphrase entered" : "No passphrase", "", "image/" + WalletModel.SEED.getType() + ".png");
|
super(keystore.getSeed().getType().getName(), keystore.getSeed().needsPassphrase() && (keystore.getSeed().getPassphrase() == null || keystore.getSeed().getPassphrase().length() > 0) ? "Passphrase entered" : "No passphrase", "", "image/" + WalletModel.SEED.getType() + ".png");
|
||||||
showHideLink.setVisible(false);
|
showHideLink.setVisible(false);
|
||||||
buttonBox.getChildren().clear();
|
buttonBox.getChildren().clear();
|
||||||
|
this.type = keystore.getSeed().getType();
|
||||||
|
|
||||||
showWordList(keystore.getSeed());
|
showWordList(keystore.getSeed());
|
||||||
}
|
}
|
||||||
|
@ -29,7 +33,7 @@ public class MnemonicKeystoreDisplayPane extends MnemonicKeystorePane {
|
||||||
vBox.setSpacing(10);
|
vBox.setSpacing(10);
|
||||||
|
|
||||||
wordsPane = new TilePane();
|
wordsPane = new TilePane();
|
||||||
wordsPane.setPrefRows(numWords / 3);
|
wordsPane.setPrefRows(Math.ceilDiv(numWords, 3));
|
||||||
wordsPane.setHgap(10);
|
wordsPane.setHgap(10);
|
||||||
wordsPane.setVgap(10);
|
wordsPane.setVgap(10);
|
||||||
wordsPane.setOrientation(Orientation.VERTICAL);
|
wordsPane.setOrientation(Orientation.VERTICAL);
|
||||||
|
@ -43,7 +47,7 @@ public class MnemonicKeystoreDisplayPane extends MnemonicKeystorePane {
|
||||||
wordEntriesProperty = new SimpleListProperty<>(wordEntryList);
|
wordEntriesProperty = new SimpleListProperty<>(wordEntryList);
|
||||||
List<WordEntry> wordEntries = new ArrayList<>(numWords);
|
List<WordEntry> wordEntries = new ArrayList<>(numWords);
|
||||||
for(int i = 0; i < numWords; i++) {
|
for(int i = 0; i < numWords; i++) {
|
||||||
wordEntries.add(new WordEntry(i, wordEntryList));
|
wordEntries.add(new WordEntry(i, wordEntryList, getWordlistProvider()));
|
||||||
}
|
}
|
||||||
for(int i = 0; i < numWords - 1; i++) {
|
for(int i = 0; i < numWords - 1; i++) {
|
||||||
wordEntries.get(i).setNextEntry(wordEntries.get(i + 1));
|
wordEntries.get(i).setNextEntry(wordEntries.get(i + 1));
|
||||||
|
@ -57,4 +61,9 @@ public class MnemonicKeystoreDisplayPane extends MnemonicKeystorePane {
|
||||||
stackPane.getChildren().add(vBox);
|
stackPane.getChildren().add(vBox);
|
||||||
return stackPane;
|
return stackPane;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WordlistProvider getWordlistProvider() {
|
||||||
|
return getWordListProvider(type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@ package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.wallet.Bip39MnemonicCode;
|
import com.sparrowwallet.drongo.wallet.Bip39MnemonicCode;
|
||||||
import com.sparrowwallet.drongo.wallet.DeterministicSeed;
|
import com.sparrowwallet.drongo.wallet.DeterministicSeed;
|
||||||
|
import com.sparrowwallet.drongo.wallet.MnemonicException;
|
||||||
|
import com.sparrowwallet.drongo.wallet.slip39.Slip39MnemonicCode;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.GlyphUtils;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.SimpleIntegerProperty;
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
@ -153,6 +155,10 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
|
|
||||||
protected void showWordList(DeterministicSeed seed) {
|
protected void showWordList(DeterministicSeed seed) {
|
||||||
List<String> words = seed.getMnemonicCode();
|
List<String> words = seed.getMnemonicCode();
|
||||||
|
showWordList(words);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void showWordList(List<String> words) {
|
||||||
setContent(getMnemonicWordsEntry(words.size(), true, true));
|
setContent(getMnemonicWordsEntry(words.size(), true, true));
|
||||||
setExpanded(true);
|
setExpanded(true);
|
||||||
|
|
||||||
|
@ -175,7 +181,7 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
vBox.setSpacing(10);
|
vBox.setSpacing(10);
|
||||||
|
|
||||||
wordsPane = new TilePane();
|
wordsPane = new TilePane();
|
||||||
wordsPane.setPrefRows(numWords/3);
|
wordsPane.setPrefRows(Math.ceilDiv(numWords, 3));
|
||||||
wordsPane.setHgap(10);
|
wordsPane.setHgap(10);
|
||||||
wordsPane.setVgap(10);
|
wordsPane.setVgap(10);
|
||||||
wordsPane.setOrientation(Orientation.VERTICAL);
|
wordsPane.setOrientation(Orientation.VERTICAL);
|
||||||
|
@ -189,7 +195,7 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
wordEntriesProperty = new SimpleListProperty<>(wordEntryList);
|
wordEntriesProperty = new SimpleListProperty<>(wordEntryList);
|
||||||
List<WordEntry> wordEntries = new ArrayList<>(numWords);
|
List<WordEntry> wordEntries = new ArrayList<>(numWords);
|
||||||
for(int i = 0; i < numWords; i++) {
|
for(int i = 0; i < numWords; i++) {
|
||||||
wordEntries.add(new WordEntry(i, wordEntryList));
|
wordEntries.add(new WordEntry(i, wordEntryList, getWordlistProvider()));
|
||||||
}
|
}
|
||||||
for(int i = 0; i < numWords - 1; i++) {
|
for(int i = 0; i < numWords - 1; i++) {
|
||||||
wordEntries.get(i).setNextEntry(wordEntries.get(i + 1));
|
wordEntries.get(i).setNextEntry(wordEntries.get(i + 1));
|
||||||
|
@ -215,7 +221,7 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
buttonPane.getChildren().add(leftBox);
|
buttonPane.getChildren().add(leftBox);
|
||||||
AnchorPane.setLeftAnchor(leftBox, 0.0);
|
AnchorPane.setLeftAnchor(leftBox, 0.0);
|
||||||
|
|
||||||
validLabel = new Label("Valid checksum", getValidGlyph());
|
validLabel = new Label("Valid checksum", GlyphUtils.getSuccessGlyph());
|
||||||
validLabel.setContentDisplay(ContentDisplay.LEFT);
|
validLabel.setContentDisplay(ContentDisplay.LEFT);
|
||||||
validLabel.setGraphicTextGap(5.0);
|
validLabel.setGraphicTextGap(5.0);
|
||||||
validLabel.managedProperty().bind(validLabel.visibleProperty());
|
validLabel.managedProperty().bind(validLabel.visibleProperty());
|
||||||
|
@ -224,7 +230,7 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
AnchorPane.setTopAnchor(validLabel, 5.0);
|
AnchorPane.setTopAnchor(validLabel, 5.0);
|
||||||
AnchorPane.setLeftAnchor(validLabel, 0.0);
|
AnchorPane.setLeftAnchor(validLabel, 0.0);
|
||||||
|
|
||||||
invalidLabel = new Label("Invalid checksum", getInvalidGlyph());
|
invalidLabel = new Label("Invalid checksum", GlyphUtils.getInvalidGlyph());
|
||||||
invalidLabel.setContentDisplay(ContentDisplay.LEFT);
|
invalidLabel.setContentDisplay(ContentDisplay.LEFT);
|
||||||
invalidLabel.setGraphicTextGap(5.0);
|
invalidLabel.setGraphicTextGap(5.0);
|
||||||
invalidLabel.managedProperty().bind(invalidLabel.visibleProperty());
|
invalidLabel.managedProperty().bind(invalidLabel.visibleProperty());
|
||||||
|
@ -242,7 +248,7 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
empty = false;
|
empty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!WordEntry.isValid(word)) {
|
if(!getWordlistProvider().isValid(word)) {
|
||||||
validWords = false;
|
validWords = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,13 +284,20 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
//nothing by default
|
//nothing by default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected WordlistProvider getWordlistProvider() {
|
||||||
|
return getWordListProvider(DeterministicSeed.Type.BIP39);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WordlistProvider getWordListProvider(DeterministicSeed.Type type) {
|
||||||
|
return type == DeterministicSeed.Type.SLIP39 ? new Slip39WordlistProvider() : new Bip39WordlistProvider();
|
||||||
|
}
|
||||||
|
|
||||||
protected static class WordEntry extends HBox {
|
protected static class WordEntry extends HBox {
|
||||||
private static List<String> wordList;
|
|
||||||
private final TextField wordField;
|
private final TextField wordField;
|
||||||
private WordEntry nextEntry;
|
private WordEntry nextEntry;
|
||||||
private TextField nextField;
|
private TextField nextField;
|
||||||
|
|
||||||
public WordEntry(int wordNumber, ObservableList<String> wordEntryList) {
|
public WordEntry(int wordNumber, ObservableList<String> wordEntryList, WordlistProvider wordlistProvider) {
|
||||||
super();
|
super();
|
||||||
setAlignment(Pos.CENTER_RIGHT);
|
setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
|
||||||
|
@ -302,7 +315,7 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
for(int i = 0; i < words.length; i++) {
|
for(int i = 0; i < words.length; i++) {
|
||||||
String word = words[i];
|
String word = words[i];
|
||||||
if(entry.nextField != null) {
|
if(entry.nextField != null) {
|
||||||
if(i == words.length - 2 && isValid(word)) {
|
if(i == words.length - 2 && wordlistProvider.isValid(word)) {
|
||||||
label.requestFocus();
|
label.requestFocus();
|
||||||
} else {
|
} else {
|
||||||
entry.nextField.requestFocus();
|
entry.nextField.requestFocus();
|
||||||
|
@ -335,8 +348,7 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
});
|
});
|
||||||
wordField.setTextFormatter(formatter);
|
wordField.setTextFormatter(formatter);
|
||||||
|
|
||||||
wordList = Bip39MnemonicCode.INSTANCE.getWordList();
|
AutoCompletionBinding<String> autoCompletionBinding = TextFields.bindAutoCompletion(wordField, new WordlistSuggestionProvider(wordlistProvider, wordNumber, wordEntryList));
|
||||||
AutoCompletionBinding<String> autoCompletionBinding = TextFields.bindAutoCompletion(wordField, new WordlistSuggestionProvider(wordList, wordNumber, wordEntryList));
|
|
||||||
autoCompletionBinding.setDelay(50);
|
autoCompletionBinding.setDelay(50);
|
||||||
autoCompletionBinding.setOnAutoCompleted(event -> {
|
autoCompletionBinding.setOnAutoCompleted(event -> {
|
||||||
if(nextField != null) {
|
if(nextField != null) {
|
||||||
|
@ -357,7 +369,7 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
ValidationSupport validationSupport = new ValidationSupport();
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
validationSupport.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
validationSupport.registerValidator(wordField, Validator.combine(
|
validationSupport.registerValidator(wordField, Validator.combine(
|
||||||
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid word", (newValue.length() > 0 || !lastWord) && !wordList.contains(newValue))
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid word", (newValue.length() > 0 || !lastWord) && !wordlistProvider.isValid(newValue))
|
||||||
));
|
));
|
||||||
|
|
||||||
wordField.textProperty().addListener((observable, oldValue, newValue) -> {
|
wordField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
@ -378,28 +390,24 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
public void setNextField(TextField field) {
|
public void setNextField(TextField field) {
|
||||||
this.nextField = field;
|
this.nextField = field;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValid(String word) {
|
|
||||||
return wordList.contains(word);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class WordlistSuggestionProvider implements Callback<AutoCompletionBinding.ISuggestionRequest, Collection<String>> {
|
protected static class WordlistSuggestionProvider implements Callback<AutoCompletionBinding.ISuggestionRequest, Collection<String>> {
|
||||||
private final List<String> wordList;
|
private final WordlistProvider wordlistProvider;
|
||||||
private final int wordNumber;
|
private final int wordNumber;
|
||||||
private final ObservableList<String> wordEntryList;
|
private final ObservableList<String> wordEntryList;
|
||||||
|
|
||||||
public WordlistSuggestionProvider(List<String> wordList, int wordNumber, ObservableList<String> wordEntryList) {
|
public WordlistSuggestionProvider(WordlistProvider wordlistProvider, int wordNumber, ObservableList<String> wordEntryList) {
|
||||||
this.wordList = wordList;
|
this.wordlistProvider = wordlistProvider;
|
||||||
this.wordNumber = wordNumber;
|
this.wordNumber = wordNumber;
|
||||||
this.wordEntryList = wordEntryList;
|
this.wordEntryList = wordEntryList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> call(AutoCompletionBinding.ISuggestionRequest request) {
|
public Collection<String> call(AutoCompletionBinding.ISuggestionRequest request) {
|
||||||
if(wordNumber == wordEntryList.size() - 1 && allPreviousWordsValid()) {
|
if(wordlistProvider.supportsPossibleLastWords() && wordNumber == wordEntryList.size() - 1 && allPreviousWordsValid()) {
|
||||||
try {
|
try {
|
||||||
List<String> possibleLastWords = Bip39MnemonicCode.INSTANCE.getPossibleLastWords(wordEntryList.subList(0, wordEntryList.size() - 1));
|
List<String> possibleLastWords = wordlistProvider.getPossibleLastWords(wordEntryList.subList(0, wordEntryList.size() - 1));
|
||||||
if(!request.getUserText().isEmpty()) {
|
if(!request.getUserText().isEmpty()) {
|
||||||
possibleLastWords.removeIf(s -> !s.startsWith(request.getUserText()));
|
possibleLastWords.removeIf(s -> !s.startsWith(request.getUserText()));
|
||||||
}
|
}
|
||||||
|
@ -412,7 +420,7 @@ public class MnemonicKeystorePane 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 : wordlistProvider.getWordlist()) {
|
||||||
if(word.startsWith(request.getUserText())) {
|
if(word.startsWith(request.getUserText())) {
|
||||||
suggestions.add(word);
|
suggestions.add(word);
|
||||||
}
|
}
|
||||||
|
@ -424,7 +432,7 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
|
|
||||||
private boolean allPreviousWordsValid() {
|
private boolean allPreviousWordsValid() {
|
||||||
for(int i = 0; i < wordEntryList.size() - 1; i++) {
|
for(int i = 0; i < wordEntryList.size() - 1; i++) {
|
||||||
if(!WordEntry.isValid(wordEntryList.get(i))) {
|
if(!wordlistProvider.isValid(wordEntryList.get(i))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -485,17 +493,53 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Glyph getValidGlyph() {
|
protected interface WordlistProvider {
|
||||||
Glyph validGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.CHECK_CIRCLE);
|
List<String> getWordlist();
|
||||||
validGlyph.getStyleClass().add("success");
|
boolean isValid(String word);
|
||||||
validGlyph.setFontSize(12);
|
boolean supportsPossibleLastWords();
|
||||||
return validGlyph;
|
List<String> getPossibleLastWords(List<String> previousWords) throws MnemonicException.MnemonicLengthException, MnemonicException.MnemonicWordException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Glyph getInvalidGlyph() {
|
private static class Bip39WordlistProvider implements WordlistProvider {
|
||||||
Glyph invalidGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_CIRCLE);
|
@Override
|
||||||
invalidGlyph.getStyleClass().add("failure");
|
public List<String> getWordlist() {
|
||||||
invalidGlyph.setFontSize(12);
|
return Bip39MnemonicCode.INSTANCE.getWordList();
|
||||||
return invalidGlyph;
|
}
|
||||||
|
|
||||||
|
public boolean isValid(String word) {
|
||||||
|
return getWordlist().contains(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsPossibleLastWords() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getPossibleLastWords(List<String> previousWords) throws MnemonicException.MnemonicLengthException, MnemonicException.MnemonicWordException {
|
||||||
|
return Bip39MnemonicCode.INSTANCE.getPossibleLastWords(previousWords);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Slip39WordlistProvider implements WordlistProvider {
|
||||||
|
@Override
|
||||||
|
public List<String> getWordlist() {
|
||||||
|
return Slip39MnemonicCode.INSTANCE.getWordList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String word) {
|
||||||
|
return getWordlist().contains(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsPossibleLastWords() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getPossibleLastWords(List<String> previousWords) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,313 @@
|
||||||
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.KeyDerivation;
|
||||||
|
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||||
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
|
import com.sparrowwallet.drongo.wallet.DeterministicSeed;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
|
import com.sparrowwallet.drongo.wallet.MnemonicException;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
|
import com.sparrowwallet.drongo.wallet.slip39.Share;
|
||||||
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
|
import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||||
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
import com.sparrowwallet.sparrow.glyphfont.GlyphUtils;
|
||||||
|
import com.sparrowwallet.sparrow.io.ImportException;
|
||||||
|
import com.sparrowwallet.sparrow.io.KeystoreMnemonicShareImport;
|
||||||
|
import com.sparrowwallet.sparrow.io.Slip39;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
|
import org.controlsfx.validation.ValidationResult;
|
||||||
|
import org.controlsfx.validation.ValidationSupport;
|
||||||
|
import org.controlsfx.validation.Validator;
|
||||||
|
import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class MnemonicShareKeystoreImportPane extends MnemonicKeystorePane {
|
||||||
|
protected final Wallet wallet;
|
||||||
|
private final KeystoreMnemonicShareImport importer;
|
||||||
|
private final KeyDerivation defaultDerivation;
|
||||||
|
private final List<List<String>> mnemonicShares = new ArrayList<>();
|
||||||
|
|
||||||
|
private SplitMenuButton importButton;
|
||||||
|
|
||||||
|
private Button calculateButton;
|
||||||
|
private Button backButton;
|
||||||
|
private Button nextButton;
|
||||||
|
private int currentShare;
|
||||||
|
|
||||||
|
public MnemonicShareKeystoreImportPane(Wallet wallet, KeystoreMnemonicShareImport importer, KeyDerivation defaultDerivation) {
|
||||||
|
super(importer.getName(), "Enter seed share", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png");
|
||||||
|
this.wallet = wallet;
|
||||||
|
this.importer = importer;
|
||||||
|
this.defaultDerivation = defaultDerivation;
|
||||||
|
|
||||||
|
createImportButton();
|
||||||
|
buttonBox.getChildren().add(importButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Control createButton() {
|
||||||
|
createEnterMnemonicButton();
|
||||||
|
return enterMnemonicButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createEnterMnemonicButton() {
|
||||||
|
enterMnemonicButton = new SplitMenuButton();
|
||||||
|
enterMnemonicButton.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
enterMnemonicButton.setText("Use 20 Words");
|
||||||
|
defaultWordSizeProperty = new SimpleIntegerProperty(20);
|
||||||
|
defaultWordSizeProperty.addListener((observable, oldValue, newValue) -> {
|
||||||
|
enterMnemonicButton.setText("Use " + newValue + " Words");
|
||||||
|
});
|
||||||
|
enterMnemonicButton.setOnAction(event -> {
|
||||||
|
resetShares();
|
||||||
|
enterMnemonic(defaultWordSizeProperty.get());
|
||||||
|
});
|
||||||
|
int[] numberWords = new int[] {20, 33};
|
||||||
|
for(int i = 0; i < numberWords.length; i++) {
|
||||||
|
MenuItem item = new MenuItem("Use " + numberWords[i] + " Words");
|
||||||
|
final int words = numberWords[i];
|
||||||
|
item.setOnAction(event -> {
|
||||||
|
resetShares();
|
||||||
|
defaultWordSizeProperty.set(words);
|
||||||
|
enterMnemonic(words);
|
||||||
|
});
|
||||||
|
enterMnemonicButton.getItems().add(item);
|
||||||
|
}
|
||||||
|
enterMnemonicButton.managedProperty().bind(enterMnemonicButton.visibleProperty());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Node> createRightButtons() {
|
||||||
|
calculateButton = new Button("Create Keystore");
|
||||||
|
calculateButton.setDefaultButton(true);
|
||||||
|
calculateButton.setOnAction(event -> {
|
||||||
|
prepareImport();
|
||||||
|
});
|
||||||
|
calculateButton.managedProperty().bind(calculateButton.visibleProperty());
|
||||||
|
calculateButton.setTooltip(new Tooltip("Create the keystore from the provided shares"));
|
||||||
|
calculateButton.setVisible(false);
|
||||||
|
|
||||||
|
backButton = new Button("Back");
|
||||||
|
backButton.setOnAction(event -> {
|
||||||
|
lastShare();
|
||||||
|
});
|
||||||
|
backButton.managedProperty().bind(backButton.visibleProperty());
|
||||||
|
backButton.setTooltip(new Tooltip("Display the last share added"));
|
||||||
|
backButton.setVisible(currentShare > 0);
|
||||||
|
|
||||||
|
nextButton = new Button("Next");
|
||||||
|
nextButton.setOnAction(event -> {
|
||||||
|
nextShare();
|
||||||
|
});
|
||||||
|
nextButton.managedProperty().bind(nextButton.visibleProperty());
|
||||||
|
nextButton.setTooltip(new Tooltip("Add the next share"));
|
||||||
|
nextButton.visibleProperty().bind(calculateButton.visibleProperty().not());
|
||||||
|
nextButton.setDefaultButton(true);
|
||||||
|
nextButton.setDisable(true);
|
||||||
|
|
||||||
|
return List.of(backButton, nextButton, calculateButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetShares() {
|
||||||
|
currentShare = 0;
|
||||||
|
mnemonicShares.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lastShare() {
|
||||||
|
currentShare--;
|
||||||
|
showWordList(mnemonicShares.get(currentShare));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nextShare() {
|
||||||
|
if(currentShare == mnemonicShares.size()) {
|
||||||
|
mnemonicShares.add(wordEntriesProperty.get());
|
||||||
|
} else {
|
||||||
|
mnemonicShares.set(currentShare, wordEntriesProperty.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
currentShare++;
|
||||||
|
|
||||||
|
if(currentShare < mnemonicShares.size()) {
|
||||||
|
showWordList(mnemonicShares.get(currentShare));
|
||||||
|
} else {
|
||||||
|
setContent(getMnemonicWordsEntry(defaultWordSizeProperty.get(), true, true));
|
||||||
|
}
|
||||||
|
setExpanded(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onWordChange(boolean empty, boolean validWords, boolean validChecksum) {
|
||||||
|
boolean validSet = false;
|
||||||
|
boolean complete = false;
|
||||||
|
if(!empty && validWords) {
|
||||||
|
try {
|
||||||
|
Share.fromMnemonic(String.join(" ", wordEntriesProperty.get()));
|
||||||
|
validChecksum = true;
|
||||||
|
|
||||||
|
List<List<String>> existing = new ArrayList<>(mnemonicShares);
|
||||||
|
if(currentShare >= mnemonicShares.size()) {
|
||||||
|
existing.add(wordEntriesProperty.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
importer.getKeystore(wallet.getScriptType().getDefaultDerivation(), existing, passphraseProperty.get());
|
||||||
|
validSet = true;
|
||||||
|
complete = true;
|
||||||
|
} catch(MnemonicException e) {
|
||||||
|
invalidLabel.setText(e.getTitle());
|
||||||
|
invalidLabel.setTooltip(new Tooltip(e.getMessage()));
|
||||||
|
} catch(Slip39.Slip39ProgressException e) {
|
||||||
|
validSet = true;
|
||||||
|
invalidLabel.setText(e.getTitle());
|
||||||
|
invalidLabel.setTooltip(new Tooltip(e.getMessage()));
|
||||||
|
} catch(ImportException e) {
|
||||||
|
if(e.getCause() instanceof MnemonicException mnemonicException) {
|
||||||
|
invalidLabel.setText(mnemonicException.getTitle());
|
||||||
|
invalidLabel.setTooltip(new Tooltip(mnemonicException.getMessage()));
|
||||||
|
} else {
|
||||||
|
invalidLabel.setText("Import Error");
|
||||||
|
invalidLabel.setTooltip(new Tooltip(e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateButton.setVisible(complete);
|
||||||
|
backButton.setVisible(currentShare > 0 && !complete);
|
||||||
|
nextButton.setDisable(!validChecksum || !validSet);
|
||||||
|
validLabel.setVisible(complete);
|
||||||
|
validLabel.setText(mnemonicShares.isEmpty() ? "Valid checksum" : "Completed share set");
|
||||||
|
invalidLabel.setVisible(!complete && !empty);
|
||||||
|
invalidLabel.setGraphic(validChecksum && validSet ? getIncompleteGlyph() : GlyphUtils.getFailureGlyph());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createImportButton() {
|
||||||
|
importButton = new SplitMenuButton();
|
||||||
|
importButton.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
importButton.setText("Import Keystore");
|
||||||
|
importButton.getStyleClass().add("default-button");
|
||||||
|
importButton.setOnAction(event -> {
|
||||||
|
importButton.setDisable(true);
|
||||||
|
importKeystore(getDefaultDerivation(), false);
|
||||||
|
});
|
||||||
|
String[] accounts = new String[] {"Import Default Account #0", "Import Account #1", "Import Account #2", "Import Account #3", "Import Account #4", "Import Account #5", "Import Account #6", "Import Account #7", "Import Account #8", "Import Account #9"};
|
||||||
|
int scriptAccountsLength = ScriptType.P2SH.equals(wallet.getScriptType()) ? 1 : accounts.length;
|
||||||
|
for(int i = 0; i < scriptAccountsLength; 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 List<ChildNumber> getDefaultDerivation() {
|
||||||
|
return defaultDerivation == null || defaultDerivation.getDerivation().isEmpty() ? wallet.getScriptType().getDefaultDerivation() : defaultDerivation.getDerivation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareImport() {
|
||||||
|
nextShare();
|
||||||
|
backButton.setVisible(false);
|
||||||
|
|
||||||
|
if(importKeystore(wallet.getScriptType().getDefaultDerivation(), true)) {
|
||||||
|
setExpanded(true);
|
||||||
|
enterMnemonicButton.setVisible(false);
|
||||||
|
importButton.setVisible(true);
|
||||||
|
importButton.setDisable(false);
|
||||||
|
setDescription("Ready to import");
|
||||||
|
showHideLink.setText("Show Derivation...");
|
||||||
|
showHideLink.setVisible(false);
|
||||||
|
setContent(getDerivationEntry(getDefaultDerivation()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean importKeystore(List<ChildNumber> derivation, boolean dryrun) {
|
||||||
|
importButton.setDisable(true);
|
||||||
|
try {
|
||||||
|
Keystore keystore = importer.getKeystore(derivation, mnemonicShares, passphraseProperty.get());
|
||||||
|
if(!dryrun) {
|
||||||
|
if(passphraseProperty.get() != null && !passphraseProperty.get().isEmpty()) {
|
||||||
|
KeystorePassphraseDialog keystorePassphraseDialog = new KeystorePassphraseDialog(null, keystore, true);
|
||||||
|
keystorePassphraseDialog.initOwner(this.getScene().getWindow());
|
||||||
|
Optional<String> optPassphrase = keystorePassphraseDialog.showAndWait();
|
||||||
|
if(optPassphrase.isEmpty() || !optPassphrase.get().equals(passphraseProperty.get())) {
|
||||||
|
throw new ImportException("Re-entered passphrase did not match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventManager.get().post(new KeystoreImportEvent(keystore));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (ImportException e) {
|
||||||
|
String errorMessage = e.getMessage();
|
||||||
|
if(e.getCause() instanceof MnemonicException.MnemonicChecksumException) {
|
||||||
|
errorMessage = "Invalid word list - checksum incorrect";
|
||||||
|
} else 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 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.setValidationDecorator(new StyleClassValidationDecoration());
|
||||||
|
validationSupport.registerValidator(derivationField, Validator.combine(
|
||||||
|
Validator.createEmptyValidator("Derivation is required"),
|
||||||
|
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Invalid derivation", !KeyDerivation.isValid(newValue))
|
||||||
|
));
|
||||||
|
|
||||||
|
Button importDerivationButton = new Button("Import Custom Derivation Keystore");
|
||||||
|
importDerivationButton.setDisable(true);
|
||||||
|
importDerivationButton.setOnAction(event -> {
|
||||||
|
showHideLink.setVisible(true);
|
||||||
|
setExpanded(false);
|
||||||
|
List<ChildNumber> importDerivation = KeyDerivation.parsePath(derivationField.getText());
|
||||||
|
importKeystore(importDerivation, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
derivationField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
importButton.setDisable(newValue.isEmpty() || !KeyDerivation.isValid(newValue) || !KeyDerivation.parsePath(newValue).equals(derivation));
|
||||||
|
importDerivationButton.setDisable(newValue.isEmpty() || !KeyDerivation.isValid(newValue) || KeyDerivation.parsePath(newValue).equals(derivation));
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Glyph getIncompleteGlyph() {
|
||||||
|
Glyph warningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.PLUS_CIRCLE);
|
||||||
|
warningGlyph.getStyleClass().add("warn-icon");
|
||||||
|
warningGlyph.setFontSize(12);
|
||||||
|
return warningGlyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WordlistProvider getWordlistProvider() {
|
||||||
|
return getWordListProvider(DeterministicSeed.Type.SLIP39);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.control;
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.wallet.DeterministicSeed;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.SeedQR;
|
import com.sparrowwallet.drongo.wallet.SeedQR;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
|
@ -17,7 +18,7 @@ public class SeedDisplayDialog extends Dialog<Void> {
|
||||||
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
|
||||||
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
AppServices.setStageIcon(dialogPane.getScene().getWindow());
|
||||||
|
|
||||||
int lines = decryptedKeystore.getSeed().getMnemonicCode().size() / 3;
|
int lines = Math.ceilDiv(decryptedKeystore.getSeed().getMnemonicCode().size(), 3);
|
||||||
int height = lines * 40;
|
int height = lines * 40;
|
||||||
|
|
||||||
StackPane stackPane = new StackPane();
|
StackPane stackPane = new StackPane();
|
||||||
|
@ -43,15 +44,19 @@ public class SeedDisplayDialog extends Dialog<Void> {
|
||||||
|
|
||||||
stackPane.getChildren().addAll(anchorPane);
|
stackPane.getChildren().addAll(anchorPane);
|
||||||
|
|
||||||
final ButtonType seedQRButtonType = new javafx.scene.control.ButtonType("Show SeedQR", ButtonBar.ButtonData.LEFT);
|
if(decryptedKeystore.getSeed().getType() == DeterministicSeed.Type.BIP39) {
|
||||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
final ButtonType seedQRButtonType = new javafx.scene.control.ButtonType("Show SeedQR", ButtonBar.ButtonData.LEFT);
|
||||||
dialogPane.getButtonTypes().addAll(seedQRButtonType, cancelButtonType);
|
dialogPane.getButtonTypes().add(seedQRButtonType);
|
||||||
|
|
||||||
Button seedQRButton = (Button)dialogPane.lookupButton(seedQRButtonType);
|
Button seedQRButton = (Button)dialogPane.lookupButton(seedQRButtonType);
|
||||||
seedQRButton.addEventFilter(ActionEvent.ACTION, event -> {
|
seedQRButton.addEventFilter(ActionEvent.ACTION, event -> {
|
||||||
event.consume();
|
event.consume();
|
||||||
showSeedQR(decryptedKeystore);
|
showSeedQR(decryptedKeystore);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||||
|
dialogPane.getButtonTypes().add(cancelButtonType);
|
||||||
|
|
||||||
dialogPane.setPrefWidth(500);
|
dialogPane.setPrefWidth(500);
|
||||||
dialogPane.setPrefHeight(150 + height);
|
dialogPane.setPrefHeight(150 + height);
|
||||||
|
|
|
@ -183,6 +183,13 @@ public class GlyphUtils {
|
||||||
return successGlyph;
|
return successGlyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Glyph getInvalidGlyph() {
|
||||||
|
Glyph invalidGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_CIRCLE);
|
||||||
|
invalidGlyph.getStyleClass().add("failure");
|
||||||
|
invalidGlyph.setFontSize(12);
|
||||||
|
return invalidGlyph;
|
||||||
|
}
|
||||||
|
|
||||||
public static Glyph getWarningGlyph() {
|
public static Glyph getWarningGlyph() {
|
||||||
Glyph warningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_TRIANGLE);
|
Glyph warningGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_TRIANGLE);
|
||||||
warningGlyph.getStyleClass().add("warn-icon");
|
warningGlyph.getStyleClass().add("warn-icon");
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||||
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface KeystoreMnemonicShareImport extends KeystoreImport {
|
||||||
|
Keystore getKeystore(List<ChildNumber> derivation, List<List<String>> mnemonicShares, String passphrase) throws ImportException;
|
||||||
|
}
|
60
src/main/java/com/sparrowwallet/sparrow/io/Slip39.java
Normal file
60
src/main/java/com/sparrowwallet/sparrow/io/Slip39.java
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package com.sparrowwallet.sparrow.io;
|
||||||
|
|
||||||
|
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||||
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
|
import com.sparrowwallet.drongo.wallet.slip39.RecoveryState;
|
||||||
|
import com.sparrowwallet.drongo.wallet.slip39.Share;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Slip39 implements KeystoreMnemonicShareImport {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Mnemonic Shares (SLIP39)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WalletModel getWalletModel() {
|
||||||
|
return WalletModel.SEED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKeystoreImportDescription() {
|
||||||
|
return "Import your 20 or 33 word mnemonic shares and optional passphrase.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Keystore getKeystore(List<ChildNumber> derivation, List<List<String>> mnemonicShares, String passphrase) throws ImportException {
|
||||||
|
try {
|
||||||
|
RecoveryState recoveryState = new RecoveryState();
|
||||||
|
for(List<String> mnemonicWords : mnemonicShares) {
|
||||||
|
Share share = Share.fromMnemonic(String.join(" ", mnemonicWords));
|
||||||
|
recoveryState.addShare(share);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(recoveryState.isComplete()) {
|
||||||
|
byte[] secret = recoveryState.recover(passphrase.getBytes(StandardCharsets.UTF_8));
|
||||||
|
DeterministicSeed seed = new DeterministicSeed(secret, passphrase, System.currentTimeMillis(), DeterministicSeed.Type.SLIP39);
|
||||||
|
return Keystore.fromSeed(seed, derivation);
|
||||||
|
} else {
|
||||||
|
throw new Slip39ProgressException(recoveryState.getShortStatus(), recoveryState.getStatus());
|
||||||
|
}
|
||||||
|
} catch(MnemonicException e) {
|
||||||
|
throw new ImportException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Slip39ProgressException extends ImportException {
|
||||||
|
private final String title;
|
||||||
|
|
||||||
|
public Slip39ProgressException(String title, String message) {
|
||||||
|
super(message);
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,7 +63,7 @@ public interface KeystoreDao {
|
||||||
long id = insertSeed(seed.getType().ordinal(), null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), seed.needsPassphrase(), seed.getCreationTimeSeconds());
|
long id = insertSeed(seed.getType().ordinal(), null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), seed.needsPassphrase(), seed.getCreationTimeSeconds());
|
||||||
seed.setId(id);
|
seed.setId(id);
|
||||||
} else {
|
} else {
|
||||||
long id = insertSeed(seed.getType().ordinal(), seed.getMnemonicString().asString(), null, null, null, null, null, seed.needsPassphrase(), seed.getCreationTimeSeconds());
|
long id = insertSeed(seed.getType().ordinal(), seed.getMnemonicString(true).asString(), null, null, null, null, null, seed.needsPassphrase(), seed.getCreationTimeSeconds());
|
||||||
seed.setId(id);
|
seed.setId(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ public interface KeystoreDao {
|
||||||
EncryptedData data = seed.getEncryptedData();
|
EncryptedData data = seed.getEncryptedData();
|
||||||
updateSeed(seed.getType().ordinal(), null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), seed.needsPassphrase(), seed.getCreationTimeSeconds(), seed.getId());
|
updateSeed(seed.getType().ordinal(), null, data.getInitialisationVector(), data.getEncryptedBytes(), data.getKeySalt(), data.getEncryptionType().getDeriver().ordinal(), data.getEncryptionType().getCrypter().ordinal(), seed.needsPassphrase(), seed.getCreationTimeSeconds(), seed.getId());
|
||||||
} else {
|
} else {
|
||||||
updateSeed(seed.getType().ordinal(), seed.getMnemonicString().asString(), null, null, null, null, null, seed.needsPassphrase(), seed.getCreationTimeSeconds(), seed.getId());
|
updateSeed(seed.getType().ordinal(), seed.getMnemonicString(true).asString(), null, null, null, null, null, seed.needsPassphrase(), seed.getCreationTimeSeconds(), seed.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package com.sparrowwallet.sparrow.keystoreimport;
|
package com.sparrowwallet.sparrow.keystoreimport;
|
||||||
|
|
||||||
import com.sparrowwallet.sparrow.control.FileKeystoreImportPane;
|
import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.control.MnemonicKeystoreImportPane;
|
|
||||||
import com.sparrowwallet.sparrow.control.TitledDescriptionPane;
|
|
||||||
import com.sparrowwallet.sparrow.control.XprvKeystoreImportPane;
|
|
||||||
import com.sparrowwallet.sparrow.io.*;
|
import com.sparrowwallet.sparrow.io.*;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Accordion;
|
import javafx.scene.control.Accordion;
|
||||||
|
@ -15,7 +12,7 @@ public class SwController extends KeystoreImportDetailController {
|
||||||
private Accordion importAccordion;
|
private Accordion importAccordion;
|
||||||
|
|
||||||
public void initializeView() {
|
public void initializeView() {
|
||||||
List<KeystoreImport> importers = List.of(new Bip39(), new Electrum(), new Bip32());
|
List<KeystoreImport> importers = List.of(new Bip39(), new Bip32(), new Slip39());
|
||||||
|
|
||||||
for(KeystoreImport importer : importers) {
|
for(KeystoreImport importer : importers) {
|
||||||
if(importer.isDeprecated() && !Config.get().isShowDeprecatedImportExport()) {
|
if(importer.isDeprecated() && !Config.get().isShowDeprecatedImportExport()) {
|
||||||
|
@ -30,6 +27,8 @@ public class SwController extends KeystoreImportDetailController {
|
||||||
importPane = new MnemonicKeystoreImportPane(getMasterController().getWallet(), (KeystoreMnemonicImport)importer, getMasterController().getDefaultDerivation());
|
importPane = new MnemonicKeystoreImportPane(getMasterController().getWallet(), (KeystoreMnemonicImport)importer, getMasterController().getDefaultDerivation());
|
||||||
} else if(importer instanceof KeystoreXprvImport) {
|
} else if(importer instanceof KeystoreXprvImport) {
|
||||||
importPane = new XprvKeystoreImportPane(getMasterController().getWallet(), (KeystoreXprvImport)importer, getMasterController().getDefaultDerivation());
|
importPane = new XprvKeystoreImportPane(getMasterController().getWallet(), (KeystoreXprvImport)importer, getMasterController().getDefaultDerivation());
|
||||||
|
} else if(importer instanceof KeystoreMnemonicShareImport) {
|
||||||
|
importPane = new MnemonicShareKeystoreImportPane(getMasterController().getWallet(), (KeystoreMnemonicShareImport)importer, getMasterController().getDefaultDerivation());
|
||||||
} 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());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue