Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scan disputes for accounts where same user used diff. real names. #4484

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
@Getter
@ToString
@Slf4j
public abstract class BankAccountPayload extends CountryBasedPaymentAccountPayload {
public abstract class BankAccountPayload extends CountryBasedPaymentAccountPayload implements PayloadWithHolderName {
protected String holderName = "";
@Nullable
protected String bankName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
@Setter
@Getter
@Slf4j
public class CashDepositAccountPayload extends CountryBasedPaymentAccountPayload {
public class CashDepositAccountPayload extends CountryBasedPaymentAccountPayload implements PayloadWithHolderName {
private String holderName = "";
@Nullable
private String holderEmail;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
@Setter
@Getter
@Slf4j
public final class ChaseQuickPayAccountPayload extends PaymentAccountPayload {
public final class ChaseQuickPayAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName {
private String email = "";
private String holderName = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
@Setter
@Getter
@Slf4j
public final class ClearXchangeAccountPayload extends PaymentAccountPayload {
public final class ClearXchangeAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName {
private String emailOrMobileNr = "";
private String holderName = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
@Setter
@Getter
@Slf4j
public final class InteracETransferAccountPayload extends PaymentAccountPayload {
public final class InteracETransferAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName {
private String email = "";
private String holderName = "";
private String question = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
@Setter
@Getter
@Slf4j
public final class JapanBankAccountPayload extends PaymentAccountPayload {
public final class JapanBankAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName {
// bank
private String bankName = "";
private String bankCode = "";
Expand Down Expand Up @@ -137,4 +137,9 @@ public byte[] getAgeWitnessInputData() {
String all = this.bankName + this.bankBranchName + this.bankAccountType + this.bankAccountNumber + this.bankAccountName;
return super.getAgeWitnessInputData(all.getBytes(StandardCharsets.UTF_8));
}

@Override
public String getHolderName() {
return bankAccountName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
@Setter
@Getter
@Slf4j
public class MoneyGramAccountPayload extends PaymentAccountPayload {
public class MoneyGramAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName {
private String holderName;
private String countryCode = "";
private String state = ""; // is optional. we don't use @Nullable because it would makes UI code more complex.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.core.payment.payload;

public interface PayloadWithHolderName {
String getHolderName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
@Setter
@Getter
@Slf4j
public final class PopmoneyAccountPayload extends PaymentAccountPayload {
public final class PopmoneyAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName {
private String accountId = "";
private String holderName = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
@ToString
@Getter
@Slf4j
public final class SepaAccountPayload extends CountryBasedPaymentAccountPayload {
public final class SepaAccountPayload extends CountryBasedPaymentAccountPayload implements PayloadWithHolderName {
@Setter
private String holderName = "";
@Setter
Expand Down Expand Up @@ -158,6 +158,7 @@ public byte[] getAgeWitnessInputData() {
// slight changes in holder name (e.g. add or remove middle name)
return super.getAgeWitnessInputData(ArrayUtils.addAll(iban.getBytes(StandardCharsets.UTF_8), bic.getBytes(StandardCharsets.UTF_8)));
}

@Override
public String getOwnerId() {
return holderName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
@ToString
@Getter
@Slf4j
public final class SepaInstantAccountPayload extends CountryBasedPaymentAccountPayload {
public final class SepaInstantAccountPayload extends CountryBasedPaymentAccountPayload implements PayloadWithHolderName {
@Setter
private String holderName = "";
@Setter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
@Setter
@Getter
@Slf4j
public final class SwishAccountPayload extends PaymentAccountPayload {
public final class SwishAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName {
private String mobileNr = "";
private String holderName = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
@Setter
@Getter
@Slf4j
public final class USPostalMoneyOrderAccountPayload extends PaymentAccountPayload {
public final class USPostalMoneyOrderAccountPayload extends PaymentAccountPayload implements PayloadWithHolderName {
private String postalAddress = "";
private String holderName = "";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
@Setter
@Getter
@Slf4j
public class WesternUnionAccountPayload extends CountryBasedPaymentAccountPayload {
public class WesternUnionAccountPayload extends CountryBasedPaymentAccountPayload implements PayloadWithHolderName {
private String holderName;
private String city;
private String state = ""; // is optional. we don't use @Nullable because it would makes UI code more complex.
Expand Down
215 changes: 215 additions & 0 deletions core/src/main/java/bisq/core/support/dispute/agent/FraudDetection.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/

package bisq.core.support.dispute.agent;

import bisq.core.locale.Res;
import bisq.core.payment.payload.PayloadWithHolderName;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.DisputeList;
import bisq.core.support.dispute.DisputeManager;
import bisq.core.trade.Contract;

import bisq.common.crypto.Hash;
import bisq.common.crypto.PubKeyRing;
import bisq.common.util.Utilities;

import javafx.collections.ListChangeListener;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

import lombok.Getter;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class FraudDetection {
public interface Listener {
void onSuspiciousDisputeDetected();
}

private final DisputeManager<? extends DisputeList<? extends DisputeList>> disputeManager;
private Map<String, List<RealNameAccountInfo>> buyerRealNameAccountByAddressMap = new HashMap<>();
private Map<String, List<RealNameAccountInfo>> sellerRealNameAccountByAddressMap = new HashMap<>();
@Getter
private Map<String, List<RealNameAccountInfo>> accountsUsingMultipleNames = new HashMap<>();
private List<Listener> listeners = new CopyOnWriteArrayList<>();


///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////

public FraudDetection(DisputeManager<? extends DisputeList<? extends DisputeList>> disputeManager) {
this.disputeManager = disputeManager;

disputeManager.getDisputesAsObservableList().addListener((ListChangeListener<Dispute>) c -> {
c.next();
if (c.wasAdded()) {
checkForMultipleHolderNames();
}
});
}


///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////

public void checkForMultipleHolderNames() {
log.error("checkForMultipleHolderNames");
buildRealNameAccountMaps();
detectUsageOfDifferentUserNames();
log.error("hasSuspiciousDisputeDetected() " + hasSuspiciousDisputeDetected());
}

public boolean hasSuspiciousDisputeDetected() {
return !accountsUsingMultipleNames.isEmpty();
}

public String getAccountsUsingMultipleNamesAsString() {
return accountsUsingMultipleNames.entrySet().stream()
.map(entry -> {
String pubKeyHash = entry.getKey();
String accountInfo = entry.getValue().stream()
.map(info -> {
String tradeId = info.getDispute().getShortTradeId();
String holderName = info.getPayloadWithHolderName().getHolderName();
return " Account owner name: '" + holderName +
"'; Trade ID: '" + tradeId +
"'; Address: '" + info.getAddress() +
"'; Payment method: '" + Res.get(info.getPaymentAccountPayload().getPaymentMethodId()) +
"'; Role: " + (info.isBuyer() ? "'Buyer'" : "'Seller'");

})
.collect(Collectors.joining("\n"));
return "Trader with multiple identities:\n" +
accountInfo;
})
.collect(Collectors.joining("\n\n"));
}

public void addListener(Listener listener) {
listeners.add(listener);
}

public void removeListener(Listener listener) {
listeners.remove(listener);
}


///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////

private void buildRealNameAccountMaps() {
buyerRealNameAccountByAddressMap.clear();
sellerRealNameAccountByAddressMap.clear();
disputeManager.getDisputesAsObservableList()
.forEach(dispute -> {
Contract contract = dispute.getContract();
PubKeyRing traderPubKeyRing = dispute.getTraderPubKeyRing();
String traderPubKeyHash = getTraderPuKeyHash(traderPubKeyRing);
String buyerPubKeyHash = getTraderPuKeyHash(contract.getBuyerPubKeyRing());
boolean isBuyer = contract.isMyRoleBuyer(traderPubKeyRing);
chimp1984 marked this conversation as resolved.
Show resolved Hide resolved

if (buyerPubKeyHash.equals(traderPubKeyHash)) {
PaymentAccountPayload buyerPaymentAccountPayload = contract.getBuyerPaymentAccountPayload();
String buyersAddress = contract.getBuyerNodeAddress().getFullAddress();
addToMap(traderPubKeyHash, buyerRealNameAccountByAddressMap, buyerPaymentAccountPayload, buyersAddress, dispute, isBuyer);
} else {
PaymentAccountPayload sellerPaymentAccountPayload = contract.getSellerPaymentAccountPayload();
String sellerAddress = contract.getSellerNodeAddress().getFullAddress();
addToMap(traderPubKeyHash, sellerRealNameAccountByAddressMap, sellerPaymentAccountPayload, sellerAddress, dispute, isBuyer);
}
});
}

private String getTraderPuKeyHash(PubKeyRing pubKeyRing) {
return Utilities.encodeToHex(Hash.getRipemd160hash(pubKeyRing.toProtoMessage().toByteArray()));
}

private void addToMap(String pubKeyHash, Map<String, List<RealNameAccountInfo>> map,
PaymentAccountPayload paymentAccountPayload,
String address,
Dispute dispute,
boolean isBuyer) {
if (paymentAccountPayload instanceof PayloadWithHolderName) {
chimp1984 marked this conversation as resolved.
Show resolved Hide resolved
map.putIfAbsent(pubKeyHash, new ArrayList<>());
RealNameAccountInfo info = new RealNameAccountInfo(address,
(PayloadWithHolderName) paymentAccountPayload,
paymentAccountPayload,
dispute,
isBuyer);
map.get(pubKeyHash).add(info);
}
}

private void detectUsageOfDifferentUserNames() {
detectUsageOfDifferentUserNames(buyerRealNameAccountByAddressMap);
detectUsageOfDifferentUserNames(sellerRealNameAccountByAddressMap);
}

private void detectUsageOfDifferentUserNames(Map<String, List<RealNameAccountInfo>> map) {
String previous = accountsUsingMultipleNames.toString();
map.forEach((key, value) -> {
Set<String> userNames = value.stream()
.map(info -> info.getPayloadWithHolderName().getHolderName())
.collect(Collectors.toSet());
if (userNames.size() > 1) {
accountsUsingMultipleNames.put(key, value);
}
});
String updated = accountsUsingMultipleNames.toString();
if (!previous.equals(updated)) {
listeners.forEach(Listener::onSuspiciousDisputeDetected);
}
}


///////////////////////////////////////////////////////////////////////////////////////////
// Static class
///////////////////////////////////////////////////////////////////////////////////////////

@Value
private static class RealNameAccountInfo {
private final String address;
private final PayloadWithHolderName payloadWithHolderName;
private final Dispute dispute;
private final boolean isBuyer;
private final PaymentAccountPayload paymentAccountPayload;

RealNameAccountInfo(String address,
PayloadWithHolderName payloadWithHolderName,
PaymentAccountPayload paymentAccountPayload,
Dispute dispute,
boolean isBuyer) {
this.address = address;
this.payloadWithHolderName = payloadWithHolderName;
this.paymentAccountPayload = paymentAccountPayload;
this.dispute = dispute;
this.isBuyer = isBuyer;
}
}
}
5 changes: 5 additions & 0 deletions desktop/src/main/java/bisq/desktop/bisq.css
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,11 @@ tree-table-view:focused {
-fx-padding: 27 2 0 2;
}

.alert-icon {
-fx-fill: -bs-rd-error-red;
-fx-cursor: hand;
}

.close-icon {
-fx-fill: -bs-text-color;
}
Expand Down
Loading