diff --git a/build.gradle b/build.gradle index 86ee1ff1..5761dc7c 100644 --- a/build.gradle +++ b/build.gradle @@ -115,7 +115,7 @@ run { "--add-opens=java.base/java.net=com.sparrowwallet.sparrow"] if(os.macOsX) { - applicationDefaultJvmArgs += ["-Xdock:name=Sparrow", "-Xdock:icon=/Users/scy/git/sparrow/src/main/resources/sparrow.png", + applicationDefaultJvmArgs += ["-Xdock:name=Sparrow", "-Xdock:icon=/Users/scy/git/sparrow/src/main/resources/sparrow-large.png", "--add-opens=javafx.graphics/com.sun.glass.ui.mac=centerdevice.nsmenufx"] } } diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index 434909a4..15c99599 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -334,7 +334,7 @@ public class AppServices { stage.setMinWidth(650); stage.setMinHeight(730); stage.setScene(scene); - stage.getIcons().add(new Image(MainApp.class.getResourceAsStream("/image/sparrow.png"))); + stage.getIcons().add(new Image(MainApp.class.getResourceAsStream("/image/sparrow-large.png"))); appController.initializeView(); stage.show(); @@ -453,7 +453,7 @@ public class AppServices { public static void setStageIcon(Window window) { Stage stage = (Stage)window; - stage.getIcons().add(new Image(AppServices.class.getResourceAsStream("/image/sparrow.png"))); + stage.getIcons().add(new Image(AppServices.class.getResourceAsStream("/image/sparrow-large.png"))); if(stage.getScene() != null && Config.get().getTheme() == Theme.DARK) { stage.getScene().getStylesheets().add(AppServices.class.getResource("darktheme.css").toExternalForm()); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java index d510d9e8..fd2cdf73 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java @@ -81,7 +81,10 @@ public class FileWalletExportPane extends TitledDescriptionPane { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Export " + exporter.getWalletModel().toDisplayString() + " File"); - fileChooser.setInitialFileName(wallet.getName() + "-" + exporter.getWalletModel().toDisplayString().toLowerCase() + "." + exporter.getExportFileExtension()); + String extension = exporter.getExportFileExtension(wallet); + fileChooser.setInitialFileName(wallet.getName() + "-" + + exporter.getWalletModel().toDisplayString().toLowerCase().replace(" ", "") + + (extension == null || extension.isEmpty() ? "" : "." + extension)); File file = fileChooser.showSaveDialog(window); if(file != null) { @@ -90,9 +93,8 @@ public class FileWalletExportPane extends TitledDescriptionPane { } private void exportWallet(File file) { - Wallet copy = wallet.copy(); - - if(copy.isEncrypted()) { + if(wallet.isEncrypted() && exporter.walletExportRequiresDecryption()) { + Wallet copy = wallet.copy(); WalletPasswordDialog dlg = new WalletPasswordDialog(wallet.getName(), WalletPasswordDialog.PasswordRequirement.LOAD); Optional password = dlg.showAndWait(); if(password.isPresent()) { @@ -101,7 +103,12 @@ public class FileWalletExportPane extends TitledDescriptionPane { decryptWalletService.setOnSucceeded(workerStateEvent -> { EventManager.get().post(new StorageEvent(walletFile, TimedEvent.Action.END, "Done")); Wallet decryptedWallet = decryptWalletService.getValue(); - exportWallet(file, decryptedWallet); + + try { + exportWallet(file, decryptedWallet); + } finally { + decryptedWallet.clearPrivate(); + } }); decryptWalletService.setOnFailed(workerStateEvent -> { EventManager.get().post(new StorageEvent(walletFile, TimedEvent.Action.END, "Failed")); @@ -111,19 +118,19 @@ public class FileWalletExportPane extends TitledDescriptionPane { decryptWalletService.start(); } } else { - exportWallet(file, copy); + exportWallet(file, wallet); } } - private void exportWallet(File file, Wallet decryptedWallet) { + private void exportWallet(File file, Wallet exportWallet) { try { if(file != null) { OutputStream outputStream = new FileOutputStream(file); - exporter.exportWallet(decryptedWallet, outputStream); - EventManager.get().post(new WalletExportEvent(decryptedWallet)); + exporter.exportWallet(exportWallet, outputStream); + EventManager.get().post(new WalletExportEvent(exportWallet)); } else { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - exporter.exportWallet(decryptedWallet, outputStream); + exporter.exportWallet(exportWallet, outputStream); QRDisplayDialog qrDisplayDialog; if(exporter instanceof CoboVaultMultisig) { qrDisplayDialog = new QRDisplayDialog(RegistryType.BYTES.toString(), outputStream.toByteArray(), true); @@ -138,8 +145,6 @@ public class FileWalletExportPane extends TitledDescriptionPane { errorMessage = e.getCause().getMessage(); } setError("Export Error", errorMessage); - } finally { - decryptedWallet.clearPrivate(); } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java index 14e98d4f..f53f98ef 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java @@ -41,9 +41,9 @@ public class WalletExportDialog extends Dialog { List exporters; if(wallet.getPolicyType() == PolicyType.SINGLE) { - exporters = List.of(new Electrum(), new SpecterDesktop()); + exporters = List.of(new Electrum(), new SpecterDesktop(), new Sparrow()); } else if(wallet.getPolicyType() == PolicyType.MULTI) { - exporters = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new SpecterDesktop(), new BlueWalletMultisig()); + exporters = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow()); } else { throw new UnsupportedOperationException("Cannot export wallet with policy type " + wallet.getPolicyType()); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/CoboVaultSinglesig.java b/src/main/java/com/sparrowwallet/sparrow/io/CoboVaultSinglesig.java index 156f5bb2..3862cf2b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/CoboVaultSinglesig.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/CoboVaultSinglesig.java @@ -7,6 +7,8 @@ import com.sparrowwallet.drongo.policy.Policy; import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.wallet.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.InputStream; @@ -14,6 +16,8 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; public class CoboVaultSinglesig implements KeystoreFileImport, WalletImport { + private static final Logger log = LoggerFactory.getLogger(CoboVaultSinglesig.class); + @Override public String getName() { return "Cobo Vault"; @@ -53,7 +57,8 @@ public class CoboVaultSinglesig implements KeystoreFileImport, WalletImport { return keystore; } catch (Exception e) { - throw new ImportException(e); + log.error("Error getting Cobo Vault keystore", e); + throw new ImportException("Error getting Cobo Vault keystore", e); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java b/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java index 9f027ce0..6d425b1a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ColdcardMultisig.java @@ -11,6 +11,8 @@ import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.KeystoreSource; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.WalletModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.*; import java.nio.charset.StandardCharsets; @@ -19,6 +21,8 @@ import java.util.List; import java.util.Set; public class ColdcardMultisig implements WalletImport, KeystoreFileImport, WalletExport { + private static final Logger log = LoggerFactory.getLogger(ColdcardMultisig.class); + @Override public String getName() { return "Coldcard Multisig"; @@ -81,7 +85,7 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle } @Override - public String getExportFileExtension() { + public String getExportFileExtension(Wallet wallet) { return "txt"; } @@ -146,7 +150,8 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle return wallet; } catch(Exception e) { - throw new ImportException(e); + log.error("Error importing Coldcard multisig wallet", e); + throw new ImportException("Error importing Coldcard multisig wallet", e); } } @@ -199,7 +204,8 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle writer.flush(); writer.close(); } catch(Exception e) { - throw new ExportException(e); + log.error("Error exporting Coldcard multisig wallet", e); + throw new ExportException("Error exporting Coldcard multisig wallet", e); } } @@ -227,4 +233,9 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle public boolean isWalletExportScannable() { return false; } + + @Override + public boolean walletExportRequiresDecryption() { + return false; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ColdcardSinglesig.java b/src/main/java/com/sparrowwallet/sparrow/io/ColdcardSinglesig.java index 32cf28e1..52aa34fd 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ColdcardSinglesig.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ColdcardSinglesig.java @@ -9,6 +9,8 @@ import com.sparrowwallet.drongo.policy.Policy; import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.wallet.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.InputStream; @@ -18,6 +20,8 @@ import java.nio.charset.StandardCharsets; import java.util.Map; public class ColdcardSinglesig implements KeystoreFileImport, WalletImport { + private static final Logger log = LoggerFactory.getLogger(ColdcardSinglesig.class); + @Override public String getName() { return "Coldcard"; @@ -82,7 +86,8 @@ public class ColdcardSinglesig implements KeystoreFileImport, WalletImport { } } } catch (Exception e) { - throw new ImportException(e); + log.error("Error getting Coldcard keystore", e); + throw new ImportException("Error getting Coldcard keystore", e); } throw new ImportException("Correct derivation not found for script type: " + scriptType); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java b/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java index caa36fa6..26df2aed 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java @@ -14,6 +14,8 @@ import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.Sha256Hash; import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.wallet.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.*; import java.lang.reflect.Type; @@ -22,6 +24,8 @@ import java.util.*; import java.util.zip.InflaterInputStream; public class Electrum implements KeystoreFileImport, WalletImport, WalletExport { + private static final Logger log = LoggerFactory.getLogger(Electrum.class); + @Override public String getName() { return "Electrum"; @@ -246,7 +250,8 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport return wallet; } catch (Exception e) { - throw new ImportException(e); + log.error("Error importing Electrum Wallet", e); + throw new ImportException("Error importing Electrum Wallet", e); } } @@ -273,7 +278,7 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport } @Override - public String getExportFileExtension() { + public String getExportFileExtension(Wallet wallet) { return "json"; } @@ -350,7 +355,8 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport outputStream.flush(); outputStream.close(); } catch (Exception e) { - throw new ExportException(e); + log.error("Error exporting Electrum Wallet", e); + throw new ExportException("Error exporting Electrum Wallet", e); } } @@ -379,6 +385,11 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport return "Export this wallet as an Electrum wallet file."; } + @Override + public boolean walletExportRequiresDecryption() { + return true; + } + private static class ElectrumJsonWallet { public Map keystores = new LinkedHashMap<>(); public String wallet_type; diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java b/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java new file mode 100644 index 00000000..ce7576ae --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java @@ -0,0 +1,62 @@ +package com.sparrowwallet.sparrow.io; + +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.WalletModel; +import com.sparrowwallet.sparrow.AppServices; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.OutputStream; +import java.nio.file.Files; + +public class Sparrow implements WalletExport { + private static final Logger log = LoggerFactory.getLogger(Sparrow.class); + + @Override + public String getName() { + return "Sparrow"; + } + + @Override + public WalletModel getWalletModel() { + return WalletModel.SPARROW; + } + + @Override + public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + try { + Storage storage = AppServices.get().getOpenWallets().get(wallet); + Files.copy(storage.getWalletFile().toPath(), outputStream); + outputStream.flush(); + outputStream.close(); + } catch(Exception e) { + log.error("Error exporting Sparrow wallet file", e); + throw new ExportException("Error exporting Sparrow wallet file", e); + } + } + + @Override + public String getWalletExportDescription() { + return "Exports a copy of your Sparrow wallet file, which can be loaded in another Sparrow instance running on any supported platform."; + } + + @Override + public String getExportFileExtension(Wallet wallet) { + Storage storage = AppServices.get().getOpenWallets().get(wallet); + if(storage != null && (storage.getEncryptionPubKey() == null || Storage.NO_PASSWORD_KEY.equals(storage.getEncryptionPubKey()))) { + return "json"; + } + + return ""; + } + + @Override + public boolean isWalletExportScannable() { + return false; + } + + @Override + public boolean walletExportRequiresDecryption() { + return false; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java b/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java index 637437e8..e1fe2f2d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java @@ -7,6 +7,8 @@ import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.KeystoreSource; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.WalletModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -15,6 +17,8 @@ import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; public class SpecterDIY implements KeystoreFileImport { + private static final Logger log = LoggerFactory.getLogger(SpecterDIY.class); + @Override public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException { try { @@ -34,7 +38,8 @@ public class SpecterDIY implements KeystoreFileImport { return keystore; } catch(IOException e) { - throw new ImportException(e); + log.error("Error getting Specter DIY keystore", e); + throw new ImportException("Error getting Specter DIY keystore", e); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/SpecterDesktop.java b/src/main/java/com/sparrowwallet/sparrow/io/SpecterDesktop.java index 48c95cc7..6d83ae55 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/SpecterDesktop.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/SpecterDesktop.java @@ -7,6 +7,8 @@ import com.sparrowwallet.drongo.wallet.BlockTransactionHash; import com.sparrowwallet.drongo.wallet.InvalidWalletException; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.WalletModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.InputStream; @@ -15,6 +17,8 @@ import java.io.OutputStream; import java.nio.charset.StandardCharsets; public class SpecterDesktop implements WalletImport, WalletExport { + private static final Logger log = LoggerFactory.getLogger(SpecterDesktop.class); + @Override public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { try { @@ -29,7 +33,8 @@ public class SpecterDesktop implements WalletImport, WalletExport { outputStream.flush(); outputStream.close(); } catch(Exception e) { - throw new ExportException(e); + log.error("Error exporting Specter Desktop wallet", e); + throw new ExportException("Error exporting Specter Desktop wallet", e); } } @@ -39,7 +44,7 @@ public class SpecterDesktop implements WalletImport, WalletExport { } @Override - public String getExportFileExtension() { + public String getExportFileExtension(Wallet wallet) { return "json"; } @@ -68,7 +73,8 @@ public class SpecterDesktop implements WalletImport, WalletExport { return wallet; } } catch(Exception e) { - throw new ImportException(e); + log.error("Error importing Specter Desktop wallet", e); + throw new ImportException("Error importing Specter Desktop wallet", e); } throw new ImportException("File was not a valid Specter Desktop wallet"); @@ -99,6 +105,11 @@ public class SpecterDesktop implements WalletImport, WalletExport { return WalletModel.SPECTER_DESKTOP; } + @Override + public boolean walletExportRequiresDecryption() { + return false; + } + public static class SpecterWallet { public String label; public Integer blockheight; diff --git a/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java b/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java index 75bf5009..254afd69 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java @@ -7,6 +7,7 @@ import java.io.OutputStream; public interface WalletExport extends Export { void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException; String getWalletExportDescription(); - String getExportFileExtension(); + String getExportFileExtension(Wallet wallet); boolean isWalletExportScannable(); + boolean walletExportRequiresDecryption(); } diff --git a/src/main/resources/image/sparrow-large.png b/src/main/resources/image/sparrow-large.png new file mode 100644 index 00000000..091dfd29 Binary files /dev/null and b/src/main/resources/image/sparrow-large.png differ diff --git a/src/main/resources/image/sparrow.png b/src/main/resources/image/sparrow.png index 091dfd29..c6b35b6f 100644 Binary files a/src/main/resources/image/sparrow.png and b/src/main/resources/image/sparrow.png differ diff --git a/src/main/resources/image/sparrow@2x.png b/src/main/resources/image/sparrow@2x.png new file mode 100644 index 00000000..6d12f842 Binary files /dev/null and b/src/main/resources/image/sparrow@2x.png differ diff --git a/src/main/resources/image/sparrow@3x.png b/src/main/resources/image/sparrow@3x.png new file mode 100644 index 00000000..0931786a Binary files /dev/null and b/src/main/resources/image/sparrow@3x.png differ