From 078af174afd19ed78d6c378d0898ae252db339d5 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 16 Jul 2026 12:32:29 +0200 Subject: [PATCH] check all open tabs when verifying a scanned or loaded transaction matches the originating psbt --- .../sparrowwallet/sparrow/AppController.java | 47 +++++++++++++++---- .../sparrow/event/ViewPSBTEvent.java | 16 ++++++- .../sparrow/event/ViewTransactionEvent.java | 12 +++++ .../transaction/HeadersController.java | 14 +----- 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index ab15c02b..1ccf222c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -1951,20 +1951,14 @@ public class AppController implements Initializable { if(PSBT.isPSBT(bytes)) { //Don't verify signatures here - provided PSBT may omit UTXO data that can be found when combining with an existing PSBT PSBT psbt = new PSBT(bytes, false); - if(contextPsbt == null || contextPsbt.matches(psbt)) { + if(verifyTransactionContext(contextPsbt, null, psbt, "loaded")) { addTransactionTab(name, file, psbt); - } else { - AppServices.showErrorDialog("Mismatched Transaction", "The loaded transaction does not match the transaction in this tab.\n\nCheck that the correct transaction was signed and exported from the signing device."); } } else if(Transaction.isTransaction(bytes)) { try { Transaction transaction = new Transaction(bytes); - if(contextPsbt == null || contextPsbt.matches(transaction)) { + if(verifyTransactionContext(contextPsbt, transaction, null, "loaded")) { addTransactionTab(name, file, transaction); - } else if(contextPsbt.possibleUnverifiableSilentPaymentsTransaction(transaction)) { - AppServices.showErrorDialog("Silent Payments Transaction", "This transaction pays a silent payment address.\n\nThe signing device must return the PSBT rather than the final transaction, so the silent payment outputs can be verified."); - } else { - AppServices.showErrorDialog("Mismatched Transaction", "The loaded transaction does not match the transaction in this tab.\n\nCheck that the correct transaction was signed and exported from the signing device."); } } catch(Exception e) { throw new TransactionParseException(e.getMessage()); @@ -2198,6 +2192,37 @@ public class AppController implements Initializable { tabs.getSelectionModel().select(tab); } + private boolean verifyTransactionContext(PSBT contextPsbt, Transaction transaction, PSBT psbt, String source) { + if(contextPsbt == null || matchesOpenTransactionTab(transaction, psbt)) { + return true; + } + + if(psbt == null && contextPsbt.possibleUnverifiableSilentPaymentsTransaction(transaction)) { + AppServices.showErrorDialog("Silent Payments Transaction", "This transaction pays a silent payment address.\n\nThe signing device must return the PSBT rather than the final transaction, so the silent payment outputs can be verified."); + } else { + AppServices.showErrorDialog("Mismatched Transaction", "The " + source + " transaction does not match the transaction in this or any other open tab.\n\nCheck that the correct transaction was signed and exported from the signing device."); + } + + return false; + } + + private boolean matchesOpenTransactionTab(Transaction transaction, PSBT psbt) { + for(Tab tab : tabs.getTabs()) { + TabData tabData = (TabData)tab.getUserData(); + if(tabData instanceof TransactionTabData transactionTabData) { + if(transactionTabData.getPsbt() != null) { + if(psbt != null ? transactionTabData.getPsbt().matches(psbt) : transactionTabData.getPsbt().matches(transaction)) { + return true; + } + } else if(transactionTabData.getTransaction().calculateTxId(false).equals(psbt != null ? psbt.getTransaction().getTxId() : transaction.getTxId())) { + return true; + } + } + } + + return false; + } + private boolean openUnverifiableTransaction(String tabName) { Optional result = AppServices.showWarningDialog( "Unverifiable Silent Payments Transaction", @@ -3236,7 +3261,7 @@ public class AppController implements Initializable { if(tabs.getScene().getWindow().equals(event.getWindow())) { if(event.getBlockTransaction() != null) { addTransactionTab(event.getBlockTransaction(), event.getInitialView(), event.getInitialIndex()); - } else { + } else if(verifyTransactionContext(event.getContextPsbt(), event.getTransaction(), null, "scanned")) { addTransactionTab(event.getTransaction(), event.getInitialView(), event.getInitialIndex()); } } @@ -3245,7 +3270,9 @@ public class AppController implements Initializable { @Subscribe public void viewPSBT(ViewPSBTEvent event) { if(tabs.getScene().getWindow().equals(event.getWindow())) { - addTransactionTab(event.getLabel(), event.getFile(), event.getPsbt()); + if(verifyTransactionContext(event.getContextPsbt(), null, event.getPsbt(), "scanned")) { + addTransactionTab(event.getLabel(), event.getFile(), event.getPsbt()); + } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/ViewPSBTEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/ViewPSBTEvent.java index 87d9ca42..d36c6bb7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/ViewPSBTEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/ViewPSBTEvent.java @@ -11,18 +11,28 @@ public class ViewPSBTEvent { private final String label; private final File file; private final PSBT psbt; + private final PSBT contextPsbt; private final TransactionView initialView; private final Integer initialIndex; public ViewPSBTEvent(Window window, String label, File file, PSBT psbt) { - this(window, label, file, psbt, TransactionView.HEADERS, null); + this(window, label, file, psbt, null, TransactionView.HEADERS, null); + } + + public ViewPSBTEvent(Window window, String label, File file, PSBT psbt, PSBT contextPsbt) { + this(window, label, file, psbt, contextPsbt, TransactionView.HEADERS, null); } public ViewPSBTEvent(Window window, String label, File file, PSBT psbt, TransactionView initialView, Integer initialIndex) { + this(window, label, file, psbt, null, initialView, initialIndex); + } + + public ViewPSBTEvent(Window window, String label, File file, PSBT psbt, PSBT contextPsbt, TransactionView initialView, Integer initialIndex) { this.window = window; this.label = label; this.file = file; this.psbt = psbt; + this.contextPsbt = contextPsbt; this.initialView = initialView; this.initialIndex = initialIndex; } @@ -43,6 +53,10 @@ public class ViewPSBTEvent { return psbt; } + public PSBT getContextPsbt() { + return contextPsbt; + } + public TransactionView getInitialView() { return initialView; } diff --git a/src/main/java/com/sparrowwallet/sparrow/event/ViewTransactionEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/ViewTransactionEvent.java index 4e749cea..91cf53bb 100644 --- a/src/main/java/com/sparrowwallet/sparrow/event/ViewTransactionEvent.java +++ b/src/main/java/com/sparrowwallet/sparrow/event/ViewTransactionEvent.java @@ -1,6 +1,7 @@ package com.sparrowwallet.sparrow.event; import com.sparrowwallet.drongo.protocol.Transaction; +import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.wallet.BlockTransaction; import com.sparrowwallet.sparrow.transaction.TransactionView; import com.sparrowwallet.sparrow.wallet.HashIndexEntry; @@ -9,13 +10,19 @@ import javafx.stage.Window; public class ViewTransactionEvent { private final Window window; private final Transaction transaction; + private final PSBT contextPsbt; private final BlockTransaction blockTransaction; private final TransactionView initialView; private final Integer initialIndex; public ViewTransactionEvent(Window window, Transaction transaction) { + this(window, transaction, null); + } + + public ViewTransactionEvent(Window window, Transaction transaction, PSBT contextPsbt) { this.window = window; this.transaction = transaction; + this.contextPsbt = contextPsbt; this.blockTransaction = null; this.initialView = TransactionView.HEADERS; this.initialIndex = null; @@ -32,6 +39,7 @@ public class ViewTransactionEvent { public ViewTransactionEvent(Window window, BlockTransaction blockTransaction, TransactionView initialView, Integer initialIndex) { this.window = window; this.transaction = blockTransaction.getTransaction(); + this.contextPsbt = null; this.blockTransaction = blockTransaction; this.initialView = initialView; this.initialIndex = initialIndex; @@ -45,6 +53,10 @@ public class ViewTransactionEvent { return transaction; } + public PSBT getContextPsbt() { + return contextPsbt; + } + public BlockTransaction getBlockTransaction() { return blockTransaction; } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index 77a452f3..dbf3757c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -1037,19 +1037,9 @@ public class HeadersController extends TransactionFormController implements Init if(optionalResult.isPresent()) { QRScanDialog.Result result = optionalResult.get(); if(result.transaction != null) { - if(headersForm.getPsbt().matches(result.transaction)) { - EventManager.get().post(new ViewTransactionEvent(toggleButton.getScene().getWindow(), result.transaction)); - } else if(headersForm.getPsbt().possibleUnverifiableSilentPaymentsTransaction(result.transaction)) { - AppServices.showErrorDialog("Silent Payments Transaction", "This transaction pays a silent payment address.\n\nThe signing device must return the PSBT rather than the final transaction, so the silent payment outputs can be verified."); - } else { - AppServices.showErrorDialog("Mismatched Transaction", "The scanned transaction does not match the transaction in this tab.\n\nCheck that the correct transaction was signed and exported from the signing device."); - } + EventManager.get().post(new ViewTransactionEvent(toggleButton.getScene().getWindow(), result.transaction, headersForm.getPsbt())); } else if(result.psbt != null) { - if(headersForm.getPsbt().matches(result.psbt)) { - EventManager.get().post(new ViewPSBTEvent(toggleButton.getScene().getWindow(), null, null, result.psbt)); - } else { - AppServices.showErrorDialog("Mismatched Transaction", "The scanned transaction does not match the transaction in this tab.\n\nCheck that the correct transaction was signed and exported from the signing device."); - } + EventManager.get().post(new ViewPSBTEvent(toggleButton.getScene().getWindow(), null, null, result.psbt, headersForm.getPsbt())); } else if(result.seed != null) { signFromSeed(result.seed); } else if(result.exception != null) {