add all singlesig importers to sp airgapped keystore import
What changed, and why it matters
This commit expands the list of hardware-wallet and air-gapped importers that Sparrow Wallet shows when a user creates a single-signature wallet using a 'single SP' policy type. Previously only ColdcardSinglesig was offered; now all singlesig file importers (Jade, Keystone, Passport, SeedSigner, etc.) and all card importers (Tapsigner, Satochip, etc.) are available. The change also broadens an exception handler in SpecterDIY so that non-IO errors during import are reported to the user instead of crashing or being swallowed. There is no direct security vulnerability in the diff; it is a feature/robustness improvement.
No security action required. Treat as a normal feature/UX commit. If reviewing for release notes, note that additional air-gapped and card-based hardware wallets are now supported for single-signature 'single SP' wallets.
Security signals we found
No input validation removed
No cryptographic operations changed
No privilege escalation or secret exposure introduced
Exception handling broadened, which can improve error reporting
UI importer list expanded for SINGLE_SP policy type
Evidence from the diff
HwAirgappedController.initializeView() now treats PolicyType.SINGLE_SP the same as SINGLE_HD for file importers, and removes the separate SINGLE_SP branch that only listed ColdcardSinglesig. It also removes the policy-type guard around card importers, making Tapsigner/Satochip/Satschip/Keycard available for SINGLE_SP wallets. In SpecterDIY.java the catch clause changes from IOException to Exception, and the internal ‘wrong keystore count’ check now throws IllegalArgumentException instead of ImportException so it is caught and re-thrown as a user-facing ImportException. These are UI/availability and error-handling changes, not cryptographic or trust-model changes.
Changed components
src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.javasrc/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.javaInspect captured patch +5 / −10
diff --git a/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java b/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java
index 30db6b3..c7465f8 100644
--- a/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java
+++ b/src/main/java/com/sparrowwallet/sparrow/io/SpecterDIY.java
@@ -26,16 +26,16 @@ public class SpecterDIY implements KeystoreFileImport, WalletExport {
Wallet wallet = outputDescriptor.toWallet();
if(wallet.getKeystores().size() != 1) {
- throw new ImportException("Could not determine keystore from import");
+ throw new IllegalArgumentException("Could not determine keystore from import");
}
- Keystore keystore = wallet.getKeystores().get(0);
+ Keystore keystore = wallet.getKeystores().getFirst();
keystore.setLabel(getName());
keystore.setWalletModel(getWalletModel());
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
return keystore;
- } catch(IOException e) {
+ } catch(Exception e) {
throw new ImportException("Error getting " + getName() + " keystore", e);
}
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java
index 39cdeb9..78afe00 100644
--- a/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java
+++ b/src/main/java/com/sparrowwallet/sparrow/keystoreimport/HwAirgappedController.java
@@ -26,12 +26,10 @@ public class HwAirgappedController extends KeystoreImportDetailController {
public void initializeView() {
List<KeystoreFileImport> fileImporters = Collections.emptyList();
- if(getMasterController().getWallet().getPolicyType().equals(PolicyType.SINGLE_HD)) {
+ if(getMasterController().getWallet().getPolicyType().equals(PolicyType.SINGLE_HD) || getMasterController().getWallet().getPolicyType().equals(PolicyType.SINGLE_SP)) {
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_HD)) {
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());
- } else if(getMasterController().getWallet().getPolicyType().equals(PolicyType.SINGLE_SP)) {
- fileImporters = List.of(new ColdcardSinglesig());
}
for(KeystoreFileImport importer : fileImporters) {
@@ -43,10 +41,7 @@ public class HwAirgappedController extends KeystoreImportDetailController {
}
}
- List<KeystoreCardImport> cardImporters = Collections.emptyList();
- if(getMasterController().getWallet().getPolicyType().equals(PolicyType.SINGLE_HD) || getMasterController().getWallet().getPolicyType().equals(PolicyType.MULTI_HD)) {
- cardImporters = List.of(new Tapsigner(), new Satochip(), new Satschip(), new Keycard());
- }
+ List<KeystoreCardImport> cardImporters = List.of(new Tapsigner(), new Satochip(), new Satschip(), new Keycard());
for(KeystoreCardImport importer : cardImporters) {
if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) {
CardImportPane importPane = new CardImportPane(getMasterController().getWallet(), importer, getMasterController().getDefaultDerivation(), getMasterController().getRequiredDerivation());
Why this scored 19/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.