offer book view checks for invalid signer or signature

merge OfferFilter into OfferFilterService
This commit is contained in:
woodser 2022-09-07 03:45:48 -04:00
parent b95c689190
commit a51aeeb484
6 changed files with 42 additions and 244 deletions

View file

@ -24,8 +24,8 @@ import bisq.core.offer.CreateOfferService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.offer.OfferDirection;
import bisq.core.offer.OfferFilter;
import bisq.core.offer.OfferFilter.Result;
import bisq.core.offer.OfferFilterService;
import bisq.core.offer.OfferFilterService.Result;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.OpenOffer;
import bisq.core.offer.OpenOfferManager;
@ -65,7 +65,7 @@ import static java.util.Comparator.comparing;
@Singleton
@Slf4j
class CoreOffersService {
public class CoreOffersService {
private final Supplier<Comparator<Offer>> priceComparator = () -> comparing(Offer::getPrice);
private final Supplier<Comparator<Offer>> reversePriceComparator = () -> comparing(Offer::getPrice).reversed();
@ -78,7 +78,7 @@ class CoreOffersService {
private final CoreWalletsService coreWalletsService;
private final CreateOfferService createOfferService;
private final OfferBookService offerBookService;
private final OfferFilter offerFilter;
private final OfferFilterService offerFilter;
private final OpenOfferManager openOfferManager;
private final User user;
private final XmrWalletService xmrWalletService;
@ -89,7 +89,7 @@ class CoreOffersService {
CoreWalletsService coreWalletsService,
CreateOfferService createOfferService,
OfferBookService offerBookService,
OfferFilter offerFilter,
OfferFilterService offerFilter,
OpenOfferManager openOfferManager,
OfferUtil offerUtil,
User user,

View file

@ -1,225 +0,0 @@
/*
* This file is part of Haveno.
*
* Haveno is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Haveno is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.offer;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.filter.FilterManager;
import bisq.core.locale.CurrencyUtil;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountUtil;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.trade.TradeUtils;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.common.app.Version;
import org.bitcoinj.core.Coin;
import javax.inject.Inject;
import javax.inject.Singleton;
import javafx.collections.SetChangeListener;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Singleton
public class OfferFilter {
private final User user;
private final Preferences preferences;
private final FilterManager filterManager;
private final AccountAgeWitnessService accountAgeWitnessService;
private final Map<String, Boolean> insufficientCounterpartyTradeLimitCache = new HashMap<>();
private final Map<String, Boolean> myInsufficientTradeLimitCache = new HashMap<>();
@Inject
public OfferFilter(User user,
Preferences preferences,
FilterManager filterManager,
AccountAgeWitnessService accountAgeWitnessService) {
this.user = user;
this.preferences = preferences;
this.filterManager = filterManager;
this.accountAgeWitnessService = accountAgeWitnessService;
if (user != null && user.getPaymentAccountsAsObservable() != null) {
// If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c ->
myInsufficientTradeLimitCache.clear());
}
}
public enum Result {
VALID(true),
API_DISABLED,
HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER,
HAS_NOT_SAME_PROTOCOL_VERSION,
IS_IGNORED,
IS_OFFER_BANNED,
IS_CURRENCY_BANNED,
IS_PAYMENT_METHOD_BANNED,
IS_NODE_ADDRESS_BANNED,
REQUIRE_UPDATE_TO_NEW_VERSION,
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
IS_MY_INSUFFICIENT_TRADE_LIMIT,
SIGNATURE_NOT_VALIDATED;
@Getter
private final boolean isValid;
Result(boolean isValid) {
this.isValid = isValid;
}
Result() {
this(false);
}
}
public Result canTakeOffer(Offer offer, boolean isTakerApiUser) {
if (isTakerApiUser && filterManager.getFilter() != null && filterManager.getFilter().isDisableApi()) {
return Result.API_DISABLED;
}
if (!hasSameProtocolVersion(offer)) {
return Result.HAS_NOT_SAME_PROTOCOL_VERSION;
}
if (isIgnored(offer)) {
return Result.IS_IGNORED;
}
if (isOfferBanned(offer)) {
return Result.IS_OFFER_BANNED;
}
if (isCurrencyBanned(offer)) {
return Result.IS_CURRENCY_BANNED;
}
if (isPaymentMethodBanned(offer)) {
return Result.IS_PAYMENT_METHOD_BANNED;
}
if (isNodeAddressBanned(offer)) {
return Result.IS_NODE_ADDRESS_BANNED;
}
if (requireUpdateToNewVersion()) {
return Result.REQUIRE_UPDATE_TO_NEW_VERSION;
}
if (isInsufficientCounterpartyTradeLimit(offer)) {
return Result.IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT;
}
if (isMyInsufficientTradeLimit(offer)) {
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
}
if (!hasValidSignature(offer)) {
return Result.SIGNATURE_NOT_VALIDATED; // TODO (woodser): handle this wherever IS_MY_INSUFFICIENT_TRADE_LIMIT is handled
}
if (!isAnyPaymentAccountValidForOffer(offer)) {
return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER;
}
return Result.VALID;
}
public boolean isAnyPaymentAccountValidForOffer(Offer offer) {
return user.getPaymentAccounts() != null &&
PaymentAccountUtil.isAnyPaymentAccountValidForOffer(offer, user.getPaymentAccounts());
}
public boolean hasSameProtocolVersion(Offer offer) {
return offer.getProtocolVersion() == Version.TRADE_PROTOCOL_VERSION;
}
public boolean isIgnored(Offer offer) {
return preferences.getIgnoreTradersList().stream()
.anyMatch(i -> i.equals(offer.getMakerNodeAddress().getFullAddress()));
}
public boolean isOfferBanned(Offer offer) {
return filterManager.isOfferIdBanned(offer.getId());
}
public boolean isCurrencyBanned(Offer offer) {
return filterManager.isCurrencyBanned(offer.getCurrencyCode());
}
public boolean isPaymentMethodBanned(Offer offer) {
return filterManager.isPaymentMethodBanned(offer.getPaymentMethod());
}
public boolean isNodeAddressBanned(Offer offer) {
return filterManager.isNodeAddressBanned(offer.getMakerNodeAddress());
}
public boolean requireUpdateToNewVersion() {
return filterManager.requireUpdateToNewVersionForTrading();
}
// This call is a bit expensive so we cache results
public boolean isInsufficientCounterpartyTradeLimit(Offer offer) {
String offerId = offer.getId();
if (insufficientCounterpartyTradeLimitCache.containsKey(offerId)) {
return insufficientCounterpartyTradeLimitCache.get(offerId);
}
boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) &&
!accountAgeWitnessService.verifyPeersTradeAmount(offer, offer.getAmount(),
errorMessage -> {
});
insufficientCounterpartyTradeLimitCache.put(offerId, result);
return result;
}
// This call is a bit expensive so we cache results
public boolean isMyInsufficientTradeLimit(Offer offer) {
String offerId = offer.getId();
if (myInsufficientTradeLimitCache.containsKey(offerId)) {
return myInsufficientTradeLimitCache.get(offerId);
}
Optional<PaymentAccount> accountOptional = PaymentAccountUtil.getMostMaturePaymentAccountForOffer(offer,
user.getPaymentAccounts(),
accountAgeWitnessService);
long myTradeLimit = accountOptional
.map(paymentAccount -> accountAgeWitnessService.getMyTradeLimit(paymentAccount,
offer.getCurrencyCode(), offer.getMirroredDirection()))
.orElse(0L);
long offerMinAmount = offer.getMinAmount().value;
log.debug("isInsufficientTradeLimit accountOptional={}, myTradeLimit={}, offerMinAmount={}, ",
accountOptional.isPresent() ? accountOptional.get().getAccountName() : "null",
Coin.valueOf(myTradeLimit).toFriendlyString(),
Coin.valueOf(offerMinAmount).toFriendlyString());
boolean result = CurrencyUtil.isFiatCurrency(offer.getCurrencyCode()) &&
accountOptional.isPresent() &&
myTradeLimit < offerMinAmount;
myInsufficientTradeLimitCache.put(offerId, result);
return result;
}
public boolean hasValidSignature(Offer offer) {
// get arbitrator
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
if (arbitrator == null) return false; // invalid arbitrator
// validate arbitrator signature
return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
}
}

View file

@ -21,10 +21,11 @@ import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.filter.FilterManager;
import bisq.core.payment.PaymentAccount;
import bisq.core.payment.PaymentAccountUtil;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.trade.TradeUtils;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.common.app.DevEnv;
import bisq.common.app.Version;
import org.bitcoinj.core.Coin;
@ -61,7 +62,7 @@ public class OfferFilterService {
this.filterManager = filterManager;
this.accountAgeWitnessService = accountAgeWitnessService;
if (user != null) {
if (user != null && user.getPaymentAccountsAsObservable() != null) {
// If our accounts have changed we reset our myInsufficientTradeLimitCache as it depends on account data
user.getPaymentAccountsAsObservable().addListener((SetChangeListener<PaymentAccount>) c ->
myInsufficientTradeLimitCache.clear());
@ -81,7 +82,8 @@ public class OfferFilterService {
REQUIRE_UPDATE_TO_NEW_VERSION,
IS_INSUFFICIENT_COUNTERPARTY_TRADE_LIMIT,
IS_MY_INSUFFICIENT_TRADE_LIMIT,
HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED;
ARBITRATOR_NOT_VALIDATED,
SIGNATURE_NOT_VALIDATED;
@Getter
private final boolean isValid;
@ -99,9 +101,6 @@ public class OfferFilterService {
if (isTakerApiUser && filterManager.getFilter() != null && filterManager.getFilter().isDisableApi()) {
return Result.API_DISABLED;
}
if (!isAnyPaymentAccountValidForOffer(offer)) {
return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER;
}
if (!hasSameProtocolVersion(offer)) {
return Result.HAS_NOT_SAME_PROTOCOL_VERSION;
}
@ -129,6 +128,15 @@ public class OfferFilterService {
if (isMyInsufficientTradeLimit(offer)) {
return Result.IS_MY_INSUFFICIENT_TRADE_LIMIT;
}
if (!hasValidArbitrator(offer)) {
return Result.ARBITRATOR_NOT_VALIDATED;
}
if (!hasValidSignature(offer)) {
return Result.SIGNATURE_NOT_VALIDATED;
}
if (!isAnyPaymentAccountValidForOffer(offer)) {
return Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER;
}
return Result.VALID;
}
@ -207,4 +215,15 @@ public class OfferFilterService {
myInsufficientTradeLimitCache.put(offerId, result);
return result;
}
public boolean hasValidArbitrator(Offer offer) {
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
return arbitrator != null;
}
public boolean hasValidSignature(Offer offer) {
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
if (arbitrator == null) return false; // invalid arbitrator
return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
}
}

View file

@ -90,9 +90,7 @@ public class TradeUtils {
// verify arbitrator signature
boolean isValid = true;
try {
isValid = Sig.verify(arbitrator.getPubKeyRing().getSignaturePubKey(), // TODO (woodser): assign isValid
unsignedOfferAsJson,
signature);
isValid = Sig.verify(arbitrator.getPubKeyRing().getSignaturePubKey(), unsignedOfferAsJson, signature);
} catch (Exception e) {
isValid = false;
}

View file

@ -415,6 +415,9 @@ offerbook.warning.requireUpdateToNewVersion=Your version of Haveno is not compat
offerbook.warning.offerWasAlreadyUsedInTrade=You cannot take this offer because you already took it earlier. \
It could be that your previous take-offer attempt resulted in a failed trade.
offerbook.warning.arbitratorNotValidated=This offer cannot be taken because the arbitrator is invalid
offerbook.warning.signatureNotValidated=This offer cannot be taken because the arbitrator's signature is invalid
offerbook.info.sellAtMarketPrice=You will sell at market price (updated every minute).
offerbook.info.buyAtMarketPrice=You will buy at market price (updated every minute).
offerbook.info.sellBelowMarketPrice=You will get {0} less than the current market price (updated every minute).

View file

@ -685,8 +685,11 @@ abstract public class OfferBookView<R extends GridPane, M extends OfferBookViewM
"isInsufficientTradeLimit case.");
}
break;
case HIDE_BSQ_SWAPS_DUE_DAO_DEACTIVATED:
new Popup().warning(Res.get("offerbook.warning.hideBsqSwapsDueDaoDeactivated")).show();
case ARBITRATOR_NOT_VALIDATED:
new Popup().warning(Res.get("offerbook.warning.arbitratorNotValidated")).show();
break;
case SIGNATURE_NOT_VALIDATED:
new Popup().warning(Res.get("offerbook.warning.signatureNotValidated")).show();
break;
case VALID:
default: