refuse bitbox02 keystore import and discovery for legacy p2sh and p2pkh wallets it cannot sign for, and hide those script types from the device import menus
What changed, and why it matters
This commit tightens how Sparrow Wallet handles BitBox02 hardware wallets when working with older Bitcoin address formats (legacy P2PKH and P2SH). Previously, the app could let a user import or discover a wallet that the BitBox02 cannot actually sign transactions for, which could lead to funds being stuck or user confusion. The fix filters out unsupported script types from device menus and shows a clear error if a user tries to import or discover such a wallet.
No immediate security response required; this is a hardening/usability fix. Users relying on BitBox02 should ensure they are on a Sparrow version containing this commit to avoid accidentally creating wallets with unsupported legacy script types. Review the updated `lark` submodule changes for completeness of `supportsScriptType` mappings.
Security signals we found
Prevents user from configuring a signing device for wallet types the device cannot sign for
Could avoid funds becoming unspendable or requiring complex recovery if a user unknowingly imported an unsupported legacy script type
Replaces hard-coded device-specific logic with a generic capability model, reducing future similar issues
Adds explicit user-facing error messages for unsupported script types
Evidence from the diff
The patch adds a supportsScriptType(ScriptType) method to the Device class, populated from the underlying HardwareClient (updated in the lark submodule). It then uses this capability check to: (1) filter PolicyAndScriptType entries when building import menus, (2) filter script types for existing wallets during import, (3) block keystore import and discovery with an explicit error when the wallet’s script type is unsupported, and (4) replace a hard-coded BitBox02 P2PKH exclusion in wallet discovery with the generic capability filter. This prevents users from setting up BitBox02-backed wallets using P2PKH or P2SH that the device firmware cannot sign.
Changed components
Sparrow Wallet desktop applicationBitBox02 hardware wallet integrationDevice import/discovery UI flowslark hardware wallet library submoduleInspect captured patch +23 / −7
### lark
@@ -1 +1 @@
-Subproject commit a34efcc647438fc438acced493001077e71fcb13
+Subproject commit b15a676e592f82914c1bc662481ea8d8f0c90cd0
### src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java
@@ -309,7 +309,9 @@ private void createImportButton() {
List<PolicyAndScriptType> types = new ArrayList<>();
for(PolicyType policyType : List.of(PolicyType.SINGLE_HD, PolicyType.SINGLE_SP)) {
for(ScriptType scriptType : ScriptType.getAddressableScriptTypes(policyType)) {
- types.add(new PolicyAndScriptType(policyType, scriptType));
+ if(device.supportsScriptType(scriptType)) {
+ types.add(new PolicyAndScriptType(policyType, scriptType));
+ }
}
}
for(PolicyAndScriptType type : types) {
@@ -323,7 +325,7 @@ private void createImportButton() {
importMenuButton.getItems().add(item);
}
} else {
- List<ScriptType> scriptTypes = ScriptType.getScriptTypesForPolicyType(wallet.getPolicyType());
+ List<ScriptType> scriptTypes = ScriptType.getScriptTypesForPolicyType(wallet.getPolicyType()).stream().filter(device::supportsScriptType).toList();
for(ScriptType scriptType : scriptTypes) {
MenuItem item = new MenuItem(scriptType.getDescription());
final List<ChildNumber> derivation = scriptType.getDefaultDerivation();
@@ -753,6 +755,9 @@ private void importKeystore(List<ChildNumber> derivation) {
setError("Import Error", e.getMessage());
importButton.setDisable(false);
}
+ } else if(wallet.getScriptType() != null && !device.supportsScriptType(wallet.getScriptType())) {
+ setError("Unsupported script type", "The " + device.getModel().toDisplayString() + " cannot sign for " + wallet.getScriptType().getDescription() + " wallets.");
+ importButton.setDisable(false);
} else if(device.getFingerprint() == null) {
Hwi.EnumerateService enumerateService = new Hwi.EnumerateService(passphrase.get());
enumerateService.setOnSucceeded(workerStateEvent -> {
@@ -997,10 +1002,7 @@ private void discoverWallet() {
List<StandardAccount> discoveryAccounts = new ArrayList<>(Arrays.asList(StandardAccount.values()).subList(0, optRange.get() + 1));
Map<Hwi.WalletType, String> derivationPaths = new LinkedHashMap<>();
- List<ScriptType> scriptTypes = new ArrayList<>(ScriptType.getAddressableScriptTypes(PolicyType.SINGLE_HD));
- if(device.getModel() == WalletModel.BITBOX_02) {
- scriptTypes.remove(ScriptType.P2PKH);
- }
+ List<ScriptType> scriptTypes = ScriptType.getAddressableScriptTypes(PolicyType.SINGLE_HD).stream().filter(device::supportsScriptType).toList();
for(ScriptType scriptType : scriptTypes) {
for(StandardAccount discoveryAccount : discoveryAccounts) {
derivationPaths.put(new Hwi.WalletType(scriptType, discoveryAccount), KeyDerivation.writePath(scriptType.getDefaultDerivation(discoveryAccount.getAccountNumber())));
@@ -1083,6 +1085,11 @@ private void discoverKeystores() {
return;
}
+ if(!device.supportsScriptType(wallet.getScriptType())) {
+ setError("Unsupported script type", "The " + device.getModel().toDisplayString() + " cannot sign for " + wallet.getScriptType().getDescription() + " wallets.");
+ return;
+ }
+
discoverKeystoresButton.setDisable(true);
discoverKeystoresButton.setMaxHeight(discoverKeystoresButton.getHeight());
ProgressIndicator progressIndicator = new ProgressIndicator(0);
### src/main/java/com/sparrowwallet/sparrow/io/Device.java
@@ -1,8 +1,11 @@
package com.sparrowwallet.sparrow.io;
+import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.WalletModel;
import com.sparrowwallet.lark.HardwareClient;
+import java.util.Arrays;
+import java.util.List;
import java.util.Objects;
public class Device {
@@ -15,6 +18,7 @@ public class Device {
private boolean card;
private String[][] warnings;
private String error;
+ private List<ScriptType> supportedScriptTypes;
public String getType() {
return type;
@@ -94,6 +98,10 @@ public boolean containsWarning(String warning) {
return false;
}
+ public boolean supportsScriptType(ScriptType scriptType) {
+ return supportedScriptTypes == null || supportedScriptTypes.contains(scriptType);
+ }
+
public String getError() {
return error;
}
@@ -135,6 +143,7 @@ public static Device fromHardwareClient(HardwareClient hardwareClient) {
device.card = hardwareClient.card();
device.warnings = hardwareClient.warnings();
device.error = hardwareClient.error();
+ device.supportedScriptTypes = Arrays.stream(ScriptType.values()).filter(hardwareClient::supportsScriptType).toList();
return device;
}
}Why this scored 36/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.