From e6de33034befbed01b05cb570a0312e9d160ca6b Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 16 Dec 2021 10:06:35 +0200 Subject: [PATCH] add seed tool as an airgapped hardware wallet --- drongo | 2 +- .../sparrow/control/FileImportPane.java | 16 ++++++- .../control/FileKeystoreImportPane.java | 2 +- .../sparrow/control/FileWalletImportPane.java | 2 +- .../control/FileWalletKeystoreImportPane.java | 2 +- .../sparrow/control/WalletImportDialog.java | 2 +- .../sparrow/io/KeystoreFileImport.java | 3 ++ .../sparrowwallet/sparrow/io/SeedTool.java | 45 ++++++++++++++++++ .../keystoreimport/HwAirgappedController.java | 4 +- src/main/resources/image/seedtool.png | Bin 0 -> 3008 bytes src/main/resources/image/seedtool@2x.png | Bin 0 -> 5212 bytes src/main/resources/image/seedtool@3x.png | Bin 0 -> 10101 bytes 12 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/io/SeedTool.java create mode 100644 src/main/resources/image/seedtool.png create mode 100644 src/main/resources/image/seedtool@2x.png create mode 100644 src/main/resources/image/seedtool@3x.png diff --git a/drongo b/drongo index 645be6a5..c1f6a124 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 645be6a5a5a7b434ea9a3991ae85e0b8b6d4f50b +Subproject commit c1f6a1245e4632deb19af64faaa6ffaaca475313 diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java index e7d6f121..4f2566a8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileImportPane.java @@ -42,12 +42,14 @@ public abstract class FileImportPane extends TitledDescriptionPane { protected ButtonBase importButton; private final SimpleStringProperty password = new SimpleStringProperty(""); private final boolean scannable; + private final boolean fileFormatAvailable; protected List wallets; - public FileImportPane(FileImport importer, String title, String description, String content, String imageUrl, boolean scannable) { + public FileImportPane(FileImport importer, String title, String description, String content, String imageUrl, boolean scannable, boolean fileFormatAvailable) { super(title, description, content, imageUrl); this.importer = importer; this.scannable = scannable; + this.fileFormatAvailable = fileFormatAvailable; buttonBox.getChildren().clear(); buttonBox.getChildren().add(createButton()); @@ -55,7 +57,7 @@ public abstract class FileImportPane extends TitledDescriptionPane { @Override protected Control createButton() { - if(scannable) { + if(scannable && fileFormatAvailable) { ToggleButton scanButton = new ToggleButton("Scan..."); Glyph cameraGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.CAMERA); cameraGlyph.setFontSize(12); @@ -76,6 +78,16 @@ public abstract class FileImportPane extends TitledDescriptionPane { SegmentedButton segmentedButton = new SegmentedButton(); segmentedButton.getButtons().addAll(scanButton, fileButton); return segmentedButton; + } else if(scannable) { + importButton = new Button("Scan..."); + Glyph cameraGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.CAMERA); + cameraGlyph.setFontSize(12); + importButton.setGraphic(cameraGlyph); + importButton.setAlignment(Pos.CENTER_RIGHT); + importButton.setOnAction(event -> { + importQR(); + }); + return importButton; } else { importButton = new Button("Import File..."); importButton.setAlignment(Pos.CENTER_RIGHT); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileKeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileKeystoreImportPane.java index 3006eb6d..dfc09ac1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileKeystoreImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileKeystoreImportPane.java @@ -16,7 +16,7 @@ public class FileKeystoreImportPane extends FileImportPane { private final KeyDerivation requiredDerivation; public FileKeystoreImportPane(Wallet wallet, KeystoreFileImport importer, KeyDerivation requiredDerivation) { - super(importer, importer.getName(), "Keystore import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isKeystoreImportScannable()); + super(importer, importer.getName(), "Keystore import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isKeystoreImportScannable(), importer.isFileFormatAvailable()); this.wallet = wallet; this.importer = importer; this.requiredDerivation = requiredDerivation; diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletImportPane.java index 8fb3b055..8d030d5d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletImportPane.java @@ -12,7 +12,7 @@ public class FileWalletImportPane extends FileImportPane { private final WalletImport importer; public FileWalletImportPane(WalletImport importer) { - super(importer, importer.getName(), "Wallet import", importer.getWalletImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isWalletImportScannable()); + super(importer, importer.getName(), "Wallet import", importer.getWalletImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isWalletImportScannable(), true); this.importer = importer; } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java index 66c9920d..471b79e2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java @@ -40,7 +40,7 @@ public class FileWalletKeystoreImportPane extends FileImportPane { private byte[] fileBytes; public FileWalletKeystoreImportPane(KeystoreFileImport importer) { - super(importer, importer.getName(), "Wallet import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isKeystoreImportScannable()); + super(importer, importer.getName(), "Wallet import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isKeystoreImportScannable(), importer.isFileFormatAvailable()); this.importer = importer; } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java index 65c9a733..c3959491 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java @@ -48,7 +48,7 @@ public class WalletImportDialog extends Dialog { AnchorPane.setRightAnchor(scrollPane, 0.0); importAccordion = new Accordion(); - List keystoreImporters = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new KeystoneSinglesig(), new PassportSinglesig(), new SpecterDIY()); + List keystoreImporters = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new KeystoneSinglesig(), new PassportSinglesig(), new SeedTool(), new SpecterDIY()); for(KeystoreFileImport importer : keystoreImporters) { FileWalletKeystoreImportPane importPane = new FileWalletKeystoreImportPane(importer); importAccordion.getPanes().add(importPane); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/KeystoreFileImport.java b/src/main/java/com/sparrowwallet/sparrow/io/KeystoreFileImport.java index aff61c42..c0e3827f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/KeystoreFileImport.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/KeystoreFileImport.java @@ -8,4 +8,7 @@ import java.io.InputStream; public interface KeystoreFileImport extends KeystoreImport, FileImport { Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException; boolean isKeystoreImportScannable(); + default boolean isFileFormatAvailable() { + return true; + }; } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/SeedTool.java b/src/main/java/com/sparrowwallet/sparrow/io/SeedTool.java new file mode 100644 index 00000000..bc865e73 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/SeedTool.java @@ -0,0 +1,45 @@ +package com.sparrowwallet.sparrow.io; + +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.WalletModel; + +import java.io.File; +import java.io.InputStream; + +public class SeedTool implements KeystoreFileImport { + @Override + public boolean isEncrypted(File file) { + return false; + } + + @Override + public String getName() { + return "Seed Tool"; + } + + @Override + public WalletModel getWalletModel() { + return WalletModel.SEED_TOOL; + } + + @Override + public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException { + throw new ImportException("Only QR imports are supported."); + } + + @Override + public boolean isKeystoreImportScannable() { + return true; + } + + @Override + public String getKeystoreImportDescription() { + return "Select your seed and scan the QR code created by Authenticate > Derive Key > Other Key Derivations > Account Descriptor. Click the share icon at the bottom to show the QR."; + } + + @Override + public boolean isFileFormatAvailable() { + return false; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java index c35b7342..c6bf736e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java +++ b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java @@ -18,9 +18,9 @@ public class HwAirgappedController extends KeystoreImportDetailController { public void initializeView() { List importers = Collections.emptyList(); if(getMasterController().getWallet().getPolicyType().equals(PolicyType.SINGLE)) { - importers = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new KeystoneSinglesig(), new PassportSinglesig(), new SeedSigner(), new SpecterDIY()); + importers = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new KeystoneSinglesig(), new PassportSinglesig(), new SeedSigner(), new SeedTool(), new SpecterDIY()); } else if(getMasterController().getWallet().getPolicyType().equals(PolicyType.MULTI)) { - importers = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new KeystoneMultisig(), new PassportMultisig(), new SeedSigner(), new SpecterDIY()); + importers = List.of(new ColdcardMultisig(), new CoboVaultMultisig(), new KeystoneMultisig(), new PassportMultisig(), new SeedSigner(), new SeedTool(), new SpecterDIY()); } for(KeystoreFileImport importer : importers) { diff --git a/src/main/resources/image/seedtool.png b/src/main/resources/image/seedtool.png new file mode 100644 index 0000000000000000000000000000000000000000..a0964cb1f113cb71906c8165c4d3b3e34a64549d GIT binary patch literal 3008 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/image/seedtool@3x.png b/src/main/resources/image/seedtool@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..1eee9972c19b67ac1359b9b39cf4da66370a6bc5 GIT binary patch literal 10101 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 literal 0 HcmV?d00001