From 555260e954eb4cc347ba3e7d27c25fe29170cf19 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 8 Feb 2023 08:03:06 +0200 Subject: [PATCH] implement bip329 for importing and exporting wallet labels --- drongo | 2 +- .../sparrowwallet/sparrow/AppController.java | 20 +- .../sparrow/control/DevicePane.java | 2 +- .../sparrow/control/WalletExportDialog.java | 4 +- .../sparrow/control/WalletImportDialog.java | 9 +- .../event/WalletEntryLabelsChangedEvent.java | 11 + .../sparrow/io/WalletLabels.java | 296 ++++++++++++++++++ .../sparrow/wallet/KeystoreController.java | 25 +- .../sparrow/wallet/WalletForm.java | 3 +- src/main/resources/image/labels.png | Bin 0 -> 979 bytes src/main/resources/image/labels@2x.png | Bin 0 -> 1815 bytes src/main/resources/image/labels@3x.png | Bin 0 -> 4014 bytes 12 files changed, 359 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/io/WalletLabels.java create mode 100644 src/main/resources/image/labels.png create mode 100644 src/main/resources/image/labels@2x.png create mode 100644 src/main/resources/image/labels@3x.png diff --git a/drongo b/drongo index b4873964..d48054ac 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit b487396417fbdf3c73c24399a778855c97a26584 +Subproject commit d48054ac6ba071b49180017fe12e3acb41635b9c diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 0954e47d..d2674f1c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -1068,11 +1068,14 @@ public class AppController implements Initializable { } public void importWallet(ActionEvent event) { - WalletImportDialog dlg = new WalletImportDialog(); + List selectedWalletForms = getSelectedWalletForms(); + WalletImportDialog dlg = new WalletImportDialog(selectedWalletForms); Optional optionalWallet = dlg.showAndWait(); if(optionalWallet.isPresent()) { Wallet wallet = optionalWallet.get(); - addImportedWallet(wallet); + if(selectedWalletForms.isEmpty() || wallet != selectedWalletForms.get(0).getWallet()) { + addImportedWallet(wallet); + } } } @@ -1653,6 +1656,19 @@ public class AppController implements Initializable { return null; } + public List getSelectedWalletForms() { + Tab selectedTab = tabs.getSelectionModel().getSelectedItem(); + if(selectedTab != null) { + TabData tabData = (TabData) selectedTab.getUserData(); + if(tabData instanceof WalletTabData) { + TabPane subTabs = (TabPane) selectedTab.getContent(); + return subTabs.getTabs().stream().map(subTab -> ((WalletTabData) subTab.getUserData()).getWalletForm()).collect(Collectors.toList()); + } + } + + return Collections.emptyList(); + } + private void addTransactionTab(String name, File file, String string) throws ParseException, PSBTParseException, TransactionParseException { if(Utils.isBase64(string) && !Utils.isHex(string)) { addTransactionTab(name, file, Base64.getDecoder().decode(string)); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java b/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java index 6d6c9551..4b2df0b9 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java @@ -975,7 +975,7 @@ public class DevicePane extends TitledDescriptionPane { } importButton.setVisible(true); showHideLink.setText("Show derivation..."); - showHideLink.setVisible(true); + showHideLink.setVisible(!device.isCard()); List defaultDerivation = wallet.getScriptType() == null ? ScriptType.P2WPKH.getDefaultDerivation() : wallet.getScriptType().getDefaultDerivation(); setContent(getDerivationEntry(keyDerivation == null ? defaultDerivation : keyDerivation.getDerivation())); } else if(deviceOperation.equals(DeviceOperation.SIGN)) { diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletExportDialog.java index f3e460ff..a160e976 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 ElectrumPersonalServer(), new Descriptor(), new SpecterDesktop(), new Sparrow()); + 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()); + 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()); } 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 eb589b93..0b18ee5c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java @@ -9,6 +9,7 @@ import com.sparrowwallet.sparrow.event.WalletImportEvent; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands; import com.sparrowwallet.sparrow.io.*; +import com.sparrowwallet.sparrow.wallet.WalletForm; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.scene.control.*; @@ -16,6 +17,7 @@ import javafx.scene.layout.AnchorPane; import javafx.scene.layout.StackPane; import org.controlsfx.glyphfont.Glyph; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -24,7 +26,7 @@ public class WalletImportDialog extends Dialog { private final Accordion importAccordion; private final Button scanButton; - public WalletImportDialog() { + public WalletImportDialog(List selectedWalletForms) { EventManager.get().register(this); setOnCloseRequest(event -> { EventManager.get().unregister(this); @@ -57,7 +59,10 @@ public class WalletImportDialog extends Dialog { } } - List walletImporters = 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 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)); + } for(WalletImport importer : walletImporters) { if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) { FileWalletImportPane importPane = new FileWalletImportPane(importer); diff --git a/src/main/java/com/sparrowwallet/sparrow/event/WalletEntryLabelsChangedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/WalletEntryLabelsChangedEvent.java index ae0f6759..7f766286 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/WalletEntryLabelsChangedEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/WalletEntryLabelsChangedEvent.java @@ -11,22 +11,29 @@ import java.util.*; public class WalletEntryLabelsChangedEvent extends WalletChangedEvent { //Contains the changed entry mapped to the entry that changed it, if changed recursively (otherwise null) private final Map entrySourceMap; + private final boolean propagate; public WalletEntryLabelsChangedEvent(Wallet wallet, Entry entry) { this(wallet, List.of(entry)); } public WalletEntryLabelsChangedEvent(Wallet wallet, List entries) { + this(wallet, entries, true); + } + + public WalletEntryLabelsChangedEvent(Wallet wallet, List entries, boolean propagate) { super(wallet); this.entrySourceMap = new LinkedHashMap<>(); for(Entry entry : entries) { entrySourceMap.put(entry, null); } + this.propagate = propagate; } public WalletEntryLabelsChangedEvent(Wallet wallet, Map entrySourceMap) { super(wallet); this.entrySourceMap = entrySourceMap; + this.propagate = true; } public Collection getEntries() { @@ -36,4 +43,8 @@ public class WalletEntryLabelsChangedEvent extends WalletChangedEvent { public Entry getSource(Entry entry) { return entrySourceMap.get(entry); } + + public boolean propagate() { + return propagate; + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/WalletLabels.java b/src/main/java/com/sparrowwallet/sparrow/io/WalletLabels.java new file mode 100644 index 00000000..13faf217 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/WalletLabels.java @@ -0,0 +1,296 @@ +package com.sparrowwallet.sparrow.io; + +import com.google.gson.Gson; +import com.sparrowwallet.drongo.KeyPurpose; +import com.sparrowwallet.drongo.OutputDescriptor; +import com.sparrowwallet.drongo.wallet.*; +import com.sparrowwallet.sparrow.AppServices; +import com.sparrowwallet.sparrow.EventManager; +import com.sparrowwallet.sparrow.event.KeystoreLabelsChangedEvent; +import com.sparrowwallet.sparrow.event.WalletEntryLabelsChangedEvent; +import com.sparrowwallet.sparrow.wallet.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.*; + +public class WalletLabels implements WalletImport, WalletExport { + private static final Logger log = LoggerFactory.getLogger(WalletLabels.class); + + private final List walletForms; + + public WalletLabels() { + this.walletForms = Collections.emptyList(); + } + + public WalletLabels(List walletForms) { + this.walletForms = walletForms; + } + + @Override + public boolean isEncrypted(File file) { + return false; + } + + @Override + public String getName() { + return "Wallet Labels"; + } + + @Override + public WalletModel getWalletModel() { + return WalletModel.LABELS; + } + + @Override + public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + List