From f95cb67912a3cffe4f6d3159485d81e47e569d2b Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Mon, 24 Aug 2020 12:05:32 +0200 Subject: [PATCH] add coldcard single sig wallet import --- .../sparrowwallet/sparrow/AppController.java | 12 ++ .../sparrow/control/FileImportPane.java | 2 +- .../control/FileWalletKeystoreImportPane.java | 106 ++++++++++++++++++ .../sparrow/control/WalletImportDialog.java | 14 ++- 4 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index ca4d9347..bd7c978f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -770,6 +770,18 @@ public class AppController implements Initializable { if(optionalWallet.isPresent()) { Wallet wallet = optionalWallet.get(); File walletFile = Storage.getWalletFile(wallet.getName()); + + if(walletFile.exists()) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setTitle("Existing wallet found"); + alert.setHeaderText("Replace existing wallet?"); + alert.setContentText("Wallet file " + wallet.getName() + " already exists"); + Optional result = alert.showAndWait(); + if(result.isPresent() && result.get() == ButtonType.CANCEL) { + return; + } + } + Storage storage = new Storage(walletFile); Tab tab = addWalletTab(storage, wallet); tabs.getSelectionModel().select(tab); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java index 5796b651..d77a15d5 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java @@ -28,7 +28,7 @@ public abstract class FileImportPane extends TitledDescriptionPane { private static final Logger log = LoggerFactory.getLogger(FileImportPane.class); private final FileImport importer; - private Button importButton; + protected Button importButton; private final SimpleStringProperty password = new SimpleStringProperty(""); public FileImportPane(FileImport importer, String title, String description, String content, String imageUrl) { diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java new file mode 100644 index 00000000..e160cdf6 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java @@ -0,0 +1,106 @@ +package com.sparrowwallet.sparrow.control; + +import com.google.common.io.ByteStreams; +import com.google.gson.JsonParseException; +import com.sparrowwallet.drongo.policy.Policy; +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.WalletImportEvent; +import com.sparrowwallet.sparrow.io.ImportException; +import com.sparrowwallet.sparrow.io.KeystoreFileImport; +import javafx.collections.FXCollections; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class FileWalletKeystoreImportPane extends FileImportPane { + private static final Logger log = LoggerFactory.getLogger(FileWalletKeystoreImportPane.class); + + private final KeystoreFileImport importer; + private String fileName; + private byte[] fileBytes; + + public FileWalletKeystoreImportPane(KeystoreFileImport importer) { + super(importer, importer.getName(), "Wallet file import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png"); + this.importer = importer; + } + + protected void importFile(String fileName, InputStream inputStream, String password) throws ImportException { + this.fileName = fileName; + try { + fileBytes = ByteStreams.toByteArray(inputStream); + } catch(IOException e) { + throw new ImportException("Could not read file", e); + } + + setContent(getScriptTypeEntry()); + setExpanded(true); + importButton.setDisable(true); + } + + private void importWallet(ScriptType scriptType) throws ImportException { + ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes); + Keystore keystore = importer.getKeystore(scriptType, bais, ""); + + Wallet wallet = new Wallet(); + wallet.setName(fileName); + wallet.setPolicyType(PolicyType.SINGLE); + wallet.setScriptType(scriptType); + wallet.getKeystores().add(keystore); + wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, scriptType, wallet.getKeystores(), null)); + + EventManager.get().post(new WalletImportEvent(wallet)); + } + + private Node getScriptTypeEntry() { + Label label = new Label("Script Type:"); + ComboBox scriptTypeComboBox = new ComboBox<>(FXCollections.observableArrayList(ScriptType.getAddressableScriptTypes(PolicyType.SINGLE))); + scriptTypeComboBox.setValue(ScriptType.P2WPKH); + + Region region = new Region(); + HBox.setHgrow(region, Priority.SOMETIMES); + + Button importFileButton = new Button("Import"); + importFileButton.setOnAction(event -> { + showHideLink.setVisible(true); + setExpanded(false); + try { + importWallet(scriptTypeComboBox.getValue()); + } catch(ImportException e) { + log.error("Error importing file", e); + String errorMessage = e.getMessage(); + if(e.getCause() instanceof JsonParseException) { + errorMessage = "File was not in JSON format"; + } else if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) { + errorMessage = e.getCause().getMessage(); + } + setError("Import Error", errorMessage); + importButton.setDisable(false); + } + }); + + HBox contentBox = new HBox(); + contentBox.setAlignment(Pos.CENTER_RIGHT); + contentBox.setSpacing(20); + contentBox.getChildren().addAll(label, scriptTypeComboBox, region, importFileButton); + contentBox.setPadding(new Insets(10, 30, 10, 30)); + contentBox.setPrefHeight(60); + + return contentBox; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java index ee68ae6e..a2c7c18e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java @@ -29,16 +29,22 @@ public class WalletImportDialog extends Dialog { stackPane.getChildren().add(anchorPane); ScrollPane scrollPane = new ScrollPane(); - scrollPane.setPrefHeight(280); + scrollPane.setPrefHeight(320); scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); anchorPane.getChildren().add(scrollPane); scrollPane.setFitToWidth(true); AnchorPane.setLeftAnchor(scrollPane, 0.0); AnchorPane.setRightAnchor(scrollPane, 0.0); - List importers = List.of(new ColdcardMultisig(), new Electrum()); Accordion importAccordion = new Accordion(); - for(WalletImport importer : importers) { + List keystoreImporters = List.of(new ColdcardSinglesig()); + for(KeystoreFileImport importer : keystoreImporters) { + FileWalletKeystoreImportPane importPane = new FileWalletKeystoreImportPane(importer); + importAccordion.getPanes().add(importPane); + } + + List walletImporters = List.of(new ColdcardMultisig(), new Electrum()); + for(WalletImport importer : walletImporters) { FileWalletImportPane importPane = new FileWalletImportPane(importer); importAccordion.getPanes().add(importPane); } @@ -47,7 +53,7 @@ public class WalletImportDialog extends Dialog { final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); dialogPane.getButtonTypes().addAll(cancelButtonType); dialogPane.setPrefWidth(500); - dialogPane.setPrefHeight(360); + dialogPane.setPrefHeight(400); setResultConverter(dialogButton -> dialogButton != cancelButtonType ? wallet : null); }