add keycard shell importers for singlesig and multisig
What changed, and why it matters
This commit adds support for importing Bitcoin wallet data from a new hardware device called Keycard Shell. It is a straightforward feature addition: two new import handlers (one for single-signature wallets, one for multi-signature wallets), wiring them into the existing import menus, and adding device icons. There is no indication of a security bug, vulnerability, or malicious change in the diff.
No security action required. Review as normal feature code if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces KeycardShellSinglesig (extending KeystoneSinglesig) and KeycardShellMultisig (extending ColdcardMultisig), registers them in WalletImportDialog and HwAirgappedController, and adds four SVG icons. The new classes only override metadata methods (name, description, wallet model) and set a label/wallet model on the keystore returned by the parent importer. No cryptographic logic, parsing changes, network calls, or permission changes are present.
Changed components
WalletImportDialogHwAirgappedControllerKeycardShellSinglesig importerKeycardShellMultisig importerKeycard Shell wallet model iconsInspect captured patch +148 / −3
diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java
index eef5a15..207418f 100644
--- a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java
+++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java
@@ -52,7 +52,7 @@ public class WalletImportDialog extends Dialog<Wallet> {
importAccordion = new Accordion();
List<KeystoreFileImport> keystoreImporters = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new Jade(), new KeystoneSinglesig(), new PassportSinglesig(),
- new GordianSeedTool(), new SeedSigner(), new SpecterDIY(), new Krux(), new AirGapVault(), new Samourai());
+ new GordianSeedTool(), new SeedSigner(), new SpecterDIY(), new Krux(), new AirGapVault(), new Samourai(), new KeycardShellSinglesig());
for(KeystoreFileImport importer : keystoreImporters) {
if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) {
FileWalletKeystoreImportPane importPane = new FileWalletKeystoreImportPane(importer);
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/KeycardShellMultisig.java b/src/main/java/com/sparrowwallet/sparrow/io/KeycardShellMultisig.java
new file mode 100644
index 0000000..98dd093
--- /dev/null
+++ b/src/main/java/com/sparrowwallet/sparrow/io/KeycardShellMultisig.java
@@ -0,0 +1,53 @@
+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.InputStream;
+
+public class KeycardShellMultisig extends ColdcardMultisig {
+ @Override
+ public String getName() {
+ return "Keycard Shell Multisig";
+ }
+
+ @Override
+ public String getKeystoreImportDescription(int account) {
+ return "Import QR created on your Keycard Shell by selecting Connect software wallet > Bitcoin MultiSig";
+ }
+
+ @Override
+ public WalletModel getWalletModel() {
+ return WalletModel.KEYCARD_SHELL;
+ }
+
+ @Override
+ public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException {
+ Keystore keystore = super.getKeystore(scriptType, inputStream, password);
+ keystore.setLabel("Keycard Shell");
+ keystore.setWalletModel(getWalletModel());
+
+ return keystore;
+ }
+
+ @Override
+ public boolean isFileFormatAvailable() {
+ return false;
+ }
+
+ @Override
+ public boolean isWalletImportFileFormatAvailable() {
+ return false;
+ }
+
+ @Override
+ public boolean isWalletImportScannable() {
+ return false;
+ }
+
+ @Override
+ public boolean isWalletExportScannable() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/KeycardShellSinglesig.java b/src/main/java/com/sparrowwallet/sparrow/io/KeycardShellSinglesig.java
new file mode 100644
index 0000000..dc65b59
--- /dev/null
+++ b/src/main/java/com/sparrowwallet/sparrow/io/KeycardShellSinglesig.java
@@ -0,0 +1,44 @@
+package com.sparrowwallet.sparrow.io;
+
+import com.sparrowwallet.drongo.Network;
+import com.sparrowwallet.drongo.protocol.ScriptType;
+import com.sparrowwallet.drongo.wallet.Keystore;
+import com.sparrowwallet.drongo.wallet.WalletModel;
+
+import java.io.InputStream;
+
+public class KeycardShellSinglesig extends KeystoneSinglesig {
+ @Override
+ public String getName() {
+ return "Keycard Shell";
+ }
+
+ @Override
+ public String getKeystoreImportDescription(int account) {
+ return "Import QR created on your Keycard Shell by selecting Connect software wallet > Bitcoin" + (Network.getCanonical() == Network.TESTNET ? " Testnet" : "");
+ }
+
+ @Override
+ public WalletModel getWalletModel() {
+ return WalletModel.KEYCARD_SHELL;
+ }
+
+ @Override
+ public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException {
+ Keystore keystore = super.getKeystore(scriptType, inputStream, password);
+ keystore.setLabel("Keycard Shell");
+ keystore.setWalletModel(getWalletModel());
+
+ return keystore;
+ }
+
+ @Override
+ public boolean isFileFormatAvailable() {
+ return false;
+ }
+
+ @Override
+ public boolean isWalletImportFileFormatAvailable() {
+ 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 9a504de..f18588d 100644
--- a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java
@@ -26,9 +26,9 @@ public class HwAirgappedController extends KeystoreImportDetailController {
public void initializeView() {
List<KeystoreFileImport> fileImporters = Collections.emptyList();
if(getMasterController().getWallet().getPolicyType().equals(PolicyType.SINGLE)) {
- fileImporters = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new Jade(), new KeystoneSinglesig(), new PassportSinglesig(), new SeedSigner(), new GordianSeedTool(), new SpecterDIY(), new Krux(), new AirGapVault());
+ fileImporters = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new Jade(), new KeystoneSinglesig(), new PassportSinglesig(), new SeedSigner(), new GordianSeedTool(), new SpecterDIY(), new Krux(), new AirGapVault(), new KeycardShellSinglesig());
} else if(getMasterController().getWallet().getPolicyType().equals(PolicyType.MULTI)) {
- fileImporters = List.of(new Bip129(), new ColdcardMultisig(), new CoboVaultMultisig(), new JadeMultisig(), new KeystoneMultisig(), new PassportMultisig(), new SeedSigner(), new GordianSeedTool(), new SpecterDIY(), new Krux());
+ fileImporters = List.of(new Bip129(), new ColdcardMultisig(), new CoboVaultMultisig(), new JadeMultisig(), new KeystoneMultisig(), new PassportMultisig(), new SeedSigner(), new GordianSeedTool(), new SpecterDIY(), new Krux(), new KeycardShellMultisig());
}
for(KeystoreFileImport importer : fileImporters) {
diff --git a/src/main/resources/image/walletmodel/keycard-icon-invert.svg b/src/main/resources/image/walletmodel/keycard-icon-invert.svg
new file mode 100644
index 0000000..59c61ab
--- /dev/null
+++ b/src/main/resources/image/walletmodel/keycard-icon-invert.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="Livello_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 15 15">
+ <!-- Generator: Adobe Illustrator 29.5.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 141) -->
+ <defs>
+ <style>
+ .st0 {
+ fill: #dddada;
+ }
+ </style>
+ </defs>
+ <path class="st0" d="M10.12,12.67l-1.65-2.42,1.53-1.74.22-.25h-1.65l-.05.05-1.2,1.38v-2.22c1.18-.33,1.99-1.4,1.99-2.64,0-1.51-1.22-2.74-2.73-2.74s-2.72,1.23-2.72,2.74c0,1.23.84,2.32,2.01,2.64v5.43h1.45v-1.36l.28-.32,1.08,1.61.04.07h1.54l-.16-.24ZM6.6,3.37c.8,0,1.45.66,1.45,1.46s-.65,1.46-1.45,1.46-1.45-.65-1.45-1.46.65-1.46,1.45-1.46Z"/>
+</svg>
\ No newline at end of file
diff --git a/src/main/resources/image/walletmodel/keycard-icon.svg b/src/main/resources/image/walletmodel/keycard-icon.svg
new file mode 100644
index 0000000..1e45518
--- /dev/null
+++ b/src/main/resources/image/walletmodel/keycard-icon.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="Livello_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 15 15">
+ <!-- Generator: Adobe Illustrator 29.5.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 141) -->
+ <defs>
+ <style>
+ .st0 {
+ fill: #1d1d1b;
+ }
+ </style>
+ </defs>
+ <path class="st0" d="M10.12,12.67l-1.65-2.42,1.53-1.74.22-.25h-1.65l-.05.05-1.2,1.38v-2.22c1.18-.33,1.99-1.4,1.99-2.64,0-1.51-1.22-2.74-2.73-2.74s-2.72,1.23-2.72,2.74c0,1.23.84,2.32,2.01,2.64v5.43h1.45v-1.36l.28-.32,1.08,1.61.04.07h1.54l-.16-.24ZM6.6,3.37c.8,0,1.45.66,1.45,1.46s-.65,1.46-1.45,1.46-1.45-.65-1.45-1.46.65-1.46,1.45-1.46Z"/>
+</svg>
\ No newline at end of file
diff --git a/src/main/resources/image/walletmodel/keycard-invert.svg b/src/main/resources/image/walletmodel/keycard-invert.svg
new file mode 100644
index 0000000..221f1f8
--- /dev/null
+++ b/src/main/resources/image/walletmodel/keycard-invert.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="Livello_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 50 50">
+ <!-- Generator: Adobe Illustrator 29.5.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 141) -->
+ <defs>
+ <style>
+ .st0 {
+ fill: #d6d2d2;
+ }
+ </style>
+ </defs>
+ <path class="st0" d="M34.08,42.14l-5.46-8,5.08-5.76.73-.83h-5.45l-.15.17-3.99,4.56v-7.37c3.91-1.09,6.6-4.63,6.6-8.75,0-5.01-4.05-9.09-9.03-9.09s-9.03,4.08-9.03,9.09c0,4.08,2.78,7.7,6.66,8.76v18h4.8v-4.5l.93-1.05,3.58,5.34.15.22h5.12l-.53-.78ZM22.41,11.32c2.65,0,4.81,2.17,4.81,4.84s-2.16,4.84-4.81,4.84-4.81-2.17-4.81-4.84,2.16-4.84,4.81-4.84Z"/>
+</svg>
\ No newline at end of file
diff --git a/src/main/resources/image/walletmodel/keycard.svg b/src/main/resources/image/walletmodel/keycard.svg
new file mode 100644
index 0000000..9ed9622
--- /dev/null
+++ b/src/main/resources/image/walletmodel/keycard.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="Livello_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 50 50">
+ <!-- Generator: Adobe Illustrator 29.5.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 141) -->
+ <defs>
+ <style>
+ .st0 {
+ fill: #1d1d1b;
+ }
+ </style>
+ </defs>
+ <path class="st0" d="M34.08,42.14l-5.46-8,5.08-5.76.73-.83h-5.45l-.15.17-3.99,4.56v-7.37c3.91-1.09,6.6-4.63,6.6-8.75,0-5.01-4.05-9.09-9.03-9.09s-9.03,4.08-9.03,9.09c0,4.08,2.78,7.7,6.66,8.76v18h4.8v-4.5l.93-1.05,3.58,5.34.15.22h5.12l-.53-.78ZM22.41,11.32c2.65,0,4.81,2.17,4.81,4.84s-2.16,4.84-4.81,4.84-4.81-2.17-4.81-4.84,2.16-4.84,4.81-4.84Z"/>
+</svg>
\ No newline at end of file
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.