From d139ca2706dff01875047452e74d173e3fae388a Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Mon, 22 Aug 2022 14:33:03 +0200 Subject: [PATCH] add wallet export to electrum personal server config file --- drongo | 2 +- .../sparrow/control/WalletExportDialog.java | 4 +- .../sparrow/io/ElectrumPersonalServer.java | 114 ++++++++++++++++++ .../sparrow/net/ElectrumServer.java | 2 +- src/main/resources/image/eps.png | Bin 0 -> 2128 bytes src/main/resources/image/eps@2x.png | Bin 0 -> 2917 bytes src/main/resources/image/eps@3x.png | Bin 0 -> 4604 bytes 7 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java create mode 100644 src/main/resources/image/eps.png create mode 100644 src/main/resources/image/eps@2x.png create mode 100644 src/main/resources/image/eps@3x.png diff --git a/drongo b/drongo index aa459d00..311afd04 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit aa459d0084b3cc72c49c8922d571338c4f5efaf4 +Subproject commit 311afd0409b7cb442e5ee1efebdc3c7dd628346a diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java index ac1908e1..b98f97f2 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 Descriptor(), new SpecterDesktop(), new Sparrow()); + exporters = List.of(new Electrum(), new ElectrumPersonalServer(), 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 Descriptor(), new PassportMultisig(), new SpecterDesktop(), new BlueWalletMultisig(), new SpecterDIY(), new Sparrow()); + exporters = List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new ElectrumPersonalServer(), 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/io/ElectrumPersonalServer.java b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java new file mode 100644 index 00000000..ca299cc1 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/ElectrumPersonalServer.java @@ -0,0 +1,114 @@ +package com.sparrowwallet.sparrow.io; + +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 java.io.BufferedWriter; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; + +public class ElectrumPersonalServer implements WalletExport { + @Override + public String getName() { + return "Electrum Personal Server"; + } + + @Override + public WalletModel getWalletModel() { + return WalletModel.EPS; + } + + @Override + public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + if(wallet.getScriptType() == ScriptType.P2TR) { + throw new ExportException(getName() + " does not support Taproot wallets."); + } + + try { + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8)); + 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() + " "); + } + + 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(" "); + } + } + + if(purposeIterator.hasNext()) { + writer.write(" "); + } + } + + writer.newLine(); + } + } + + writer.write("\n# Important: If this wallet receives any BIP47 payments, redo this export"); + } + + writer.flush(); + } catch(Exception e) { + throw new ExportException("Could not export wallet", e); + } + } + + @Override + public String getWalletExportDescription() { + return "Export this wallet as a configuration file fragment to copy into your Electrum Personal Server (EPS) config.ini file."; + } + + @Override + public String getExportFileExtension(Wallet wallet) { + return "ini"; + } + + @Override + public boolean isWalletExportScannable() { + return false; + } + + @Override + public boolean walletExportRequiresDecryption() { + return false; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index 9998c05c..2b742123 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -364,7 +364,7 @@ public class ElectrumServer { } private int getGapLimitSize(Wallet wallet, Map> nodeTransactionMap) { - int highestIndex = nodeTransactionMap.keySet().stream().map(WalletNode::getIndex).max(Comparator.comparing(Integer::valueOf)).orElse(-1); + int highestIndex = nodeTransactionMap.keySet().stream().filter(node -> node.getDerivation().size() > 1).map(WalletNode::getIndex).max(Comparator.comparing(Integer::valueOf)).orElse(-1); return highestIndex + wallet.getGapLimit() + 1; } diff --git a/src/main/resources/image/eps.png b/src/main/resources/image/eps.png new file mode 100644 index 0000000000000000000000000000000000000000..4b1f6578c4c6f776e4cdb982a4ea1bdf1e3111ca GIT binary patch literal 2128 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-#^NA%Cx&(BWL^R}$5JCa(|mmy zw18|526jdv24;{FAY@>aVqgWc85oinrQz%>Mh&PMCZHNa1|Z2G3dBW>MqsuGkloRq z$-n~DI|oREKmrg0O@+__nH8xyxrrtDMG65$`3gpQCVD`38W|WFSQ%JY8JH^=Sb`Xq z#taP13m6e@TEGN1*JuGVf(u(V``M3hAM z`dB6B=jtV<VstT4fPE4v1tQ(7VbV2T@{H1I5m}Irs1#)B!g31N;2H4 zg3_WKa6qIa>!;?V=BDPA6a)1c>cjmH^qvjK1 zoRlJ5GJz%*14GTu)BvgnT>`7dNL(6`B#<-$1JBC2C>4}&L-O-;?69R{8+~+DXa@M^ zr(~v8x+IpQ+JREMu7QQFfq4il#Y40ssX^BbOp+;yC5bToMj-u0Mj?hqRwkxa#unNJ zMpg#EFaSBi7g;TmIiN_h3Q8@`&nX3^w9vfF5<4RseQd($x}5WK3yM;U!9fkQ1*`>G z3|+l{Q6?~|6G7I1ViT+$Sqxo$1j1Q%mSzS(pl=8wp<0op(X~e6*NQBSq%|eks<<>M zD>b4QDCWH#|4ZESP5gtRnId=oq>UA zvZsqAoHzff8-`i>6EroT$li(Mah9J4>vJhVGKoi&|0F)S^7Q#R4VXMs49z zQYv_?)RdvTYpR%|qvHZ0!{9I75}vNDe{J>eHqW14oWAU}$+Q}Mn>oe%KmWg1d~U9% z&~)nud^?!mH6T-`A0Jqyb+|0xUii|!F8KqKx5TYEYkun9q3jB_&jOdNnvN&(=d=p> z98|GsnjYZscgwFx=H))`uinZqOka3(OW-zflZ$&kx`k-^pRHt__#-rFy;5Q8yQfFi z3(Rlq-N4JXK)5Zzb`G28#@QQKuUzMqXfJ!wr2Ija>-4VO>yO3d%#NLDHfzSV1u_2_ zr5&u8yQVG>O^TA`Zi@&$VWM&M_RcANOF!>4KKtJH^6@3}FV~p;oW*1AeKH*j5ORIf+iH2VCO}BI-%Yt_OKP^~g zwD^YEcTM5V0%|`d{_#9<*^z6W^NRKTFI`uB`?$<&-P{zx$_E zi948wzIZJ4^H9_)rxNa5Et{%I8QIemblm>BUKQ!RrvIaDT}eT=*Zrx<%RVMfj-T@P zHg}{+#KHF6TQ2{cQndZ){|y4U+IKT1-3ti$f6D2c@$R}4_k614TFx$D?R5+}dojbN zqWi=2ioiwDE3KEWnYQ8EKTD0~ZI@Isq-B}@>Q8KQNNrV_eyBH4b1VC;Z5!o}cnM1^ z3)sIsDcMG~PGnPl_k|`cF};v%55L{X<~i)Ss(%+>QCgND7lWE4fV>TxWPY$VPTMB@ ToO}0PP^IYU>gTe~DWM4fAMwg_ literal 0 HcmV?d00001 diff --git a/src/main/resources/image/eps@2x.png b/src/main/resources/image/eps@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..047c483f381176a5cad52771a190c89131df4bc8 GIT binary patch literal 2917 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