From b5f9bc307baf2974f740b0134e476ef4074ed66c Mon Sep 17 00:00:00 2001
From: woodser <13068859+woodser@users.noreply.github.com>
Date: Thu, 20 Mar 2025 08:04:30 -0400
Subject: [PATCH] verify offer versions when signing

---
 .../main/java/haveno/common/app/Version.java  | 19 +++++++++++++
 .../haveno/core/app/DomainInitialisation.java |  7 ++---
 .../haveno/core/filter/FilterManager.java     |  4 +++
 .../haveno/core/offer/OpenOfferManager.java   | 28 ++++++++++++++++++-
 .../core/xmr/wallet/XmrWalletService.java     |  4 +--
 5 files changed, 55 insertions(+), 7 deletions(-)

diff --git a/common/src/main/java/haveno/common/app/Version.java b/common/src/main/java/haveno/common/app/Version.java
index 74f3ceb9d6..1d7b99f747 100644
--- a/common/src/main/java/haveno/common/app/Version.java
+++ b/common/src/main/java/haveno/common/app/Version.java
@@ -72,6 +72,25 @@ public class Version {
             return false;
     }
 
+    public static int compare(String version1, String version2) {
+        if (version1.equals(version2))
+            return 0;
+        else if (getMajorVersion(version1) > getMajorVersion(version2))
+            return 1;
+        else if (getMajorVersion(version1) < getMajorVersion(version2))
+            return -1;
+        else if (getMinorVersion(version1) > getMinorVersion(version2))
+            return 1;
+        else if (getMinorVersion(version1) < getMinorVersion(version2))
+            return -1;
+        else if (getPatchVersion(version1) > getPatchVersion(version2))
+            return 1;
+        else if (getPatchVersion(version1) < getPatchVersion(version2))
+            return -1;
+        else
+            return 0;
+    }
+
     private static int getSubVersion(String version, int index) {
         final String[] split = version.split("\\.");
         checkArgument(split.length == 3, "Version number must be in semantic version format (contain 2 '.'). version=" + version);
diff --git a/core/src/main/java/haveno/core/app/DomainInitialisation.java b/core/src/main/java/haveno/core/app/DomainInitialisation.java
index 646d80d9dd..220fb05040 100644
--- a/core/src/main/java/haveno/core/app/DomainInitialisation.java
+++ b/core/src/main/java/haveno/core/app/DomainInitialisation.java
@@ -178,6 +178,9 @@ public class DomainInitialisation {
         closedTradableManager.onAllServicesInitialized();
         failedTradesManager.onAllServicesInitialized();
 
+        filterManager.setFilterWarningHandler(filterWarningHandler);
+        filterManager.onAllServicesInitialized();
+
         openOfferManager.onAllServicesInitialized();
 
         balances.onAllServicesInitialized();
@@ -199,10 +202,6 @@ public class DomainInitialisation {
         priceFeedService.setCurrencyCodeOnInit();
         priceFeedService.startRequestingPrices();
 
-        filterManager.setFilterWarningHandler(filterWarningHandler);
-        filterManager.onAllServicesInitialized();
-
-
         mobileNotificationService.onAllServicesInitialized();
         myOfferTakenEvents.onAllServicesInitialized();
         tradeEvents.onAllServicesInitialized();
diff --git a/core/src/main/java/haveno/core/filter/FilterManager.java b/core/src/main/java/haveno/core/filter/FilterManager.java
index cb7e0e9b21..2cebb66f3a 100644
--- a/core/src/main/java/haveno/core/filter/FilterManager.java
+++ b/core/src/main/java/haveno/core/filter/FilterManager.java
@@ -406,6 +406,10 @@ public class FilterManager {
                         .anyMatch(e -> e.equals(address));
     }
 
+    public String getDisableTradeBelowVersion() {
+        return getFilter() == null || getFilter().getDisableTradeBelowVersion() == null || getFilter().getDisableTradeBelowVersion().isEmpty() ? null : getFilter().getDisableTradeBelowVersion();
+    }
+
     public boolean requireUpdateToNewVersionForTrading() {
         if (getFilter() == null) {
             return false;
diff --git a/core/src/main/java/haveno/core/offer/OpenOfferManager.java b/core/src/main/java/haveno/core/offer/OpenOfferManager.java
index 81d2b2b821..91e2284d46 100644
--- a/core/src/main/java/haveno/core/offer/OpenOfferManager.java
+++ b/core/src/main/java/haveno/core/offer/OpenOfferManager.java
@@ -737,7 +737,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
         doCancelOffer(openOffer, true);
     }
 
-    // remove open offer which thaws its key images
+    // cancel open offer which thaws its key images
     private void doCancelOffer(@NotNull OpenOffer openOffer, boolean resetAddressEntries) {
         Offer offer = openOffer.getOffer();
         offer.setState(Offer.State.REMOVED);
@@ -1396,6 +1396,32 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
                 return;
             }
 
+            // verify the trade protocol version
+            if (request.getOfferPayload().getProtocolVersion() != Version.TRADE_PROTOCOL_VERSION) {
+                errorMessage = "Unsupported protocol version: " + request.getOfferPayload().getProtocolVersion();
+                log.warn(errorMessage);
+                sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
+                return;
+            }
+
+            // verify the min version number
+            if (filterManager.getDisableTradeBelowVersion() != null) {
+                if (Version.compare(request.getOfferPayload().getVersionNr(), filterManager.getDisableTradeBelowVersion()) < 0) {
+                    errorMessage = "Offer version number is too low: " + request.getOfferPayload().getVersionNr() + " < " + filterManager.getDisableTradeBelowVersion();
+                    log.warn(errorMessage);
+                    sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
+                    return;
+                }
+            }
+
+            // verify the max version number
+            if (Version.compare(request.getOfferPayload().getVersionNr(), Version.VERSION) > 0) {
+                errorMessage = "Offer version number is too high: " + request.getOfferPayload().getVersionNr() + " > " + Version.VERSION;
+                log.warn(errorMessage);
+                sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage);
+                return;
+            }
+
             // verify maker and taker fees
             boolean hasBuyerAsTakerWithoutDeposit = offer.getDirection() == OfferDirection.SELL && offer.isPrivateOffer() && offer.getChallengeHash() != null && offer.getChallengeHash().length() > 0 && offer.getTakerFeePct() == 0;
             if (hasBuyerAsTakerWithoutDeposit) {
diff --git a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java
index 97aef9545f..7bfc37f8e2 100644
--- a/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java
+++ b/core/src/main/java/haveno/core/xmr/wallet/XmrWalletService.java
@@ -767,7 +767,7 @@ public class XmrWalletService extends XmrWalletBase {
                 BigInteger minerFeeEstimate = getFeeEstimate(tx.getWeight());
                 double minerFeeDiff = tx.getFee().subtract(minerFeeEstimate).abs().doubleValue() / minerFeeEstimate.doubleValue();
                 if (minerFeeDiff > MINER_FEE_TOLERANCE) throw new RuntimeException("Miner fee is not within " + (MINER_FEE_TOLERANCE * 100) + "% of estimated fee, expected " + minerFeeEstimate + " but was " + tx.getFee() + ", diff%=" + minerFeeDiff);
-                log.info("Trade tx fee {} is within tolerance, diff%={}", tx.getFee(), minerFeeDiff);
+                log.info("Trade miner fee {} is within tolerance, diff%={}", tx.getFee(), minerFeeDiff);
 
                 // verify proof to fee address
                 BigInteger actualTradeFee = BigInteger.ZERO;
@@ -785,7 +785,7 @@ public class XmrWalletService extends XmrWalletBase {
                 // verify trade fee amount
                 if (!actualTradeFee.equals(tradeFeeAmount)) {
                     if (equalsWithinFractionError(actualTradeFee, tradeFeeAmount)) {
-                        log.warn("Trade tx fee amount is within fraction error, expected " + tradeFeeAmount + " but was " + actualTradeFee);
+                        log.warn("Trade fee amount is within fraction error, expected " + tradeFeeAmount + " but was " + actualTradeFee);
                     } else {
                         throw new RuntimeException("Invalid trade fee amount, expected " + tradeFeeAmount + " but was " + actualTradeFee);
                     }