do not offer copy and qr export actions for an extended private key pasted into the keystore xpub field
What changed, and why it matters
This commit fixes a UI behavior in Sparrow Wallet where a user could accidentally paste a secret extended private key (xprv) into the public-key-only 'xpub' field of a watch-only wallet. Previously, the app treated it as valid, offered copy/QR-code export actions, and could leak the private key through those export paths. Now the app rejects xprv input in that field, removes the export context menu, and shows a validation error.
Users should upgrade to a Sparrow Wallet release containing this commit and avoid pasting xprv/extended private keys into the xpub field. Developers should review whether similar public-key-only fields elsewhere in the application also reject private key material.
Security signals we found
Private key material accepted in public-key field before fix
Copy and QR export actions exposed for private key material
Validation now rejects extended private keys in watch-only xpub field
Context menu cleared when input is not pubkey-only
Evidence from the diff
In KeystoreController.java, the xpub text listener now parses the pasted value with ExtendedKey.fromDescriptor() and only accepts it if extendedKey.getKey().isPubKeyOnly() is true. If the key is a private extended key, valid is false, setXpubContext() is not called, and the silent-payment scan context menu is cleared. A new validator rule explicitly reports ‘An extended private key cannot be used in a watch only wallet’. This prevents the copy and QR export actions from being offered for xprv material pasted into the keystore xpub field.
Changed components
src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.javaWatch-only wallet keystore xpub fieldSilent Payment scan address context menuForm validation supportInspect captured patch +6 / −3
### src/main/java/com/sparrowwallet/sparrow/wallet/KeystoreController.java
@@ -203,11 +203,11 @@ public void initializeView() {
}
});
xpub.textProperty().addListener((observable, oldValue, newValue) -> {
- boolean valid = ExtendedKey.isValid(newValue);
+ ExtendedKey extendedKey = ExtendedKey.isValid(newValue) ? ExtendedKey.fromDescriptor(newValue) : null;
+ boolean valid = extendedKey != null && extendedKey.getKey().isPubKeyOnly();
if(valid) {
- ExtendedKey extendedKey = ExtendedKey.fromDescriptor(newValue);
setXpubContext(extendedKey);
- if(!extendedKey.equals(keystore.getExtendedPublicKey()) && extendedKey.getKey().isPubKeyOnly()) {
+ if(!extendedKey.equals(keystore.getExtendedPublicKey())) {
keystore.setExtendedPublicKey(extendedKey);
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_XPUB));
@@ -234,6 +234,8 @@ public void initializeView() {
keystore.setSilentPaymentScanAddress(silentPaymentScanAddress);
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet(), SettingsChangedEvent.Type.KEYSTORE_SP_SCAN));
}
+ } else {
+ spScan.setContextMenu(null);
}
});
@@ -327,6 +329,7 @@ private void setupValidation() {
validationSupport.registerValidator(xpub, Validator.combine(
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, Network.get().getXpubHeader().getDisplayName() + " is required", getWalletForm().getWallet().getPolicyType() != PolicyType.SINGLE_SP && newValue.trim().isEmpty()),
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, Network.get().getXpubHeader().getDisplayName() + " is invalid", getWalletForm().getWallet().getPolicyType() != PolicyType.SINGLE_SP && !ExtendedKey.isValid(newValue)),
+ (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "An extended private key cannot be used in a watch only wallet", getWalletForm().getWallet().getPolicyType() != PolicyType.SINGLE_SP && ExtendedKey.isValid(newValue) && !ExtendedKey.fromDescriptor(newValue).getKey().isPubKeyOnly()),
(Control c, String newValue) -> {
if(getWalletForm().getWallet().getPolicyType() == PolicyType.SINGLE_SP || !ExtendedKey.isValid(newValue)) {
return new ValidationResult();Why this scored 59/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.