mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-23 20:36:44 +00:00
show seed words dialog, fix electrum import
This commit is contained in:
parent
ce60fe3dc6
commit
53478d9b22
9 changed files with 190 additions and 51 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit 04576bddff218f284e0cf925a1f790011203eec4
|
||||
Subproject commit eb07a7ffa3c46cda83ac951d3b1ebd529460554c
|
|
@ -2,13 +2,8 @@ package com.sparrowwallet.sparrow.control;
|
|||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
|
||||
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.FileImport;
|
||||
import com.sparrowwallet.sparrow.io.ImportException;
|
||||
import com.sparrowwallet.sparrow.io.KeystoreFileImport;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
|
@ -21,6 +16,8 @@ import javafx.stage.FileChooser;
|
|||
import javafx.stage.Stage;
|
||||
import org.controlsfx.control.textfield.CustomPasswordField;
|
||||
import org.controlsfx.control.textfield.TextFields;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
|
@ -28,6 +25,8 @@ import java.io.FileInputStream;
|
|||
import java.io.InputStream;
|
||||
|
||||
public abstract class FileImportPane extends TitledDescriptionPane {
|
||||
private static final Logger log = LoggerFactory.getLogger(FileImportPane.class);
|
||||
|
||||
private final FileImport importer;
|
||||
private Button importButton;
|
||||
private final SimpleStringProperty password = new SimpleStringProperty("");
|
||||
|
@ -77,6 +76,7 @@ public abstract class FileImportPane extends TitledDescriptionPane {
|
|||
importFile(file.getName(), inputStream, password);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Error importing file", e);
|
||||
String errorMessage = e.getMessage();
|
||||
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
||||
errorMessage = e.getCause().getMessage();
|
||||
|
|
|
@ -55,6 +55,16 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
|||
buttonBox.getChildren().add(importButton);
|
||||
}
|
||||
|
||||
public MnemonicKeystoreImportPane(Keystore keystore) {
|
||||
super(keystore.getSeed().getType().getName(), keystore.getSeed().needsPassphrase() ? "Passphrase enabled" : "Passphrase disabled", "", "image/" + WalletModel.SEED + ".png");
|
||||
this.wallet = null;
|
||||
this.importer = null;
|
||||
showHideLink.setVisible(false);
|
||||
buttonBox.getChildren().clear();
|
||||
|
||||
showWordList(keystore.getSeed());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Control createButton() {
|
||||
createEnterMnemonicButton();
|
||||
|
@ -108,11 +118,11 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
|||
generatedMnemonicCode = null;
|
||||
setDescription("Enter mnemonic word list");
|
||||
showHideLink.setVisible(false);
|
||||
setContent(getMnemonicWordsEntry(numWords));
|
||||
setContent(getMnemonicWordsEntry(numWords, false));
|
||||
setExpanded(true);
|
||||
}
|
||||
|
||||
private Node getMnemonicWordsEntry(int numWords) {
|
||||
private Node getMnemonicWordsEntry(int numWords, boolean displayWordsOnly) {
|
||||
VBox vBox = new VBox();
|
||||
vBox.setSpacing(10);
|
||||
|
||||
|
@ -136,52 +146,54 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
|||
|
||||
vBox.getChildren().add(wordsPane);
|
||||
|
||||
PassphraseEntry passphraseEntry = new PassphraseEntry();
|
||||
passphraseEntry.setPadding(new Insets(0, 32, 0, 10));
|
||||
vBox.getChildren().add(passphraseEntry);
|
||||
if(!displayWordsOnly) {
|
||||
PassphraseEntry passphraseEntry = new PassphraseEntry();
|
||||
passphraseEntry.setPadding(new Insets(0, 32, 0, 10));
|
||||
vBox.getChildren().add(passphraseEntry);
|
||||
|
||||
AnchorPane buttonPane = new AnchorPane();
|
||||
buttonPane.setPadding(new Insets(0, 32, 0, 10));
|
||||
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);
|
||||
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);
|
||||
confirmButton.setDefaultButton(true);
|
||||
buttonPane.getChildren().add(confirmButton);
|
||||
AnchorPane.setRightAnchor(confirmButton, 0.0);
|
||||
confirmButton = new Button("Confirm Backup");
|
||||
confirmButton.setOnAction(event -> {
|
||||
confirmBackup();
|
||||
});
|
||||
confirmButton.managedProperty().bind(confirmButton.visibleProperty());
|
||||
confirmButton.setVisible(false);
|
||||
confirmButton.setDefaultButton(true);
|
||||
buttonPane.getChildren().add(confirmButton);
|
||||
AnchorPane.setRightAnchor(confirmButton, 0.0);
|
||||
|
||||
verifyButton = new Button("Verify");
|
||||
verifyButton.setDisable(true);
|
||||
verifyButton.setDefaultButton(true);
|
||||
verifyButton.setOnAction(event -> {
|
||||
prepareImport();
|
||||
});
|
||||
verifyButton.managedProperty().bind(verifyButton.visibleProperty());
|
||||
verifyButton = new Button("Verify");
|
||||
verifyButton.setDisable(true);
|
||||
verifyButton.setDefaultButton(true);
|
||||
verifyButton.setOnAction(event -> {
|
||||
prepareImport();
|
||||
});
|
||||
verifyButton.managedProperty().bind(verifyButton.visibleProperty());
|
||||
|
||||
wordEntriesProperty.addListener((ListChangeListener<String>) c -> {
|
||||
for(String word : wordEntryList) {
|
||||
if(!WordEntry.isValid(word)) {
|
||||
verifyButton.setDisable(true);
|
||||
return;
|
||||
wordEntriesProperty.addListener((ListChangeListener<String>) c -> {
|
||||
for(String word : wordEntryList) {
|
||||
if(!WordEntry.isValid(word)) {
|
||||
verifyButton.setDisable(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verifyButton.setDisable(false);
|
||||
});
|
||||
buttonPane.getChildren().add(verifyButton);
|
||||
AnchorPane.setRightAnchor(verifyButton, 0.0);
|
||||
verifyButton.setDisable(false);
|
||||
});
|
||||
buttonPane.getChildren().add(verifyButton);
|
||||
AnchorPane.setRightAnchor(verifyButton, 0.0);
|
||||
|
||||
vBox.getChildren().add(buttonPane);
|
||||
vBox.getChildren().add(buttonPane);
|
||||
}
|
||||
|
||||
return vBox;
|
||||
}
|
||||
|
@ -212,7 +224,7 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
|||
private void confirmBackup() {
|
||||
setDescription("Confirm backup by re-entering words");
|
||||
showHideLink.setVisible(false);
|
||||
setContent(getMnemonicWordsEntry(wordEntriesProperty.get().size()));
|
||||
setContent(getMnemonicWordsEntry(wordEntriesProperty.get().size(), false));
|
||||
setExpanded(true);
|
||||
}
|
||||
|
||||
|
@ -368,4 +380,16 @@ public class MnemonicKeystoreImportPane extends TitledDescriptionPane {
|
|||
|
||||
return contentBox;
|
||||
}
|
||||
|
||||
private void showWordList(DeterministicSeed seed) {
|
||||
List<String> words = seed.getMnemonicCode();
|
||||
setContent(getMnemonicWordsEntry(words.size(), true));
|
||||
setExpanded(true);
|
||||
|
||||
for (int i = 0; i < wordsPane.getChildren().size(); i++) {
|
||||
WordEntry wordEntry = (WordEntry)wordsPane.getChildren().get(i);
|
||||
wordEntry.getEditor().setText(words.get(i));
|
||||
wordEntry.getEditor().setEditable(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.sparrow.AppController;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
public class SeedDisplayDialog extends Dialog<Void> {
|
||||
public SeedDisplayDialog(Keystore decryptedKeystore) {
|
||||
final DialogPane dialogPane = getDialogPane();
|
||||
dialogPane.getStylesheets().add(AppController.class.getResource("general.css").toExternalForm());
|
||||
|
||||
int lines = decryptedKeystore.getSeed().getMnemonicCode().size() / 3;
|
||||
int height = lines * 40;
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
dialogPane.setContent(stackPane);
|
||||
|
||||
AnchorPane anchorPane = new AnchorPane();
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
scrollPane.setPrefHeight(74 + height);
|
||||
scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
anchorPane.getChildren().add(scrollPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
AnchorPane.setLeftAnchor(scrollPane, 0.0);
|
||||
AnchorPane.setRightAnchor(scrollPane, 0.0);
|
||||
|
||||
Accordion keystoreAccordion = new Accordion();
|
||||
scrollPane.setContent(keystoreAccordion);
|
||||
|
||||
MnemonicKeystoreImportPane keystorePane = new MnemonicKeystoreImportPane(decryptedKeystore);
|
||||
keystoreAccordion.getPanes().add(keystorePane);
|
||||
|
||||
stackPane.getChildren().addAll(anchorPane);
|
||||
|
||||
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||
dialogPane.getButtonTypes().addAll(cancelButtonType);
|
||||
|
||||
dialogPane.setPrefWidth(500);
|
||||
dialogPane.setPrefHeight(150 + height);
|
||||
|
||||
Platform.runLater(() -> keystoreAccordion.setExpandedPane(keystorePane));
|
||||
}
|
||||
}
|
|
@ -163,6 +163,10 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
|||
}
|
||||
|
||||
private String decrypt(String encrypted, String password) {
|
||||
if(encrypted == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
KeyDeriver keyDeriver = new DoubleSha256KeyDeriver();
|
||||
Key key = keyDeriver.deriveKey(password);
|
||||
byte[] encryptedBytes = Base64.getDecoder().decode(encrypted);
|
||||
|
|
|
@ -15,6 +15,7 @@ import javafx.scene.control.DialogPane;
|
|||
import org.controlsfx.tools.Borders;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public class KeystoreImportDialog extends Dialog<Keystore> {
|
||||
private final KeystoreImportController keystoreImportController;
|
||||
|
@ -46,6 +47,10 @@ public class KeystoreImportDialog extends Dialog<Keystore> {
|
|||
}
|
||||
}
|
||||
|
||||
public static List<KeystoreSource> getSupportedSources() {
|
||||
return List.of(KeystoreSource.HW_USB, KeystoreSource.HW_AIRGAPPED, KeystoreSource.SW_SEED);
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void keystoreImported(KeystoreImportEvent event) {
|
||||
this.keystore = event.getKeystore();
|
||||
|
|
|
@ -752,7 +752,7 @@ public class HeadersController extends TransactionFormController implements Init
|
|||
|
||||
@Subscribe
|
||||
public void openWallets(OpenWalletsEvent event) {
|
||||
if(headersForm.getPsbt() != null) {
|
||||
if(headersForm.getPsbt() != null && headersForm.getBlockTransaction() == null) {
|
||||
List<Wallet> availableWallets = event.getWallets().stream().filter(wallet -> wallet.canSign(headersForm.getPsbt())).collect(Collectors.toList());
|
||||
Map<Wallet, Storage> availableWalletsMap = new LinkedHashMap<>(event.getWalletsMap());
|
||||
availableWalletsMap.keySet().retainAll(availableWallets);
|
||||
|
|
|
@ -3,10 +3,19 @@ package com.sparrowwallet.sparrow.wallet;
|
|||
import com.google.common.eventbus.Subscribe;
|
||||
import com.sparrowwallet.drongo.ExtendedKey;
|
||||
import com.sparrowwallet.drongo.KeyDerivation;
|
||||
import com.sparrowwallet.drongo.SecureString;
|
||||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.AppController;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.control.SeedDisplayDialog;
|
||||
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
||||
import com.sparrowwallet.sparrow.event.StorageEvent;
|
||||
import com.sparrowwallet.sparrow.event.TimedEvent;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.keystoreimport.KeystoreImportDialog;
|
||||
import com.sparrowwallet.sparrow.event.SettingsChangedEvent;
|
||||
import javafx.application.Platform;
|
||||
|
@ -14,7 +23,9 @@ import javafx.event.ActionEvent;
|
|||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
import org.controlsfx.validation.ValidationResult;
|
||||
import org.controlsfx.validation.ValidationSupport;
|
||||
import org.controlsfx.validation.Validator;
|
||||
|
@ -37,6 +48,9 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
@FXML
|
||||
private Label type;
|
||||
|
||||
@FXML
|
||||
private Button importButton;
|
||||
|
||||
@FXML
|
||||
private TextField label;
|
||||
|
||||
|
@ -156,6 +170,15 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
|
||||
private void updateType() {
|
||||
type.setText(getTypeLabel(keystore));
|
||||
if(keystore.getSource() == KeystoreSource.SW_SEED) {
|
||||
Glyph searchGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EYE);
|
||||
searchGlyph.setFontSize(12);
|
||||
type.setGraphic(searchGlyph);
|
||||
} else {
|
||||
type.setGraphic(null);
|
||||
}
|
||||
|
||||
importButton.setText(keystore.getSource() == KeystoreSource.SW_WATCH ? "Import..." : "Edit...");
|
||||
|
||||
boolean editable = (keystore.getSource() == KeystoreSource.SW_WATCH);
|
||||
fingerprint.setEditable(editable);
|
||||
|
@ -178,7 +201,12 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
}
|
||||
|
||||
public void importKeystore(ActionEvent event) {
|
||||
launchImportDialog(KeystoreSource.HW_USB);
|
||||
KeystoreSource initialSource = keystore.getSource();
|
||||
if(initialSource == null || !KeystoreImportDialog.getSupportedSources().contains(initialSource)) {
|
||||
initialSource = KeystoreImportDialog.getSupportedSources().get(0);
|
||||
}
|
||||
|
||||
launchImportDialog(initialSource);
|
||||
}
|
||||
|
||||
private void launchImportDialog(KeystoreSource initialSource) {
|
||||
|
@ -204,6 +232,37 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
}
|
||||
}
|
||||
|
||||
public void showSeed(MouseEvent event) {
|
||||
int keystoreIndex = getWalletForm().getWallet().getKeystores().indexOf(keystore);
|
||||
Wallet copy = getWalletForm().getWallet().copy();
|
||||
|
||||
if(copy.isEncrypted()) {
|
||||
WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD);
|
||||
Optional<SecureString> password = dlg.showAndWait();
|
||||
if(password.isPresent()) {
|
||||
Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(copy, password.get());
|
||||
decryptWalletService.setOnSucceeded(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(getWalletForm().getWalletFile(), TimedEvent.Action.END, "Done"));
|
||||
Wallet decryptedWallet = decryptWalletService.getValue();
|
||||
showSeed(decryptedWallet.getKeystores().get(keystoreIndex));
|
||||
});
|
||||
decryptWalletService.setOnFailed(workerStateEvent -> {
|
||||
EventManager.get().post(new StorageEvent(getWalletForm().getWalletFile(), TimedEvent.Action.END, "Failed"));
|
||||
AppController.showErrorDialog("Incorrect Password", decryptWalletService.getException().getMessage());
|
||||
});
|
||||
EventManager.get().post(new StorageEvent(getWalletForm().getWalletFile(), TimedEvent.Action.START, "Decrypting wallet..."));
|
||||
decryptWalletService.start();
|
||||
}
|
||||
} else {
|
||||
showSeed(keystore);
|
||||
}
|
||||
}
|
||||
|
||||
private void showSeed(Keystore keystore) {
|
||||
SeedDisplayDialog dlg = new SeedDisplayDialog(keystore);
|
||||
dlg.showAndWait();
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void update(SettingsChangedEvent event) {
|
||||
if(walletForm.getWallet().equals(event.getWallet()) && event.getType().equals(SettingsChangedEvent.Type.SCRIPT_TYPE)) {
|
||||
|
|
|
@ -20,9 +20,9 @@
|
|||
<Form fx:id="keystoreForm" GridPane.columnIndex="0" GridPane.rowIndex="0">
|
||||
<Fieldset inputGrow="SOMETIMES" text="">
|
||||
<Field text="Type:">
|
||||
<Label fx:id="type"/>
|
||||
<Label fx:id="type" contentDisplay="RIGHT" graphicTextGap="5" onMouseClicked="#showSeed"/>
|
||||
<Pane HBox.hgrow="ALWAYS" />
|
||||
<Button text="Import..." onAction="#importKeystore"/>
|
||||
<Button fx:id="importButton" text="Import..." onAction="#importKeystore"/>
|
||||
</Field>
|
||||
<Field text="Label:">
|
||||
<TextField fx:id="label" maxWidth="160"/>
|
||||
|
|
Loading…
Reference in a new issue