disable the sign button in the sign message dialog while an encrypted wallet is decrypting
What changed, and why it matters
This commit fixes a user-interface timing issue in Sparrow Wallet's 'Sign Message' feature. When a user tried to sign a message with an encrypted wallet, the 'Sign' button stayed active while the wallet was still decrypting in the background. If the user clicked it again during that brief window, the app could attempt to sign twice or behave unexpectedly. The patch now disables the Sign button while decryption is in progress and re-enables it only when decryption finishes or fails.
Review whether other dialogs with asynchronous decryption (e.g., transaction signing, PSBT signing) have the same missing busy-state guard. Consider a broader pattern for disabling primary action buttons while background services run.
Security signals we found
UI race condition between user action and background decryption
Repeated sign button clicks possible during asynchronous wallet decryption
Potential double-signing or inconsistent dialog state
No cryptographic flaw, but UX-level issue that could produce unexpected signatures
Evidence from the diff
MessageSignDialog previously looked up the sign button as a local variable and only disabled it based on canSign/address validity. It did not account for the asynchronous decryptWalletService. The patch promotes signButton to a field, adds setDecrypting(boolean), and calls setDecrypting(true) before starting the service and setDecrypting(false) on both success and failure. The button is disabled during decryption and re-enabled only if decryption completes and the address is still valid.
Changed components
src/main/java/com/sparrowwallet/sparrow/control/MessageSignDialog.javaSign Message dialogdecryptWalletService asynchronous flowInspect captured patch +11 / −1
### src/main/java/com/sparrowwallet/sparrow/control/MessageSignDialog.java
@@ -63,6 +63,7 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
private final ToggleButton formatBip322;
private final Wallet wallet;
private WalletNode walletNode;
+ private Button signButton;
private boolean canSign;
private boolean closed;
@@ -218,7 +219,7 @@ public MessageSignDialog(Wallet wallet, WalletNode walletNode, String title, Str
Node showQrButton = dialogPane.lookupButton(showQrButtonType);
- Button signButton = (Button) dialogPane.lookupButton(signButtonType);
+ signButton = (Button) dialogPane.lookupButton(signButtonType);
signButton.setDisable(!canSign);
signButton.setGraphic(getGlyph(getSignGlyph()));
signButton.setGraphicTextGap(5);
@@ -765,16 +766,25 @@ public void openWallets(OpenWalletsEvent event) {
Wallet decryptedWallet = decryptWalletService.getValue();
signUnencryptedKeystore(decryptedWallet);
decryptedWallet.clearPrivate();
+ setDecrypting(false);
});
decryptWalletService.setOnFailed(workerStateEvent -> {
EventManager.get().post(new StorageEvent(storage.getWalletId(wallet), TimedEvent.Action.END, "Failed"));
+ setDecrypting(false);
AppServices.showErrorDialog("Incorrect Password", "The password was incorrect.");
});
EventManager.get().post(new StorageEvent(storage.getWalletId(wallet), TimedEvent.Action.START, "Decrypting wallet..."));
+ setDecrypting(true);
decryptWalletService.start();
}
}
+ private void setDecrypting(boolean decrypting) {
+ if(signButton != null) {
+ signButton.setDisable(decrypting || !isValidAddress());
+ }
+ }
+
private class MessageSignDialogPane extends DialogPane {
@Override
protected Node createButton(ButtonType buttonType) {Why this scored 35/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.