From c24f953e526ffc333097994fca0465e3a6b69b1c Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Tue, 23 Aug 2022 08:48:35 +0200 Subject: [PATCH] export all related wallets when exporting to electrum personal server --- .../sparrow/control/FileWalletExportPane.java | 2 +- .../sparrow/io/ElectrumPersonalServer.java | 106 +++++++++++------- .../com/sparrowwallet/sparrow/io/Sparrow.java | 5 + .../sparrow/io/WalletExport.java | 3 + 4 files changed, 73 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java index 6c867042..467f6e88 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java @@ -82,7 +82,7 @@ public class FileWalletExportPane extends TitledDescriptionPane { fileChooser.setTitle("Export " + exporter.getWalletModel().toDisplayString() + " File"); String extension = exporter.getExportFileExtension(wallet); String fileName = wallet.getFullName() + "-" + exporter.getWalletModel().toDisplayString().toLowerCase(Locale.ROOT).replace(" ", ""); - if(exporter instanceof Sparrow) { + if(exporter.exportsAllWallets()) { fileName = wallet.getMasterName(); } fileChooser.setInitialFileName(fileName + (extension == null || extension.isEmpty() ? "" : "." + extension)); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java index ca299cc1..3e1b8684 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java @@ -4,18 +4,20 @@ import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.KeyPurpose; 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.drongo.wallet.WalletModel; -import com.sparrowwallet.drongo.wallet.WalletNode; +import com.sparrowwallet.drongo.wallet.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.BufferedWriter; +import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.Iterator; public class ElectrumPersonalServer implements WalletExport { + private static final Logger log = LoggerFactory.getLogger(ElectrumPersonalServer.class); + @Override public String getName() { return "Electrum Personal Server"; @@ -37,58 +39,73 @@ public class ElectrumPersonalServer implements WalletExport { writer.write("# Electrum Personal Server configuration file fragments\n"); writer.write("# Copy the lines below into the relevant sections in your EPS config.ini file\n\n"); writer.write("# Copy into [master-public-keys] section\n"); - writer.write(wallet.getFullName().replace(' ', '_') + " = "); - - ExtendedKey.Header xpubHeader = ExtendedKey.Header.fromScriptType(wallet.getScriptType(), false); - if(wallet.getPolicyType() == PolicyType.MULTI) { - writer.write(wallet.getDefaultPolicy().getNumSignaturesRequired() + " "); + Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet(); + writeWalletXpub(masterWallet, writer); + for(Wallet childWallet : masterWallet.getChildWallets()) { + if(!childWallet.isNested()) { + writeWalletXpub(childWallet, writer); + } } - for(Iterator iter = wallet.getKeystores().iterator(); iter.hasNext(); ) { - Keystore keystore = iter.next(); - writer.write(keystore.getExtendedPublicKey().toString(xpubHeader)); + writeBip47Addresses(masterWallet, writer); - if(iter.hasNext()) { - writer.write(" "); - } + writer.flush(); + } catch(Exception e) { + log.error("Could not export EPS wallet", e); + throw new ExportException("Could not export EPS wallet", e); + } + } + + private static void writeWalletXpub(Wallet wallet, BufferedWriter writer) throws IOException { + writer.write(wallet.getFullName().replace(' ', '_') + " = "); + + ExtendedKey.Header xpubHeader = ExtendedKey.Header.fromScriptType(wallet.getScriptType(), false); + if(wallet.getPolicyType() == PolicyType.MULTI) { + writer.write(wallet.getDefaultPolicy().getNumSignaturesRequired() + " "); + } + + for(Iterator iter = wallet.getKeystores().iterator(); iter.hasNext(); ) { + Keystore keystore = iter.next(); + writer.write(keystore.getExtendedPublicKey().toString(xpubHeader)); + + if(iter.hasNext()) { + writer.write(" "); } + } - writer.newLine(); - - if(wallet.hasPaymentCode()) { - writer.write("\n# Copy into [watch-only-addresses] section\n"); - WalletNode notificationNode = wallet.getNotificationWallet().getNode(KeyPurpose.NOTIFICATION); - writer.write(wallet.getFullName().replace(' ', '_') + "-notification_addr = " + notificationNode.getAddress().toString() + "\n"); - - for(Wallet childWallet : wallet.getChildWallets()) { - if(childWallet.isBip47()) { - writer.write(childWallet.getFullName().replace(' ', '_') + " = "); - for(Iterator purposeIterator = KeyPurpose.DEFAULT_PURPOSES.iterator(); purposeIterator.hasNext(); ) { - KeyPurpose keyPurpose = purposeIterator.next(); - for(Iterator iter = childWallet.getNode(keyPurpose).getChildren().iterator(); iter.hasNext(); ) { - WalletNode receiveNode = iter.next(); - writer.write(receiveNode.getAddress().toString()); - - if(iter.hasNext()) { - writer.write(" "); - } - } + writer.newLine(); + } - if(purposeIterator.hasNext()) { + private static void writeBip47Addresses(Wallet wallet, BufferedWriter writer) throws IOException { + if(wallet.hasPaymentCode()) { + writer.write("\n# Copy into [watch-only-addresses] section\n"); + WalletNode notificationNode = wallet.getNotificationWallet().getNode(KeyPurpose.NOTIFICATION); + writer.write(wallet.getFullName().replace(' ', '_') + "-notification_addr = " + notificationNode.getAddress().toString() + "\n"); + + for(Wallet childWallet : wallet.getChildWallets()) { + if(childWallet.isBip47()) { + writer.write(childWallet.getFullName().replace(' ', '_') + " = "); + for(Iterator purposeIterator = KeyPurpose.DEFAULT_PURPOSES.iterator(); purposeIterator.hasNext(); ) { + KeyPurpose keyPurpose = purposeIterator.next(); + for(Iterator iter = childWallet.getNode(keyPurpose).getChildren().iterator(); iter.hasNext(); ) { + WalletNode receiveNode = iter.next(); + writer.write(receiveNode.getAddress().toString()); + + if(iter.hasNext()) { writer.write(" "); } } - writer.newLine(); + if(purposeIterator.hasNext()) { + writer.write(" "); + } } - } - writer.write("\n# Important: If this wallet receives any BIP47 payments, redo this export"); + writer.newLine(); + } } - writer.flush(); - } catch(Exception e) { - throw new ExportException("Could not export wallet", e); + writer.write("\n# Important: If this wallet receives any BIP47 payments, redo this export"); } } @@ -111,4 +128,9 @@ public class ElectrumPersonalServer implements WalletExport { public boolean walletExportRequiresDecryption() { return false; } + + @Override + public boolean exportsAllWallets() { + return true; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java b/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java index 15033802..476b4138 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Sparrow.java @@ -144,4 +144,9 @@ public class Sparrow implements WalletImport, WalletExport { public boolean isWalletImportScannable() { return false; } + + @Override + public boolean exportsAllWallets() { + return true; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java b/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java index 254afd69..3653e735 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/WalletExport.java @@ -10,4 +10,7 @@ public interface WalletExport extends Export { String getExportFileExtension(Wallet wallet); boolean isWalletExportScannable(); boolean walletExportRequiresDecryption(); + default boolean exportsAllWallets() { + return false; + } }