disable transaction version and locktime editing once signed
What changed, and why it matters
This commit tightens Sparrow Wallet's user interface so that once a Bitcoin transaction has been signed, the user can no longer edit its version number or locktime fields. Before the change, the form relied on a simpler 'isEditable' check that could leave these fields enabled after signatures were added. Editing those fields after signing would change the transaction's hash (TXID), making the existing signatures invalid and potentially confusing the user into broadcasting a transaction that wallets or the network will reject. The fix centralizes the disable logic and makes editability explicitly depend on whether the transaction already contains script signatures or witness data.
Treat as a low-severity defensive hardening fix. Verify that all other post-signing editable fields (inputs, outputs, fee fields, etc.) are similarly guarded by the updated isEditable() logic, and that no alternate code path can bypass the UI disable state. Consider adding an automated UI test that asserts version and locktime controls are disabled once a transaction has scriptSigs or witnesses.
Security signals we found
UI field left editable after cryptographic signing could allow user to invalidate signatures
Transaction malleability / signature invalidation via post-sign field edits
Centralization of editability logic reduces chance of inconsistent disable states
No cryptographic or serialization hardening; purely defensive UX control
Evidence from the diff
The patch changes TransactionForm.isEditable() to return false whenever getTransaction().hasScriptSigs() or getTransaction().hasWitnesses() is true. It also refactors HeadersController to use a new updateEditable(boolean) helper that disables the version TextField and all locktime radio buttons/fields when the form is not editable. Previously, version was disabled only by !headersForm.isEditable() during initialization, and locktime controls were disabled by a combination of isEditable() and isLocktimeSequenceEnabled(). The new helper is additionally called from updateBlockchainForm(false) so that switching to the blockchain view also locks these fields. The change is UI-level: it prevents the user from modifying fields that would invalidate signatures, but it does not alter transaction serialization or signing code itself.
Changed components
HeadersController.javaTransactionForm.javaTransaction version input fieldTransaction locktime input fields and radio buttonsPost-signing transaction detail viewInspect captured patch +16 / −16
### src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java
@@ -294,7 +294,6 @@ private void initializeView() {
EventManager.get().post(new TransactionChangedEvent(tx));
}
});
- version.setDisable(!headersForm.isEditable());
updateType();
@@ -438,13 +437,7 @@ public LocalDate fromString(String value) {
}
});
- boolean locktimeEnabled = headersForm.getTransaction().isLocktimeSequenceEnabled();
- locktimeNoneType.setDisable(!headersForm.isEditable() || !locktimeEnabled);
- locktimeBlockType.setDisable(!headersForm.isEditable() || !locktimeEnabled);
- locktimeDateType.setDisable(!headersForm.isEditable() || !locktimeEnabled);
- locktimeBlock.setDisable(!headersForm.isEditable() || !locktimeEnabled);
- locktimeDate.setDisable(!headersForm.isEditable() || !locktimeEnabled);
- locktimeCurrentHeight.setDisable(!headersForm.isEditable() || !locktimeEnabled);
+ updateEditable(headersForm.isEditable());
updateSize();
@@ -813,9 +806,22 @@ private BlockTransactionHashIndex getBlockTransactionOutput(TransactionOutput tx
return null;
}
+ private void updateEditable(boolean editable) {
+ version.setDisable(!editable);
+
+ boolean locktimeEnabled = editable && headersForm.getTransaction().isLocktimeSequenceEnabled();
+ locktimeNoneType.setDisable(!locktimeEnabled);
+ locktimeBlockType.setDisable(!locktimeEnabled);
+ locktimeDateType.setDisable(!locktimeEnabled);
+ locktimeBlock.setDisable(!locktimeEnabled);
+ locktimeDate.setDisable(!locktimeEnabled);
+ locktimeCurrentHeight.setDisable(!locktimeEnabled);
+ }
+
private void updateBlockchainForm(BlockTransaction blockTransaction, Integer currentHeight) {
signaturesForm.setVisible(false);
blockchainForm.setVisible(true);
+ updateEditable(false);
if(Sha256Hash.ZERO_HASH.equals(blockTransaction.getBlockHash()) && blockTransaction.getHeight() == 0 && headersForm.getSigningWallet() == null) {
//A zero block hash indicates that this blocktransaction is incomplete and the height is likely incorrect if we are not sending a tx
@@ -1495,13 +1501,7 @@ public void close() {
public void transactionChanged(TransactionChangedEvent event) {
if(headersForm.getTransaction().equals(event.getTransaction())) {
updateTxId();
- boolean locktimeEnabled = headersForm.isEditable() && headersForm.getTransaction().isLocktimeSequenceEnabled();
- locktimeNoneType.setDisable(!locktimeEnabled);
- locktimeBlockType.setDisable(!locktimeEnabled);
- locktimeBlock.setDisable(!locktimeEnabled);
- locktimeDateType.setDisable(!locktimeEnabled);
- locktimeDate.setDisable(!locktimeEnabled);
- locktimeCurrentHeight.setDisable(!locktimeEnabled);
+ updateEditable(headersForm.isEditable());
}
}
### src/main/java/com/sparrowwallet/sparrow/transaction/TransactionForm.java
@@ -130,7 +130,7 @@ public boolean isEditable() {
return txdata.getSigningWallet() == null;
}
- return true;
+ return !getTransaction().hasScriptSigs() && !getTransaction().hasWitnesses();
}
public boolean isTransactionFinalized() {Why this scored 44/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.