reject extended private keys when creating a terminal watch only wallet, and show import errors
What changed, and why it matters
This commit fixes a bug in Sparrow Wallet's terminal (command-line) watch-only wallet creation. Previously, a user could accidentally paste an extended private key (xprv) into a dialog meant only for public keys or output descriptors, and the wallet would silently fail or create an empty wallet. Now the app rejects xprv keys with a clear error message and also shows import errors to the user instead of logging them silently.
Users building from source or using terminal Sparrow should update to include this commit. Review whether any other wallet-import paths (GUI, script imports) similarly allow xprv input where only xpub/output descriptors are expected.
Security signals we found
Prevents accidental import of extended private keys into watch-only wallet context
Surfaces previously swallowed import errors to the user via error dialog
Changes exception handling from ImportException-only to all exceptions
Evidence from the diff
In WatchOnlyDialog.java, the getWallets() method now strips whitespace, validates whether the input is an ExtendedKey, and explicitly throws ImportException if the key is not public-key-only. It also restructures parsing so xpub and output-descriptor paths are handled separately, and failures are surfaced to the user. NewWalletDialog.java now catches Exception (not just ImportException) and displays an error dialog with the exception message, preventing silent failures.
Changed components
Sparrow Wallet terminal UINewWalletDialog.javaWatchOnlyDialog.javaInspect captured patch +19 / −15
diff --git a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/NewWalletDialog.java b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/NewWalletDialog.java
index a21b89b..a60b416 100644
--- a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/NewWalletDialog.java
+++ b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/NewWalletDialog.java
@@ -47,8 +47,9 @@ public abstract class NewWalletDialog extends DialogWindow {
try {
discoverAndSaveWallet(getWallets());
- } catch(ImportException e) {
+ } catch(Exception e) {
log.error("Cannot import wallet", e);
+ showErrorDialog("Error Creating Wallet", e.getMessage());
}
}
diff --git a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/WatchOnlyDialog.java b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/WatchOnlyDialog.java
index bd974d4..5be2c30 100644
--- a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/WatchOnlyDialog.java
+++ b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/WatchOnlyDialog.java
@@ -93,23 +93,26 @@ public class WatchOnlyDialog extends NewWalletDialog {
@Override
protected List<Wallet> getWallets() throws ImportException {
- try {
- return getWalletFromXpub();
- } catch(Exception e1) {
- try {
- return getWalletFromOutputDescriptor();
- } catch(Exception e2) {
- log.error("Could not determine wallet from descriptor: " + descriptor.getText(), e2);
+ String text = descriptor.getText().replaceAll("\\s+", "");
+
+ if(ExtendedKey.isValid(text)) {
+ ExtendedKey extendedKey = ExtendedKey.fromDescriptor(text);
+ if(!extendedKey.getKey().isPubKeyOnly()) {
+ throw new ImportException("An extended private key cannot be used to create a watch only wallet. Enter an extended public key, or an output descriptor if the private key is intended to be imported.");
}
+
+ return getWalletFromXpub(extendedKey, ExtendedKey.Header.fromExtendedKey(text));
}
- return Collections.emptyList();
+ try {
+ return getWalletFromOutputDescriptor(text);
+ } catch(Exception e) {
+ log.error("Could not determine wallet from descriptor: " + text, e);
+ throw new ImportException("Could not determine wallet from descriptor: " + e.getMessage(), e);
+ }
}
- private List<Wallet> getWalletFromXpub() {
- ExtendedKey xpub = ExtendedKey.fromDescriptor(descriptor.getText().replaceAll("\\s+", ""));
- ExtendedKey.Header header = ExtendedKey.Header.fromExtendedKey(descriptor.getText());
-
+ private List<Wallet> getWalletFromXpub(ExtendedKey xpub, ExtendedKey.Header header) {
Set<ScriptType> scriptTypes = new LinkedHashSet<>();
scriptTypes.add(ScriptType.P2WPKH);
scriptTypes.add(header.getDefaultScriptType());
@@ -136,8 +139,8 @@ public class WatchOnlyDialog extends NewWalletDialog {
return wallets;
}
- private List<Wallet> getWalletFromOutputDescriptor() {
- OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(descriptor.getText().replaceAll("\\s+", ""));
+ private List<Wallet> getWalletFromOutputDescriptor(String text) {
+ OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(text);
Wallet wallet = outputDescriptor.toWallet();
wallet.setName(walletName);
return List.of(wallet);
Why this scored 42/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.