From fc5d48de6fbe25b7914a6eab8a65164b95e6c5b3 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 23 Feb 2023 12:02:06 +0200 Subject: [PATCH] bip129 round 2 support (wallet import and export) --- drongo | 2 +- .../sparrowwallet/sparrow/AppController.java | 13 +++- .../sparrow/control/FileWalletExportPane.java | 8 ++- .../sparrow/control/WalletExportDialog.java | 2 +- .../sparrow/control/WalletImportDialog.java | 2 +- .../event/ExistingWalletImportedEvent.java | 21 ++++++ .../com/sparrowwallet/sparrow/io/Bip129.java | 64 +++++++++++++++++- .../sparrow/wallet/KeystoreController.java | 4 +- .../sparrow/wallet/SettingsController.java | 48 +++++++++---- .../sparrow/wallet/WalletController.java | 1 + .../sparrow/wallet/WalletForm.java | 9 +++ src/main/resources/image/bsms.png | Bin 0 -> 2609 bytes src/main/resources/image/bsms@2x.png | Bin 0 -> 4090 bytes src/main/resources/image/bsms@3x.png | Bin 0 -> 7529 bytes src/main/resources/image/seed.png | Bin 1067 -> 2739 bytes src/main/resources/image/seed@2x.png | Bin 1588 -> 3976 bytes src/main/resources/image/seed@3x.png | Bin 2940 -> 6779 bytes 17 files changed, 150 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/ExistingWalletImportedEvent.java create mode 100644 src/main/resources/image/bsms.png create mode 100644 src/main/resources/image/bsms@2x.png create mode 100644 src/main/resources/image/bsms@3x.png diff --git a/drongo b/drongo index caed93ca..d0da764a 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit caed93ca6d3e29433c13ed9db705e34e057caf43 +Subproject commit d0da764aadfc9edc2a7fb35540eca0ee4c20ba0f diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index cca315f8..a558fe57 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -1073,7 +1073,18 @@ public class AppController implements Initializable { Optional optionalWallet = dlg.showAndWait(); if(optionalWallet.isPresent()) { Wallet wallet = optionalWallet.get(); - if(selectedWalletForms.isEmpty() || wallet != selectedWalletForms.get(0).getWallet()) { + + List walletTabData = getOpenWalletTabData(); + List xpubs = wallet.getKeystores().stream().map(Keystore::getExtendedPublicKey).collect(Collectors.toList()); + Optional optNewWalletForm = walletTabData.stream() + .map(WalletTabData::getWalletForm) + .filter(wf -> wf.getSettingsWalletForm() != null && wf.getSettingsWalletForm().getWallet().getPolicyType() == PolicyType.MULTI && + wf.getSettingsWalletForm().getWallet().getScriptType() == wallet.getScriptType() && !wf.getSettingsWalletForm().getWallet().isValid() && + wf.getSettingsWalletForm().getWallet().getKeystores().stream().map(Keystore::getExtendedPublicKey).anyMatch(xpubs::contains)).findFirst(); + if(optNewWalletForm.isPresent()) { + EventManager.get().post(new ExistingWalletImportedEvent(optNewWalletForm.get().getWalletId(), wallet)); + selectTab(optNewWalletForm.get().getWallet()); + } else if(selectedWalletForms.isEmpty() || wallet != selectedWalletForms.get(0).getWallet()) { addImportedWallet(wallet); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java index 9226e555..0504759a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletExportPane.java @@ -92,9 +92,11 @@ public class FileWalletExportPane extends TitledDescriptionPane { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Export " + exporter.getWalletModel().toDisplayString() + " File"); String extension = exporter.getExportFileExtension(wallet); - String fileName = wallet.getFullName() + "-" + exporter.getWalletModel().toDisplayString().toLowerCase(Locale.ROOT).replace(" ", ""); + String walletModel = exporter.getWalletModel().toDisplayString().toLowerCase(Locale.ROOT).replace(" ", ""); + String postfix = walletModel.equals(extension) ? "" : "-" + walletModel; + String fileName = wallet.getFullName() + postfix; if(exporter.exportsAllWallets()) { - fileName = wallet.getMasterName(); + fileName = wallet.getMasterName() + postfix; } fileChooser.setInitialFileName(fileName + (extension == null || extension.isEmpty() ? "" : "." + extension)); @@ -148,7 +150,7 @@ public class FileWalletExportPane extends TitledDescriptionPane { QRDisplayDialog qrDisplayDialog; if(exporter instanceof CoboVaultMultisig) { qrDisplayDialog = new QRDisplayDialog(RegistryType.BYTES.toString(), outputStream.toByteArray(), true); - } else if(exporter instanceof PassportMultisig || exporter instanceof KeystoneMultisig || exporter instanceof JadeMultisig) { + } else if(exporter instanceof PassportMultisig || exporter instanceof KeystoneMultisig || exporter instanceof JadeMultisig || exporter instanceof Bip129) { qrDisplayDialog = new QRDisplayDialog(RegistryType.BYTES.toString(), outputStream.toByteArray(), false); } else { qrDisplayDialog = new QRDisplayDialog(outputStream.toString(StandardCharsets.UTF_8)); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java index a160e976..d87feb3f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java @@ -44,7 +44,7 @@ public class WalletExportDialog extends Dialog { if(wallet.getPolicyType() == PolicyType.SINGLE) { exporters = List.of(new Electrum(), new ElectrumPersonalServer(), new Descriptor(), new SpecterDesktop(), new Sparrow(), new WalletLabels()); } else if(wallet.getPolicyType() == PolicyType.MULTI) { - exporters = List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new ElectrumPersonalServer(), new KeystoneMultisig(), new Descriptor(), new JadeMultisig(), new PassportMultisig(), new SpecterDesktop(), new BlueWalletMultisig(), new SpecterDIY(), new Sparrow(), new WalletLabels()); + exporters = List.of(new Bip129(), new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new ElectrumPersonalServer(), new KeystoneMultisig(), new Descriptor(), new JadeMultisig(), new PassportMultisig(), new SpecterDesktop(), new BlueWalletMultisig(), new SpecterDIY(), new Sparrow(), new WalletLabels()); } 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 0b18ee5c..ba213cd2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java @@ -59,7 +59,7 @@ public class WalletImportDialog extends Dialog { } } - List walletImporters = new ArrayList<>(List.of(new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new Descriptor(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow())); + List walletImporters = new ArrayList<>(List.of(new Bip129(), new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new Descriptor(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow())); if(!selectedWalletForms.isEmpty()) { walletImporters.add(new WalletLabels(selectedWalletForms)); } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/ExistingWalletImportedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/ExistingWalletImportedEvent.java new file mode 100644 index 00000000..fd5a3639 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/ExistingWalletImportedEvent.java @@ -0,0 +1,21 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.wallet.Wallet; + +public class ExistingWalletImportedEvent { + private final String existingWalletId; + private final Wallet importedWallet; + + public ExistingWalletImportedEvent(String existingWalletId, Wallet importedWallet) { + this.existingWalletId = existingWalletId; + this.importedWallet = importedWallet; + } + + public String getExistingWalletId() { + return existingWalletId; + } + + public Wallet getImportedWallet() { + return importedWallet; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Bip129.java b/src/main/java/com/sparrowwallet/sparrow/io/Bip129.java index 8317b80e..7f3b9244 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Bip129.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Bip129.java @@ -1,6 +1,7 @@ package com.sparrowwallet.sparrow.io; import com.google.common.io.CharStreams; +import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.OutputDescriptor; import com.sparrowwallet.drongo.Utils; import com.sparrowwallet.drongo.crypto.Pbkdf2KeyDeriver; @@ -20,7 +21,7 @@ import java.security.SignatureException; import java.util.Arrays; import java.util.List; -public class Bip129 implements KeystoreFileExport, KeystoreFileImport { +public class Bip129 implements KeystoreFileExport, KeystoreFileImport, WalletExport, WalletImport { @Override public String getName() { return "BSMS"; @@ -28,7 +29,7 @@ public class Bip129 implements KeystoreFileExport, KeystoreFileImport { @Override public WalletModel getWalletModel() { - return WalletModel.SEED; + return WalletModel.BSMS; } @Override @@ -186,4 +187,63 @@ public class Bip129 implements KeystoreFileExport, KeystoreFileImport { public boolean isKeystoreImportScannable() { return true; } + + @Override + public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + try { + String record = "BSMS 1.0\n" + + OutputDescriptor.getOutputDescriptor(wallet) + + "\n/0/*,/1/*\n" + + wallet.getNode(KeyPurpose.RECEIVE).getChildren().iterator().next().getAddress(); + outputStream.write(record.getBytes(StandardCharsets.UTF_8)); + } catch(Exception e) { + throw new ExportException("Error exporting BSMS format", e); + } + } + + @Override + public String getWalletExportDescription() { + return "Exports a multisig wallet in the Bitcoin Secure Multisig Setup (BSMS) format for import by other signers in the quorum."; + } + + @Override + public String getExportFileExtension(Wallet wallet) { + return "bsms"; + } + + @Override + public boolean isWalletExportScannable() { + return true; + } + + @Override + public boolean walletExportRequiresDecryption() { + return false; + } + + @Override + public String getWalletImportDescription() { + return "Imports a multisig wallet in the Bitcoin Secure Multisig Setup (BSMS) format that has been created by another signer in the quorum."; + } + + @Override + public Wallet importWallet(InputStream inputStream, String password) throws ImportException { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String header = reader.readLine(); + String descriptor = reader.readLine(); + String paths = reader.readLine(); + String address = reader.readLine(); + + OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(descriptor); + return outputDescriptor.toWallet(); + } catch(Exception e) { + throw new ImportException("Error importing BSMS format", e); + } + } + + @Override + public boolean isWalletImportScannable() { + return true; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java index d147c766..d846d21a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java @@ -141,7 +141,7 @@ public class KeystoreController extends WalletFormController implements Initiali displayXpubQR.managedProperty().bind(displayXpubQR.visibleProperty()); displayXpubQR.visibleProperty().bind(scanXpubQR.visibleProperty().not()); - updateType(false); + updateType(keystore.isValid() && !getWalletForm().getWallet().isValid()); label.setText(keystore.getLabel()); @@ -354,7 +354,7 @@ public class KeystoreController extends WalletFormController implements Initiali } private void launchImportDialog(KeystoreSource initialSource) { - boolean restrictSource = keystoreSourceToggleGroup.getToggles().stream().anyMatch(toggle -> ((ToggleButton)toggle).isDisabled()); + boolean restrictSource = keystore.getSource() != KeystoreSource.SW_WATCH && keystoreSourceToggleGroup.getToggles().stream().anyMatch(toggle -> ((ToggleButton)toggle).isDisabled()); KeyDerivation requiredDerivation = restrictSource ? keystore.getKeyDerivation() : null; WalletModel requiredModel = restrictSource ? keystore.getWalletModel() : null; KeystoreImportDialog dlg = new KeystoreImportDialog(getWalletForm().getWallet(), initialSource, requiredDerivation, requiredModel, restrictSource); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java index 47e12410..f114f2e3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java @@ -416,24 +416,28 @@ public class SettingsController extends WalletFormController implements Initiali try { OutputDescriptor editedOutputDescriptor = OutputDescriptor.getOutputDescriptor(text.trim().replace("\\", "")); Wallet editedWallet = editedOutputDescriptor.toWallet(); - - editedWallet.setName(getWalletForm().getWallet().getName()); - editedWallet.setBirthDate(getWalletForm().getWallet().getBirthDate()); - editedWallet.setGapLimit(getWalletForm().getWallet().getGapLimit()); - editedWallet.setWatchLast(getWalletForm().getWallet().getWatchLast()); - keystoreTabs.getTabs().removeAll(keystoreTabs.getTabs()); - totalKeystores.unbind(); - totalKeystores.setValue(0); - walletForm.setWallet(editedWallet); - initialising = true; - setFieldsFromWallet(editedWallet); - - EventManager.get().post(new SettingsChangedEvent(editedWallet, SettingsChangedEvent.Type.POLICY)); + replaceWallet(editedWallet); } catch(Exception e) { AppServices.showErrorDialog("Invalid output descriptor", e.getMessage()); } } + private void replaceWallet(Wallet editedWallet) { + editedWallet.setName(getWalletForm().getWallet().getName()); + editedWallet.setBirthDate(getWalletForm().getWallet().getBirthDate()); + editedWallet.setGapLimit(getWalletForm().getWallet().getGapLimit()); + editedWallet.setWatchLast(getWalletForm().getWallet().getWatchLast()); + editedWallet.setMasterWallet(getWalletForm().getWallet().getMasterWallet()); + keystoreTabs.getTabs().removeAll(keystoreTabs.getTabs()); + totalKeystores.unbind(); + totalKeystores.setValue(0); + walletForm.setWallet(editedWallet); + initialising = true; + setFieldsFromWallet(editedWallet); + + EventManager.get().post(new SettingsChangedEvent(editedWallet, SettingsChangedEvent.Type.POLICY)); + } + public void showDescriptor(ActionEvent event) { OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(walletForm.getWallet(), KeyPurpose.DEFAULT_PURPOSES, null); String outputDescriptorString = outputDescriptor.toString(walletForm.getWallet().isValid()); @@ -726,6 +730,24 @@ public class SettingsController extends WalletFormController implements Initiali } } + @Subscribe + public void existingWalletImported(ExistingWalletImportedEvent event) { + if(event.getExistingWalletId().equals(getWalletForm().getWalletId())) { + List importedKeystores = event.getImportedWallet().getKeystores(); + List nonWatchKeystores = walletForm.getWallet().getKeystores().stream().filter(k -> k.isValid() && k.getSource() != KeystoreSource.SW_WATCH).collect(Collectors.toList()); + for(Keystore nonWatchKeystore : nonWatchKeystores) { + Optional optReplacedKeystore = importedKeystores.stream().filter(k -> nonWatchKeystore.getExtendedPublicKey().equals(k.getExtendedPublicKey())).findFirst(); + if(optReplacedKeystore.isPresent()) { + int index = importedKeystores.indexOf(optReplacedKeystore.get()); + importedKeystores.remove(index); + importedKeystores.add(index, nonWatchKeystore); + } + } + + replaceWallet(event.getImportedWallet()); + } + } + private void saveWallet(boolean changePassword, boolean suggestChangePassword) { ECKey existingPubKey = walletForm.getStorage().getEncryptionPubKey(); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java index 0c8297a6..a5a891ad 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java @@ -103,6 +103,7 @@ public class WalletController extends WalletFormController implements Initializa WalletForm walletForm = getWalletForm(); if(function.equals(Function.SETTINGS)) { walletForm = new SettingsWalletForm(getWalletForm().getStorage(), getWalletForm().getWallet()); + getWalletForm().setSettingsWalletForm(walletForm); } controller.setWalletForm(walletForm); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index b6d30980..6510d633 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -41,6 +41,7 @@ public class WalletForm { private final PublishSubject refreshNodesSubject; + private WalletForm settingsWalletForm; private final List nestedWalletForms = new ArrayList<>(); private WalletTransactionsEntry walletTransactionsEntry; @@ -96,6 +97,14 @@ public class WalletForm { throw new UnsupportedOperationException("Only SettingsWalletForm supports setWallet"); } + public WalletForm getSettingsWalletForm() { + return settingsWalletForm; + } + + void setSettingsWalletForm(WalletForm settingsWalletForm) { + this.settingsWalletForm = settingsWalletForm; + } + public List getNestedWalletForms() { return nestedWalletForms; } diff --git a/src/main/resources/image/bsms.png b/src/main/resources/image/bsms.png new file mode 100644 index 0000000000000000000000000000000000000000..999f237fec0fee4f3faaf0b999d4f06cc6666d1b GIT binary patch literal 2609 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 Ck*6a7 literal 0 HcmV?d00001 diff --git a/src/main/resources/image/bsms@3x.png b/src/main/resources/image/bsms@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..4aa9cd603e9c0ec50a53ce51550459779b8a99a9 GIT binary patch literal 7529 zcmdUUcU03^*KPm>L>Qzi#V0FbCC%R{k! z_ti^?k8R5ymSNbY*M4bGV(qutIDf65jr*rwcQ)Q%HWmi}34(;df*>#m1QZeigCxO%;((MU zs5sVj!&Uha0s!D=y?SxH3+{OW0C?3fy@$w$8tPyxxHG?nHQdsM-`m;sN(mtC4aS#8EWlq7q@xVWLk(@<1Gu{lP>f%YUyubt1Ox)5-K}lG zPO*lwo1HTIkQ`_c(35VSlaUg7_mXY=Qz;9swbKL4kjx zVUztczW>4Uws8HgBqYrCzpQoT`JMJ_Gk%Xz`YHnOuP^+OJj~g~1%b^60umII{uSH* z^8A(E-~pl z!+f`Lu3(-g2*9iT@EhV$UY=0Up)p#{;1-wsdD19`S;_J1Ok|K9Ac|JlAbZ}+f>QiW z*XhaXt3~32F9B^n@o$(Q$n{~*i%CgD-Zi>ZuX1ARiPA?2+(ItwyE`Au!($w2@}@4g zSZO;i7h2RK_CJ3JCWwh?&LO)L$7pEWebVC%+LET5PV*j{i|jx6#*Z?#80aw9FfR1% zP2?8HkZ{Y*L3McNk<^>e5bC3^>jg8lNrdX8%t zNgkfq6});HZfNK-p$Sr^>e17)YkJ^5p@ug?C|MAOkK-R(@#w8`;jPeyd(KVmRl@|= z8F)?A1pVb%m*3YP;H5N`#ZTb$dMmXa49a?^4J@FVnNs}YH43_YdZ<2tl9QvnkhQ1z zA*}HZ2K)qSd7XpHo1?O|>MUA3&?QEV330py1yiHQ?u6VcPrj^JvaSh8rTbAF-EP%m zJ@8?#tU;O4cvpQeUAtM&?PrcJo#HFWyQMRQG2@5a5n?bRgi_?sXKvP^StmIU4tSpy=stJk3_;8yL=V1BvX0+k8tM{$9A&VtaZXKG2Hs1Yh@$POQfZ;H*z zqx7`C-P>@8vx4}V+Xff-fTd;EfLi9e6o$l_ceIUr$y5R5CmhSM?H%4f{b@k_xKCDW zZW*n{w8V%SlENGD%x;>X@STs$`|s`<9qLu= zkXqBJicTO|gfV34{vuexq(1r|D$7SXlsX%G`PMJyUu~&%NlE``61mket~ff^;V3m+ znqeKw7yr6=&^v6ov`$TVN+CaamgGm=4+lR3dX7V~8{m6gOt;uD82t=F=MkUMrit7 z>R#A8Wv%HgL42JrO4^@3hR^8Fxm6R@z7|4F0&IcFo zCxxfaQ(gx10mEsXLz^%D{_}==GpLI3by10_$hputzM&#I&;2dGj~|1+;iKr+ngh8~ zrqxrjS~~lb9rSrzP%HUSTJx90?0&)qgn6Vj`;zr)-ajrOMJ}!8&noJSN=CmLo;)q0 zJcDEP&TRFNunR)CAx5 zThnNy@KR}-BBut2XP@iz%q?7QX~!0vPgwH#w7#!w_Qd?W|PHhqiJy)WT9iRG9}x)(R*Ii!6WJI_!K|zf@n^kiZ5?2 zuj9vcx@q&7285ldA~Va=)uso_s>?_+;$wJN3xsVhFuVDF0-)D4F~_6PS)^B`RTa!uL+gCGaL@HyOBa`N9@> zV+qXZO>$oLfc7$iH*&Bcbvr_aW_wrY&55*Sz26<{G<2fXkUp!7jhKF(t|7np>pE2u z_c=2$Q<@#Q#%JSuu1yY>UqfG9Gdb)tB(BHNbRezJN*FBp_MSHEn_6KBF74R5hHmq&6xc5Wo%tNIT;PH}L-MA4xu5JzU|PG~-v{lQG&;W5 zZj5|oN8nwXC3JHeLsR$8(9OYM2iMOm-8!j%X3x#1I&oF(L<`wr9{eixF|866liJWY z`tgPeWk-J8hHjDOK+$i_a*mwqaSgB34o=MR+|F8~quv>uCXw~VQiSAX&>xczZZWh3 zUCtdH9cB9eIN}~d+tSQ7ZZeLho{}ycEMG*5JPmR&Ct)){6f_{#Jc)^k`Q~Z;c>_dA zP2?Nb+)HPVYi>SexWJa*BK4*Dx6$d|%b7=9U9Bw#aZht!&taqa{!!1Eune)<-|*Pg z^&6dwc}xq#Q>FkGOid{WKZ$KxVZ{dIAUvj-X_LFiH250%dfon;nPQjA>SFo}@e|*D zfI#XNEuTNPT!+|1FkfzB(w;eKg6KIGVFbE{mU19jU>=TRH@lZDdn<4N517c$)vbf!RjDzS16ad?>sB)P4iNGm!cxkYYW>NGogdm{>I_q8+s zQfpjx{mvsz%NE647Y^Z1!i#D2itmS+66ezbAibs(NBB=8Kyu{mtF0#&CTa{$`oA=cd;0hXET?w4Sra2l1w`@K^-@q@Te3-bF99 zx_qk@3YBvX7_jWq?Oj>>fui2XF`f_Xg!D%Sbc+KuNn0sef#EoMx*BZ04+Zoiss&aW z1+1VoY)u?vsL-P2z@LS@8wX`0!^^qjjnny^gfK(- z+U_*&?nNG=9-fqT+s>EXH_dy#4>L&{2C-2N#>=j??elq750ZpT<|6!yX>xufjMi-7%_8U; z5kMo3$dF{oB+$=Rh-{*fE!`?Utw%seBs^qxe&kdf_feh?Ceffka%ktR>~VVGhlap! z98j(Bmp!Xf8b!T1ahJm|P7#Ok^`A_~@+e;Gfyc5S)d|A;)Aef{UdUzE>hbk>P^y)3V~^m zrvq?$@76*((K@J7s12IDJK#sX2hJoZ6WdhN^gH@W|0TggB^UZNpa;6L1ua=?184M%F&dp>Ngr%!Zn6G<2z9sy`LNh zex5JRMNh`8FziSM8k$+svn;*;05$PmgL{wXW1MP59nEnwAjWPXr>;qL{Pl^Cxhg0~ z{M)^3up4ve-ZeMIrAr6ReRv1FO)WaN;pev#>9p8gWQt%BKFf#Vps(BO>k@4Xx4oTh zXs7E}j4p)C-C`} z*1mPO%GLT*;xD85%`xx~+ASS!F5>TX<#;5<4UU$^%eMUjOrX6bT0-V3ZUIt3j%F`a z6Y>{X)<0$7&p~41Ap^VK=!7$#9lgySHSd|SoZ0M01bur|-z?ek;&i2oNComf2U^0P zuNe5)d*)o@x!ZM{ljJz&TR+G7=}E4HkVNzxZr`qOhN6Cd_mN?G2xnO}#piWLaF*~e zz%qwni!-_k_N-C=V_vBhZMA)ci8BG3k@QBDzPP<3(>6rT|4jZ(sPtQBA+ceh%j9+G z4G#6B)#vVCYrW%#^os|`rrn^$*NiRyFSk3g3b-m>2L@5qvYee-=Bv(jBJYBA4m>@^ zLGN{T?Vs?;M8`6*dU zc`MD!Jt@)(55a^}bEyugtQ^_T`iyRlj(W7u@rOmyECencH?hB9nxl6uvUTucYogvG z(jfX1ik9xhCq+k(3zrqRM)k`o zR#l78e++5Wa@*8wLhGbdvnwj7ZYwKR;$1p(KraqvB%;eUuZJk=0OIL3rav=pigSxH zEtYF7)p)WdWELxPC%h}J9_%nD3FckLndzha$#elf>GEr*Cmh=^sx(-N(GG3-;CS|g zRaj?Qzu;WZ(M0wh+=K^W=GOi@^ zpcT(|)WaKTD^L@RehQp~cSz0h6arK8qa)OW;`I~R1gzMMn#Ntr$w>0$dn)mlPQVR6 z=}#1`wM%kKoZr&0o&wl=sG^o-%SfqK-iPW&XD`RP`Q4gRCfTb8mt+oJr|BucideF3 zE;!IkYU9iG&0kcxe3UyS$_h^Tyj=(SphXn5c%;YhWhmG zxr7QUjj~GcGwZ@G$x>cwLJzQioWt$l2xA)cFS>RQG55^8@hpzI(;tVJB#<~epy-o6 zQOO;=S~*Yfa&rkf6i&<)*E1HWdm)a|pBGEAHIDSi^zI);GKg(I}>*_r6@K z&&P_|*p+`6j9hPs?&q=2_^hkO7t!$9m&e?xlE@B7{20{_Mrm{~MJWJw`Z^&K?M(Od zXjKq)Wsbfj_dUs&fnCpxtkhjzhti=z^%toP_b2M zsV600MMX4TW5$Iy!oOub{N>@4BWVLrP`n$D+TsbBVh*u<7VsGw|HgpLgU3Fx1XCTE zvZc>Kw_XOJucagcIB&ic!%Ya1F5cc@_@d8+cLQQ(rRE?Em7WH>-XuI+M@6n;6I@ z*Ny9JkBg-l)MsQ1T(~}1vk#4+_FcIj$|4cdsEyx7oltE)M%CmzyKAD4#^+i!#!C7i z%v`t<0|EZAb)T0`Lcb`9sTEKlDh-*exx=>VBW-~i>aTt<(?ycvq31;TZVDvIw!Dh; zUuTqT)~;VE4*QLf&XRO0**ooZ_vEKA%_dYSXI{2t>F8MrP4|NzN87w#sALrk(rdm zw*}hpc$`~Owr`dZ(JeoBKzs`9WD@=V=mx!&&{|{u5(nvS=WB+qTZjF#5TK%BAiqCrdPI7 zubn6?eC6Zde*3|bNv~fg+6%5m@^5nr1VN{Y1xA^d4w?m-UMoDCa6Ey0e{(}>s#<2F z!2DsPVn^WG>;ng)XCF?-boS#=lfO9UsmNSEQPSd4_>6T>TNiVbIpR(*R(o>Bb4!@4 z+O;0WZW*-tgwQFww(XzZ@xT{!}`z!%P$C@v;DFbpP+9nKdR7!bh2Xfg!WW> z_%J%vZm|80dI*geKFZ<+rq$niLu<^{ zVVNE|ndCS?bD2FtQ=T3;k4JfLMnx$oRl{oyx*Shj^8Vdy%hB@(C#w2}?@dN#*kO+A zXKRFpYIBcW5Nst1U9sLJj||#3<@=XMXT|WcpRR4q9a}!Ce$yvFX1qUl9L%@8wsA^l zaFt05#+)n?ptKT>1n*C!T*1YqHhYv*HfD+bR7`kwV|r5NXnObl=CCh8!VhDYG77p+ z4-HM_sqg4VTS(2;7k-w~qO$v+TTdG`4fge&Q|tdQgWCNiws*XA<7%jZXJ}rIbP@?L zfn+&gnS=@;1xXUn14w8AGV?M3cuHu0lXpuPE!apxHW&a}0X1mSk1zn~1^a{0E*?J6 zggkxS-53$HpkNA@#TL^94k9g(>mi-wdC6}$@VwM_8PWmZ86m_kV0nmxHwWV1Gx3zL zH_D0t0I48`hZon2PP<$_L5zy;rz(tBhK&UJ%flU)r#)HKQ!vBatIMh05Q~PY5WD%C?IJ(o3&DDkX&hEi zn7EC2E3|l^Fya-5`iFWc^gD7JlflJ<3y>wpOHtn6^*;)@SiKbL%4X0YGS26US%|jy zY{4Ff67z8pKfjWH{uE+A9tK^@Utba)wy!G<`U^-OcO-7~2#^`dBn5bEUs-Rir4qhxMu37Y$u1H4_HNKIzsI=OUGX;Vgg670BxdrQ7CvEHBn7j3vr5WJsv%$@DlQ&X94clO zZtFzDqoSgQ&D%6_vudu^fM=G07O!Xcbb2~-gsXP$#I|Ee>!*Ef0px`Nd11%JCU{?0 zf+8e6ue+NKcm3)X_;!-;#%+ zpL%VO9d^D~>6oi%3`vSi+`&=h!IKsW<%h(W!n^!Sj5hU*D4(k~Ks^p;2VFrDLjZ zOo!da&cN00taN_S^Y-$JDE`fJ?0p@_()N+1LuNT7>+;;DZf zE~JL;-FuC+Ctb9s`?_ItV~DC9Vvx_0o8<+J`@Cz_ptcC>=Q2v(ZKunYkF<(la`Ah6 z;!9iHtdE^Ux@F(fZBETew2&fZR*sn4-H59WZ)=qo7{k$Xij%T8DsQc+h`*`}Zv@;C zDkpb75|w(^`;{E6kYp@NRF4%Bt=NaS)_I`MnlbWL3zc}HZ)PRBfrC>?n!(!I7TB22 z02mqj*3T=F)9x78ZQb&pe%$5#ovUGr4K4#FgmeDPeP$AvLx?J4PaLi;Z5;Oykw8z> zB7rFf1veWVK3S)>JRVg|T0uYEj@l%u9Z-#!*;%Q}v-eX^QJOe>^4iM-`k~r=uA%&x zhO1`{r{#=Bq6rT2f~u-2e&i&Betn|0sjhFN&BJ6(82Jy!tmvr{{T!>?PXi@VOqG{+ z1$GV1_`sQJ0d)z-S5LySLSbi>*~|*d$I-hR{J}Oa)3qB!s|ru8An&fI z8I-@85{=llr{`GNj_xa_V)H2#}gk5(+gk6rOW#0 ePCh))t!DX|`v)SgHedYPcl^zTc*EZJ(EkChxK3gK delta 1027 zcmV+e1pNE66{`r4B!7izLqkwWLqi~Na&Km7Y-IodD3N`UJxIeq7>3`bN{dt+>>%Qh zp}J^MRK!uMP=pGhR%q41fkB}f zp+$^$9QW|v_rBbH2MF~t)2xm$K+|nAolJ`_MHnHP!+#i+nPtpMQVPD~>mC8V z-^F>Bf8C#>Tg_Vx2#CZp%rI@@b>gW_+u*!U9Azb0B|aw}G3kQDk6c$ge&bwpS>Tyb zGn1YpjuMN-Hdfl0l}wFzk~pSnI^_#lk5$fFoV9Y5HSft^7|H9)OI)WlfFu^M2niw- zRI!0FY{Y5TNq@1Brt^f4zu)ysk2{osG_ySG+hV%$rLB!JEr$N3lp zLc2ht<~ZNSj?*{+g3rK}-tt%K!1O2SwU!n;0(!QAi|dxA>;acMK;M%go3bkfX$ge_ z@P0<$lmmKifv#1rZ_RU@J^&f&Rq6&fI0S}@l)dKj?h$Z%-~O#>_U{LQt8%UN=Awv` zfdUtQ00(qQO+^Rf2nPo&6}<%+i~s-vIY~r8RA}Dqn$K$!K@`V7wmB$;QbppWt?kWR z>ZPC(O>eabX~9E#$bZmF>3`5a!Gi}+1y5cSK}D&wkhYh4=%I%iFo!Cb^hcyLDQbvr z;=x%7f$W;>o1NXTUw9Ch%p{+EZ|1#yyAUaVSi0cjZ`}aWz&#)V#DPKJ94G-fU=4T$ zdl%J=SJkU4`I3^$CDH@UTwVW&F&cw3?L$x$9x>gv+?)^U@$+J?4fYaHnO>OTZJCc@^n0z64zLxJCoW0(&Z#%mCM1 z=2$>X(j%WVXLF}4FS^;k8LF@kr9t+84T0pOK{gG6Y)XR+7=W+~P0^dE-v%~%RuWeIAt#Uzx}>GYGoPi}pcL|mfi2*37(Fu7%X=gO xkx;&iusXuu*FgSDDLU)p9uT0{EFwkHq(6?E)XpDL>D~YU002ovPDHLkV1lXR>8$_& diff --git a/src/main/resources/image/seed@2x.png b/src/main/resources/image/seed@2x.png index c540bf2742457afd88bf405fd51fb88f2adb053a..e0521b7a57208c3760507ef0f72b7791d3b7df8d 100644 GIT binary patch literal 3976 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 delta 1552 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 delta 2915 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 Jtuynw{copbYJ~s*