From 2debc0737544c7d1977a96640784e635c879b42d Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 14 Oct 2021 14:27:13 +0200 Subject: [PATCH] import and export a wallet as an output descriptor in a text file --- .../sparrowwallet/sparrow/AppController.java | 1 + .../sparrow/control/WalletExportDialog.java | 4 +- .../sparrow/control/WalletImportDialog.java | 2 +- .../sparrowwallet/sparrow/io/Descriptor.java | 80 ++++++++++++++++++ src/main/resources/image/bitcoincore.png | Bin 0 -> 3316 bytes src/main/resources/image/bitcoincore@2x.png | Bin 0 -> 5516 bytes src/main/resources/image/bitcoincore@3x.png | Bin 0 -> 10467 bytes 7 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java create mode 100644 src/main/resources/image/bitcoincore.png create mode 100644 src/main/resources/image/bitcoincore@2x.png create mode 100644 src/main/resources/image/bitcoincore@3x.png diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 9c207841..15ae9c28 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -978,6 +978,7 @@ public class AppController implements Initializable { List walletImporters = List.of(new ColdcardSinglesig(), new ColdcardMultisig(), new Electrum(), new SpecterDesktop(), + new Descriptor(), new CoboVaultSinglesig(), new CoboVaultMultisig(), new PassportSinglesig(), new KeystoneSinglesig(), new KeystoneMultisig(), diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java index b4c588a9..ac1908e1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java @@ -42,9 +42,9 @@ public class WalletExportDialog extends Dialog { List exporters; if(wallet.getPolicyType() == PolicyType.SINGLE) { - exporters = List.of(new Electrum(), new SpecterDesktop(), new Sparrow()); + exporters = List.of(new Electrum(), new Descriptor(), new SpecterDesktop(), new Sparrow()); } else if(wallet.getPolicyType() == PolicyType.MULTI) { - exporters = List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new PassportMultisig(), new SpecterDesktop(), new BlueWalletMultisig(), new SpecterDIY(), new Sparrow()); + exporters = List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new Descriptor(), new PassportMultisig(), new SpecterDesktop(), new BlueWalletMultisig(), new SpecterDIY(), new Sparrow()); } else { throw new UnsupportedOperationException("Cannot export wallet with policy type " + wallet.getPolicyType()); } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java index 3b91c4c0..65c9a733 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java @@ -54,7 +54,7 @@ public class WalletImportDialog extends Dialog { importAccordion.getPanes().add(importPane); } - List walletImporters = List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow()); + List walletImporters = List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new Descriptor(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow()); for(WalletImport importer : walletImporters) { FileWalletImportPane importPane = new FileWalletImportPane(importer); importAccordion.getPanes().add(importPane); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java b/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java new file mode 100644 index 00000000..5e2976c9 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java @@ -0,0 +1,80 @@ +package com.sparrowwallet.sparrow.io; + +import com.sparrowwallet.drongo.OutputDescriptor; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.drongo.wallet.WalletModel; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + +public class Descriptor implements WalletImport, WalletExport { + + @Override + public String getName() { + return "Output Descriptor"; + } + + @Override + public WalletModel getWalletModel() { + return WalletModel.BITCOIN_CORE; + } + + @Override + public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + try { + OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(wallet); + String outputDescriptorString = outputDescriptor.toString(true); + outputStream.write(outputDescriptorString.getBytes(StandardCharsets.UTF_8)); + outputStream.flush(); + } catch(Exception e) { + throw new ExportException("Error writing output descriptor", e); + } + } + + @Override + public String getWalletExportDescription() { + return "The output descriptor is a standardized description of the wallet compatible with Bitcoin Core, and can be used to create a watch-only copy using the Edit button on the Settings tab of a new Sparrow wallet."; + } + + @Override + public String getExportFileExtension(Wallet wallet) { + return "txt"; + } + + @Override + public boolean isWalletExportScannable() { + return false; + } + + @Override + public boolean walletExportRequiresDecryption() { + return false; + } + + @Override + public boolean isEncrypted(File file) { + return false; + } + + @Override + public String getWalletImportDescription() { + return "Import a file containing the output descriptor of a wallet. The output descriptor is a standardized description of the wallet compatible with Bitcoin Core."; + } + + @Override + public Wallet importWallet(InputStream inputStream, String password) throws ImportException { + try { + String outputDescriptor = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n")); + OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor(outputDescriptor.trim()); + return descriptor.toWallet(); + } catch(Exception e) { + throw new ImportException("Error importing output descriptor", e); + } + } + + @Override + public boolean isWalletImportScannable() { + return false; + } +} diff --git a/src/main/resources/image/bitcoincore.png b/src/main/resources/image/bitcoincore.png new file mode 100644 index 0000000000000000000000000000000000000000..79503e259f49e56c8f96a38281a90da1c60b4db8 GIT binary patch literal 3316 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 BFslFn literal 0 HcmV?d00001 diff --git a/src/main/resources/image/bitcoincore@2x.png b/src/main/resources/image/bitcoincore@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d840474a103d1066ce09db4f94d0cdd5a7993d32 GIT binary patch literal 5516 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/image/bitcoincore@3x.png b/src/main/resources/image/bitcoincore@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..84a361853c98daf9a260073c80ae9c69b64fae50 GIT binary patch literal 10467 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